Passed
Push — 1.11.x ( d6c56f...c7cd24 )
by Julito
09:39
created

learnpath   F

Complexity

Total Complexity 1902

Size/Duplication

Total Lines 14049
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 7775
dl 0
loc 14049
rs 0.8
c 3
b 0
f 0
wmc 1902

How to fix   Complexity   

Complex Class

Complex classes like learnpath often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use learnpath, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
6
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CItemProperty;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CTool;
17
use Chamilo\UserBundle\Entity\User;
18
use ChamiloSession as Session;
19
use Gedmo\Sortable\Entity\Repository\SortableRepository;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\Finder\Finder;
22
23
/**
24
 * Class learnpath
25
 * This class defines the parent attributes and methods for Chamilo learnpaths
26
 * and SCORM learnpaths. It is used by the scorm class.
27
 *
28
 * @todo decouple class
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
    const STATUS_CSS_CLASS_NAME = [
37
        'not attempted' => 'scorm_not_attempted',
38
        'incomplete' => 'scorm_not_attempted',
39
        'failed' => 'scorm_failed',
40
        'completed' => 'scorm_completed',
41
        'passed' => 'scorm_completed',
42
        'succeeded' => 'scorm_completed',
43
        'browsed' => 'scorm_completed',
44
    ];
45
46
    public $attempt = 0; // The number for the current ID view.
47
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
48
    public $current; // Id of the current item the user is viewing.
49
    public $current_score; // The score of the current item.
50
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
51
    public $current_time_stop; // The time the user closed this resource.
52
    public $default_status = 'not attempted';
53
    public $encoding = 'UTF-8';
54
    public $error = '';
55
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
56
    public $index; // The index of the active learnpath_item in $ordered_items array.
57
    /** @var learnpathItem[] */
58
    public $items = [];
59
    public $last; // item_id of last item viewed in the learning path.
60
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
61
    public $license; // Which license this course has been given - not used yet on 20060522.
62
    public $lp_id; // DB iid for this learnpath.
63
    public $lp_view_id; // DB ID for lp_view
64
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
65
    public $message = '';
66
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
67
    public $name; // Learnpath name (they generally have one).
68
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
69
    public $path = ''; // Path inside the scorm directory (if scorm).
70
    public $theme; // The current theme of the learning path.
71
    public $preview_image; // The current image of the learning path.
72
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
73
    public $accumulateWorkTime; // The min time of learnpath
74
75
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
76
    public $prevent_reinit = 1;
77
78
    // Describes the mode of progress bar display.
79
    public $seriousgame_mode = 0;
80
    public $progress_bar_mode = '%';
81
82
    // Percentage progress as saved in the db.
83
    public $progress_db = 0;
84
    public $proximity; // Wether the content is distant or local or unknown.
85
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
86
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
87
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
88
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
89
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
90
    public $user_id; //ID of the user that is viewing/using the course
91
    public $update_queue = [];
92
    public $scorm_debug = 0;
93
    public $arrMenu = []; // Array for the menu items.
94
    public $debug = 0; // Logging level.
95
    public $lp_session_id = 0;
96
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
97
    public $prerequisite = 0;
98
    public $use_max_score = 1; // 1 or 0
99
    public $subscribeUsers = 0; // Subscribe users or not
100
    public $created_on = '';
101
    public $modified_on = '';
102
    public $publicated_on = '';
103
    public $expired_on = '';
104
    public $ref = null;
105
    public $course_int_id;
106
    public $course_info = [];
107
    public $categoryId;
108
109
    /**
110
     * Constructor.
111
     * Needs a database handler, a course code and a learnpath id from the database.
112
     * Also builds the list of items into $this->items.
113
     *
114
     * @param string $course  Course code
115
     * @param int    $lp_id   c_lp.iid
116
     * @param int    $user_id
117
     */
118
    public function __construct($course, $lp_id, $user_id)
119
    {
120
        $debug = $this->debug;
121
        $this->encoding = api_get_system_encoding();
122
        if ($debug) {
123
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
124
        }
125
        if (empty($course)) {
126
            $course = api_get_course_id();
127
        }
128
        $course_info = api_get_course_info($course);
129
        if (!empty($course_info)) {
130
            $this->cc = $course_info['code'];
131
            $this->course_info = $course_info;
132
            $course_id = $course_info['real_id'];
133
        } else {
134
            $this->error = 'Course code does not exist in database.';
135
        }
136
137
        $lp_id = (int) $lp_id;
138
        $course_id = (int) $course_id;
139
        $this->set_course_int_id($course_id);
140
        // Check learnpath ID.
141
        if (empty($lp_id) || empty($course_id)) {
142
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
143
        } else {
144
            // TODO: Make it flexible to use any course_code (still using env course code here).
145
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
146
            $sql = "SELECT * FROM $lp_table
147
                    WHERE iid = $lp_id";
148
            if ($debug) {
149
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
150
            }
151
            $res = Database::query($sql);
152
            if (Database::num_rows($res) > 0) {
153
                $this->lp_id = $lp_id;
154
                $row = Database::fetch_array($res);
155
                $this->type = $row['lp_type'];
156
                $this->name = stripslashes($row['name']);
157
                $this->proximity = $row['content_local'];
158
                $this->theme = $row['theme'];
159
                $this->maker = $row['content_maker'];
160
                $this->prevent_reinit = $row['prevent_reinit'];
161
                $this->seriousgame_mode = $row['seriousgame_mode'];
162
                $this->license = $row['content_license'];
163
                $this->scorm_debug = $row['debug'];
164
                $this->js_lib = $row['js_lib'];
165
                $this->path = $row['path'];
166
                $this->preview_image = $row['preview_image'];
167
                $this->author = $row['author'];
168
                $this->hide_toc_frame = $row['hide_toc_frame'];
169
                $this->lp_session_id = $row['session_id'];
170
                $this->use_max_score = $row['use_max_score'];
171
                $this->subscribeUsers = $row['subscribe_users'];
172
                $this->created_on = $row['created_on'];
173
                $this->modified_on = $row['modified_on'];
174
                $this->ref = $row['ref'];
175
                $this->categoryId = $row['category_id'];
176
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
177
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
178
179
                if (!empty($row['publicated_on'])) {
180
                    $this->publicated_on = $row['publicated_on'];
181
                }
182
183
                if (!empty($row['expired_on'])) {
184
                    $this->expired_on = $row['expired_on'];
185
                }
186
                if ($this->type == 2) {
187
                    if ($row['force_commit'] == 1) {
188
                        $this->force_commit = true;
189
                    }
190
                }
191
                $this->mode = $row['default_view_mod'];
192
193
                // Check user ID.
194
                if (empty($user_id)) {
195
                    $this->error = 'User ID is empty';
196
                } else {
197
                    $userInfo = api_get_user_info($user_id);
198
                    if (!empty($userInfo)) {
199
                        $this->user_id = $userInfo['user_id'];
200
                    } else {
201
                        $this->error = 'User ID does not exist in database #'.$user_id;
202
                    }
203
                }
204
205
                // End of variables checking.
206
                $session_id = api_get_session_id();
207
                //  Get the session condition for learning paths of the base + session.
208
                $session = api_get_session_condition($session_id);
209
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
210
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
211
212
                // Selecting by view_count descending allows to get the highest view_count first.
213
                $sql = "SELECT * FROM $lp_table
214
                        WHERE
215
                            c_id = $course_id AND
216
                            lp_id = $lp_id AND
217
                            user_id = $user_id
218
                            $session
219
                        ORDER BY view_count DESC";
220
                $res = Database::query($sql);
221
                if ($debug) {
222
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
223
                }
224
225
                if (Database::num_rows($res) > 0) {
226
                    if ($debug) {
227
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
228
                    }
229
                    $row = Database::fetch_array($res);
230
                    $this->attempt = $row['view_count'];
231
                    $this->lp_view_id = $row['id'];
232
                    $this->last_item_seen = $row['last_item'];
233
                    $this->progress_db = $row['progress'];
234
                    $this->lp_view_session_id = $row['session_id'];
235
                } elseif (!api_is_invitee()) {
236
                    if ($debug) {
237
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
238
                    }
239
                    $this->attempt = 1;
240
                    $params = [
241
                        'c_id' => $course_id,
242
                        'lp_id' => $lp_id,
243
                        'user_id' => $user_id,
244
                        'view_count' => 1,
245
                        'session_id' => $session_id,
246
                        'last_item' => 0,
247
                    ];
248
                    $this->last_item_seen = 0;
249
                    $this->lp_view_session_id = $session_id;
250
                    $this->lp_view_id = Database::insert($lp_table, $params);
251
                    if (!empty($this->lp_view_id)) {
252
                        $sql = "UPDATE $lp_table SET id = iid
253
                                WHERE iid = ".$this->lp_view_id;
254
                        Database::query($sql);
255
                    }
256
                }
257
258
                // Initialise items.
259
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
260
                $sql = "SELECT * FROM $lp_item_table
261
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
262
                        ORDER BY parent_item_id, display_order";
263
                $res = Database::query($sql);
264
265
                $lp_item_id_list = [];
266
                while ($row = Database::fetch_array($res)) {
267
                    $lp_item_id_list[] = $row['iid'];
268
                    switch ($this->type) {
269
                        case 3: //aicc
270
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
271
                            if (is_object($oItem)) {
272
                                $my_item_id = $oItem->get_id();
273
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
274
                                $oItem->set_prevent_reinit($this->prevent_reinit);
275
                                // Don't use reference here as the next loop will make the pointed object change.
276
                                $this->items[$my_item_id] = $oItem;
277
                                $this->refs_list[$oItem->ref] = $my_item_id;
278
                                if ($debug) {
279
                                    error_log(
280
                                        'learnpath::__construct() - '.
281
                                        'aicc object with id '.$my_item_id.
282
                                        ' set in items[]',
283
                                        0
284
                                    );
285
                                }
286
                            }
287
                            break;
288
                        case 2:
289
                            $oItem = new scormItem('db', $row['iid'], $course_id);
290
                            if (is_object($oItem)) {
291
                                $my_item_id = $oItem->get_id();
292
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
293
                                $oItem->set_prevent_reinit($this->prevent_reinit);
294
                                // Don't use reference here as the next loop will make the pointed object change.
295
                                $this->items[$my_item_id] = $oItem;
296
                                $this->refs_list[$oItem->ref] = $my_item_id;
297
                                if ($debug) {
298
                                    error_log('object with id '.$my_item_id.' set in items[]');
299
                                }
300
                            }
301
                            break;
302
                        case 1:
303
                        default:
304
                            if ($debug) {
305
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
306
                            }
307
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
308
309
                            if ($debug) {
310
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
311
                            }
312
                            if (is_object($oItem)) {
313
                                $my_item_id = $oItem->get_id();
314
                                // Moved down to when we are sure the item_view exists.
315
                                //$oItem->set_lp_view($this->lp_view_id);
316
                                $oItem->set_prevent_reinit($this->prevent_reinit);
317
                                // Don't use reference here as the next loop will make the pointed object change.
318
                                $this->items[$my_item_id] = $oItem;
319
                                $this->refs_list[$my_item_id] = $my_item_id;
320
                                if ($debug) {
321
                                    error_log(
322
                                        'learnpath::__construct() '.__LINE__.
323
                                        ' - object with id '.$my_item_id.' set in items[]'
324
                                    );
325
                                }
326
                            }
327
                            break;
328
                    }
329
330
                    // Setting the object level with variable $this->items[$i][parent]
331
                    foreach ($this->items as $itemLPObject) {
332
                        $level = self::get_level_for_item(
333
                            $this->items,
334
                            $itemLPObject->db_id
335
                        );
336
                        $itemLPObject->level = $level;
337
                    }
338
339
                    // Setting the view in the item object.
340
                    if (is_object($this->items[$row['iid']])) {
341
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
342
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
343
                            $this->items[$row['iid']]->current_start_time = 0;
344
                            $this->items[$row['iid']]->current_stop_time = 0;
345
                        }
346
                    }
347
                }
348
349
                if (!empty($lp_item_id_list)) {
350
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
351
                    if (!empty($lp_item_id_list_to_string)) {
352
                        // Get last viewing vars.
353
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
354
                        // This query should only return one or zero result.
355
                        $sql = "SELECT lp_item_id, status
356
                                FROM $itemViewTable
357
                                WHERE
358
                                    c_id = $course_id AND
359
                                    lp_view_id = ".$this->get_view_id()." AND
360
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
361
                                ORDER BY view_count DESC ";
362
                        $status_list = [];
363
                        $res = Database::query($sql);
364
                        while ($row = Database:: fetch_array($res)) {
365
                            $status_list[$row['lp_item_id']] = $row['status'];
366
                        }
367
368
                        foreach ($lp_item_id_list as $item_id) {
369
                            if (isset($status_list[$item_id])) {
370
                                $status = $status_list[$item_id];
371
                                if (is_object($this->items[$item_id])) {
372
                                    $this->items[$item_id]->set_status($status);
373
                                    if (empty($status)) {
374
                                        $this->items[$item_id]->set_status(
375
                                            $this->default_status
376
                                        );
377
                                    }
378
                                }
379
                            } else {
380
                                if (!api_is_invitee()) {
381
                                    if (is_object($this->items[$item_id])) {
382
                                        $this->items[$item_id]->set_status(
383
                                            $this->default_status
384
                                        );
385
                                    }
386
387
                                    if (!empty($this->lp_view_id)) {
388
                                        // Add that row to the lp_item_view table so that
389
                                        // we have something to show in the stats page.
390
                                        $params = [
391
                                            'c_id' => $course_id,
392
                                            'lp_item_id' => $item_id,
393
                                            'lp_view_id' => $this->lp_view_id,
394
                                            'view_count' => 1,
395
                                            'status' => 'not attempted',
396
                                            'start_time' => time(),
397
                                            'total_time' => 0,
398
                                            'score' => 0,
399
                                        ];
400
                                        $insertId = Database::insert($itemViewTable, $params);
401
402
                                        if ($insertId) {
403
                                            $sql = "UPDATE $itemViewTable SET id = iid
404
                                                    WHERE iid = $insertId";
405
                                            Database::query($sql);
406
                                        }
407
408
                                        $this->items[$item_id]->set_lp_view(
409
                                            $this->lp_view_id,
410
                                            $course_id
411
                                        );
412
                                    }
413
                                }
414
                            }
415
                        }
416
                    }
417
                }
418
419
                $this->ordered_items = self::get_flat_ordered_items_list(
420
                    $this->get_id(),
421
                    0,
422
                    $course_id
423
                );
424
                $this->max_ordered_items = 0;
425
                foreach ($this->ordered_items as $index => $dummy) {
426
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
427
                        $this->max_ordered_items = $index;
428
                    }
429
                }
430
                // TODO: Define the current item better.
431
                $this->first();
432
                if ($debug) {
433
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
434
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
435
                }
436
            } else {
437
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
438
            }
439
        }
440
    }
441
442
    /**
443
     * @return string
444
     */
445
    public function getCourseCode()
446
    {
447
        return $this->cc;
448
    }
449
450
    /**
451
     * @return int
452
     */
453
    public function get_course_int_id()
454
    {
455
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
456
    }
457
458
    /**
459
     * @param $course_id
460
     *
461
     * @return int
462
     */
463
    public function set_course_int_id($course_id)
464
    {
465
        return $this->course_int_id = (int) $course_id;
466
    }
467
468
    /**
469
     * Function rewritten based on old_add_item() from Yannick Warnier.
470
     * Due the fact that users can decide where the item should come, I had to overlook this function and
471
     * I found it better to rewrite it. Old function is still available.
472
     * Added also the possibility to add a description.
473
     *
474
     * @param int    $parent
475
     * @param int    $previous
476
     * @param string $type
477
     * @param int    $id               resource ID (ref)
478
     * @param string $title
479
     * @param string $description
480
     * @param int    $prerequisites
481
     * @param int    $max_time_allowed
482
     * @param int    $userId
483
     *
484
     * @return int
485
     */
486
    public function add_item(
487
        $parent,
488
        $previous,
489
        $type = 'dir',
490
        $id,
491
        $title,
492
        $description,
493
        $prerequisites = 0,
494
        $max_time_allowed = 0,
495
        $userId = 0
496
    ) {
497
        $course_id = $this->course_info['real_id'];
498
        if (empty($course_id)) {
499
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
500
            $this->course_info = api_get_course_info($this->cc);
501
            $course_id = $this->course_info['real_id'];
502
        }
503
        $userId = empty($userId) ? api_get_user_id() : $userId;
504
        $sessionId = api_get_session_id();
505
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
506
        $_course = $this->course_info;
507
        $parent = (int) $parent;
508
        $previous = (int) $previous;
509
        $id = (int) $id;
510
        $max_time_allowed = htmlentities($max_time_allowed);
511
        if (empty($max_time_allowed)) {
512
            $max_time_allowed = 0;
513
        }
514
        $sql = "SELECT COUNT(iid) AS num
515
                FROM $tbl_lp_item
516
                WHERE
517
                    c_id = $course_id AND
518
                    lp_id = ".$this->get_id()." AND
519
                    parent_item_id = $parent ";
520
521
        $res_count = Database::query($sql);
522
        $row = Database::fetch_array($res_count);
523
        $num = $row['num'];
524
525
        $tmp_previous = 0;
526
        $display_order = 0;
527
        $next = 0;
528
        if ($num > 0) {
529
            if (empty($previous)) {
530
                $sql = "SELECT iid, next_item_id, display_order
531
                        FROM $tbl_lp_item
532
                        WHERE
533
                            c_id = $course_id AND
534
                            lp_id = ".$this->get_id()." AND
535
                            parent_item_id = $parent AND
536
                            previous_item_id = 0 OR
537
                            previous_item_id = $parent";
538
                $result = Database::query($sql);
539
                $row = Database::fetch_array($result);
540
                if ($row) {
541
                    $next = $row['iid'];
542
                }
543
            } else {
544
                $previous = (int) $previous;
545
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
546
						FROM $tbl_lp_item
547
                        WHERE
548
                            c_id = $course_id AND
549
                            lp_id = ".$this->get_id()." AND
550
                            id = $previous";
551
                $result = Database::query($sql);
552
                $row = Database::fetch_array($result);
553
                if ($row) {
554
                    $tmp_previous = $row['iid'];
555
                    $next = $row['next_item_id'];
556
                    $display_order = $row['display_order'];
557
                }
558
            }
559
        }
560
561
        $id = (int) $id;
562
        $typeCleaned = Database::escape_string($type);
563
        $max_score = 100;
564
        if ($type === 'quiz' && $id) {
565
            $sql = 'SELECT SUM(ponderation)
566
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
567
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
568
                    ON
569
                        quiz_question.id = quiz_rel_question.question_id AND
570
                        quiz_question.c_id = quiz_rel_question.c_id
571
                    WHERE
572
                        quiz_rel_question.exercice_id = '.$id." AND
573
                        quiz_question.c_id = $course_id AND
574
                        quiz_rel_question.c_id = $course_id ";
575
            $rsQuiz = Database::query($sql);
576
            $max_score = Database::result($rsQuiz, 0, 0);
577
578
            // Disabling the exercise if we add it inside a LP
579
            $exercise = new Exercise($course_id);
580
            $exercise->read($id);
581
            $exercise->disable();
582
            $exercise->save();
583
        }
584
585
        $params = [
586
            'c_id' => $course_id,
587
            'lp_id' => $this->get_id(),
588
            'item_type' => $typeCleaned,
589
            'ref' => '',
590
            'title' => $title,
591
            'description' => $description,
592
            'path' => $id,
593
            'max_score' => $max_score,
594
            'parent_item_id' => $parent,
595
            'previous_item_id' => $previous,
596
            'next_item_id' => (int) $next,
597
            'display_order' => $display_order + 1,
598
            'prerequisite' => $prerequisites,
599
            'max_time_allowed' => $max_time_allowed,
600
            'min_score' => 0,
601
            'launch_data' => '',
602
        ];
603
604
        if ($prerequisites != 0) {
605
            $params['prerequisite'] = $prerequisites;
606
        }
607
608
        $new_item_id = Database::insert($tbl_lp_item, $params);
609
        if ($new_item_id) {
610
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
611
            Database::query($sql);
612
613
            if (!empty($next)) {
614
                $sql = "UPDATE $tbl_lp_item
615
                        SET previous_item_id = $new_item_id
616
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
617
                Database::query($sql);
618
            }
619
620
            // Update the item that should be before the new item.
621
            if (!empty($tmp_previous)) {
622
                $sql = "UPDATE $tbl_lp_item
623
                        SET next_item_id = $new_item_id
624
                        WHERE c_id = $course_id AND id = $tmp_previous";
625
                Database::query($sql);
626
            }
627
628
            // Update all the items after the new item.
629
            $sql = "UPDATE $tbl_lp_item
630
                        SET display_order = display_order + 1
631
                    WHERE
632
                        c_id = $course_id AND
633
                        lp_id = ".$this->get_id()." AND
634
                        iid <> $new_item_id AND
635
                        parent_item_id = $parent AND
636
                        display_order > $display_order";
637
            Database::query($sql);
638
639
            // Update the item that should come after the new item.
640
            $sql = "UPDATE $tbl_lp_item
641
                    SET ref = $new_item_id
642
                    WHERE c_id = $course_id AND iid = $new_item_id";
643
            Database::query($sql);
644
645
            $sql = "UPDATE $tbl_lp_item
646
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
647
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
648
            Database::query($sql);
649
650
            // Upload audio.
651
            if (!empty($_FILES['mp3']['name'])) {
652
                // Create the audio folder if it does not exist yet.
653
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
654
                if (!is_dir($filepath.'audio')) {
655
                    mkdir(
656
                        $filepath.'audio',
657
                        api_get_permissions_for_new_directories()
658
                    );
659
                    $audio_id = add_document(
660
                        $_course,
661
                        '/audio',
662
                        'folder',
663
                        0,
664
                        'audio',
665
                        '',
666
                        0,
667
                        true,
668
                        null,
669
                        $sessionId,
670
                        $userId
671
                    );
672
                    api_item_property_update(
673
                        $_course,
674
                        TOOL_DOCUMENT,
675
                        $audio_id,
676
                        'FolderCreated',
677
                        $userId,
678
                        null,
679
                        null,
680
                        null,
681
                        null,
682
                        $sessionId
683
                    );
684
                    api_item_property_update(
685
                        $_course,
686
                        TOOL_DOCUMENT,
687
                        $audio_id,
688
                        'invisible',
689
                        $userId,
690
                        null,
691
                        null,
692
                        null,
693
                        null,
694
                        $sessionId
695
                    );
696
                }
697
698
                $file_path = handle_uploaded_document(
699
                    $_course,
700
                    $_FILES['mp3'],
701
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
702
                    '/audio',
703
                    $userId,
704
                    '',
705
                    '',
706
                    '',
707
                    '',
708
                    false
709
                );
710
711
                // Store the mp3 file in the lp_item table.
712
                $sql = "UPDATE $tbl_lp_item SET
713
                          audio = '".Database::escape_string($file_path)."'
714
                        WHERE iid = '".intval($new_item_id)."'";
715
                Database::query($sql);
716
            }
717
        }
718
719
        return $new_item_id;
720
    }
721
722
    /**
723
     * Static admin function allowing addition of a learnpath to a course.
724
     *
725
     * @param string $courseCode
726
     * @param string $name
727
     * @param string $description
728
     * @param string $learnpath
729
     * @param string $origin
730
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
731
     * @param string $publicated_on
732
     * @param string $expired_on
733
     * @param int    $categoryId
734
     * @param int    $userId
735
     *
736
     * @return int The new learnpath ID on success, 0 on failure
737
     */
738
    public static function add_lp(
739
        $courseCode,
740
        $name,
741
        $description = '',
742
        $learnpath = 'guess',
743
        $origin = 'zip',
744
        $zipname = '',
745
        $publicated_on = '',
746
        $expired_on = '',
747
        $categoryId = 0,
748
        $userId = 0
749
    ) {
750
        global $charset;
751
752
        if (!empty($courseCode)) {
753
            $courseInfo = api_get_course_info($courseCode);
754
            $course_id = $courseInfo['real_id'];
755
        } else {
756
            $course_id = api_get_course_int_id();
757
            $courseInfo = api_get_course_info();
758
        }
759
760
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
761
        // Check course code exists.
762
        // Check lp_name doesn't exist, otherwise append something.
763
        $i = 0;
764
        $categoryId = (int) $categoryId;
765
        // Session id.
766
        $session_id = api_get_session_id();
767
        $userId = empty($userId) ? api_get_user_id() : $userId;
768
769
        if (empty($publicated_on)) {
770
            $publicated_on = null;
771
        } else {
772
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
773
        }
774
775
        if (empty($expired_on)) {
776
            $expired_on = null;
777
        } else {
778
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
779
        }
780
781
        $check_name = "SELECT * FROM $tbl_lp
782
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
783
        $res_name = Database::query($check_name);
784
785
        while (Database::num_rows($res_name)) {
786
            // There is already one such name, update the current one a bit.
787
            $i++;
788
            $name = $name.' - '.$i;
789
            $check_name = "SELECT * FROM $tbl_lp
790
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
791
            $res_name = Database::query($check_name);
792
        }
793
        // New name does not exist yet; keep it.
794
        // Escape description.
795
        // Kevin: added htmlentities().
796
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
797
        $type = 1;
798
        switch ($learnpath) {
799
            case 'guess':
800
                break;
801
            case 'dokeos':
802
            case 'chamilo':
803
                $type = 1;
804
                break;
805
            case 'aicc':
806
                break;
807
        }
808
809
        switch ($origin) {
810
            case 'zip':
811
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
812
                break;
813
            case 'manual':
814
            default:
815
                $get_max = "SELECT MAX(display_order)
816
                            FROM $tbl_lp WHERE c_id = $course_id";
817
                $res_max = Database::query($get_max);
818
                if (Database::num_rows($res_max) < 1) {
819
                    $dsp = 1;
820
                } else {
821
                    $row = Database::fetch_array($res_max);
822
                    $dsp = $row[0] + 1;
823
                }
824
825
                $params = [
826
                    'c_id' => $course_id,
827
                    'lp_type' => $type,
828
                    'name' => $name,
829
                    'description' => $description,
830
                    'path' => '',
831
                    'default_view_mod' => 'embedded',
832
                    'default_encoding' => 'UTF-8',
833
                    'display_order' => $dsp,
834
                    'content_maker' => 'Chamilo',
835
                    'content_local' => 'local',
836
                    'js_lib' => '',
837
                    'session_id' => $session_id,
838
                    'created_on' => api_get_utc_datetime(),
839
                    'modified_on' => api_get_utc_datetime(),
840
                    'publicated_on' => $publicated_on,
841
                    'expired_on' => $expired_on,
842
                    'category_id' => $categoryId,
843
                    'force_commit' => 0,
844
                    'content_license' => '',
845
                    'debug' => 0,
846
                    'theme' => '',
847
                    'preview_image' => '',
848
                    'author' => '',
849
                    'prerequisite' => 0,
850
                    'hide_toc_frame' => 0,
851
                    'seriousgame_mode' => 0,
852
                    'autolaunch' => 0,
853
                    'max_attempts' => 0,
854
                    'subscribe_users' => 0,
855
                    'accumulate_scorm_time' => 1,
856
                ];
857
                $id = Database::insert($tbl_lp, $params);
858
859
                if ($id > 0) {
860
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
861
                    Database::query($sql);
862
863
                    // Insert into item_property.
864
                    api_item_property_update(
865
                        $courseInfo,
866
                        TOOL_LEARNPATH,
867
                        $id,
868
                        'LearnpathAdded',
869
                        $userId
870
                    );
871
                    api_set_default_visibility(
872
                        $id,
873
                        TOOL_LEARNPATH,
874
                        0,
875
                        $courseInfo,
876
                        $session_id,
877
                        $userId
878
                    );
879
880
                    return $id;
881
                }
882
                break;
883
        }
884
    }
885
886
    /**
887
     * Auto completes the parents of an item in case it's been completed or passed.
888
     *
889
     * @param int $item Optional ID of the item from which to look for parents
890
     */
891
    public function autocomplete_parents($item)
892
    {
893
        $debug = $this->debug;
894
895
        if (empty($item)) {
896
            $item = $this->current;
897
        }
898
899
        $currentItem = $this->getItem($item);
900
        if ($currentItem) {
901
            $parent_id = $currentItem->get_parent();
902
            $parent = $this->getItem($parent_id);
903
            if ($parent) {
904
                // if $item points to an object and there is a parent.
905
                if ($debug) {
906
                    error_log(
907
                        'Autocompleting parent of item '.$item.' '.
908
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
909
                        0
910
                    );
911
                }
912
913
                // New experiment including failed and browsed in completed status.
914
                //$current_status = $currentItem->get_status();
915
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
916
                // Fixes chapter auto complete
917
                if (true) {
918
                    // If the current item is completed or passes or succeeded.
919
                    $updateParentStatus = true;
920
                    if ($debug) {
921
                        error_log('Status of current item is alright');
922
                    }
923
924
                    foreach ($parent->get_children() as $childItemId) {
925
                        $childItem = $this->getItem($childItemId);
926
927
                        // If children was not set try to get the info
928
                        if (empty($childItem->db_item_view_id)) {
929
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
930
                        }
931
932
                        // Check all his brothers (parent's children) for completion status.
933
                        if ($childItemId != $item) {
934
                            if ($debug) {
935
                                error_log(
936
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
937
                                    0
938
                                );
939
                            }
940
                            // Trying completing parents of failed and browsed items as well.
941
                            if ($childItem->status_is(
942
                                [
943
                                    'completed',
944
                                    'passed',
945
                                    'succeeded',
946
                                    'browsed',
947
                                    'failed',
948
                                ]
949
                            )
950
                            ) {
951
                                // Keep completion status to true.
952
                                continue;
953
                            } else {
954
                                if ($debug > 2) {
955
                                    error_log(
956
                                        '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,
957
                                        0
958
                                    );
959
                                }
960
                                $updateParentStatus = false;
961
                                break;
962
                            }
963
                        }
964
                    }
965
966
                    if ($updateParentStatus) {
967
                        // If all the children were completed:
968
                        $parent->set_status('completed');
969
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
970
                        // Force the status to "completed"
971
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
972
                        $this->update_queue[$parent->get_id()] = 'completed';
973
                        if ($debug) {
974
                            error_log(
975
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
976
                                print_r($this->update_queue, 1),
977
                                0
978
                            );
979
                        }
980
                        // Recursive call.
981
                        $this->autocomplete_parents($parent->get_id());
982
                    }
983
                }
984
            } else {
985
                if ($debug) {
986
                    error_log("Parent #$parent_id does not exists");
987
                }
988
            }
989
        } else {
990
            if ($debug) {
991
                error_log("#$item is an item that doesn't have parents");
992
            }
993
        }
994
    }
995
996
    /**
997
     * Closes the current resource.
998
     *
999
     * Stops the timer
1000
     * Saves into the database if required
1001
     * Clears the current resource data from this object
1002
     *
1003
     * @return bool True on success, false on failure
1004
     */
1005
    public function close()
1006
    {
1007
        if (empty($this->lp_id)) {
1008
            $this->error = 'Trying to close this learnpath but no ID is set';
1009
1010
            return false;
1011
        }
1012
        $this->current_time_stop = time();
1013
        $this->ordered_items = [];
1014
        $this->index = 0;
1015
        unset($this->lp_id);
1016
        //unset other stuff
1017
        return true;
1018
    }
1019
1020
    /**
1021
     * Static admin function allowing removal of a learnpath.
1022
     *
1023
     * @param array  $courseInfo
1024
     * @param int    $id         Learnpath ID
1025
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1026
     *
1027
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1028
     */
1029
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1030
    {
1031
        $course_id = api_get_course_int_id();
1032
        if (!empty($courseInfo)) {
1033
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1034
        }
1035
1036
        // TODO: Implement a way of getting this to work when the current object is not set.
1037
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1038
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1039
        if (!empty($id) && ($id != $this->lp_id)) {
1040
            return false;
1041
        }
1042
1043
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1044
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1045
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1046
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1047
1048
        // Delete lp item id.
1049
        foreach ($this->items as $lpItemId => $dummy) {
1050
            $sql = "DELETE FROM $lp_item_view
1051
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1052
            Database::query($sql);
1053
        }
1054
1055
        // Proposed by Christophe (nickname: clefevre)
1056
        $sql = "DELETE FROM $lp_item
1057
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1058
        Database::query($sql);
1059
1060
        $sql = "DELETE FROM $lp_view
1061
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1062
        Database::query($sql);
1063
1064
        self::toggle_publish($this->lp_id, 'i');
1065
1066
        if ($this->type == 2 || $this->type == 3) {
1067
            // This is a scorm learning path, delete the files as well.
1068
            $sql = "SELECT path FROM $lp
1069
                    WHERE iid = ".$this->lp_id;
1070
            $res = Database::query($sql);
1071
            if (Database::num_rows($res) > 0) {
1072
                $row = Database::fetch_array($res);
1073
                $path = $row['path'];
1074
                $sql = "SELECT id FROM $lp
1075
                        WHERE
1076
                            c_id = $course_id AND
1077
                            path = '$path' AND
1078
                            iid != ".$this->lp_id;
1079
                $res = Database::query($sql);
1080
                if (Database::num_rows($res) > 0) {
1081
                    // Another learning path uses this directory, so don't delete it.
1082
                    if ($this->debug > 2) {
1083
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1084
                    }
1085
                } else {
1086
                    // No other LP uses that directory, delete it.
1087
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1088
                    // The absolute system path for this course.
1089
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1090
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1091
                        if ($this->debug > 2) {
1092
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1093
                        }
1094
                        // Proposed by Christophe (clefevre).
1095
                        if (strcmp(substr($path, -2), "/.") == 0) {
1096
                            $path = substr($path, 0, -1); // Remove "." at the end.
1097
                        }
1098
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1099
                        rmdirr($course_scorm_dir.$path);
1100
                    }
1101
                }
1102
            }
1103
        }
1104
1105
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1106
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1107
        // Delete tools
1108
        $sql = "DELETE FROM $tbl_tool
1109
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1110
        Database::query($sql);
1111
1112
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1113
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1114
            $sql = "DELETE FROM $table
1115
                    WHERE
1116
                        lp_id = {$this->lp_id} AND
1117
                        c_id = $course_id ";
1118
            Database::query($sql);
1119
        }
1120
1121
        $sql = "DELETE FROM $lp
1122
                WHERE iid = ".$this->lp_id;
1123
        Database::query($sql);
1124
        // Updates the display order of all lps.
1125
        $this->update_display_order();
1126
1127
        api_item_property_update(
1128
            api_get_course_info(),
1129
            TOOL_LEARNPATH,
1130
            $this->lp_id,
1131
            'delete',
1132
            api_get_user_id()
1133
        );
1134
1135
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1136
            api_get_course_id(),
1137
            4,
1138
            $id,
1139
            api_get_session_id()
1140
        );
1141
1142
        if ($link_info !== false) {
1143
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1144
        }
1145
1146
        if (api_get_setting('search_enabled') == 'true') {
1147
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1148
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1149
        }
1150
    }
1151
1152
    /**
1153
     * Removes all the children of one item - dangerous!
1154
     *
1155
     * @param int $id Element ID of which children have to be removed
1156
     *
1157
     * @return int Total number of children removed
1158
     */
1159
    public function delete_children_items($id)
1160
    {
1161
        $course_id = $this->course_info['real_id'];
1162
1163
        $num = 0;
1164
        $id = (int) $id;
1165
        if (empty($id) || empty($course_id)) {
1166
            return false;
1167
        }
1168
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1169
        $sql = "SELECT * FROM $lp_item
1170
                WHERE c_id = $course_id AND parent_item_id = $id";
1171
        $res = Database::query($sql);
1172
        while ($row = Database::fetch_array($res)) {
1173
            $num += $this->delete_children_items($row['iid']);
1174
            $sql = "DELETE FROM $lp_item
1175
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1176
            Database::query($sql);
1177
            $num++;
1178
        }
1179
1180
        return $num;
1181
    }
1182
1183
    /**
1184
     * Removes an item from the current learnpath.
1185
     *
1186
     * @param int $id Elem ID (0 if first)
1187
     *
1188
     * @return int Number of elements moved
1189
     *
1190
     * @todo implement resource removal
1191
     */
1192
    public function delete_item($id)
1193
    {
1194
        $course_id = api_get_course_int_id();
1195
        $id = (int) $id;
1196
        // TODO: Implement the resource removal.
1197
        if (empty($id) || empty($course_id)) {
1198
            return false;
1199
        }
1200
        // First select item to get previous, next, and display order.
1201
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1202
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1203
        $res_sel = Database::query($sql_sel);
1204
        if (Database::num_rows($res_sel) < 1) {
1205
            return false;
1206
        }
1207
        $row = Database::fetch_array($res_sel);
1208
        $previous = $row['previous_item_id'];
1209
        $next = $row['next_item_id'];
1210
        $display = $row['display_order'];
1211
        $parent = $row['parent_item_id'];
1212
        $lp = $row['lp_id'];
1213
        // Delete children items.
1214
        $this->delete_children_items($id);
1215
        // Now delete the item.
1216
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1217
        Database::query($sql_del);
1218
        // Now update surrounding items.
1219
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1220
                    WHERE iid = $previous";
1221
        Database::query($sql_upd);
1222
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1223
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1224
        Database::query($sql_upd);
1225
        // Now update all following items with new display order.
1226
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1227
                    WHERE
1228
                        c_id = $course_id AND
1229
                        lp_id = $lp AND
1230
                        parent_item_id = $parent AND
1231
                        display_order > $display";
1232
        Database::query($sql_all);
1233
1234
        //Removing prerequisites since the item will not longer exist
1235
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1236
                    WHERE c_id = $course_id AND prerequisite = $id";
1237
        Database::query($sql_all);
1238
1239
        $sql = "UPDATE $lp_item
1240
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1241
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1242
        Database::query($sql);
1243
1244
        // Remove from search engine if enabled.
1245
        if (api_get_setting('search_enabled') === 'true') {
1246
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1247
            $sql = 'SELECT * FROM %s
1248
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1249
                    LIMIT 1';
1250
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1251
            $res = Database::query($sql);
1252
            if (Database::num_rows($res) > 0) {
1253
                $row2 = Database::fetch_array($res);
1254
                $di = new ChamiloIndexer();
1255
                $di->remove_document($row2['search_did']);
1256
            }
1257
            $sql = 'DELETE FROM %s
1258
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1259
                    LIMIT 1';
1260
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1261
            Database::query($sql);
1262
        }
1263
    }
1264
1265
    /**
1266
     * Updates an item's content in place.
1267
     *
1268
     * @param int    $id               Element ID
1269
     * @param int    $parent           Parent item ID
1270
     * @param int    $previous         Previous item ID
1271
     * @param string $title            Item title
1272
     * @param string $description      Item description
1273
     * @param string $prerequisites    Prerequisites (optional)
1274
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1275
     * @param int    $max_time_allowed
1276
     * @param string $url
1277
     *
1278
     * @return bool True on success, false on error
1279
     */
1280
    public function edit_item(
1281
        $id,
1282
        $parent,
1283
        $previous,
1284
        $title,
1285
        $description,
1286
        $prerequisites = '0',
1287
        $audio = [],
1288
        $max_time_allowed = 0,
1289
        $url = ''
1290
    ) {
1291
        $course_id = api_get_course_int_id();
1292
        $_course = api_get_course_info();
1293
        $id = (int) $id;
1294
1295
        if (empty($max_time_allowed)) {
1296
            $max_time_allowed = 0;
1297
        }
1298
1299
        if (empty($id) || empty($_course)) {
1300
            return false;
1301
        }
1302
1303
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1304
        $sql = "SELECT * FROM $tbl_lp_item
1305
                WHERE iid = $id";
1306
        $res_select = Database::query($sql);
1307
        $row_select = Database::fetch_array($res_select);
1308
        $audio_update_sql = '';
1309
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1310
            // Create the audio folder if it does not exist yet.
1311
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1312
            if (!is_dir($filepath.'audio')) {
1313
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1314
                $audio_id = add_document(
1315
                    $_course,
1316
                    '/audio',
1317
                    'folder',
1318
                    0,
1319
                    'audio'
1320
                );
1321
                api_item_property_update(
1322
                    $_course,
1323
                    TOOL_DOCUMENT,
1324
                    $audio_id,
1325
                    'FolderCreated',
1326
                    api_get_user_id(),
1327
                    null,
1328
                    null,
1329
                    null,
1330
                    null,
1331
                    api_get_session_id()
1332
                );
1333
                api_item_property_update(
1334
                    $_course,
1335
                    TOOL_DOCUMENT,
1336
                    $audio_id,
1337
                    'invisible',
1338
                    api_get_user_id(),
1339
                    null,
1340
                    null,
1341
                    null,
1342
                    null,
1343
                    api_get_session_id()
1344
                );
1345
            }
1346
1347
            // Upload file in documents.
1348
            $pi = pathinfo($audio['name']);
1349
            if ($pi['extension'] === 'mp3') {
1350
                $c_det = api_get_course_info($this->cc);
1351
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1352
                $path = handle_uploaded_document(
1353
                    $c_det,
1354
                    $audio,
1355
                    $bp,
1356
                    '/audio',
1357
                    api_get_user_id(),
1358
                    0,
1359
                    null,
1360
                    0,
1361
                    'rename',
1362
                    false,
1363
                    0
1364
                );
1365
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1366
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1367
            }
1368
        }
1369
1370
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1371
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1372
1373
        // TODO: htmlspecialchars to be checked for encoding related problems.
1374
        if ($same_parent && $same_previous) {
1375
            // Only update title and description.
1376
            $sql = "UPDATE $tbl_lp_item
1377
                    SET title = '".Database::escape_string($title)."',
1378
                        prerequisite = '".$prerequisites."',
1379
                        description = '".Database::escape_string($description)."'
1380
                        ".$audio_update_sql.",
1381
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1382
                    WHERE iid = $id";
1383
            Database::query($sql);
1384
        } else {
1385
            $old_parent = $row_select['parent_item_id'];
1386
            $old_previous = $row_select['previous_item_id'];
1387
            $old_next = $row_select['next_item_id'];
1388
            $old_order = $row_select['display_order'];
1389
            $old_prerequisite = $row_select['prerequisite'];
1390
            $old_max_time_allowed = $row_select['max_time_allowed'];
1391
1392
            /* BEGIN -- virtually remove the current item id */
1393
            /* for the next and previous item it is like the current item doesn't exist anymore */
1394
            if ($old_previous != 0) {
1395
                // Next
1396
                $sql = "UPDATE $tbl_lp_item
1397
                        SET next_item_id = $old_next
1398
                        WHERE iid = $old_previous";
1399
                Database::query($sql);
1400
            }
1401
1402
            if (!empty($old_next)) {
1403
                // Previous
1404
                $sql = "UPDATE $tbl_lp_item
1405
                        SET previous_item_id = $old_previous
1406
                        WHERE iid = $old_next";
1407
                Database::query($sql);
1408
            }
1409
1410
            // display_order - 1 for every item with a display_order
1411
            // bigger then the display_order of the current item.
1412
            $sql = "UPDATE $tbl_lp_item
1413
                    SET display_order = display_order - 1
1414
                    WHERE
1415
                        c_id = $course_id AND
1416
                        display_order > $old_order AND
1417
                        lp_id = ".$this->lp_id." AND
1418
                        parent_item_id = $old_parent";
1419
            Database::query($sql);
1420
            /* END -- virtually remove the current item id */
1421
1422
            /* BEGIN -- update the current item id to his new location */
1423
            if ($previous == 0) {
1424
                // Select the data of the item that should come after the current item.
1425
                $sql = "SELECT id, display_order
1426
                        FROM $tbl_lp_item
1427
                        WHERE
1428
                            c_id = $course_id AND
1429
                            lp_id = ".$this->lp_id." AND
1430
                            parent_item_id = $parent AND
1431
                            previous_item_id = $previous";
1432
                $res_select_old = Database::query($sql);
1433
                $row_select_old = Database::fetch_array($res_select_old);
1434
1435
                // If the new parent didn't have children before.
1436
                if (Database::num_rows($res_select_old) == 0) {
1437
                    $new_next = 0;
1438
                    $new_order = 1;
1439
                } else {
1440
                    $new_next = $row_select_old['id'];
1441
                    $new_order = $row_select_old['display_order'];
1442
                }
1443
            } else {
1444
                // Select the data of the item that should come before the current item.
1445
                $sql = "SELECT next_item_id, display_order
1446
                        FROM $tbl_lp_item
1447
                        WHERE iid = $previous";
1448
                $res_select_old = Database::query($sql);
1449
                $row_select_old = Database::fetch_array($res_select_old);
1450
                $new_next = $row_select_old['next_item_id'];
1451
                $new_order = $row_select_old['display_order'] + 1;
1452
            }
1453
1454
            // TODO: htmlspecialchars to be checked for encoding related problems.
1455
            // Update the current item with the new data.
1456
            $sql = "UPDATE $tbl_lp_item
1457
                    SET
1458
                        title = '".Database::escape_string($title)."',
1459
                        description = '".Database::escape_string($description)."',
1460
                        parent_item_id = $parent,
1461
                        previous_item_id = $previous,
1462
                        next_item_id = $new_next,
1463
                        display_order = $new_order
1464
                        $audio_update_sql
1465
                    WHERE iid = $id";
1466
            Database::query($sql);
1467
1468
            if ($previous != 0) {
1469
                // Update the previous item's next_item_id.
1470
                $sql = "UPDATE $tbl_lp_item
1471
                        SET next_item_id = $id
1472
                        WHERE iid = $previous";
1473
                Database::query($sql);
1474
            }
1475
1476
            if (!empty($new_next)) {
1477
                // Update the next item's previous_item_id.
1478
                $sql = "UPDATE $tbl_lp_item
1479
                        SET previous_item_id = $id
1480
                        WHERE iid = $new_next";
1481
                Database::query($sql);
1482
            }
1483
1484
            if ($old_prerequisite != $prerequisites) {
1485
                $sql = "UPDATE $tbl_lp_item
1486
                        SET prerequisite = '$prerequisites'
1487
                        WHERE iid = $id";
1488
                Database::query($sql);
1489
            }
1490
1491
            if ($old_max_time_allowed != $max_time_allowed) {
1492
                // update max time allowed
1493
                $sql = "UPDATE $tbl_lp_item
1494
                        SET max_time_allowed = $max_time_allowed
1495
                        WHERE iid = $id";
1496
                Database::query($sql);
1497
            }
1498
1499
            // Update all the items with the same or a bigger display_order than the current item.
1500
            $sql = "UPDATE $tbl_lp_item
1501
                    SET display_order = display_order + 1
1502
                    WHERE
1503
                       c_id = $course_id AND
1504
                       lp_id = ".$this->get_id()." AND
1505
                       iid <> $id AND
1506
                       parent_item_id = $parent AND
1507
                       display_order >= $new_order";
1508
            Database::query($sql);
1509
        }
1510
1511
        if ($row_select['item_type'] == 'link') {
1512
            $link = new Link();
1513
            $linkId = $row_select['path'];
1514
            $link->updateLink($linkId, $url);
1515
        }
1516
    }
1517
1518
    /**
1519
     * Updates an item's prereq in place.
1520
     *
1521
     * @param int    $id              Element ID
1522
     * @param string $prerequisite_id Prerequisite Element ID
1523
     * @param int    $minScore        Prerequisite min score
1524
     * @param int    $maxScore        Prerequisite max score
1525
     *
1526
     * @return bool True on success, false on error
1527
     */
1528
    public function edit_item_prereq(
1529
        $id,
1530
        $prerequisite_id,
1531
        $minScore = 0,
1532
        $maxScore = 100
1533
    ) {
1534
        $id = (int) $id;
1535
1536
        if (empty($id)) {
1537
            return false;
1538
        }
1539
1540
        $prerequisite_id = (int) $prerequisite_id;
1541
1542
        if (empty($minScore) || $minScore < 0) {
1543
            $minScore = 0;
1544
        }
1545
1546
        if (empty($maxScore) || $maxScore < 0) {
1547
            $maxScore = 100;
1548
        }
1549
1550
        $minScore = (float) $minScore;
1551
        $maxScore = (float) $maxScore;
1552
1553
        if (empty($prerequisite_id)) {
1554
            $prerequisite_id = 'NULL';
1555
            $minScore = 0;
1556
            $maxScore = 100;
1557
        }
1558
1559
        $table = Database::get_course_table(TABLE_LP_ITEM);
1560
        $sql = " UPDATE $table
1561
                 SET
1562
                    prerequisite = $prerequisite_id ,
1563
                    prerequisite_min_score = $minScore ,
1564
                    prerequisite_max_score = $maxScore
1565
                 WHERE iid = $id";
1566
        Database::query($sql);
1567
1568
        return true;
1569
    }
1570
1571
    /**
1572
     * Get the specific prefix index terms of this learning path.
1573
     *
1574
     * @param string $prefix
1575
     *
1576
     * @return array Array of terms
1577
     */
1578
    public function get_common_index_terms_by_prefix($prefix)
1579
    {
1580
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1581
        $terms = get_specific_field_values_list_by_prefix(
1582
            $prefix,
1583
            $this->cc,
1584
            TOOL_LEARNPATH,
1585
            $this->lp_id
1586
        );
1587
        $prefix_terms = [];
1588
        if (!empty($terms)) {
1589
            foreach ($terms as $term) {
1590
                $prefix_terms[] = $term['value'];
1591
            }
1592
        }
1593
1594
        return $prefix_terms;
1595
    }
1596
1597
    /**
1598
     * Gets the number of items currently completed.
1599
     *
1600
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1601
     *
1602
     * @return int The number of items currently completed
1603
     */
1604
    public function get_complete_items_count($failedStatusException = false)
1605
    {
1606
        $i = 0;
1607
        $completedStatusList = [
1608
            'completed',
1609
            'passed',
1610
            'succeeded',
1611
            'browsed',
1612
        ];
1613
1614
        if (!$failedStatusException) {
1615
            $completedStatusList[] = 'failed';
1616
        }
1617
1618
        foreach ($this->items as $id => $dummy) {
1619
            // Trying failed and browsed considered "progressed" as well.
1620
            if ($this->items[$id]->status_is($completedStatusList) &&
1621
                $this->items[$id]->get_type() != 'dir'
1622
            ) {
1623
                $i++;
1624
            }
1625
        }
1626
1627
        return $i;
1628
    }
1629
1630
    /**
1631
     * Gets the current item ID.
1632
     *
1633
     * @return int The current learnpath item id
1634
     */
1635
    public function get_current_item_id()
1636
    {
1637
        $current = 0;
1638
        if (!empty($this->current)) {
1639
            $current = (int) $this->current;
1640
        }
1641
1642
        return $current;
1643
    }
1644
1645
    /**
1646
     * Force to get the first learnpath item id.
1647
     *
1648
     * @return int The current learnpath item id
1649
     */
1650
    public function get_first_item_id()
1651
    {
1652
        $current = 0;
1653
        if (is_array($this->ordered_items)) {
1654
            $current = $this->ordered_items[0];
1655
        }
1656
1657
        return $current;
1658
    }
1659
1660
    /**
1661
     * Gets the total number of items available for viewing in this SCORM.
1662
     *
1663
     * @return int The total number of items
1664
     */
1665
    public function get_total_items_count()
1666
    {
1667
        return count($this->items);
1668
    }
1669
1670
    /**
1671
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1672
     *
1673
     * @return int The total no-chapters number of items
1674
     */
1675
    public function getTotalItemsCountWithoutDirs()
1676
    {
1677
        $total = 0;
1678
        $typeListNotToCount = self::getChapterTypes();
1679
        foreach ($this->items as $temp2) {
1680
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1681
                $total++;
1682
            }
1683
        }
1684
1685
        return $total;
1686
    }
1687
1688
    /**
1689
     *  Sets the first element URL.
1690
     */
1691
    public function first()
1692
    {
1693
        if ($this->debug > 0) {
1694
            error_log('In learnpath::first()', 0);
1695
            error_log('$this->last_item_seen '.$this->last_item_seen);
1696
        }
1697
1698
        // Test if the last_item_seen exists and is not a dir.
1699
        if (count($this->ordered_items) == 0) {
1700
            $this->index = 0;
1701
        }
1702
1703
        if (!empty($this->last_item_seen) &&
1704
            !empty($this->items[$this->last_item_seen]) &&
1705
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1706
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1707
            //&& !$this->items[$this->last_item_seen]->is_done()
1708
        ) {
1709
            if ($this->debug > 2) {
1710
                error_log(
1711
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1712
                    $this->items[$this->last_item_seen]->get_type()
1713
                );
1714
            }
1715
            $index = -1;
1716
            foreach ($this->ordered_items as $myindex => $item_id) {
1717
                if ($item_id == $this->last_item_seen) {
1718
                    $index = $myindex;
1719
                    break;
1720
                }
1721
            }
1722
            if ($index == -1) {
1723
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1724
                if ($this->debug > 2) {
1725
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1726
                }
1727
1728
                return false;
1729
            } else {
1730
                $this->last = $this->last_item_seen;
1731
                $this->current = $this->last_item_seen;
1732
                $this->index = $index;
1733
            }
1734
        } else {
1735
            if ($this->debug > 2) {
1736
                error_log('In learnpath::first() - No last item seen', 0);
1737
            }
1738
            $index = 0;
1739
            // Loop through all ordered items and stop at the first item that is
1740
            // not a directory *and* that has not been completed yet.
1741
            while (!empty($this->ordered_items[$index]) &&
1742
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1743
                (
1744
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1745
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1746
                ) && $index < $this->max_ordered_items) {
1747
                $index++;
1748
            }
1749
1750
            $this->last = $this->current;
1751
            // current is
1752
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1753
            $this->index = $index;
1754
            if ($this->debug > 2) {
1755
                error_log('$index '.$index);
1756
                error_log('In learnpath::first() - No last item seen');
1757
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1758
            }
1759
        }
1760
        if ($this->debug > 2) {
1761
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1762
        }
1763
    }
1764
1765
    /**
1766
     * Gets the js library from the database.
1767
     *
1768
     * @return string The name of the javascript library to be used
1769
     */
1770
    public function get_js_lib()
1771
    {
1772
        $lib = '';
1773
        if (!empty($this->js_lib)) {
1774
            $lib = $this->js_lib;
1775
        }
1776
1777
        return $lib;
1778
    }
1779
1780
    /**
1781
     * Gets the learnpath database ID.
1782
     *
1783
     * @return int Learnpath ID in the lp table
1784
     */
1785
    public function get_id()
1786
    {
1787
        if (!empty($this->lp_id)) {
1788
            return (int) $this->lp_id;
1789
        }
1790
1791
        return 0;
1792
    }
1793
1794
    /**
1795
     * Gets the last element URL.
1796
     *
1797
     * @return string URL to load into the viewer
1798
     */
1799
    public function get_last()
1800
    {
1801
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1802
        if (count($this->ordered_items) > 0) {
1803
            $this->index = count($this->ordered_items) - 1;
1804
1805
            return $this->ordered_items[$this->index];
1806
        }
1807
1808
        return false;
1809
    }
1810
1811
    /**
1812
     * Get the last element in the first level.
1813
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1814
     *
1815
     * @return mixed
1816
     */
1817
    public function getLastInFirstLevel()
1818
    {
1819
        try {
1820
            $lastId = Database::getManager()
1821
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1822
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1823
                ->setMaxResults(1)
1824
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1825
                ->getSingleScalarResult();
1826
1827
            return $lastId;
1828
        } catch (Exception $exception) {
1829
            return 0;
1830
        }
1831
    }
1832
1833
    /**
1834
     * Get the learning path name by id
1835
     *
1836
     * @param int $lpId
1837
     *
1838
     * @return mixed
1839
     */
1840
    public static function getLpNameById($lpId)
1841
    {
1842
        $em = Database::getManager();
1843
1844
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1845
            WHERE clp.iid = :iid')
1846
            ->setParameter('iid', $lpId)
1847
            ->getSingleScalarResult();
1848
    }
1849
1850
    /**
1851
     * Gets the navigation bar for the learnpath display screen.
1852
     *
1853
     * @param string $barId
1854
     *
1855
     * @return string The HTML string to use as a navigation bar
1856
     */
1857
    public function get_navigation_bar($barId = '')
1858
    {
1859
        if (empty($barId)) {
1860
            $barId = 'control-top';
1861
        }
1862
        $lpId = $this->lp_id;
1863
        $mycurrentitemid = $this->get_current_item_id();
1864
1865
        $reportingText = get_lang('Reporting');
1866
        $previousText = get_lang('ScormPrevious');
1867
        $nextText = get_lang('ScormNext');
1868
        $fullScreenText = get_lang('ScormExitFullScreen');
1869
1870
        $settings = api_get_configuration_value('lp_view_settings');
1871
        $display = isset($settings['display']) ? $settings['display'] : false;
1872
        $reportingIcon = '
1873
            <a class="icon-toolbar"
1874
                id="stats_link"
1875
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1876
                onclick="window.parent.API.save_asset(); return true;"
1877
                target="content_name" title="'.$reportingText.'">
1878
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1879
            </a>';
1880
1881
        if (!empty($display)) {
1882
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1883
            if ($showReporting === false) {
1884
                $reportingIcon = '';
1885
            }
1886
        }
1887
1888
        $hideArrows = false;
1889
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1890
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1891
        }
1892
1893
        $previousIcon = '';
1894
        $nextIcon = '';
1895
        if ($hideArrows === false) {
1896
            $previousIcon = '
1897
                <a class="icon-toolbar" id="scorm-previous" href="#"
1898
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1899
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1900
                </a>';
1901
1902
            $nextIcon = '
1903
                <a class="icon-toolbar" id="scorm-next" href="#"
1904
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1905
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1906
                </a>';
1907
        }
1908
1909
        if ($this->mode === 'fullscreen') {
1910
            $navbar = '
1911
                  <span id="'.$barId.'" class="buttons">
1912
                    '.$reportingIcon.'
1913
                    '.$previousIcon.'
1914
                    '.$nextIcon.'
1915
                    <a class="icon-toolbar" id="view-embedded"
1916
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1917
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1918
                    </a>
1919
                  </span>';
1920
        } else {
1921
            $navbar = '
1922
                 <span id="'.$barId.'" class="buttons text-right">
1923
                    '.$reportingIcon.'
1924
                    '.$previousIcon.'
1925
                    '.$nextIcon.'
1926
                </span>';
1927
        }
1928
1929
        return $navbar;
1930
    }
1931
1932
    /**
1933
     * Gets the next resource in queue (url).
1934
     *
1935
     * @return string URL to load into the viewer
1936
     */
1937
    public function get_next_index()
1938
    {
1939
        // TODO
1940
        $index = $this->index;
1941
        $index++;
1942
        while (
1943
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1944
            $index < $this->max_ordered_items
1945
        ) {
1946
            $index++;
1947
            if ($index == $this->max_ordered_items) {
1948
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1949
                    return $this->index;
1950
                }
1951
1952
                return $index;
1953
            }
1954
        }
1955
        if (empty($this->ordered_items[$index])) {
1956
            return $this->index;
1957
        }
1958
1959
        return $index;
1960
    }
1961
1962
    /**
1963
     * Gets item_id for the next element.
1964
     *
1965
     * @return int Next item (DB) ID
1966
     */
1967
    public function get_next_item_id()
1968
    {
1969
        $new_index = $this->get_next_index();
1970
        if (!empty($new_index)) {
1971
            if (isset($this->ordered_items[$new_index])) {
1972
                return $this->ordered_items[$new_index];
1973
            }
1974
        }
1975
1976
        return 0;
1977
    }
1978
1979
    /**
1980
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1981
     *
1982
     * Generally, the package provided is in the form of a zip file, so the function
1983
     * has been written to test a zip file. If not a zip, the function will return the
1984
     * default return value: ''
1985
     *
1986
     * @param string $file_path the path to the file
1987
     * @param string $file_name the original name of the file
1988
     *
1989
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1990
     */
1991
    public static function getPackageType($file_path, $file_name)
1992
    {
1993
        // Get name of the zip file without the extension.
1994
        $file_info = pathinfo($file_name);
1995
        $extension = $file_info['extension']; // Extension only.
1996
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1997
                'dll',
1998
                'exe',
1999
            ])) {
2000
            return 'oogie';
2001
        }
2002
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2003
                'dll',
2004
                'exe',
2005
            ])) {
2006
            return 'woogie';
2007
        }
2008
2009
        $zipFile = new PclZip($file_path);
2010
        // Check the zip content (real size and file extension).
2011
        $zipContentArray = $zipFile->listContent();
2012
        $package_type = '';
2013
        $manifest = '';
2014
        $aicc_match_crs = 0;
2015
        $aicc_match_au = 0;
2016
        $aicc_match_des = 0;
2017
        $aicc_match_cst = 0;
2018
        $countItems = 0;
2019
2020
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2021
        if (is_array($zipContentArray)) {
2022
            $countItems = count($zipContentArray);
2023
            if ($countItems > 0) {
2024
                foreach ($zipContentArray as $thisContent) {
2025
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2026
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2027
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2028
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2029
                        $package_type = 'scorm';
2030
                        break; // Exit the foreach loop.
2031
                    } elseif (
2032
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2033
                        in_array(
2034
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2035
                            ['crs', 'au', 'des', 'cst']
2036
                        )
2037
                    ) {
2038
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2039
                        switch ($ext) {
2040
                            case 'crs':
2041
                                $aicc_match_crs = 1;
2042
                                break;
2043
                            case 'au':
2044
                                $aicc_match_au = 1;
2045
                                break;
2046
                            case 'des':
2047
                                $aicc_match_des = 1;
2048
                                break;
2049
                            case 'cst':
2050
                                $aicc_match_cst = 1;
2051
                                break;
2052
                            default:
2053
                                break;
2054
                        }
2055
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2056
                    } else {
2057
                        $package_type = '';
2058
                    }
2059
                }
2060
            }
2061
        }
2062
2063
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2064
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2065
            $package_type = 'aicc';
2066
        }
2067
2068
        // Try with chamilo course builder
2069
        if (empty($package_type)) {
2070
            // Sometimes users will try to upload an empty zip, or a zip with
2071
            // only a folder. Catch that and make the calling function aware.
2072
            // If the single file was the imsmanifest.xml, then $package_type
2073
            // would be 'scorm' and we wouldn't be here.
2074
            if ($countItems < 2) {
2075
                return 'error-empty-package';
2076
            }
2077
            $package_type = 'chamilo';
2078
        }
2079
2080
        return $package_type;
2081
    }
2082
2083
    /**
2084
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2085
     *
2086
     * @return string URL to load into the viewer
2087
     */
2088
    public function get_previous_index()
2089
    {
2090
        $index = $this->index;
2091
        if (isset($this->ordered_items[$index - 1])) {
2092
            $index--;
2093
            while (isset($this->ordered_items[$index]) &&
2094
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2095
            ) {
2096
                $index--;
2097
                if ($index < 0) {
2098
                    return $this->index;
2099
                }
2100
            }
2101
        }
2102
2103
        return $index;
2104
    }
2105
2106
    /**
2107
     * Gets item_id for the next element.
2108
     *
2109
     * @return int Previous item (DB) ID
2110
     */
2111
    public function get_previous_item_id()
2112
    {
2113
        $index = $this->get_previous_index();
2114
2115
        return $this->ordered_items[$index];
2116
    }
2117
2118
    /**
2119
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2120
     *
2121
     * @param int    $lpItemId
2122
     * @param string $autostart
2123
     *
2124
     * @return string The mediaplayer HTML
2125
     */
2126
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2127
    {
2128
        $course_id = api_get_course_int_id();
2129
        $courseInfo = api_get_course_info();
2130
        $lpItemId = (int) $lpItemId;
2131
2132
        if (empty($courseInfo) || empty($lpItemId)) {
2133
            return '';
2134
        }
2135
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2136
2137
        if (empty($item)) {
2138
            return '';
2139
        }
2140
2141
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2142
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2143
        $itemViewId = (int) $item->db_item_view_id;
2144
2145
        // Getting all the information about the item.
2146
        $sql = "SELECT lp_view.status
2147
                FROM $tbl_lp_item as lpi
2148
                INNER JOIN $tbl_lp_item_view as lp_view
2149
                ON (lpi.iid = lp_view.lp_item_id)
2150
                WHERE
2151
                    lp_view.iid = $itemViewId AND
2152
                    lpi.iid = $lpItemId AND
2153
                    lp_view.c_id = $course_id";
2154
        $result = Database::query($sql);
2155
        $row = Database::fetch_assoc($result);
2156
        $output = '';
2157
        $audio = $item->audio;
2158
2159
        if (!empty($audio)) {
2160
            $list = $_SESSION['oLP']->get_toc();
2161
2162
            switch ($item->get_type()) {
2163
                case 'quiz':
2164
                    $type_quiz = false;
2165
                    foreach ($list as $toc) {
2166
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2167
                            $type_quiz = true;
2168
                        }
2169
                    }
2170
2171
                    if ($type_quiz) {
2172
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2173
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2174
                        } else {
2175
                            $autostart_audio = $autostart;
2176
                        }
2177
                    }
2178
                    break;
2179
                case TOOL_READOUT_TEXT:
2180
                    $autostart_audio = 'false';
2181
                    break;
2182
                default:
2183
                    $autostart_audio = 'true';
2184
            }
2185
2186
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2187
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2188
2189
            $player = Display::getMediaPlayer(
2190
                $file,
2191
                [
2192
                    'id' => 'lp_audio_media_player',
2193
                    'url' => $url,
2194
                    'autoplay' => $autostart_audio,
2195
                    'width' => '100%',
2196
                ]
2197
            );
2198
2199
            // The mp3 player.
2200
            $output = '<div id="container">';
2201
            $output .= $player;
2202
            $output .= '</div>';
2203
        }
2204
2205
        return $output;
2206
    }
2207
2208
    /**
2209
     * @param int   $studentId
2210
     * @param int   $prerequisite
2211
     * @param array $courseInfo
2212
     * @param int   $sessionId
2213
     *
2214
     * @return bool
2215
     */
2216
    public static function isBlockedByPrerequisite(
2217
        $studentId,
2218
        $prerequisite,
2219
        $courseInfo,
2220
        $sessionId
2221
    ) {
2222
        if (empty($courseInfo)) {
2223
            return false;
2224
        }
2225
2226
        $courseId = $courseInfo['real_id'];
2227
2228
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2229
        if ($allow) {
2230
            if (api_is_allowed_to_edit() ||
2231
                api_is_platform_admin(true) ||
2232
                api_is_drh() ||
2233
                api_is_coach($sessionId, $courseId, false)
2234
            ) {
2235
                return false;
2236
            }
2237
        }
2238
2239
        $isBlocked = false;
2240
        if (!empty($prerequisite)) {
2241
            $progress = self::getProgress(
2242
                $prerequisite,
2243
                $studentId,
2244
                $courseId,
2245
                $sessionId
2246
            );
2247
            if ($progress < 100) {
2248
                $isBlocked = true;
2249
            }
2250
2251
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2252
                // Block if it does not exceed minimum time
2253
                // Minimum time (in minutes) to pass the learning path
2254
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2255
2256
                if ($accumulateWorkTime > 0) {
2257
                    // Total time in course (sum of times in learning paths from course)
2258
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2259
2260
                    // Connect with the plugin_licences_course_session table
2261
                    // which indicates what percentage of the time applies
2262
                    // Minimum connection percentage
2263
                    $perc = 100;
2264
                    // Time from the course
2265
                    $tc = $accumulateWorkTimeTotal;
2266
2267
                    // Percentage of the learning paths
2268
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2269
                    // Minimum time for each learning path
2270
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2271
2272
                    // Spent time (in seconds) so far in the learning path
2273
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2274
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2275
2276
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2277
                        $isBlocked = true;
2278
                    }
2279
                }
2280
            }
2281
        }
2282
2283
        return $isBlocked;
2284
    }
2285
2286
    /**
2287
     * Checks if the learning path is visible for student after the progress
2288
     * of its prerequisite is completed, considering the time availability and
2289
     * the LP visibility.
2290
     *
2291
     * @param int   $lp_id
2292
     * @param int   $student_id
2293
     * @param array $courseInfo
2294
     * @param int   $sessionId
2295
     *
2296
     * @return bool
2297
     */
2298
    public static function is_lp_visible_for_student(
2299
        $lp_id,
2300
        $student_id,
2301
        $courseInfo = [],
2302
        $sessionId = 0
2303
    ) {
2304
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2305
        $lp_id = (int) $lp_id;
2306
        $sessionId = (int) $sessionId;
2307
2308
        if (empty($courseInfo)) {
2309
            return false;
2310
        }
2311
2312
        if (empty($sessionId)) {
2313
            $sessionId = api_get_session_id();
2314
        }
2315
2316
        $courseId = $courseInfo['real_id'];
2317
2318
        $itemInfo = api_get_item_property_info(
2319
            $courseId,
2320
            TOOL_LEARNPATH,
2321
            $lp_id,
2322
            $sessionId
2323
        );
2324
2325
        // If the item was deleted.
2326
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2327
            return false;
2328
        }
2329
2330
        // @todo remove this query and load the row info as a parameter
2331
        $table = Database::get_course_table(TABLE_LP_MAIN);
2332
        // Get current prerequisite
2333
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2334
                FROM $table
2335
                WHERE iid = $lp_id";
2336
        $rs = Database::query($sql);
2337
        $now = time();
2338
        if (Database::num_rows($rs) > 0) {
2339
            $row = Database::fetch_array($rs, 'ASSOC');
2340
2341
            if (!empty($row['category_id'])) {
2342
                $category = self::getCategory($row['category_id']);
2343
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2344
                    return false;
2345
                }
2346
            }
2347
2348
            $prerequisite = $row['prerequisite'];
2349
            $is_visible = true;
2350
2351
            $isBlocked = self::isBlockedByPrerequisite(
2352
                $student_id,
2353
                $prerequisite,
2354
                $courseInfo,
2355
                $sessionId
2356
            );
2357
2358
            if ($isBlocked) {
2359
                $is_visible = false;
2360
            }
2361
2362
            // Also check the time availability of the LP
2363
            if ($is_visible) {
2364
                // Adding visibility restrictions
2365
                if (!empty($row['publicated_on'])) {
2366
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2367
                        $is_visible = false;
2368
                    }
2369
                }
2370
                // Blocking empty start times see BT#2800
2371
                global $_custom;
2372
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2373
                    $_custom['lps_hidden_when_no_start_date']
2374
                ) {
2375
                    if (empty($row['publicated_on'])) {
2376
                        $is_visible = false;
2377
                    }
2378
                }
2379
2380
                if (!empty($row['expired_on'])) {
2381
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2382
                        $is_visible = false;
2383
                    }
2384
                }
2385
            }
2386
2387
            if ($is_visible) {
2388
                $subscriptionSettings = self::getSubscriptionSettings();
2389
2390
                // Check if the subscription users/group to a LP is ON
2391
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2392
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2393
                ) {
2394
                    // Try group
2395
                    $is_visible = false;
2396
                    // Checking only the user visibility
2397
                    $userVisibility = api_get_item_visibility(
2398
                        $courseInfo,
2399
                        'learnpath',
2400
                        $row['id'],
2401
                        $sessionId,
2402
                        $student_id,
2403
                        'LearnpathSubscription'
2404
                    );
2405
2406
                    if ($userVisibility == 1) {
2407
                        $is_visible = true;
2408
                    } else {
2409
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2410
                        if (!empty($userGroups)) {
2411
                            foreach ($userGroups as $groupInfo) {
2412
                                $groupId = $groupInfo['iid'];
2413
                                $userVisibility = api_get_item_visibility(
2414
                                    $courseInfo,
2415
                                    'learnpath',
2416
                                    $row['id'],
2417
                                    $sessionId,
2418
                                    null,
2419
                                    'LearnpathSubscription',
2420
                                    $groupId
2421
                                );
2422
2423
                                if ($userVisibility == 1) {
2424
                                    $is_visible = true;
2425
                                    break;
2426
                                }
2427
                            }
2428
                        }
2429
                    }
2430
                }
2431
            }
2432
2433
            return $is_visible;
2434
        }
2435
2436
        return false;
2437
    }
2438
2439
    /**
2440
     * @param int $lpId
2441
     * @param int $userId
2442
     * @param int $courseId
2443
     * @param int $sessionId
2444
     *
2445
     * @return int
2446
     */
2447
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2448
    {
2449
        $lpId = (int) $lpId;
2450
        $userId = (int) $userId;
2451
        $courseId = (int) $courseId;
2452
        $sessionId = (int) $sessionId;
2453
2454
        $sessionCondition = api_get_session_condition($sessionId);
2455
        $table = Database::get_course_table(TABLE_LP_VIEW);
2456
        $sql = "SELECT progress FROM $table
2457
                WHERE
2458
                    c_id = $courseId AND
2459
                    lp_id = $lpId AND
2460
                    user_id = $userId $sessionCondition ";
2461
        $res = Database::query($sql);
2462
2463
        $progress = 0;
2464
        if (Database::num_rows($res) > 0) {
2465
            $row = Database::fetch_array($res);
2466
            $progress = (int) $row['progress'];
2467
        }
2468
2469
        return $progress;
2470
    }
2471
2472
    /**
2473
     * @param array $lpList
2474
     * @param int   $userId
2475
     * @param int   $courseId
2476
     * @param int   $sessionId
2477
     *
2478
     * @return array
2479
     */
2480
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2481
    {
2482
        $lpList = array_map('intval', $lpList);
2483
        if (empty($lpList)) {
2484
            return [];
2485
        }
2486
2487
        $lpList = implode("','", $lpList);
2488
2489
        $userId = (int) $userId;
2490
        $courseId = (int) $courseId;
2491
        $sessionId = (int) $sessionId;
2492
2493
        $sessionCondition = api_get_session_condition($sessionId);
2494
        $table = Database::get_course_table(TABLE_LP_VIEW);
2495
        $sql = "SELECT lp_id, progress FROM $table
2496
                WHERE
2497
                    c_id = $courseId AND
2498
                    lp_id IN ('".$lpList."') AND
2499
                    user_id = $userId $sessionCondition ";
2500
        $res = Database::query($sql);
2501
2502
        if (Database::num_rows($res) > 0) {
2503
            $list = [];
2504
            while ($row = Database::fetch_array($res)) {
2505
                $list[$row['lp_id']] = $row['progress'];
2506
            }
2507
2508
            return $list;
2509
        }
2510
2511
        return [];
2512
    }
2513
2514
    /**
2515
     * Displays a progress bar
2516
     * completed so far.
2517
     *
2518
     * @param int    $percentage Progress value to display
2519
     * @param string $text_add   Text to display near the progress value
2520
     *
2521
     * @return string HTML string containing the progress bar
2522
     */
2523
    public static function get_progress_bar($percentage = -1, $text_add = '')
2524
    {
2525
        $text = $percentage.$text_add;
2526
        $output = '<div class="progress">
2527
            <div id="progress_bar_value"
2528
                class="progress-bar progress-bar-success" role="progressbar"
2529
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2530
            '.$text.'
2531
            </div>
2532
        </div>';
2533
2534
        return $output;
2535
    }
2536
2537
    /**
2538
     * @param string $mode can be '%' or 'abs'
2539
     *                     otherwise this value will be used $this->progress_bar_mode
2540
     *
2541
     * @return string
2542
     */
2543
    public function getProgressBar($mode = null)
2544
    {
2545
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2546
2547
        return self::get_progress_bar($percentage, $text_add);
2548
    }
2549
2550
    /**
2551
     * Gets the progress bar info to display inside the progress bar.
2552
     * Also used by scorm_api.php.
2553
     *
2554
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2555
     *                     we display a number of completed elements per total elements
2556
     * @param int    $add  Additional steps to fake as completed
2557
     *
2558
     * @return array Percentage or number and symbol (% or /xx)
2559
     */
2560
    public function get_progress_bar_text($mode = '', $add = 0)
2561
    {
2562
        if (empty($mode)) {
2563
            $mode = $this->progress_bar_mode;
2564
        }
2565
        $text = '';
2566
        $percentage = 0;
2567
        // If the option to use the score as progress is set for this learning
2568
        // path, then the rules are completely different: we assume only one
2569
        // item exists and the progress of the LP depends on the score
2570
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2571
        if ($scoreAsProgressSetting === true) {
2572
            $scoreAsProgress = $this->getUseScoreAsProgress();
2573
            if ($scoreAsProgress) {
2574
                // Get single item's score
2575
                $itemId = $this->get_current_item_id();
2576
                $item = $this->getItem($itemId);
2577
                $score = $item->get_score();
2578
                $maxScore = $item->get_max();
2579
                if ($mode = '%') {
2580
                    if (!empty($maxScore)) {
2581
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2582
                    }
2583
                    $percentage = number_format($percentage, 0);
2584
                    $text = '%';
2585
                } else {
2586
                    $percentage = $score;
2587
                    $text = '/'.$maxScore;
2588
                }
2589
2590
                return [$percentage, $text];
2591
            }
2592
        }
2593
        // otherwise just continue the normal processing of progress
2594
        $total_items = $this->getTotalItemsCountWithoutDirs();
2595
        $completeItems = $this->get_complete_items_count();
2596
        if ($add != 0) {
2597
            $completeItems += $add;
2598
        }
2599
        if ($completeItems > $total_items) {
2600
            $completeItems = $total_items;
2601
        }
2602
        if ($mode == '%') {
2603
            if ($total_items > 0) {
2604
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2605
            }
2606
            $percentage = number_format($percentage, 0);
2607
            $text = '%';
2608
        } elseif ($mode === 'abs') {
2609
            $percentage = $completeItems;
2610
            $text = '/'.$total_items;
2611
        }
2612
2613
        return [
2614
            $percentage,
2615
            $text,
2616
        ];
2617
    }
2618
2619
    /**
2620
     * Gets the progress bar mode.
2621
     *
2622
     * @return string The progress bar mode attribute
2623
     */
2624
    public function get_progress_bar_mode()
2625
    {
2626
        if (!empty($this->progress_bar_mode)) {
2627
            return $this->progress_bar_mode;
2628
        }
2629
2630
        return '%';
2631
    }
2632
2633
    /**
2634
     * Gets the learnpath theme (remote or local).
2635
     *
2636
     * @return string Learnpath theme
2637
     */
2638
    public function get_theme()
2639
    {
2640
        if (!empty($this->theme)) {
2641
            return $this->theme;
2642
        }
2643
2644
        return '';
2645
    }
2646
2647
    /**
2648
     * Gets the learnpath session id.
2649
     *
2650
     * @return int
2651
     */
2652
    public function get_lp_session_id()
2653
    {
2654
        if (!empty($this->lp_session_id)) {
2655
            return (int) $this->lp_session_id;
2656
        }
2657
2658
        return 0;
2659
    }
2660
2661
    /**
2662
     * Gets the learnpath image.
2663
     *
2664
     * @return string Web URL of the LP image
2665
     */
2666
    public function get_preview_image()
2667
    {
2668
        if (!empty($this->preview_image)) {
2669
            return $this->preview_image;
2670
        }
2671
2672
        return '';
2673
    }
2674
2675
    /**
2676
     * @param string $size
2677
     * @param string $path_type
2678
     *
2679
     * @return bool|string
2680
     */
2681
    public function get_preview_image_path($size = null, $path_type = 'web')
2682
    {
2683
        $preview_image = $this->get_preview_image();
2684
        if (isset($preview_image) && !empty($preview_image)) {
2685
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2686
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2687
2688
            if (isset($size)) {
2689
                $info = pathinfo($preview_image);
2690
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2691
2692
                if (file_exists($image_sys_path.$image_custom_size)) {
2693
                    if ($path_type == 'web') {
2694
                        return $image_path.$image_custom_size;
2695
                    } else {
2696
                        return $image_sys_path.$image_custom_size;
2697
                    }
2698
                }
2699
            } else {
2700
                if ($path_type == 'web') {
2701
                    return $image_path.$preview_image;
2702
                } else {
2703
                    return $image_sys_path.$preview_image;
2704
                }
2705
            }
2706
        }
2707
2708
        return false;
2709
    }
2710
2711
    /**
2712
     * Gets the learnpath author.
2713
     *
2714
     * @return string LP's author
2715
     */
2716
    public function get_author()
2717
    {
2718
        if (!empty($this->author)) {
2719
            return $this->author;
2720
        }
2721
2722
        return '';
2723
    }
2724
2725
    /**
2726
     * Gets hide table of contents.
2727
     *
2728
     * @return int
2729
     */
2730
    public function getHideTableOfContents()
2731
    {
2732
        return (int) $this->hide_toc_frame;
2733
    }
2734
2735
    /**
2736
     * Generate a new prerequisites string for a given item. If this item was a sco and
2737
     * its prerequisites were strings (instead of IDs), then transform those strings into
2738
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2739
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2740
     * same rule as the scormExport() method.
2741
     *
2742
     * @param int $item_id Item ID
2743
     *
2744
     * @return string Prerequisites string ready for the export as SCORM
2745
     */
2746
    public function get_scorm_prereq_string($item_id)
2747
    {
2748
        if ($this->debug > 0) {
2749
            error_log('In learnpath::get_scorm_prereq_string()');
2750
        }
2751
        if (!is_object($this->items[$item_id])) {
2752
            return false;
2753
        }
2754
        /** @var learnpathItem $oItem */
2755
        $oItem = $this->items[$item_id];
2756
        $prereq = $oItem->get_prereq_string();
2757
2758
        if (empty($prereq)) {
2759
            return '';
2760
        }
2761
        if (preg_match('/^\d+$/', $prereq) &&
2762
            isset($this->items[$prereq]) &&
2763
            is_object($this->items[$prereq])
2764
        ) {
2765
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2766
            // then simply return it (with the ITEM_ prefix).
2767
            //return 'ITEM_' . $prereq;
2768
            return $this->items[$prereq]->ref;
2769
        } else {
2770
            if (isset($this->refs_list[$prereq])) {
2771
                // It's a simple string item from which the ID can be found in the refs list,
2772
                // so we can transform it directly to an ID for export.
2773
                return $this->items[$this->refs_list[$prereq]]->ref;
2774
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2775
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2776
            } else {
2777
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2778
                // and replace them, one by one, by the internal IDs (chamilo db)
2779
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2780
                // by a space as well.
2781
                $find = [
2782
                    '&',
2783
                    '|',
2784
                    '~',
2785
                    '=',
2786
                    '<>',
2787
                    '{',
2788
                    '}',
2789
                    '*',
2790
                    '(',
2791
                    ')',
2792
                ];
2793
                $replace = [
2794
                    ' ',
2795
                    ' ',
2796
                    ' ',
2797
                    ' ',
2798
                    ' ',
2799
                    ' ',
2800
                    ' ',
2801
                    ' ',
2802
                    ' ',
2803
                    ' ',
2804
                ];
2805
                $prereq_mod = str_replace($find, $replace, $prereq);
2806
                $ids = explode(' ', $prereq_mod);
2807
                foreach ($ids as $id) {
2808
                    $id = trim($id);
2809
                    if (isset($this->refs_list[$id])) {
2810
                        $prereq = preg_replace(
2811
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2812
                            'ITEM_'.$this->refs_list[$id],
2813
                            $prereq
2814
                        );
2815
                    }
2816
                }
2817
2818
                return $prereq;
2819
            }
2820
        }
2821
    }
2822
2823
    /**
2824
     * Returns the XML DOM document's node.
2825
     *
2826
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2827
     * @param string   $id       The identifier to look for
2828
     *
2829
     * @return mixed The reference to the element found with that identifier. False if not found
2830
     */
2831
    public function get_scorm_xml_node(&$children, $id)
2832
    {
2833
        for ($i = 0; $i < $children->length; $i++) {
2834
            $item_temp = $children->item($i);
2835
            if ($item_temp->nodeName == 'item') {
2836
                if ($item_temp->getAttribute('identifier') == $id) {
2837
                    return $item_temp;
2838
                }
2839
            }
2840
            $subchildren = $item_temp->childNodes;
2841
            if ($subchildren && $subchildren->length > 0) {
2842
                $val = $this->get_scorm_xml_node($subchildren, $id);
2843
                if (is_object($val)) {
2844
                    return $val;
2845
                }
2846
            }
2847
        }
2848
2849
        return false;
2850
    }
2851
2852
    /**
2853
     * Gets the status list for all LP's items.
2854
     *
2855
     * @return array Array of [index] => [item ID => current status]
2856
     */
2857
    public function get_items_status_list()
2858
    {
2859
        $list = [];
2860
        foreach ($this->ordered_items as $item_id) {
2861
            $list[] = [
2862
                $item_id => $this->items[$item_id]->get_status(),
2863
            ];
2864
        }
2865
2866
        return $list;
2867
    }
2868
2869
    /**
2870
     * Return the number of interactions for the given learnpath Item View ID.
2871
     * This method can be used as static.
2872
     *
2873
     * @param int $lp_iv_id  Item View ID
2874
     * @param int $course_id course id
2875
     *
2876
     * @return int
2877
     */
2878
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2879
    {
2880
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2881
        $lp_iv_id = (int) $lp_iv_id;
2882
        $course_id = (int) $course_id;
2883
2884
        $sql = "SELECT count(*) FROM $table
2885
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2886
        $res = Database::query($sql);
2887
        $num = 0;
2888
        if (Database::num_rows($res)) {
2889
            $row = Database::fetch_array($res);
2890
            $num = $row[0];
2891
        }
2892
2893
        return $num;
2894
    }
2895
2896
    /**
2897
     * Return the interactions as an array for the given lp_iv_id.
2898
     * This method can be used as static.
2899
     *
2900
     * @param int $lp_iv_id Learnpath Item View ID
2901
     *
2902
     * @return array
2903
     *
2904
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2905
     */
2906
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2907
    {
2908
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2909
        $list = [];
2910
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2911
        $lp_iv_id = (int) $lp_iv_id;
2912
2913
        if (empty($lp_iv_id) || empty($course_id)) {
2914
            return [];
2915
        }
2916
2917
        $sql = "SELECT * FROM $table
2918
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2919
                ORDER BY order_id ASC";
2920
        $res = Database::query($sql);
2921
        $num = Database::num_rows($res);
2922
        if ($num > 0) {
2923
            $list[] = [
2924
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2925
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2926
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2927
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2928
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2929
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2930
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2931
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2932
                'student_response_formatted' => '',
2933
            ];
2934
            while ($row = Database::fetch_array($res)) {
2935
                $studentResponseFormatted = urldecode($row['student_response']);
2936
                $content_student_response = explode('__|', $studentResponseFormatted);
2937
                if (count($content_student_response) > 0) {
2938
                    if (count($content_student_response) >= 3) {
2939
                        // Pop the element off the end of array.
2940
                        array_pop($content_student_response);
2941
                    }
2942
                    $studentResponseFormatted = implode(',', $content_student_response);
2943
                }
2944
2945
                $list[] = [
2946
                    'order_id' => $row['order_id'] + 1,
2947
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2948
                    'type' => $row['interaction_type'],
2949
                    'time' => $row['completion_time'],
2950
                    'correct_responses' => '', // Hide correct responses from students.
2951
                    'student_response' => $row['student_response'],
2952
                    'result' => $row['result'],
2953
                    'latency' => $row['latency'],
2954
                    'student_response_formatted' => $studentResponseFormatted,
2955
                ];
2956
            }
2957
        }
2958
2959
        return $list;
2960
    }
2961
2962
    /**
2963
     * Return the number of objectives for the given learnpath Item View ID.
2964
     * This method can be used as static.
2965
     *
2966
     * @param int $lp_iv_id  Item View ID
2967
     * @param int $course_id Course ID
2968
     *
2969
     * @return int Number of objectives
2970
     */
2971
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2972
    {
2973
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2974
        $course_id = (int) $course_id;
2975
        $lp_iv_id = (int) $lp_iv_id;
2976
        $sql = "SELECT count(*) FROM $table
2977
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2978
        //@todo seems that this always returns 0
2979
        $res = Database::query($sql);
2980
        $num = 0;
2981
        if (Database::num_rows($res)) {
2982
            $row = Database::fetch_array($res);
2983
            $num = $row[0];
2984
        }
2985
2986
        return $num;
2987
    }
2988
2989
    /**
2990
     * Return the objectives as an array for the given lp_iv_id.
2991
     * This method can be used as static.
2992
     *
2993
     * @param int $lpItemViewId Learnpath Item View ID
2994
     * @param int $course_id
2995
     *
2996
     * @return array
2997
     *
2998
     * @todo    Translate labels
2999
     */
3000
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3001
    {
3002
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3003
        $lpItemViewId = (int) $lpItemViewId;
3004
3005
        if (empty($course_id) || empty($lpItemViewId)) {
3006
            return [];
3007
        }
3008
3009
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3010
        $sql = "SELECT * FROM $table
3011
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3012
                ORDER BY order_id ASC";
3013
        $res = Database::query($sql);
3014
        $num = Database::num_rows($res);
3015
        $list = [];
3016
        if ($num > 0) {
3017
            $list[] = [
3018
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3019
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3020
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3021
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3022
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3023
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3024
            ];
3025
            while ($row = Database::fetch_array($res)) {
3026
                $list[] = [
3027
                    'order_id' => $row['order_id'] + 1,
3028
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3029
                    'score_raw' => $row['score_raw'],
3030
                    'score_max' => $row['score_max'],
3031
                    'score_min' => $row['score_min'],
3032
                    'status' => $row['status'],
3033
                ];
3034
            }
3035
        }
3036
3037
        return $list;
3038
    }
3039
3040
    /**
3041
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3042
     * used by get_html_toc() to be ready to display.
3043
     *
3044
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3045
     */
3046
    public function get_toc()
3047
    {
3048
        $toc = [];
3049
        foreach ($this->ordered_items as $item_id) {
3050
            // TODO: Change this link generation and use new function instead.
3051
            $toc[] = [
3052
                'id' => $item_id,
3053
                'title' => $this->items[$item_id]->get_title(),
3054
                'status' => $this->items[$item_id]->get_status(),
3055
                'level' => $this->items[$item_id]->get_level(),
3056
                'type' => $this->items[$item_id]->get_type(),
3057
                'description' => $this->items[$item_id]->get_description(),
3058
                'path' => $this->items[$item_id]->get_path(),
3059
                'parent' => $this->items[$item_id]->get_parent(),
3060
            ];
3061
        }
3062
3063
        return $toc;
3064
    }
3065
3066
    /**
3067
     * Returns the CSS class name associated with a given item status.
3068
     *
3069
     * @param $status string an item status
3070
     *
3071
     * @return string CSS class name
3072
     */
3073
    public static function getStatusCSSClassName($status)
3074
    {
3075
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3076
            return self::STATUS_CSS_CLASS_NAME[$status];
3077
        }
3078
3079
        return '';
3080
    }
3081
3082
    /**
3083
     * Generate the tree of contents for this learnpath as an associative array tree
3084
     * with keys id, title, status, type, description, path, parent_id, children
3085
     * (title and descriptions as secured)
3086
     * and clues for CSS class composition:
3087
     *  - booleans is_current, is_parent_of_current, is_chapter
3088
     *  - string status_css_class_name.
3089
     *
3090
     * @param $parentId int restrict returned list to children of this parent
3091
     *
3092
     * @return array TOC as a table
3093
     */
3094
    public function getTOCTree($parentId = 0)
3095
    {
3096
        $toc = [];
3097
        $currentItemId = $this->get_current_item_id();
3098
3099
        foreach ($this->ordered_items as $itemId) {
3100
            $item = $this->items[$itemId];
3101
            if ($item->get_parent() == $parentId) {
3102
                $title = $item->get_title();
3103
                if (empty($title)) {
3104
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3105
                }
3106
3107
                $itemData = [
3108
                    'id' => $itemId,
3109
                    'title' => Security::remove_XSS($title),
3110
                    'status' => $item->get_status(),
3111
                    'level' => $item->get_level(), // FIXME should not be needed
3112
                    'type' => $item->get_type(),
3113
                    'description' => Security::remove_XSS($item->get_description()),
3114
                    'path' => $item->get_path(),
3115
                    'parent_id' => $item->get_parent(),
3116
                    'children' => $this->getTOCTree($itemId),
3117
                    'is_current' => ($itemId == $currentItemId),
3118
                    'is_parent_of_current' => false,
3119
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3120
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3121
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3122
                ];
3123
3124
                if (!empty($itemData['children'])) {
3125
                    foreach ($itemData['children'] as $child) {
3126
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3127
                            $itemData['is_parent_of_current'] = true;
3128
                            break;
3129
                        }
3130
                    }
3131
                }
3132
3133
                $toc[] = $itemData;
3134
            }
3135
        }
3136
3137
        return $toc;
3138
    }
3139
3140
    /**
3141
     * Generate and return the table of contents for this learnpath. The JS
3142
     * table returned is used inside of scorm_api.php.
3143
     *
3144
     * @param string $varname
3145
     *
3146
     * @return string A JS array variable construction
3147
     */
3148
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3149
    {
3150
        $toc = $varname.' = new Array();';
3151
        foreach ($this->ordered_items as $item_id) {
3152
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3153
        }
3154
3155
        return $toc;
3156
    }
3157
3158
    /**
3159
     * Gets the learning path type.
3160
     *
3161
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3162
     *
3163
     * @return mixed Type ID or name, depending on the parameter
3164
     */
3165
    public function get_type($get_name = false)
3166
    {
3167
        $res = false;
3168
        if (!empty($this->type) && (!$get_name)) {
3169
            $res = $this->type;
3170
        }
3171
3172
        return $res;
3173
    }
3174
3175
    /**
3176
     * Gets the learning path type as static method.
3177
     *
3178
     * @param int $lp_id
3179
     *
3180
     * @return mixed Type ID or name, depending on the parameter
3181
     */
3182
    public static function get_type_static($lp_id = 0)
3183
    {
3184
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3185
        $lp_id = (int) $lp_id;
3186
        $sql = "SELECT lp_type FROM $tbl_lp
3187
                WHERE iid = $lp_id";
3188
        $res = Database::query($sql);
3189
        if ($res === false) {
3190
            return null;
3191
        }
3192
        if (Database::num_rows($res) <= 0) {
3193
            return null;
3194
        }
3195
        $row = Database::fetch_array($res);
3196
3197
        return $row['lp_type'];
3198
    }
3199
3200
    /**
3201
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3202
     * This method can be used as abstract and is recursive.
3203
     *
3204
     * @param int $lp        Learnpath ID
3205
     * @param int $parent    Parent ID of the items to look for
3206
     * @param int $course_id
3207
     *
3208
     * @return array Ordered list of item IDs (empty array on error)
3209
     */
3210
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3211
    {
3212
        if (empty($course_id)) {
3213
            $course_id = api_get_course_int_id();
3214
        } else {
3215
            $course_id = (int) $course_id;
3216
        }
3217
        $list = [];
3218
3219
        if (empty($lp)) {
3220
            return $list;
3221
        }
3222
3223
        $lp = (int) $lp;
3224
        $parent = (int) $parent;
3225
3226
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3227
        $sql = "SELECT iid FROM $tbl_lp_item
3228
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3229
                ORDER BY display_order";
3230
3231
        $res = Database::query($sql);
3232
        while ($row = Database::fetch_array($res)) {
3233
            $sublist = self::get_flat_ordered_items_list(
3234
                $lp,
3235
                $row['iid'],
3236
                $course_id
3237
            );
3238
            $list[] = $row['iid'];
3239
            foreach ($sublist as $item) {
3240
                $list[] = $item;
3241
            }
3242
        }
3243
3244
        return $list;
3245
    }
3246
3247
    /**
3248
     * @return array
3249
     */
3250
    public static function getChapterTypes()
3251
    {
3252
        return [
3253
            'dir',
3254
        ];
3255
    }
3256
3257
    /**
3258
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3259
     *
3260
     * @param $tree
3261
     *
3262
     * @return array HTML TOC ready to display
3263
     */
3264
    public function getParentToc($tree)
3265
    {
3266
        if (empty($tree)) {
3267
            $tree = $this->get_toc();
3268
        }
3269
        $dirTypes = self::getChapterTypes();
3270
        $myCurrentId = $this->get_current_item_id();
3271
        $listParent = [];
3272
        $listChildren = [];
3273
        $listNotParent = [];
3274
        $list = [];
3275
        foreach ($tree as $subtree) {
3276
            if (in_array($subtree['type'], $dirTypes)) {
3277
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3278
                $subtree['children'] = $listChildren;
3279
                if (!empty($subtree['children'])) {
3280
                    foreach ($subtree['children'] as $subItem) {
3281
                        if ($subItem['id'] == $this->current) {
3282
                            $subtree['parent_current'] = 'in';
3283
                            $subtree['current'] = 'on';
3284
                        }
3285
                    }
3286
                }
3287
                $listParent[] = $subtree;
3288
            }
3289
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3290
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3291
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3292
                }
3293
3294
                $title = Security::remove_XSS($subtree['title']);
3295
                unset($subtree['title']);
3296
3297
                if (empty($title)) {
3298
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3299
                }
3300
                $classStyle = null;
3301
                if ($subtree['id'] == $this->current) {
3302
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3303
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3304
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3305
                }
3306
                $subtree['title'] = $title;
3307
                $subtree['class'] = $classStyle.' '.$cssStatus;
3308
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3309
                $subtree['current_id'] = $myCurrentId;
3310
                $listNotParent[] = $subtree;
3311
            }
3312
        }
3313
3314
        $list['are_parents'] = $listParent;
3315
        $list['not_parents'] = $listNotParent;
3316
3317
        return $list;
3318
    }
3319
3320
    /**
3321
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3322
     *
3323
     * @param array $tree
3324
     * @param int   $id
3325
     * @param bool  $parent
3326
     *
3327
     * @return array HTML TOC ready to display
3328
     */
3329
    public function getChildrenToc($tree, $id, $parent = true)
3330
    {
3331
        if (empty($tree)) {
3332
            $tree = $this->get_toc();
3333
        }
3334
3335
        $dirTypes = self::getChapterTypes();
3336
        $currentItemId = $this->get_current_item_id();
3337
        $list = [];
3338
3339
        foreach ($tree as $subtree) {
3340
            $subtree['tree'] = null;
3341
3342
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3343
                if ($subtree['id'] == $this->current) {
3344
                    $subtree['current'] = 'active';
3345
                } else {
3346
                    $subtree['current'] = null;
3347
                }
3348
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3349
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3350
                }
3351
3352
                $title = Security::remove_XSS($subtree['title']);
3353
                unset($subtree['title']);
3354
                if (empty($title)) {
3355
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3356
                }
3357
3358
                $classStyle = null;
3359
                if ($subtree['id'] == $this->current) {
3360
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3361
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3362
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3363
                }
3364
3365
                if (in_array($subtree['type'], $dirTypes)) {
3366
                    $subtree['title'] = stripslashes($title);
3367
                } else {
3368
                    $subtree['title'] = $title;
3369
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3370
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3371
                    $subtree['current_id'] = $currentItemId;
3372
                }
3373
                $list[] = $subtree;
3374
            }
3375
        }
3376
3377
        return $list;
3378
    }
3379
3380
    /**
3381
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3382
     *
3383
     * @param array $toc_list
3384
     *
3385
     * @return array HTML TOC ready to display
3386
     */
3387
    public function getListArrayToc($toc_list = [])
3388
    {
3389
        if (empty($toc_list)) {
3390
            $toc_list = $this->get_toc();
3391
        }
3392
        // Temporary variables.
3393
        $currentItemId = $this->get_current_item_id();
3394
        $list = [];
3395
        $arrayList = [];
3396
3397
        foreach ($toc_list as $item) {
3398
            $list['id'] = $item['id'];
3399
            $list['status'] = $item['status'];
3400
            $cssStatus = null;
3401
3402
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3403
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3404
            }
3405
3406
            $classStyle = ' ';
3407
            $dirTypes = self::getChapterTypes();
3408
3409
            if (in_array($item['type'], $dirTypes)) {
3410
                $classStyle = 'scorm_item_section ';
3411
            }
3412
            if ($item['id'] == $this->current) {
3413
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3414
            } elseif (!in_array($item['type'], $dirTypes)) {
3415
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3416
            }
3417
            $title = $item['title'];
3418
            if (empty($title)) {
3419
                $title = self::rl_get_resource_name(
3420
                    api_get_course_id(),
3421
                    $this->get_id(),
3422
                    $item['id']
3423
                );
3424
            }
3425
            $title = Security::remove_XSS($item['title']);
3426
3427
            if (empty($item['description'])) {
3428
                $list['description'] = $title;
3429
            } else {
3430
                $list['description'] = $item['description'];
3431
            }
3432
3433
            $list['class'] = $classStyle.' '.$cssStatus;
3434
            $list['level'] = $item['level'];
3435
            $list['type'] = $item['type'];
3436
3437
            if (in_array($item['type'], $dirTypes)) {
3438
                $list['css_level'] = 'level_'.$item['level'];
3439
            } else {
3440
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3441
            }
3442
3443
            if (in_array($item['type'], $dirTypes)) {
3444
                $list['title'] = stripslashes($title);
3445
            } else {
3446
                $list['title'] = stripslashes($title);
3447
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3448
                $list['current_id'] = $currentItemId;
3449
            }
3450
            $arrayList[] = $list;
3451
        }
3452
3453
        return $arrayList;
3454
    }
3455
3456
    /**
3457
     * Returns an HTML-formatted string ready to display with teacher buttons
3458
     * in LP view menu.
3459
     *
3460
     * @return string HTML TOC ready to display
3461
     */
3462
    public function get_teacher_toc_buttons()
3463
    {
3464
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3465
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3466
        $html = '';
3467
        if ($isAllow && $hideIcons == false) {
3468
            if ($this->get_lp_session_id() == api_get_session_id()) {
3469
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3470
                $html .= '<div class="btn-group">';
3471
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3472
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3473
                $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'>".
3474
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3475
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3476
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3477
                $html .= '</div>';
3478
                $html .= '</div>';
3479
            }
3480
        }
3481
3482
        return $html;
3483
    }
3484
3485
    /**
3486
     * Gets the learnpath maker name - generally the editor's name.
3487
     *
3488
     * @return string Learnpath maker name
3489
     */
3490
    public function get_maker()
3491
    {
3492
        if (!empty($this->maker)) {
3493
            return $this->maker;
3494
        }
3495
3496
        return '';
3497
    }
3498
3499
    /**
3500
     * Gets the learnpath name/title.
3501
     *
3502
     * @return string Learnpath name/title
3503
     */
3504
    public function get_name()
3505
    {
3506
        if (!empty($this->name)) {
3507
            return $this->name;
3508
        }
3509
3510
        return 'N/A';
3511
    }
3512
3513
    /**
3514
     * @return string
3515
     */
3516
    public function getNameNoTags()
3517
    {
3518
        return strip_tags($this->get_name());
3519
    }
3520
3521
    /**
3522
     * Gets a link to the resource from the present location, depending on item ID.
3523
     *
3524
     * @param string $type         Type of link expected
3525
     * @param int    $item_id      Learnpath item ID
3526
     * @param bool   $provided_toc
3527
     *
3528
     * @return string $provided_toc Link to the lp_item resource
3529
     */
3530
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3531
    {
3532
        $course_id = $this->get_course_int_id();
3533
        $item_id = (int) $item_id;
3534
3535
        if (empty($item_id)) {
3536
            $item_id = $this->get_current_item_id();
3537
3538
            if (empty($item_id)) {
3539
                //still empty, this means there was no item_id given and we are not in an object context or
3540
                //the object property is empty, return empty link
3541
                $this->first();
3542
3543
                return '';
3544
            }
3545
        }
3546
3547
        $file = '';
3548
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3549
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3550
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3551
3552
        $sql = "SELECT
3553
                    l.lp_type as ltype,
3554
                    l.path as lpath,
3555
                    li.item_type as litype,
3556
                    li.path as lipath,
3557
                    li.parameters as liparams
3558
        		FROM $lp_table l
3559
                INNER JOIN $lp_item_table li
3560
                ON (li.lp_id = l.iid)
3561
        		WHERE
3562
        		    li.iid = $item_id
3563
        		";
3564
        $res = Database::query($sql);
3565
        if (Database::num_rows($res) > 0) {
3566
            $row = Database::fetch_array($res);
3567
            $lp_type = $row['ltype'];
3568
            $lp_path = $row['lpath'];
3569
            $lp_item_type = $row['litype'];
3570
            $lp_item_path = $row['lipath'];
3571
            $lp_item_params = $row['liparams'];
3572
3573
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3574
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3575
            }
3576
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3577
            if ($type === 'http') {
3578
                //web path
3579
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3580
            } else {
3581
                $course_path = $sys_course_path; //system path
3582
            }
3583
3584
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3585
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3586
            if (in_array(
3587
                $lp_item_type,
3588
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3589
            )
3590
            ) {
3591
                $lp_type = 1;
3592
            }
3593
3594
            // Now go through the specific cases to get the end of the path
3595
            // @todo Use constants instead of int values.
3596
            switch ($lp_type) {
3597
                case 1:
3598
                    $file = self::rl_get_resource_link_for_learnpath(
3599
                        $course_id,
3600
                        $this->get_id(),
3601
                        $item_id,
3602
                        $this->get_view_id(),
3603
                        $this->get_lp_session_id()
3604
                    );
3605
                    switch ($lp_item_type) {
3606
                        case 'document':
3607
                            // Shows a button to download the file instead of just downloading the file directly.
3608
                            $documentPathInfo = pathinfo($file);
3609
                            if (isset($documentPathInfo['extension'])) {
3610
                                $parsed = parse_url($documentPathInfo['extension']);
3611
                                if (isset($parsed['path'])) {
3612
                                    $extension = $parsed['path'];
3613
                                    $extensionsToDownload = [
3614
                                        'zip',
3615
                                        'ppt',
3616
                                        'pptx',
3617
                                        'ods',
3618
                                        'xlsx',
3619
                                        'xls',
3620
                                        'csv',
3621
                                        'doc',
3622
                                        'docx',
3623
                                        'dot',
3624
                                    ];
3625
3626
                                    if (in_array($extension, $extensionsToDownload)) {
3627
                                        $file = api_get_path(WEB_CODE_PATH).
3628
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3629
                                    }
3630
                                }
3631
                            }
3632
                            break;
3633
                        case 'dir':
3634
                            $file = 'lp_content.php?type=dir';
3635
                            break;
3636
                        case 'link':
3637
                            if (Link::is_youtube_link($file)) {
3638
                                $src = Link::get_youtube_video_id($file);
3639
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3640
                            } elseif (Link::isVimeoLink($file)) {
3641
                                $src = Link::getVimeoLinkId($file);
3642
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3643
                            } else {
3644
                                // If the current site is HTTPS and the link is
3645
                                // HTTP, browsers will refuse opening the link
3646
                                $urlId = api_get_current_access_url_id();
3647
                                $url = api_get_access_url($urlId, false);
3648
                                $protocol = substr($url['url'], 0, 5);
3649
                                if ($protocol === 'https') {
3650
                                    $linkProtocol = substr($file, 0, 5);
3651
                                    if ($linkProtocol === 'http:') {
3652
                                        //this is the special intervention case
3653
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3654
                                    }
3655
                                }
3656
                            }
3657
                            break;
3658
                        case 'quiz':
3659
                            // Check how much attempts of a exercise exits in lp
3660
                            $lp_item_id = $this->get_current_item_id();
3661
                            $lp_view_id = $this->get_view_id();
3662
3663
                            $prevent_reinit = null;
3664
                            if (isset($this->items[$this->current])) {
3665
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3666
                            }
3667
3668
                            if (empty($provided_toc)) {
3669
                                if ($this->debug > 0) {
3670
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3671
                                }
3672
                                $list = $this->get_toc();
3673
                            } else {
3674
                                if ($this->debug > 0) {
3675
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3676
                                }
3677
                                $list = $provided_toc;
3678
                            }
3679
3680
                            $type_quiz = false;
3681
                            foreach ($list as $toc) {
3682
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3683
                                    $type_quiz = true;
3684
                                }
3685
                            }
3686
3687
                            if ($type_quiz) {
3688
                                $lp_item_id = (int) $lp_item_id;
3689
                                $lp_view_id = (int) $lp_view_id;
3690
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3691
                                        WHERE
3692
                                            c_id = $course_id AND
3693
                                            lp_item_id='".$lp_item_id."' AND
3694
                                            lp_view_id ='".$lp_view_id."' AND
3695
                                            status='completed'";
3696
                                $result = Database::query($sql);
3697
                                $row_count = Database:: fetch_row($result);
3698
                                $count_item_view = (int) $row_count[0];
3699
                                $not_multiple_attempt = 0;
3700
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3701
                                    $not_multiple_attempt = 1;
3702
                                }
3703
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3704
                            }
3705
                            break;
3706
                    }
3707
3708
                    $tmp_array = explode('/', $file);
3709
                    $document_name = $tmp_array[count($tmp_array) - 1];
3710
                    if (strpos($document_name, '_DELETED_')) {
3711
                        $file = 'blank.php?error=document_deleted';
3712
                    }
3713
                    break;
3714
                case 2:
3715
                    if ($this->debug > 2) {
3716
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3717
                    }
3718
3719
                    if ($lp_item_type != 'dir') {
3720
                        // Quite complex here:
3721
                        // We want to make sure 'http://' (and similar) links can
3722
                        // be loaded as is (withouth the Chamilo path in front) but
3723
                        // some contents use this form: resource.htm?resource=http://blablabla
3724
                        // which means we have to find a protocol at the path's start, otherwise
3725
                        // it should not be considered as an external URL.
3726
                        // if ($this->prerequisites_match($item_id)) {
3727
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3728
                            if ($this->debug > 2) {
3729
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3730
                            }
3731
                            // Distant url, return as is.
3732
                            $file = $lp_item_path;
3733
                        } else {
3734
                            if ($this->debug > 2) {
3735
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3736
                            }
3737
                            // Prevent getting untranslatable urls.
3738
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3739
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3740
                            // Prepare the path.
3741
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3742
                            // TODO: Fix this for urls with protocol header.
3743
                            $file = str_replace('//', '/', $file);
3744
                            $file = str_replace(':/', '://', $file);
3745
                            if (substr($lp_path, -1) == '/') {
3746
                                $lp_path = substr($lp_path, 0, -1);
3747
                            }
3748
3749
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3750
                                // if file not found.
3751
                                $decoded = html_entity_decode($lp_item_path);
3752
                                list($decoded) = explode('?', $decoded);
3753
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3754
                                    $file = self::rl_get_resource_link_for_learnpath(
3755
                                        $course_id,
3756
                                        $this->get_id(),
3757
                                        $item_id,
3758
                                        $this->get_view_id()
3759
                                    );
3760
                                    if (empty($file)) {
3761
                                        $file = 'blank.php?error=document_not_found';
3762
                                    } else {
3763
                                        $tmp_array = explode('/', $file);
3764
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3765
                                        if (strpos($document_name, '_DELETED_')) {
3766
                                            $file = 'blank.php?error=document_deleted';
3767
                                        } else {
3768
                                            $file = 'blank.php?error=document_not_found';
3769
                                        }
3770
                                    }
3771
                                } else {
3772
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3773
                                }
3774
                            }
3775
                        }
3776
3777
                        // We want to use parameters if they were defined in the imsmanifest
3778
                        if (strpos($file, 'blank.php') === false) {
3779
                            $lp_item_params = ltrim($lp_item_params, '?');
3780
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3781
                        }
3782
                    } else {
3783
                        $file = 'lp_content.php?type=dir';
3784
                    }
3785
                    break;
3786
                case 3:
3787
                    if ($this->debug > 2) {
3788
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3789
                    }
3790
                    // Formatting AICC HACP append URL.
3791
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3792
                    if (!empty($lp_item_params)) {
3793
                        $aicc_append .= $lp_item_params.'&';
3794
                    }
3795
                    if ($lp_item_type != 'dir') {
3796
                        // Quite complex here:
3797
                        // We want to make sure 'http://' (and similar) links can
3798
                        // be loaded as is (withouth the Chamilo path in front) but
3799
                        // some contents use this form: resource.htm?resource=http://blablabla
3800
                        // which means we have to find a protocol at the path's start, otherwise
3801
                        // it should not be considered as an external URL.
3802
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3803
                            if ($this->debug > 2) {
3804
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3805
                            }
3806
                            // Distant url, return as is.
3807
                            $file = $lp_item_path;
3808
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3809
                            /*
3810
                            if (stristr($file,'<servername>') !== false) {
3811
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3812
                            }
3813
                            */
3814
                            if (stripos($file, '<servername>') !== false) {
3815
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3816
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3817
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3818
                            }
3819
3820
                            $file .= $aicc_append;
3821
                        } else {
3822
                            if ($this->debug > 2) {
3823
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3824
                            }
3825
                            // Prevent getting untranslatable urls.
3826
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3827
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3828
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3829
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3830
                            // TODO: Fix this for urls with protocol header.
3831
                            $file = str_replace('//', '/', $file);
3832
                            $file = str_replace(':/', '://', $file);
3833
                            $file .= $aicc_append;
3834
                        }
3835
                    } else {
3836
                        $file = 'lp_content.php?type=dir';
3837
                    }
3838
                    break;
3839
                case 4:
3840
                    break;
3841
                default:
3842
                    break;
3843
            }
3844
            // Replace &amp; by & because &amp; will break URL with params
3845
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3846
        }
3847
        if ($this->debug > 2) {
3848
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3849
        }
3850
3851
        return $file;
3852
    }
3853
3854
    /**
3855
     * Gets the latest usable view or generate a new one.
3856
     *
3857
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3858
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3859
     *
3860
     * @return int DB lp_view id
3861
     */
3862
    public function get_view($attempt_num = 0, $userId = null)
3863
    {
3864
        $search = '';
3865
        // Use $attempt_num to enable multi-views management (disabled so far).
3866
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3867
            $search = 'AND view_count = '.$attempt_num;
3868
        }
3869
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3870
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3871
3872
        $course_id = api_get_course_int_id();
3873
        $sessionId = api_get_session_id();
3874
3875
        // Check user ID.
3876
        if (empty($userId)) {
3877
            if (empty($this->get_user_id())) {
3878
                $this->error = 'User ID is empty in learnpath::get_view()';
3879
3880
                return null;
3881
            } else {
3882
                $userId = $this->get_user_id();
3883
            }
3884
        }
3885
3886
        $sql = "SELECT iid, view_count FROM $lp_view_table
3887
        		WHERE
3888
        		    c_id = $course_id AND
3889
        		    lp_id = ".$this->get_id()." AND
3890
        		    user_id = ".$userId." AND
3891
        		    session_id = $sessionId
3892
        		    $search
3893
                ORDER BY view_count DESC";
3894
        $res = Database::query($sql);
3895
        if (Database::num_rows($res) > 0) {
3896
            $row = Database::fetch_array($res);
3897
            $this->lp_view_id = $row['iid'];
3898
        } elseif (!api_is_invitee()) {
3899
            // There is no database record, create one.
3900
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3901
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3902
            Database::query($sql);
3903
            $id = Database::insert_id();
3904
            $this->lp_view_id = $id;
3905
3906
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3907
            Database::query($sql);
3908
        }
3909
3910
        return $this->lp_view_id;
3911
    }
3912
3913
    /**
3914
     * Gets the current view id.
3915
     *
3916
     * @return int View ID (from lp_view)
3917
     */
3918
    public function get_view_id()
3919
    {
3920
        if (!empty($this->lp_view_id)) {
3921
            return (int) $this->lp_view_id;
3922
        }
3923
3924
        return 0;
3925
    }
3926
3927
    /**
3928
     * Gets the update queue.
3929
     *
3930
     * @return array Array containing IDs of items to be updated by JavaScript
3931
     */
3932
    public function get_update_queue()
3933
    {
3934
        return $this->update_queue;
3935
    }
3936
3937
    /**
3938
     * Gets the user ID.
3939
     *
3940
     * @return int User ID
3941
     */
3942
    public function get_user_id()
3943
    {
3944
        if (!empty($this->user_id)) {
3945
            return (int) $this->user_id;
3946
        }
3947
3948
        return false;
3949
    }
3950
3951
    /**
3952
     * Checks if any of the items has an audio element attached.
3953
     *
3954
     * @return bool True or false
3955
     */
3956
    public function has_audio()
3957
    {
3958
        $has = false;
3959
        foreach ($this->items as $i => $item) {
3960
            if (!empty($this->items[$i]->audio)) {
3961
                $has = true;
3962
                break;
3963
            }
3964
        }
3965
3966
        return $has;
3967
    }
3968
3969
    /**
3970
     * Moves an item up and down at its level.
3971
     *
3972
     * @param int    $id        Item to move up and down
3973
     * @param string $direction Direction 'up' or 'down'
3974
     *
3975
     * @return bool|int
3976
     */
3977
    public function move_item($id, $direction)
3978
    {
3979
        $course_id = api_get_course_int_id();
3980
        if (empty($id) || empty($direction)) {
3981
            return false;
3982
        }
3983
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3984
        $sql_sel = "SELECT *
3985
                    FROM $tbl_lp_item
3986
                    WHERE
3987
                        iid = $id
3988
                    ";
3989
        $res_sel = Database::query($sql_sel);
3990
        // Check if elem exists.
3991
        if (Database::num_rows($res_sel) < 1) {
3992
            return false;
3993
        }
3994
        // Gather data.
3995
        $row = Database::fetch_array($res_sel);
3996
        $previous = $row['previous_item_id'];
3997
        $next = $row['next_item_id'];
3998
        $display = $row['display_order'];
3999
        $parent = $row['parent_item_id'];
4000
        $lp = $row['lp_id'];
4001
        // Update the item (switch with previous/next one).
4002
        switch ($direction) {
4003
            case 'up':
4004
                if ($display > 1) {
4005
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4006
                                 WHERE iid = $previous";
4007
                    $res_sel2 = Database::query($sql_sel2);
4008
                    if (Database::num_rows($res_sel2) < 1) {
4009
                        $previous_previous = 0;
4010
                    }
4011
                    // Gather data.
4012
                    $row2 = Database::fetch_array($res_sel2);
4013
                    $previous_previous = $row2['previous_item_id'];
4014
                    // Update previous_previous item (switch "next" with current).
4015
                    if ($previous_previous != 0) {
4016
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4017
                                        next_item_id = $id
4018
                                    WHERE iid = $previous_previous";
4019
                        Database::query($sql_upd2);
4020
                    }
4021
                    // Update previous item (switch with current).
4022
                    if ($previous != 0) {
4023
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4024
                                    next_item_id = $next,
4025
                                    previous_item_id = $id,
4026
                                    display_order = display_order +1
4027
                                    WHERE iid = $previous";
4028
                        Database::query($sql_upd2);
4029
                    }
4030
4031
                    // Update current item (switch with previous).
4032
                    if ($id != 0) {
4033
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4034
                                        next_item_id = $previous,
4035
                                        previous_item_id = $previous_previous,
4036
                                        display_order = display_order-1
4037
                                    WHERE c_id = ".$course_id." AND id = $id";
4038
                        Database::query($sql_upd2);
4039
                    }
4040
                    // Update next item (new previous item).
4041
                    if (!empty($next)) {
4042
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4043
                                     WHERE iid = $next";
4044
                        Database::query($sql_upd2);
4045
                    }
4046
                    $display = $display - 1;
4047
                }
4048
                break;
4049
            case 'down':
4050
                if ($next != 0) {
4051
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4052
                                 WHERE iid = $next";
4053
                    $res_sel2 = Database::query($sql_sel2);
4054
                    if (Database::num_rows($res_sel2) < 1) {
4055
                        $next_next = 0;
4056
                    }
4057
                    // Gather data.
4058
                    $row2 = Database::fetch_array($res_sel2);
4059
                    $next_next = $row2['next_item_id'];
4060
                    // Update previous item (switch with current).
4061
                    if ($previous != 0) {
4062
                        $sql_upd2 = "UPDATE $tbl_lp_item
4063
                                     SET next_item_id = $next
4064
                                     WHERE iid = $previous";
4065
                        Database::query($sql_upd2);
4066
                    }
4067
                    // Update current item (switch with previous).
4068
                    if ($id != 0) {
4069
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4070
                                     previous_item_id = $next,
4071
                                     next_item_id = $next_next,
4072
                                     display_order = display_order + 1
4073
                                     WHERE iid = $id";
4074
                        Database::query($sql_upd2);
4075
                    }
4076
4077
                    // Update next item (new previous item).
4078
                    if ($next != 0) {
4079
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4080
                                     previous_item_id = $previous,
4081
                                     next_item_id = $id,
4082
                                     display_order = display_order-1
4083
                                     WHERE iid = $next";
4084
                        Database::query($sql_upd2);
4085
                    }
4086
4087
                    // Update next_next item (switch "previous" with current).
4088
                    if ($next_next != 0) {
4089
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4090
                                     previous_item_id = $id
4091
                                     WHERE iid = $next_next";
4092
                        Database::query($sql_upd2);
4093
                    }
4094
                    $display = $display + 1;
4095
                }
4096
                break;
4097
            default:
4098
                return false;
4099
        }
4100
4101
        return $display;
4102
    }
4103
4104
    /**
4105
     * Move a LP up (display_order).
4106
     *
4107
     * @param int $lp_id      Learnpath ID
4108
     * @param int $categoryId Category ID
4109
     *
4110
     * @return bool
4111
     */
4112
    public static function move_up($lp_id, $categoryId = 0)
4113
    {
4114
        $courseId = api_get_course_int_id();
4115
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4116
4117
        $categoryCondition = '';
4118
        if (!empty($categoryId)) {
4119
            $categoryId = (int) $categoryId;
4120
            $categoryCondition = " AND category_id = $categoryId";
4121
        }
4122
        $sql = "SELECT * FROM $lp_table
4123
                WHERE c_id = $courseId
4124
                $categoryCondition
4125
                ORDER BY display_order";
4126
        $res = Database::query($sql);
4127
        if ($res === false) {
4128
            return false;
4129
        }
4130
4131
        $lps = [];
4132
        $lp_order = [];
4133
        $num = Database::num_rows($res);
4134
        // First check the order is correct, globally (might be wrong because
4135
        // of versions < 1.8.4)
4136
        if ($num > 0) {
4137
            $i = 1;
4138
            while ($row = Database::fetch_array($res)) {
4139
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4140
                    $sql = "UPDATE $lp_table SET display_order = $i
4141
                            WHERE iid = ".$row['iid'];
4142
                    Database::query($sql);
4143
                }
4144
                $row['display_order'] = $i;
4145
                $lps[$row['iid']] = $row;
4146
                $lp_order[$i] = $row['iid'];
4147
                $i++;
4148
            }
4149
        }
4150
        if ($num > 1) { // If there's only one element, no need to sort.
4151
            $order = $lps[$lp_id]['display_order'];
4152
            if ($order > 1) { // If it's the first element, no need to move up.
4153
                $sql = "UPDATE $lp_table SET display_order = $order
4154
                        WHERE iid = ".$lp_order[$order - 1];
4155
                Database::query($sql);
4156
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4157
                        WHERE iid = $lp_id";
4158
                Database::query($sql);
4159
            }
4160
        }
4161
4162
        return true;
4163
    }
4164
4165
    /**
4166
     * Move a learnpath down (display_order).
4167
     *
4168
     * @param int $lp_id      Learnpath ID
4169
     * @param int $categoryId Category ID
4170
     *
4171
     * @return bool
4172
     */
4173
    public static function move_down($lp_id, $categoryId = 0)
4174
    {
4175
        $courseId = api_get_course_int_id();
4176
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4177
4178
        $categoryCondition = '';
4179
        if (!empty($categoryId)) {
4180
            $categoryId = (int) $categoryId;
4181
            $categoryCondition = " AND category_id = $categoryId";
4182
        }
4183
4184
        $sql = "SELECT * FROM $lp_table
4185
                WHERE c_id = $courseId
4186
                $categoryCondition
4187
                ORDER BY display_order";
4188
        $res = Database::query($sql);
4189
        if ($res === false) {
4190
            return false;
4191
        }
4192
        $lps = [];
4193
        $lp_order = [];
4194
        $num = Database::num_rows($res);
4195
        $max = 0;
4196
        // First check the order is correct, globally (might be wrong because
4197
        // of versions < 1.8.4).
4198
        if ($num > 0) {
4199
            $i = 1;
4200
            while ($row = Database::fetch_array($res)) {
4201
                $max = $i;
4202
                if ($row['display_order'] != $i) {
4203
                    // If we find a gap in the order, we need to fix it.
4204
                    $sql = "UPDATE $lp_table SET display_order = $i
4205
                              WHERE iid = ".$row['iid'];
4206
                    Database::query($sql);
4207
                }
4208
                $row['display_order'] = $i;
4209
                $lps[$row['iid']] = $row;
4210
                $lp_order[$i] = $row['iid'];
4211
                $i++;
4212
            }
4213
        }
4214
        if ($num > 1) { // If there's only one element, no need to sort.
4215
            $order = $lps[$lp_id]['display_order'];
4216
            if ($order < $max) { // If it's the first element, no need to move up.
4217
                $sql = "UPDATE $lp_table SET display_order = $order
4218
                        WHERE iid = ".$lp_order[$order + 1];
4219
                Database::query($sql);
4220
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4221
                        WHERE iid = $lp_id";
4222
                Database::query($sql);
4223
            }
4224
        }
4225
4226
        return true;
4227
    }
4228
4229
    /**
4230
     * Updates learnpath attributes to point to the next element
4231
     * The last part is similar to set_current_item but processing the other way around.
4232
     */
4233
    public function next()
4234
    {
4235
        if ($this->debug > 0) {
4236
            error_log('In learnpath::next()', 0);
4237
        }
4238
        $this->last = $this->get_current_item_id();
4239
        $this->items[$this->last]->save(
4240
            false,
4241
            $this->prerequisites_match($this->last)
4242
        );
4243
        $this->autocomplete_parents($this->last);
4244
        $new_index = $this->get_next_index();
4245
        if ($this->debug > 2) {
4246
            error_log('New index: '.$new_index, 0);
4247
        }
4248
        $this->index = $new_index;
4249
        if ($this->debug > 2) {
4250
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4251
        }
4252
        $this->current = $this->ordered_items[$new_index];
4253
        if ($this->debug > 2) {
4254
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4255
        }
4256
    }
4257
4258
    /**
4259
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4260
     * class, this might be redefined to allow several behaviours depending on the document type.
4261
     *
4262
     * @param int $id Resource ID
4263
     */
4264
    public function open($id)
4265
    {
4266
        // TODO:
4267
        // set the current resource attribute to this resource
4268
        // switch on element type (redefine in child class?)
4269
        // set status for this item to "opened"
4270
        // start timer
4271
        // initialise score
4272
        $this->index = 0; //or = the last item seen (see $this->last)
4273
    }
4274
4275
    /**
4276
     * Check that all prerequisites are fulfilled. Returns true and an
4277
     * empty string on success, returns false
4278
     * and the prerequisite string on error.
4279
     * This function is based on the rules for aicc_script language as
4280
     * described in the SCORM 1.2 CAM documentation page 108.
4281
     *
4282
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4283
     *
4284
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4285
     *              string otherwise
4286
     */
4287
    public function prerequisites_match($itemId = null)
4288
    {
4289
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4290
        if ($allow) {
4291
            if (api_is_allowed_to_edit() ||
4292
                api_is_platform_admin(true) ||
4293
                api_is_drh() ||
4294
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4295
            ) {
4296
                return true;
4297
            }
4298
        }
4299
4300
        $debug = $this->debug;
4301
        if ($debug > 0) {
4302
            error_log('In learnpath::prerequisites_match()');
4303
        }
4304
4305
        if (empty($itemId)) {
4306
            $itemId = $this->current;
4307
        }
4308
4309
        $currentItem = $this->getItem($itemId);
4310
4311
        if ($currentItem) {
4312
            if ($this->type == 2) {
4313
                // Getting prereq from scorm
4314
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4315
            } else {
4316
                $prereq_string = $currentItem->get_prereq_string();
4317
            }
4318
4319
            if (empty($prereq_string)) {
4320
                if ($debug > 0) {
4321
                    error_log('Found prereq_string is empty return true');
4322
                }
4323
4324
                return true;
4325
            }
4326
4327
            // Clean spaces.
4328
            $prereq_string = str_replace(' ', '', $prereq_string);
4329
            if ($debug > 0) {
4330
                error_log('Found prereq_string: '.$prereq_string, 0);
4331
            }
4332
4333
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4334
            $result = $currentItem->parse_prereq(
4335
                $prereq_string,
4336
                $this->items,
4337
                $this->refs_list,
4338
                $this->get_user_id()
4339
            );
4340
4341
            if ($result === false) {
4342
                $this->set_error_msg($currentItem->prereq_alert);
4343
            }
4344
        } else {
4345
            $result = true;
4346
            if ($debug > 1) {
4347
                error_log('$this->items['.$itemId.'] was not an object', 0);
4348
            }
4349
        }
4350
4351
        if ($debug > 1) {
4352
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4353
        }
4354
4355
        return $result;
4356
    }
4357
4358
    /**
4359
     * Updates learnpath attributes to point to the previous element
4360
     * The last part is similar to set_current_item but processing the other way around.
4361
     */
4362
    public function previous()
4363
    {
4364
        $this->last = $this->get_current_item_id();
4365
        $this->items[$this->last]->save(
4366
            false,
4367
            $this->prerequisites_match($this->last)
4368
        );
4369
        $this->autocomplete_parents($this->last);
4370
        $new_index = $this->get_previous_index();
4371
        $this->index = $new_index;
4372
        $this->current = $this->ordered_items[$new_index];
4373
    }
4374
4375
    /**
4376
     * Publishes a learnpath. This basically means show or hide the learnpath
4377
     * to normal users.
4378
     * Can be used as abstract.
4379
     *
4380
     * @param int $lp_id          Learnpath ID
4381
     * @param int $set_visibility New visibility
4382
     *
4383
     * @return bool
4384
     */
4385
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4386
    {
4387
        $action = 'visible';
4388
        if ($set_visibility != 1) {
4389
            $action = 'invisible';
4390
            self::toggle_publish($lp_id, 'i');
4391
        }
4392
4393
        return api_item_property_update(
4394
            api_get_course_info(),
4395
            TOOL_LEARNPATH,
4396
            $lp_id,
4397
            $action,
4398
            api_get_user_id()
4399
        );
4400
    }
4401
4402
    /**
4403
     * Publishes a learnpath category.
4404
     * This basically means show or hide the learnpath category to normal users.
4405
     *
4406
     * @param int $id
4407
     * @param int $visibility
4408
     *
4409
     * @throws \Doctrine\ORM\NonUniqueResultException
4410
     * @throws \Doctrine\ORM\ORMException
4411
     * @throws \Doctrine\ORM\OptimisticLockException
4412
     * @throws \Doctrine\ORM\TransactionRequiredException
4413
     *
4414
     * @return bool
4415
     */
4416
    public static function toggleCategoryVisibility($id, $visibility = 1)
4417
    {
4418
        $action = 'visible';
4419
        if ($visibility != 1) {
4420
            self::toggleCategoryPublish($id, 0);
4421
            $action = 'invisible';
4422
        }
4423
4424
        return api_item_property_update(
4425
            api_get_course_info(),
4426
            TOOL_LEARNPATH_CATEGORY,
4427
            $id,
4428
            $action,
4429
            api_get_user_id()
4430
        );
4431
    }
4432
4433
    /**
4434
     * Publishes a learnpath. This basically means show or hide the learnpath
4435
     * on the course homepage
4436
     * Can be used as abstract.
4437
     *
4438
     * @param int    $lp_id          Learnpath id
4439
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4440
     *
4441
     * @return bool
4442
     */
4443
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4444
    {
4445
        $course_id = api_get_course_int_id();
4446
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4447
        $lp_id = (int) $lp_id;
4448
        $sql = "SELECT * FROM $tbl_lp
4449
                WHERE iid = $lp_id";
4450
        $result = Database::query($sql);
4451
        if (Database::num_rows($result)) {
4452
            $row = Database::fetch_array($result);
4453
            $name = Database::escape_string($row['name']);
4454
            if ($set_visibility == 'i') {
4455
                $v = 0;
4456
            }
4457
            if ($set_visibility == 'v') {
4458
                $v = 1;
4459
            }
4460
4461
            $session_id = api_get_session_id();
4462
            $session_condition = api_get_session_condition($session_id);
4463
4464
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4465
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4466
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4467
4468
            $sql = "SELECT * FROM $tbl_tool
4469
                    WHERE
4470
                        c_id = $course_id AND
4471
                        (link = '$link' OR link = '$oldLink') AND
4472
                        image = 'scormbuilder.gif' AND
4473
                        (
4474
                            link LIKE '$link%' OR
4475
                            link LIKE '$oldLink%'
4476
                        )
4477
                        $session_condition
4478
                    ";
4479
4480
            $result = Database::query($sql);
4481
            $num = Database::num_rows($result);
4482
            if ($set_visibility == 'i' && $num > 0) {
4483
                $sql = "DELETE FROM $tbl_tool
4484
                        WHERE
4485
                            c_id = $course_id AND
4486
                            (link = '$link' OR link = '$oldLink') AND
4487
                            image='scormbuilder.gif'
4488
                            $session_condition";
4489
                Database::query($sql);
4490
            } elseif ($set_visibility == 'v' && $num == 0) {
4491
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4492
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4493
                Database::query($sql);
4494
                $insertId = Database::insert_id();
4495
                if ($insertId) {
4496
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4497
                    Database::query($sql);
4498
                }
4499
            } elseif ($set_visibility == 'v' && $num > 0) {
4500
                $sql = "UPDATE $tbl_tool SET
4501
                            c_id = $course_id,
4502
                            name = '$name',
4503
                            link = '$link',
4504
                            image = 'scormbuilder.gif',
4505
                            visibility = '$v',
4506
                            admin = '0',
4507
                            address = 'pastillegris.gif',
4508
                            added_tool = 0,
4509
                            session_id = $session_id
4510
                        WHERE
4511
                            c_id = ".$course_id." AND
4512
                            (link = '$link' OR link = '$oldLink') AND
4513
                            image='scormbuilder.gif'
4514
                            $session_condition
4515
                        ";
4516
                Database::query($sql);
4517
            }
4518
        }
4519
4520
        return false;
4521
    }
4522
4523
    /**
4524
     * Publishes a learnpath.
4525
     * Show or hide the learnpath category on the course homepage.
4526
     *
4527
     * @param int $id
4528
     * @param int $setVisibility
4529
     *
4530
     * @throws \Doctrine\ORM\NonUniqueResultException
4531
     * @throws \Doctrine\ORM\ORMException
4532
     * @throws \Doctrine\ORM\OptimisticLockException
4533
     * @throws \Doctrine\ORM\TransactionRequiredException
4534
     *
4535
     * @return bool
4536
     */
4537
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4538
    {
4539
        $courseId = api_get_course_int_id();
4540
        $sessionId = api_get_session_id();
4541
        $sessionCondition = api_get_session_condition(
4542
            $sessionId,
4543
            true,
4544
            false,
4545
            't.sessionId'
4546
        );
4547
4548
        $em = Database::getManager();
4549
        $category = self::getCategory($id);
4550
4551
        if (!$category) {
4552
            return false;
4553
        }
4554
4555
        if (empty($courseId)) {
4556
            return false;
4557
        }
4558
4559
        $link = self::getCategoryLinkForTool($id);
4560
4561
        /** @var CTool $tool */
4562
        $tool = $em->createQuery("
4563
                SELECT t FROM ChamiloCourseBundle:CTool t
4564
                WHERE
4565
                    t.cId = :course AND
4566
                    t.link = :link1 AND
4567
                    t.image = 'lp_category.gif' AND
4568
                    t.link LIKE :link2
4569
                    $sessionCondition
4570
            ")
4571
            ->setParameters([
4572
                'course' => $courseId,
4573
                'link1' => $link,
4574
                'link2' => "$link%",
4575
            ])
4576
            ->getOneOrNullResult();
4577
4578
        if ($setVisibility == 0 && $tool) {
4579
            $em->remove($tool);
4580
            $em->flush();
4581
4582
            return true;
4583
        }
4584
4585
        if ($setVisibility == 1 && !$tool) {
4586
            $tool = new CTool();
4587
            $tool
4588
                ->setCategory('authoring')
4589
                ->setCId($courseId)
4590
                ->setName(strip_tags($category->getName()))
4591
                ->setLink($link)
4592
                ->setImage('lp_category.gif')
4593
                ->setVisibility(1)
4594
                ->setAdmin(0)
4595
                ->setAddress('pastillegris.gif')
4596
                ->setAddedTool(0)
4597
                ->setSessionId($sessionId)
4598
                ->setTarget('_self');
4599
4600
            $em->persist($tool);
4601
            $em->flush();
4602
4603
            $tool->setId($tool->getIid());
4604
4605
            $em->persist($tool);
4606
            $em->flush();
4607
4608
            return true;
4609
        }
4610
4611
        if ($setVisibility == 1 && $tool) {
4612
            $tool
4613
                ->setName(strip_tags($category->getName()))
4614
                ->setVisibility(1);
4615
4616
            $em->persist($tool);
4617
            $em->flush();
4618
4619
            return true;
4620
        }
4621
4622
        return false;
4623
    }
4624
4625
    /**
4626
     * Check if the learnpath category is visible for a user.
4627
     *
4628
     * @param int
4629
     * @param int
4630
     *
4631
     * @return bool
4632
     */
4633
    public static function categoryIsVisibleForStudent(
4634
        CLpCategory $category,
4635
        User $user,
4636
        $courseId = 0,
4637
        $sessionId = 0
4638
    ) {
4639
        if (empty($category)) {
4640
            return false;
4641
        }
4642
4643
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4644
4645
        if ($isAllowedToEdit) {
4646
            return true;
4647
        }
4648
4649
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4650
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4651
4652
        $courseInfo = api_get_course_info_by_id($courseId);
4653
4654
        $categoryVisibility = api_get_item_visibility(
4655
            $courseInfo,
4656
            TOOL_LEARNPATH_CATEGORY,
4657
            $category->getId(),
4658
            $sessionId
4659
        );
4660
4661
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4662
            return false;
4663
        }
4664
4665
        $subscriptionSettings = self::getSubscriptionSettings();
4666
4667
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4668
            return true;
4669
        }
4670
4671
        $noUserSubscribed = false;
4672
        $noGroupSubscribed = true;
4673
        $users = $category->getUsers();
4674
        if (empty($users) || !$users->count()) {
4675
            $noUserSubscribed = true;
4676
        } elseif ($category->hasUserAdded($user)) {
4677
            return true;
4678
        }
4679
4680
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4681
        $em = Database::getManager();
4682
4683
        /** @var ItemPropertyRepository $itemRepo */
4684
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4685
4686
        /** @var CourseRepository $courseRepo */
4687
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4688
        $session = null;
4689
        if (!empty($sessionId)) {
4690
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4691
        }
4692
4693
        $course = $courseRepo->find($courseId);
4694
4695
        if ($courseId != 0) {
4696
            // Subscribed groups to a LP
4697
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4698
                TOOL_LEARNPATH_CATEGORY,
4699
                $category->getId(),
4700
                $course,
4701
                $session
4702
            );
4703
        }
4704
4705
        if (!empty($subscribedGroupsInLp)) {
4706
            $noGroupSubscribed = false;
4707
            if (!empty($groups)) {
4708
                $groups = array_column($groups, 'iid');
4709
                /** @var CItemProperty $item */
4710
                foreach ($subscribedGroupsInLp as $item) {
4711
                    if ($item->getGroup() &&
4712
                        in_array($item->getGroup()->getId(), $groups)
4713
                    ) {
4714
                        return true;
4715
                    }
4716
                }
4717
            }
4718
        }
4719
        $response = $noGroupSubscribed && $noUserSubscribed;
4720
4721
        return $response;
4722
    }
4723
4724
    /**
4725
     * Check if a learnpath category is published as course tool.
4726
     *
4727
     * @param int $courseId
4728
     *
4729
     * @return bool
4730
     */
4731
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4732
    {
4733
        $link = self::getCategoryLinkForTool($category->getId());
4734
        $em = Database::getManager();
4735
4736
        $tools = $em
4737
            ->createQuery("
4738
                SELECT t FROM ChamiloCourseBundle:CTool t
4739
                WHERE t.cId = :course AND
4740
                    t.name = :name AND
4741
                    t.image = 'lp_category.gif' AND
4742
                    t.link LIKE :link
4743
            ")
4744
            ->setParameters([
4745
                'course' => $courseId,
4746
                'name' => strip_tags($category->getName()),
4747
                'link' => "$link%",
4748
            ])
4749
            ->getResult();
4750
4751
        /** @var CTool $tool */
4752
        $tool = current($tools);
4753
4754
        return $tool ? $tool->getVisibility() : false;
4755
    }
4756
4757
    /**
4758
     * Restart the whole learnpath. Return the URL of the first element.
4759
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4760
     * To use a similar method  statically, use the create_new_attempt() method.
4761
     *
4762
     * @return bool
4763
     */
4764
    public function restart()
4765
    {
4766
        if ($this->debug > 0) {
4767
            error_log('In learnpath::restart()', 0);
4768
        }
4769
        // TODO
4770
        // Call autosave method to save the current progress.
4771
        //$this->index = 0;
4772
        if (api_is_invitee()) {
4773
            return false;
4774
        }
4775
        $session_id = api_get_session_id();
4776
        $course_id = api_get_course_int_id();
4777
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4778
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4779
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4780
        if ($this->debug > 2) {
4781
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4782
        }
4783
        Database::query($sql);
4784
        $view_id = Database::insert_id();
4785
4786
        if ($view_id) {
4787
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4788
            Database::query($sql);
4789
            $this->lp_view_id = $view_id;
4790
            $this->attempt = $this->attempt + 1;
4791
        } else {
4792
            $this->error = 'Could not insert into item_view table...';
4793
4794
            return false;
4795
        }
4796
        $this->autocomplete_parents($this->current);
4797
        foreach ($this->items as $index => $dummy) {
4798
            $this->items[$index]->restart();
4799
            $this->items[$index]->set_lp_view($this->lp_view_id);
4800
        }
4801
        $this->first();
4802
4803
        return true;
4804
    }
4805
4806
    /**
4807
     * Saves the current item.
4808
     *
4809
     * @return bool
4810
     */
4811
    public function save_current()
4812
    {
4813
        $debug = $this->debug;
4814
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4815
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4816
        if ($debug) {
4817
            error_log('save_current() saving item '.$this->current, 0);
4818
            error_log(''.print_r($this->items, true), 0);
4819
        }
4820
        if (isset($this->items[$this->current]) &&
4821
            is_object($this->items[$this->current])
4822
        ) {
4823
            if ($debug) {
4824
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4825
            }
4826
4827
            $res = $this->items[$this->current]->save(
4828
                false,
4829
                $this->prerequisites_match($this->current)
4830
            );
4831
            $this->autocomplete_parents($this->current);
4832
            $status = $this->items[$this->current]->get_status();
4833
            $this->update_queue[$this->current] = $status;
4834
4835
            if ($debug) {
4836
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4837
            }
4838
4839
            return $res;
4840
        }
4841
4842
        return false;
4843
    }
4844
4845
    /**
4846
     * Saves the given item.
4847
     *
4848
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4849
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4850
     *
4851
     * @return bool
4852
     */
4853
    public function save_item($item_id = null, $from_outside = true)
4854
    {
4855
        $debug = $this->debug;
4856
        if ($debug) {
4857
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4858
        }
4859
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4860
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4861
        if (empty($item_id)) {
4862
            $item_id = (int) $_REQUEST['id'];
4863
        }
4864
4865
        if (empty($item_id)) {
4866
            $item_id = $this->get_current_item_id();
4867
        }
4868
        if (isset($this->items[$item_id]) &&
4869
            is_object($this->items[$item_id])
4870
        ) {
4871
            if ($debug) {
4872
                error_log('Object exists');
4873
            }
4874
4875
            // Saving the item.
4876
            $res = $this->items[$item_id]->save(
4877
                $from_outside,
4878
                $this->prerequisites_match($item_id)
4879
            );
4880
4881
            if ($debug) {
4882
                error_log('update_queue before:');
4883
                error_log(print_r($this->update_queue, 1));
4884
            }
4885
            $this->autocomplete_parents($item_id);
4886
4887
            $status = $this->items[$item_id]->get_status();
4888
            $this->update_queue[$item_id] = $status;
4889
4890
            if ($debug) {
4891
                error_log('get_status(): '.$status);
4892
                error_log('update_queue after:');
4893
                error_log(print_r($this->update_queue, 1));
4894
            }
4895
4896
            return $res;
4897
        }
4898
4899
        return false;
4900
    }
4901
4902
    /**
4903
     * Saves the last item seen's ID only in case.
4904
     */
4905
    public function save_last($score = null)
4906
    {
4907
        $course_id = api_get_course_int_id();
4908
        $debug = $this->debug;
4909
        if ($debug) {
4910
            error_log('In learnpath::save_last()', 0);
4911
        }
4912
        $session_condition = api_get_session_condition(
4913
            api_get_session_id(),
4914
            true,
4915
            false
4916
        );
4917
        $table = Database::get_course_table(TABLE_LP_VIEW);
4918
4919
        $userId = $this->get_user_id();
4920
        if (empty($userId)) {
4921
            $userId = api_get_user_id();
4922
            if ($debug) {
4923
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4924
            }
4925
        }
4926
        if (isset($this->current) && !api_is_invitee()) {
4927
            if ($debug) {
4928
                error_log('Saving current item ('.$this->current.') for later review', 0);
4929
            }
4930
            $sql = "UPDATE $table SET
4931
                        last_item = ".$this->get_current_item_id()."
4932
                    WHERE
4933
                        c_id = $course_id AND
4934
                        lp_id = ".$this->get_id()." AND
4935
                        user_id = ".$userId." ".$session_condition;
4936
            if ($debug) {
4937
                error_log('Saving last item seen : '.$sql, 0);
4938
            }
4939
            Database::query($sql);
4940
        }
4941
4942
        if (!api_is_invitee()) {
4943
            // Save progress.
4944
            list($progress) = $this->get_progress_bar_text('%');
4945
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4946
            $scoreAsProgress = $this->getUseScoreAsProgress();
4947
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4948
                if ($debug) {
4949
                    error_log("Skip saving score with value: $score");
4950
                }
4951
                return false;
4952
            }
4953
4954
            /*if ($course_id == 220 && $scoreAsProgress && $scoreAsProgressSetting) {
4955
                error_log("HOT FIX JULIO new score has been replaced from $progress to $score");
4956
                $progress = $score;
4957
            }*/
4958
4959
            if ($progress >= 0 && $progress <= 100) {
4960
                // Check database.
4961
                $progress = (int) $progress;
4962
                $sql = "UPDATE $table SET
4963
                            progress = $progress
4964
                        WHERE
4965
                            c_id = $course_id AND
4966
                            lp_id = ".$this->get_id()." AND
4967
                            user_id = ".$userId." ".$session_condition;
4968
                // Ignore errors as some tables might not have the progress field just yet.
4969
                Database::query($sql);
4970
                if ($debug) {
4971
                    error_log($sql);
4972
                }
4973
                $this->progress_db = $progress;
4974
            }
4975
        }
4976
    }
4977
4978
    /**
4979
     * Sets the current item ID (checks if valid and authorized first).
4980
     *
4981
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4982
     */
4983
    public function set_current_item($item_id = null)
4984
    {
4985
        $debug = $this->debug;
4986
        if ($debug) {
4987
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4988
        }
4989
        if (empty($item_id)) {
4990
            if ($debug) {
4991
                error_log('No new current item given, ignore...', 0);
4992
            }
4993
            // Do nothing.
4994
        } else {
4995
            if ($debug) {
4996
                error_log('New current item given is '.$item_id.'...', 0);
4997
            }
4998
            if (is_numeric($item_id)) {
4999
                $item_id = (int) $item_id;
5000
                // TODO: Check in database here.
5001
                $this->last = $this->current;
5002
                $this->current = $item_id;
5003
                // TODO: Update $this->index as well.
5004
                foreach ($this->ordered_items as $index => $item) {
5005
                    if ($item == $this->current) {
5006
                        $this->index = $index;
5007
                        break;
5008
                    }
5009
                }
5010
                if ($debug) {
5011
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5012
                }
5013
            } else {
5014
                if ($debug) {
5015
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5016
                }
5017
            }
5018
        }
5019
    }
5020
5021
    /**
5022
     * Sets the encoding.
5023
     *
5024
     * @param string $enc New encoding
5025
     *
5026
     * @return bool
5027
     *
5028
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5029
     */
5030
    public function set_encoding($enc = 'UTF-8')
5031
    {
5032
        $enc = api_refine_encoding_id($enc);
5033
        if (empty($enc)) {
5034
            $enc = api_get_system_encoding();
5035
        }
5036
        if (api_is_encoding_supported($enc)) {
5037
            $lp = $this->get_id();
5038
            if ($lp != 0) {
5039
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5040
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5041
                        WHERE iid = ".$lp;
5042
                $res = Database::query($sql);
5043
5044
                return $res;
5045
            }
5046
        }
5047
5048
        return false;
5049
    }
5050
5051
    /**
5052
     * Sets the JS lib setting in the database directly.
5053
     * This is the JavaScript library file this lp needs to load on startup.
5054
     *
5055
     * @param string $lib Proximity setting
5056
     *
5057
     * @return bool True on update success. False otherwise.
5058
     */
5059
    public function set_jslib($lib = '')
5060
    {
5061
        $lp = $this->get_id();
5062
5063
        if ($lp != 0) {
5064
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5065
            $lib = Database::escape_string($lib);
5066
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5067
                    WHERE iid = $lp";
5068
            $res = Database::query($sql);
5069
5070
            return $res;
5071
        }
5072
5073
        return false;
5074
    }
5075
5076
    /**
5077
     * Sets the name of the LP maker (publisher) (and save).
5078
     *
5079
     * @param string $name Optional string giving the new content_maker of this learnpath
5080
     *
5081
     * @return bool True
5082
     */
5083
    public function set_maker($name = '')
5084
    {
5085
        if (empty($name)) {
5086
            return false;
5087
        }
5088
        $this->maker = $name;
5089
        $table = Database::get_course_table(TABLE_LP_MAIN);
5090
        $lp_id = $this->get_id();
5091
        $sql = "UPDATE $table SET
5092
                content_maker = '".Database::escape_string($this->maker)."'
5093
                WHERE iid = $lp_id";
5094
        Database::query($sql);
5095
5096
        return true;
5097
    }
5098
5099
    /**
5100
     * Sets the name of the current learnpath (and save).
5101
     *
5102
     * @param string $name Optional string giving the new name of this learnpath
5103
     *
5104
     * @return bool True/False
5105
     */
5106
    public function set_name($name = null)
5107
    {
5108
        if (empty($name)) {
5109
            return false;
5110
        }
5111
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5112
        $name = Database::escape_string($name);
5113
5114
        $this->name = $name;
5115
5116
        $lp_id = $this->get_id();
5117
        $course_id = $this->course_info['real_id'];
5118
        $sql = "UPDATE $lp_table SET
5119
                name = '$name'
5120
                WHERE iid = $lp_id";
5121
        $result = Database::query($sql);
5122
        // If the lp is visible on the homepage, change his name there.
5123
        if (Database::affected_rows($result)) {
5124
            $session_id = api_get_session_id();
5125
            $session_condition = api_get_session_condition($session_id);
5126
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5127
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5128
            $sql = "UPDATE $tbl_tool SET name = '$name'
5129
            	    WHERE
5130
            	        c_id = $course_id AND
5131
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5132
            Database::query($sql);
5133
5134
            return true;
5135
        }
5136
5137
        return false;
5138
    }
5139
5140
    /**
5141
     * Set index specified prefix terms for all items in this path.
5142
     *
5143
     * @param string $terms_string Comma-separated list of terms
5144
     * @param string $prefix       Xapian term prefix
5145
     *
5146
     * @return bool False on error, true otherwise
5147
     */
5148
    public function set_terms_by_prefix($terms_string, $prefix)
5149
    {
5150
        $course_id = api_get_course_int_id();
5151
        if (api_get_setting('search_enabled') !== 'true') {
5152
            return false;
5153
        }
5154
5155
        if (!extension_loaded('xapian')) {
5156
            return false;
5157
        }
5158
5159
        $terms_string = trim($terms_string);
5160
        $terms = explode(',', $terms_string);
5161
        array_walk($terms, 'trim_value');
5162
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5163
5164
        // Don't do anything if no change, verify only at DB, not the search engine.
5165
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5166
            return false;
5167
        }
5168
5169
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5170
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5171
5172
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5173
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5174
        $lp_id = (int) $_POST['lp_id'];
5175
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5176
        $result = Database::query($sql);
5177
        $di = new ChamiloIndexer();
5178
5179
        while ($lp_item = Database::fetch_array($result)) {
5180
            // Get search_did.
5181
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5182
            $sql = 'SELECT * FROM %s
5183
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5184
                    LIMIT 1';
5185
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5186
5187
            //echo $sql; echo '<br>';
5188
            $res = Database::query($sql);
5189
            if (Database::num_rows($res) > 0) {
5190
                $se_ref = Database::fetch_array($res);
5191
                // Compare terms.
5192
                $doc = $di->get_document($se_ref['search_did']);
5193
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5194
                $xterms = [];
5195
                foreach ($xapian_terms as $xapian_term) {
5196
                    $xterms[] = substr($xapian_term['name'], 1);
5197
                }
5198
5199
                $dterms = $terms;
5200
                $missing_terms = array_diff($dterms, $xterms);
5201
                $deprecated_terms = array_diff($xterms, $dterms);
5202
5203
                // Save it to search engine.
5204
                foreach ($missing_terms as $term) {
5205
                    $doc->add_term($prefix.$term, 1);
5206
                }
5207
                foreach ($deprecated_terms as $term) {
5208
                    $doc->remove_term($prefix.$term);
5209
                }
5210
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5211
                $di->getDb()->flush();
5212
            }
5213
        }
5214
5215
        return true;
5216
    }
5217
5218
    /**
5219
     * Sets the theme of the LP (local/remote) (and save).
5220
     *
5221
     * @param string $name Optional string giving the new theme of this learnpath
5222
     *
5223
     * @return bool Returns true if theme name is not empty
5224
     */
5225
    public function set_theme($name = '')
5226
    {
5227
        $this->theme = $name;
5228
        $table = Database::get_course_table(TABLE_LP_MAIN);
5229
        $lp_id = $this->get_id();
5230
        $sql = "UPDATE $table
5231
                SET theme = '".Database::escape_string($this->theme)."'
5232
                WHERE iid = $lp_id";
5233
        Database::query($sql);
5234
5235
        return true;
5236
    }
5237
5238
    /**
5239
     * Sets the image of an LP (and save).
5240
     *
5241
     * @param string $name Optional string giving the new image of this learnpath
5242
     *
5243
     * @return bool Returns true if theme name is not empty
5244
     */
5245
    public function set_preview_image($name = '')
5246
    {
5247
        $this->preview_image = $name;
5248
        $table = Database::get_course_table(TABLE_LP_MAIN);
5249
        $lp_id = $this->get_id();
5250
        $sql = "UPDATE $table SET
5251
                preview_image = '".Database::escape_string($this->preview_image)."'
5252
                WHERE iid = $lp_id";
5253
        Database::query($sql);
5254
5255
        return true;
5256
    }
5257
5258
    /**
5259
     * Sets the author of a LP (and save).
5260
     *
5261
     * @param string $name Optional string giving the new author of this learnpath
5262
     *
5263
     * @return bool Returns true if author's name is not empty
5264
     */
5265
    public function set_author($name = '')
5266
    {
5267
        $this->author = $name;
5268
        $table = Database::get_course_table(TABLE_LP_MAIN);
5269
        $lp_id = $this->get_id();
5270
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5271
                WHERE iid = $lp_id";
5272
        Database::query($sql);
5273
5274
        return true;
5275
    }
5276
5277
    /**
5278
     * Sets the hide_toc_frame parameter of a LP (and save).
5279
     *
5280
     * @param int $hide 1 if frame is hidden 0 then else
5281
     *
5282
     * @return bool Returns true if author's name is not empty
5283
     */
5284
    public function set_hide_toc_frame($hide)
5285
    {
5286
        if (intval($hide) == $hide) {
5287
            $this->hide_toc_frame = $hide;
5288
            $table = Database::get_course_table(TABLE_LP_MAIN);
5289
            $lp_id = $this->get_id();
5290
            $sql = "UPDATE $table SET
5291
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5292
                    WHERE iid = $lp_id";
5293
            Database::query($sql);
5294
5295
            return true;
5296
        }
5297
5298
        return false;
5299
    }
5300
5301
    /**
5302
     * Sets the prerequisite of a LP (and save).
5303
     *
5304
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5305
     *
5306
     * @return bool returns true if prerequisite is not empty
5307
     */
5308
    public function set_prerequisite($prerequisite)
5309
    {
5310
        $this->prerequisite = (int) $prerequisite;
5311
        $table = Database::get_course_table(TABLE_LP_MAIN);
5312
        $lp_id = $this->get_id();
5313
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5314
                WHERE iid = $lp_id";
5315
        Database::query($sql);
5316
5317
        return true;
5318
    }
5319
5320
    /**
5321
     * Sets the location/proximity of the LP (local/remote) (and save).
5322
     *
5323
     * @param string $name Optional string giving the new location of this learnpath
5324
     *
5325
     * @return bool True on success / False on error
5326
     */
5327
    public function set_proximity($name = '')
5328
    {
5329
        if (empty($name)) {
5330
            return false;
5331
        }
5332
5333
        $this->proximity = $name;
5334
        $table = Database::get_course_table(TABLE_LP_MAIN);
5335
        $lp_id = $this->get_id();
5336
        $sql = "UPDATE $table SET
5337
                    content_local = '".Database::escape_string($name)."'
5338
                WHERE iid = $lp_id";
5339
        Database::query($sql);
5340
5341
        return true;
5342
    }
5343
5344
    /**
5345
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5346
     *
5347
     * @param int $id DB ID of the item
5348
     */
5349
    public function set_previous_item($id)
5350
    {
5351
        if ($this->debug > 0) {
5352
            error_log('In learnpath::set_previous_item()', 0);
5353
        }
5354
        $this->last = $id;
5355
    }
5356
5357
    /**
5358
     * Sets use_max_score.
5359
     *
5360
     * @param int $use_max_score Optional string giving the new location of this learnpath
5361
     *
5362
     * @return bool True on success / False on error
5363
     */
5364
    public function set_use_max_score($use_max_score = 1)
5365
    {
5366
        $use_max_score = (int) $use_max_score;
5367
        $this->use_max_score = $use_max_score;
5368
        $table = Database::get_course_table(TABLE_LP_MAIN);
5369
        $lp_id = $this->get_id();
5370
        $sql = "UPDATE $table SET
5371
                    use_max_score = '".$this->use_max_score."'
5372
                WHERE iid = $lp_id";
5373
        Database::query($sql);
5374
5375
        return true;
5376
    }
5377
5378
    /**
5379
     * Sets and saves the expired_on date.
5380
     *
5381
     * @param string $expired_on Optional string giving the new author of this learnpath
5382
     *
5383
     * @throws \Doctrine\ORM\OptimisticLockException
5384
     *
5385
     * @return bool Returns true if author's name is not empty
5386
     */
5387
    public function set_expired_on($expired_on)
5388
    {
5389
        $em = Database::getManager();
5390
        /** @var CLp $lp */
5391
        $lp = $em
5392
            ->getRepository('ChamiloCourseBundle:CLp')
5393
            ->findOneBy(
5394
                [
5395
                    'iid' => $this->get_id(),
5396
                ]
5397
            );
5398
5399
        if (!$lp) {
5400
            return false;
5401
        }
5402
5403
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5404
5405
        $lp->setExpiredOn($this->expired_on);
5406
        $em->persist($lp);
5407
        $em->flush();
5408
5409
        return true;
5410
    }
5411
5412
    /**
5413
     * Sets and saves the publicated_on date.
5414
     *
5415
     * @param string $publicated_on Optional string giving the new author of this learnpath
5416
     *
5417
     * @throws \Doctrine\ORM\OptimisticLockException
5418
     *
5419
     * @return bool Returns true if author's name is not empty
5420
     */
5421
    public function set_publicated_on($publicated_on)
5422
    {
5423
        $em = Database::getManager();
5424
        /** @var CLp $lp */
5425
        $lp = $em
5426
            ->getRepository('ChamiloCourseBundle:CLp')
5427
            ->findOneBy(
5428
                [
5429
                    'iid' => $this->get_id(),
5430
                ]
5431
            );
5432
5433
        if (!$lp) {
5434
            return false;
5435
        }
5436
5437
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5438
        $lp->setPublicatedOn($this->publicated_on);
5439
        $em->persist($lp);
5440
        $em->flush();
5441
5442
        return true;
5443
    }
5444
5445
    /**
5446
     * Sets and saves the expired_on date.
5447
     *
5448
     * @return bool Returns true if author's name is not empty
5449
     */
5450
    public function set_modified_on()
5451
    {
5452
        $this->modified_on = api_get_utc_datetime();
5453
        $table = Database::get_course_table(TABLE_LP_MAIN);
5454
        $lp_id = $this->get_id();
5455
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5456
                WHERE iid = $lp_id";
5457
        Database::query($sql);
5458
5459
        return true;
5460
    }
5461
5462
    /**
5463
     * Sets the object's error message.
5464
     *
5465
     * @param string $error Error message. If empty, reinits the error string
5466
     */
5467
    public function set_error_msg($error = '')
5468
    {
5469
        if ($this->debug > 0) {
5470
            error_log('In learnpath::set_error_msg()', 0);
5471
        }
5472
        if (empty($error)) {
5473
            $this->error = '';
5474
        } else {
5475
            $this->error .= $error;
5476
        }
5477
    }
5478
5479
    /**
5480
     * Launches the current item if not 'sco'
5481
     * (starts timer and make sure there is a record ready in the DB).
5482
     *
5483
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5484
     *
5485
     * @return bool
5486
     */
5487
    public function start_current_item($allow_new_attempt = false)
5488
    {
5489
        $debug = $this->debug;
5490
        if ($debug) {
5491
            error_log('In learnpath::start_current_item()');
5492
            error_log('current: '.$this->current);
5493
        }
5494
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5495
            $type = $this->get_type();
5496
            $item_type = $this->items[$this->current]->get_type();
5497
            if (($type == 2 && $item_type != 'sco') ||
5498
                ($type == 3 && $item_type != 'au') ||
5499
                (
5500
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5501
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5502
                )
5503
            ) {
5504
                if ($debug) {
5505
                    error_log('item type: '.$item_type);
5506
                    error_log('lp type: '.$type);
5507
                }
5508
                $this->items[$this->current]->open($allow_new_attempt);
5509
                $this->autocomplete_parents($this->current);
5510
                $prereq_check = $this->prerequisites_match($this->current);
5511
                if ($debug) {
5512
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5513
                }
5514
                $this->items[$this->current]->save(false, $prereq_check);
5515
            }
5516
            // If sco, then it is supposed to have been updated by some other call.
5517
            if ($item_type == 'sco') {
5518
                $this->items[$this->current]->restart();
5519
            }
5520
        }
5521
        if ($debug) {
5522
            error_log('lp_view_session_id');
5523
            error_log($this->lp_view_session_id);
5524
            error_log('api session id');
5525
            error_log(api_get_session_id());
5526
            error_log('End of learnpath::start_current_item()');
5527
        }
5528
5529
        return true;
5530
    }
5531
5532
    /**
5533
     * Stops the processing and counters for the old item (as held in $this->last).
5534
     *
5535
     * @return bool True/False
5536
     */
5537
    public function stop_previous_item()
5538
    {
5539
        $debug = $this->debug;
5540
        if ($debug) {
5541
            error_log('In learnpath::stop_previous_item()', 0);
5542
        }
5543
5544
        if ($this->last != 0 && $this->last != $this->current &&
5545
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5546
        ) {
5547
            if ($debug) {
5548
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5549
            }
5550
            switch ($this->get_type()) {
5551
                case '3':
5552
                    if ($this->items[$this->last]->get_type() != 'au') {
5553
                        if ($debug) {
5554
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5555
                        }
5556
                        $this->items[$this->last]->close();
5557
                    } else {
5558
                        if ($debug) {
5559
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5560
                        }
5561
                    }
5562
                    break;
5563
                case '2':
5564
                    if ($this->items[$this->last]->get_type() != 'sco') {
5565
                        if ($debug) {
5566
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5567
                        }
5568
                        $this->items[$this->last]->close();
5569
                    } else {
5570
                        if ($debug) {
5571
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5572
                        }
5573
                    }
5574
                    break;
5575
                case '1':
5576
                default:
5577
                    if ($debug) {
5578
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5579
                    }
5580
                    $this->items[$this->last]->close();
5581
                    break;
5582
            }
5583
        } else {
5584
            if ($debug) {
5585
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5586
            }
5587
5588
            return false;
5589
        }
5590
5591
        return true;
5592
    }
5593
5594
    /**
5595
     * Updates the default view mode from fullscreen to embedded and inversely.
5596
     *
5597
     * @return string The current default view mode ('fullscreen' or 'embedded')
5598
     */
5599
    public function update_default_view_mode()
5600
    {
5601
        $table = Database::get_course_table(TABLE_LP_MAIN);
5602
        $sql = "SELECT * FROM $table
5603
                WHERE iid = ".$this->get_id();
5604
        $res = Database::query($sql);
5605
        if (Database::num_rows($res) > 0) {
5606
            $row = Database::fetch_array($res);
5607
            $default_view_mode = $row['default_view_mod'];
5608
            $view_mode = $default_view_mode;
5609
            switch ($default_view_mode) {
5610
                case 'fullscreen': // default with popup
5611
                    $view_mode = 'embedded';
5612
                    break;
5613
                case 'embedded': // default view with left menu
5614
                    $view_mode = 'embedframe';
5615
                    break;
5616
                case 'embedframe': //folded menu
5617
                    $view_mode = 'impress';
5618
                    break;
5619
                case 'impress':
5620
                    $view_mode = 'fullscreen';
5621
                    break;
5622
            }
5623
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5624
                    WHERE iid = ".$this->get_id();
5625
            Database::query($sql);
5626
            $this->mode = $view_mode;
5627
5628
            return $view_mode;
5629
        }
5630
5631
        return -1;
5632
    }
5633
5634
    /**
5635
     * Updates the default behaviour about auto-commiting SCORM updates.
5636
     *
5637
     * @return bool True if auto-commit has been set to 'on', false otherwise
5638
     */
5639
    public function update_default_scorm_commit()
5640
    {
5641
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5642
        $sql = "SELECT * FROM $lp_table
5643
                WHERE iid = ".$this->get_id();
5644
        $res = Database::query($sql);
5645
        if (Database::num_rows($res) > 0) {
5646
            $row = Database::fetch_array($res);
5647
            $force = $row['force_commit'];
5648
            if ($force == 1) {
5649
                $force = 0;
5650
                $force_return = false;
5651
            } elseif ($force == 0) {
5652
                $force = 1;
5653
                $force_return = true;
5654
            }
5655
            $sql = "UPDATE $lp_table SET force_commit = $force
5656
                    WHERE iid = ".$this->get_id();
5657
            Database::query($sql);
5658
            $this->force_commit = $force_return;
5659
5660
            return $force_return;
5661
        }
5662
5663
        return -1;
5664
    }
5665
5666
    /**
5667
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5668
     *
5669
     * @return bool True on success, false on failure
5670
     */
5671
    public function update_display_order()
5672
    {
5673
        $course_id = api_get_course_int_id();
5674
        $table = Database::get_course_table(TABLE_LP_MAIN);
5675
        $sql = "SELECT * FROM $table
5676
                WHERE c_id = $course_id
5677
                ORDER BY display_order";
5678
        $res = Database::query($sql);
5679
        if ($res === false) {
5680
            return false;
5681
        }
5682
5683
        $num = Database::num_rows($res);
5684
        // First check the order is correct, globally (might be wrong because
5685
        // of versions < 1.8.4).
5686
        if ($num > 0) {
5687
            $i = 1;
5688
            while ($row = Database::fetch_array($res)) {
5689
                if ($row['display_order'] != $i) {
5690
                    // If we find a gap in the order, we need to fix it.
5691
                    $sql = "UPDATE $table SET display_order = $i
5692
                            WHERE iid = ".$row['iid'];
5693
                    Database::query($sql);
5694
                }
5695
                $i++;
5696
            }
5697
        }
5698
5699
        return true;
5700
    }
5701
5702
    /**
5703
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5704
     *
5705
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5706
     */
5707
    public function update_reinit()
5708
    {
5709
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5710
        $sql = "SELECT * FROM $lp_table
5711
                WHERE iid = ".$this->get_id();
5712
        $res = Database::query($sql);
5713
        if (Database::num_rows($res) > 0) {
5714
            $row = Database::fetch_array($res);
5715
            $force = $row['prevent_reinit'];
5716
            if ($force == 1) {
5717
                $force = 0;
5718
            } elseif ($force == 0) {
5719
                $force = 1;
5720
            }
5721
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5722
                    WHERE iid = ".$this->get_id();
5723
            Database::query($sql);
5724
            $this->prevent_reinit = $force;
5725
5726
            return $force;
5727
        }
5728
5729
        return -1;
5730
    }
5731
5732
    /**
5733
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5734
     *
5735
     * @return string 'single', 'multi' or 'seriousgame'
5736
     *
5737
     * @author ndiechburg <[email protected]>
5738
     */
5739
    public function get_attempt_mode()
5740
    {
5741
        //Set default value for seriousgame_mode
5742
        if (!isset($this->seriousgame_mode)) {
5743
            $this->seriousgame_mode = 0;
5744
        }
5745
        // Set default value for prevent_reinit
5746
        if (!isset($this->prevent_reinit)) {
5747
            $this->prevent_reinit = 1;
5748
        }
5749
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5750
            return 'seriousgame';
5751
        }
5752
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5753
            return 'single';
5754
        }
5755
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5756
            return 'multiple';
5757
        }
5758
5759
        return 'single';
5760
    }
5761
5762
    /**
5763
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5764
     *
5765
     * @param string 'seriousgame', 'single' or 'multiple'
5766
     *
5767
     * @return bool
5768
     *
5769
     * @author ndiechburg <[email protected]>
5770
     */
5771
    public function set_attempt_mode($mode)
5772
    {
5773
        switch ($mode) {
5774
            case 'seriousgame':
5775
                $sg_mode = 1;
5776
                $prevent_reinit = 1;
5777
                break;
5778
            case 'single':
5779
                $sg_mode = 0;
5780
                $prevent_reinit = 1;
5781
                break;
5782
            case 'multiple':
5783
                $sg_mode = 0;
5784
                $prevent_reinit = 0;
5785
                break;
5786
            default:
5787
                $sg_mode = 0;
5788
                $prevent_reinit = 0;
5789
                break;
5790
        }
5791
        $this->prevent_reinit = $prevent_reinit;
5792
        $this->seriousgame_mode = $sg_mode;
5793
        $table = Database::get_course_table(TABLE_LP_MAIN);
5794
        $sql = "UPDATE $table SET
5795
                prevent_reinit = $prevent_reinit ,
5796
                seriousgame_mode = $sg_mode
5797
                WHERE iid = ".$this->get_id();
5798
        $res = Database::query($sql);
5799
        if ($res) {
5800
            return true;
5801
        } else {
5802
            return false;
5803
        }
5804
    }
5805
5806
    /**
5807
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5808
     *
5809
     * @author ndiechburg <[email protected]>
5810
     */
5811
    public function switch_attempt_mode()
5812
    {
5813
        $mode = $this->get_attempt_mode();
5814
        switch ($mode) {
5815
            case 'single':
5816
                $next_mode = 'multiple';
5817
                break;
5818
            case 'multiple':
5819
                $next_mode = 'seriousgame';
5820
                break;
5821
            case 'seriousgame':
5822
            default:
5823
                $next_mode = 'single';
5824
                break;
5825
        }
5826
        $this->set_attempt_mode($next_mode);
5827
    }
5828
5829
    /**
5830
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5831
     * but possibility to do again a completed item.
5832
     *
5833
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5834
     *
5835
     * @author ndiechburg <[email protected]>
5836
     */
5837
    public function set_seriousgame_mode()
5838
    {
5839
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5840
        $sql = "SELECT * FROM $lp_table
5841
                WHERE iid = ".$this->get_id();
5842
        $res = Database::query($sql);
5843
        if (Database::num_rows($res) > 0) {
5844
            $row = Database::fetch_array($res);
5845
            $force = $row['seriousgame_mode'];
5846
            if ($force == 1) {
5847
                $force = 0;
5848
            } elseif ($force == 0) {
5849
                $force = 1;
5850
            }
5851
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5852
			        WHERE iid = ".$this->get_id();
5853
            Database::query($sql);
5854
            $this->seriousgame_mode = $force;
5855
5856
            return $force;
5857
        }
5858
5859
        return -1;
5860
    }
5861
5862
    /**
5863
     * Updates the "scorm_debug" value that shows or hide the debug window.
5864
     *
5865
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5866
     */
5867
    public function update_scorm_debug()
5868
    {
5869
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5870
        $sql = "SELECT * FROM $lp_table
5871
                WHERE iid = ".$this->get_id();
5872
        $res = Database::query($sql);
5873
        if (Database::num_rows($res) > 0) {
5874
            $row = Database::fetch_array($res);
5875
            $force = $row['debug'];
5876
            if ($force == 1) {
5877
                $force = 0;
5878
            } elseif ($force == 0) {
5879
                $force = 1;
5880
            }
5881
            $sql = "UPDATE $lp_table SET debug = $force
5882
                    WHERE iid = ".$this->get_id();
5883
            Database::query($sql);
5884
            $this->scorm_debug = $force;
5885
5886
            return $force;
5887
        }
5888
5889
        return -1;
5890
    }
5891
5892
    /**
5893
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5894
     *
5895
     * @author Kevin Van Den Haute
5896
     *
5897
     * @param  array
5898
     */
5899
    public function tree_array($array)
5900
    {
5901
        $array = $this->sort_tree_array($array);
5902
        $this->create_tree_array($array);
5903
    }
5904
5905
    /**
5906
     * Creates an array with the elements of the learning path tree in it.
5907
     *
5908
     * @author Kevin Van Den Haute
5909
     *
5910
     * @param array $array
5911
     * @param int   $parent
5912
     * @param int   $depth
5913
     * @param array $tmp
5914
     */
5915
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5916
    {
5917
        if (is_array($array)) {
5918
            for ($i = 0; $i < count($array); $i++) {
5919
                if ($array[$i]['parent_item_id'] == $parent) {
5920
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5921
                        $tmp[] = $array[$i]['parent_item_id'];
5922
                        $depth++;
5923
                    }
5924
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5925
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5926
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5927
5928
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5929
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5930
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5931
                    $this->arrMenu[] = [
5932
                        'id' => $array[$i]['id'],
5933
                        'ref' => $ref,
5934
                        'item_type' => $array[$i]['item_type'],
5935
                        'title' => $array[$i]['title'],
5936
                        'title_raw' => $array[$i]['title_raw'],
5937
                        'path' => $path,
5938
                        'description' => $array[$i]['description'],
5939
                        'parent_item_id' => $array[$i]['parent_item_id'],
5940
                        'previous_item_id' => $array[$i]['previous_item_id'],
5941
                        'next_item_id' => $array[$i]['next_item_id'],
5942
                        'min_score' => $array[$i]['min_score'],
5943
                        'max_score' => $array[$i]['max_score'],
5944
                        'mastery_score' => $array[$i]['mastery_score'],
5945
                        'display_order' => $array[$i]['display_order'],
5946
                        'prerequisite' => $preq,
5947
                        'depth' => $depth,
5948
                        'audio' => $audio,
5949
                        'prerequisite_min_score' => $prerequisiteMinScore,
5950
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5951
                    ];
5952
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5953
                }
5954
            }
5955
        }
5956
    }
5957
5958
    /**
5959
     * Sorts a multi dimensional array by parent id and display order.
5960
     *
5961
     * @author Kevin Van Den Haute
5962
     *
5963
     * @param array $array (array with al the learning path items in it)
5964
     *
5965
     * @return array
5966
     */
5967
    public function sort_tree_array($array)
5968
    {
5969
        foreach ($array as $key => $row) {
5970
            $parent[$key] = $row['parent_item_id'];
5971
            $position[$key] = $row['display_order'];
5972
        }
5973
5974
        if (count($array) > 0) {
5975
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5976
        }
5977
5978
        return $array;
5979
    }
5980
5981
    /**
5982
     * Function that creates a html list of learning path items so that we can add audio files to them.
5983
     *
5984
     * @author Kevin Van Den Haute
5985
     *
5986
     * @return string
5987
     */
5988
    public function overview()
5989
    {
5990
        $return = '';
5991
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5992
5993
        // we need to start a form when we want to update all the mp3 files
5994
        if ($update_audio == 'true') {
5995
            $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">';
5996
        }
5997
        $return .= '<div id="message"></div>';
5998
        if (count($this->items) == 0) {
5999
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6000
        } else {
6001
            $return_audio = '<table class="table table-hover table-striped data_table">';
6002
            $return_audio .= '<tr>';
6003
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6004
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6005
            $return_audio .= '</tr>';
6006
6007
            if ($update_audio != 'true') {
6008
                $return .= '<div class="col-md-12">';
6009
                $return .= self::return_new_tree($update_audio);
6010
                $return .= '</div>';
6011
                $return .= Display::div(
6012
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6013
                    ['style' => 'float:left; margin-top:15px;width:100%']
6014
                );
6015
            } else {
6016
                $return_audio .= self::return_new_tree($update_audio);
6017
                $return .= $return_audio.'</table>';
6018
            }
6019
6020
            // We need to close the form when we are updating the mp3 files.
6021
            if ($update_audio == 'true') {
6022
                $return .= '<div class="footer-audio">';
6023
                $return .= Display::button(
6024
                    'save_audio',
6025
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6026
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6027
                );
6028
                $return .= '</div>';
6029
            }
6030
        }
6031
6032
        // We need to close the form when we are updating the mp3 files.
6033
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6034
            $return .= '</form>';
6035
        }
6036
6037
        return $return;
6038
    }
6039
6040
    /**
6041
     * @param string $update_audio
6042
     *
6043
     * @return array
6044
     */
6045
    public function processBuildMenuElements($update_audio = 'false')
6046
    {
6047
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6048
        $arrLP = $this->getItemsForForm();
6049
6050
        $this->tree_array($arrLP);
6051
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6052
        unset($this->arrMenu);
6053
        $default_data = null;
6054
        $default_content = null;
6055
        $elements = [];
6056
        $return_audio = null;
6057
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6058
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6059
        $countItems = count($arrLP);
6060
6061
        $upIcon = Display::return_icon(
6062
            'up.png',
6063
            get_lang('Up'),
6064
            [],
6065
            ICON_SIZE_TINY
6066
        );
6067
6068
        $disableUpIcon = Display::return_icon(
6069
            'up_na.png',
6070
            get_lang('Up'),
6071
            [],
6072
            ICON_SIZE_TINY
6073
        );
6074
6075
        $downIcon = Display::return_icon(
6076
            'down.png',
6077
            get_lang('Down'),
6078
            [],
6079
            ICON_SIZE_TINY
6080
        );
6081
6082
        $disableDownIcon = Display::return_icon(
6083
            'down_na.png',
6084
            get_lang('Down'),
6085
            [],
6086
            ICON_SIZE_TINY
6087
        );
6088
6089
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6090
6091
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6092
        $plugin = null;
6093
        if ($pluginCalendar) {
6094
            $plugin = LearningCalendarPlugin::create();
6095
        }
6096
6097
        for ($i = 0; $i < $countItems; $i++) {
6098
            $parent_id = $arrLP[$i]['parent_item_id'];
6099
            $title = $arrLP[$i]['title'];
6100
            $title_cut = $arrLP[$i]['title_raw'];
6101
            if ($show === false) {
6102
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6103
            }
6104
            // Link for the documents
6105
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6106
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6107
                $title_cut = Display::url(
6108
                    $title_cut,
6109
                    $url,
6110
                    [
6111
                        'class' => 'ajax moved',
6112
                        'data-title' => $title,
6113
                        'title' => $title,
6114
                    ]
6115
                );
6116
            }
6117
6118
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6119
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6120
                Session::write('pathItem', $arrLP[$i]['path']);
6121
            }
6122
6123
            $oddClass = 'row_even';
6124
            if (($i % 2) == 0) {
6125
                $oddClass = 'row_odd';
6126
            }
6127
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6128
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6129
6130
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6131
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6132
            } else {
6133
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6134
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6135
                } else {
6136
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6137
                        $icon = Display::return_icon('certificate.png');
6138
                    } else {
6139
                        $icon = Display::return_icon('folder_document.gif');
6140
                    }
6141
                }
6142
            }
6143
6144
            // The audio column.
6145
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6146
            $audio = '';
6147
            if (!$update_audio || $update_audio != 'true') {
6148
                if (empty($arrLP[$i]['audio'])) {
6149
                    $audio .= '';
6150
                }
6151
            } else {
6152
                $types = self::getChapterTypes();
6153
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6154
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6155
                    if (!empty($arrLP[$i]['audio'])) {
6156
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6157
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6158
                    }
6159
                }
6160
            }
6161
6162
            $return_audio .= Display::span($icon.' '.$title).
6163
                Display::tag(
6164
                    'td',
6165
                    $audio,
6166
                    ['style' => '']
6167
                );
6168
            $return_audio .= '</td>';
6169
            $move_icon = '';
6170
            $move_item_icon = '';
6171
            $edit_icon = '';
6172
            $delete_icon = '';
6173
            $audio_icon = '';
6174
            $prerequisities_icon = '';
6175
            $forumIcon = '';
6176
            $previewIcon = '';
6177
            $pluginCalendarIcon = '';
6178
            $orderIcons = '';
6179
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6180
6181
            if ($is_allowed_to_edit) {
6182
                if (!$update_audio || $update_audio != 'true') {
6183
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6184
                        $move_icon .= '<a class="moved" href="#">';
6185
                        $move_icon .= Display::return_icon(
6186
                            'move_everywhere.png',
6187
                            get_lang('Move'),
6188
                            [],
6189
                            ICON_SIZE_TINY
6190
                        );
6191
                        $move_icon .= '</a>';
6192
                    }
6193
                }
6194
6195
                // No edit for this item types
6196
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6197
                    if ($arrLP[$i]['item_type'] != 'dir') {
6198
                        $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">';
6199
                        $edit_icon .= Display::return_icon(
6200
                            'edit.png',
6201
                            get_lang('LearnpathEditModule'),
6202
                            [],
6203
                            ICON_SIZE_TINY
6204
                        );
6205
                        $edit_icon .= '</a>';
6206
6207
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6208
                            $forumThread = null;
6209
                            if (isset($this->items[$arrLP[$i]['id']])) {
6210
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6211
                                    $this->course_int_id,
6212
                                    $this->lp_session_id
6213
                                );
6214
                            }
6215
                            if ($forumThread) {
6216
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6217
                                        'action' => 'dissociate_forum',
6218
                                        'id' => $arrLP[$i]['id'],
6219
                                        'lp_id' => $this->lp_id,
6220
                                    ]);
6221
                                $forumIcon = Display::url(
6222
                                    Display::return_icon(
6223
                                        'forum.png',
6224
                                        get_lang('DissociateForumToLPItem'),
6225
                                        [],
6226
                                        ICON_SIZE_TINY
6227
                                    ),
6228
                                    $forumIconUrl,
6229
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6230
                                );
6231
                            } else {
6232
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6233
                                        'action' => 'create_forum',
6234
                                        'id' => $arrLP[$i]['id'],
6235
                                        'lp_id' => $this->lp_id,
6236
                                    ]);
6237
                                $forumIcon = Display::url(
6238
                                    Display::return_icon(
6239
                                        'forum.png',
6240
                                        get_lang('AssociateForumToLPItem'),
6241
                                        [],
6242
                                        ICON_SIZE_TINY
6243
                                    ),
6244
                                    $forumIconUrl,
6245
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6246
                                );
6247
                            }
6248
                        }
6249
                    } else {
6250
                        $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">';
6251
                        $edit_icon .= Display::return_icon(
6252
                            'edit.png',
6253
                            get_lang('LearnpathEditModule'),
6254
                            [],
6255
                            ICON_SIZE_TINY
6256
                        );
6257
                        $edit_icon .= '</a>';
6258
                    }
6259
                } else {
6260
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6261
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6262
                        $edit_icon .= Display::return_icon(
6263
                            'edit.png',
6264
                            get_lang('Edit'),
6265
                            [],
6266
                            ICON_SIZE_TINY
6267
                        );
6268
                        $edit_icon .= '</a>';
6269
                    }
6270
                }
6271
6272
                if ($pluginCalendar) {
6273
                    $pluginLink = $pluginUrl.
6274
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6275
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6276
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6277
                    if ($itemInfo && $itemInfo['value'] == 1) {
6278
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6279
                    }
6280
                    $pluginCalendarIcon = Display::url(
6281
                        $iconCalendar,
6282
                        $pluginLink,
6283
                        ['class' => 'btn btn-default']
6284
                    );
6285
                }
6286
6287
                if ($arrLP[$i]['item_type'] != 'final_item') {
6288
                    $orderIcons = Display::url(
6289
                        $upIcon,
6290
                        'javascript:void(0)',
6291
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6292
                    );
6293
                    $orderIcons .= Display::url(
6294
                        $downIcon,
6295
                        'javascript:void(0)',
6296
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6297
                    );
6298
                }
6299
6300
                $delete_icon .= ' <a
6301
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6302
                    onclick="return confirmation(\''.addslashes($title).'\');"
6303
                    class="btn btn-default">';
6304
                $delete_icon .= Display::return_icon(
6305
                    'delete.png',
6306
                    get_lang('LearnpathDeleteModule'),
6307
                    [],
6308
                    ICON_SIZE_TINY
6309
                );
6310
                $delete_icon .= '</a>';
6311
6312
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6313
                $previewImage = Display::return_icon(
6314
                    'preview_view.png',
6315
                    get_lang('Preview'),
6316
                    [],
6317
                    ICON_SIZE_TINY
6318
                );
6319
6320
                switch ($arrLP[$i]['item_type']) {
6321
                    case TOOL_DOCUMENT:
6322
                    case TOOL_LP_FINAL_ITEM:
6323
                    case TOOL_READOUT_TEXT:
6324
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6325
                        $previewIcon = Display::url(
6326
                            $previewImage,
6327
                            $urlPreviewLink,
6328
                            [
6329
                                'target' => '_blank',
6330
                                'class' => 'btn btn-default',
6331
                                'data-title' => $arrLP[$i]['title'],
6332
                                'title' => $arrLP[$i]['title'],
6333
                            ]
6334
                        );
6335
                        break;
6336
                    case TOOL_THREAD:
6337
                    case TOOL_FORUM:
6338
                    case TOOL_QUIZ:
6339
                    case TOOL_STUDENTPUBLICATION:
6340
                    case TOOL_LP_FINAL_ITEM:
6341
                    case TOOL_LINK:
6342
                        $class = 'btn btn-default';
6343
                        $target = '_blank';
6344
                        $link = self::rl_get_resource_link_for_learnpath(
6345
                            $this->course_int_id,
6346
                            $this->lp_id,
6347
                            $arrLP[$i]['id'],
6348
                            0
6349
                        );
6350
                        $previewIcon = Display::url(
6351
                            $previewImage,
6352
                            $link,
6353
                            [
6354
                                'class' => $class,
6355
                                'data-title' => $arrLP[$i]['title'],
6356
                                'title' => $arrLP[$i]['title'],
6357
                                'target' => $target,
6358
                            ]
6359
                        );
6360
                        break;
6361
                    default:
6362
                        $previewIcon = Display::url(
6363
                            $previewImage,
6364
                            $url.'&action=view_item',
6365
                            ['class' => 'btn btn-default', 'target' => '_blank']
6366
                        );
6367
                        break;
6368
                }
6369
6370
                if ($arrLP[$i]['item_type'] != 'dir') {
6371
                    $prerequisities_icon = Display::url(
6372
                        Display::return_icon(
6373
                            'accept.png',
6374
                            get_lang('LearnpathPrerequisites'),
6375
                            [],
6376
                            ICON_SIZE_TINY
6377
                        ),
6378
                        $url.'&action=edit_item_prereq',
6379
                        ['class' => 'btn btn-default']
6380
                    );
6381
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6382
                        $move_item_icon = Display::url(
6383
                            Display::return_icon(
6384
                                'move.png',
6385
                                get_lang('Move'),
6386
                                [],
6387
                                ICON_SIZE_TINY
6388
                            ),
6389
                            $url.'&action=move_item',
6390
                            ['class' => 'btn btn-default']
6391
                        );
6392
                    }
6393
                    $audio_icon = Display::url(
6394
                        Display::return_icon(
6395
                            'audio.png',
6396
                            get_lang('UplUpload'),
6397
                            [],
6398
                            ICON_SIZE_TINY
6399
                        ),
6400
                        $url.'&action=add_audio',
6401
                        ['class' => 'btn btn-default']
6402
                    );
6403
                }
6404
            }
6405
            if ($update_audio != 'true') {
6406
                $row = $move_icon.' '.$icon.
6407
                    Display::span($title_cut).
6408
                    Display::tag(
6409
                        'div',
6410
                        "<div class=\"btn-group btn-group-xs\">
6411
                                    $previewIcon
6412
                                    $audio
6413
                                    $edit_icon
6414
                                    $pluginCalendarIcon
6415
                                    $forumIcon
6416
                                    $prerequisities_icon
6417
                                    $move_item_icon
6418
                                    $audio_icon
6419
                                    $orderIcons
6420
                                    $delete_icon
6421
                                </div>",
6422
                        ['class' => 'btn-toolbar button_actions']
6423
                    );
6424
            } else {
6425
                $row =
6426
                    Display::span($title.$icon).
6427
                    Display::span($audio, ['class' => 'button_actions']);
6428
            }
6429
6430
            $default_data[$arrLP[$i]['id']] = $row;
6431
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6432
6433
            if (empty($parent_id)) {
6434
                $elements[$arrLP[$i]['id']]['data'] = $row;
6435
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6436
            } else {
6437
                $parent_arrays = [];
6438
                if ($arrLP[$i]['depth'] > 1) {
6439
                    // Getting list of parents
6440
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6441
                        foreach ($arrLP as $item) {
6442
                            if ($item['id'] == $parent_id) {
6443
                                if ($item['parent_item_id'] == 0) {
6444
                                    $parent_id = $item['id'];
6445
                                    break;
6446
                                } else {
6447
                                    $parent_id = $item['parent_item_id'];
6448
                                    if (empty($parent_arrays)) {
6449
                                        $parent_arrays[] = intval($item['id']);
6450
                                    }
6451
                                    $parent_arrays[] = $parent_id;
6452
                                    break;
6453
                                }
6454
                            }
6455
                        }
6456
                    }
6457
                }
6458
6459
                if (!empty($parent_arrays)) {
6460
                    $parent_arrays = array_reverse($parent_arrays);
6461
                    $val = '$elements';
6462
                    $x = 0;
6463
                    foreach ($parent_arrays as $item) {
6464
                        if ($x != count($parent_arrays) - 1) {
6465
                            $val .= '["'.$item.'"]["children"]';
6466
                        } else {
6467
                            $val .= '["'.$item.'"]["children"]';
6468
                        }
6469
                        $x++;
6470
                    }
6471
                    $val .= "";
6472
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6473
                    eval($code_str);
6474
                } else {
6475
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6476
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6477
                }
6478
            }
6479
        }
6480
6481
        return [
6482
            'elements' => $elements,
6483
            'default_data' => $default_data,
6484
            'default_content' => $default_content,
6485
            'return_audio' => $return_audio,
6486
        ];
6487
    }
6488
6489
    /**
6490
     * @param string $updateAudio true/false strings
6491
     *
6492
     * @return string
6493
     */
6494
    public function returnLpItemList($updateAudio)
6495
    {
6496
        $result = $this->processBuildMenuElements($updateAudio);
6497
6498
        $html = self::print_recursive(
6499
            $result['elements'],
6500
            $result['default_data'],
6501
            $result['default_content']
6502
        );
6503
6504
        if (!empty($html)) {
6505
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6506
        }
6507
6508
        return $html;
6509
    }
6510
6511
    /**
6512
     * @param string $update_audio
6513
     * @param bool   $drop_element_here
6514
     *
6515
     * @return string
6516
     */
6517
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6518
    {
6519
        $result = $this->processBuildMenuElements($update_audio);
6520
6521
        $list = '<ul id="lp_item_list">';
6522
        $tree = $this->print_recursive(
6523
            $result['elements'],
6524
            $result['default_data'],
6525
            $result['default_content']
6526
        );
6527
6528
        if (!empty($tree)) {
6529
            $list .= $tree;
6530
        } else {
6531
            if ($drop_element_here) {
6532
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6533
            }
6534
        }
6535
        $list .= '</ul>';
6536
6537
        $return = Display::panelCollapse(
6538
            $this->name,
6539
            $list,
6540
            'scorm-list',
6541
            null,
6542
            'scorm-list-accordion',
6543
            'scorm-list-collapse'
6544
        );
6545
6546
        if ($update_audio === 'true') {
6547
            $return = $result['return_audio'];
6548
        }
6549
6550
        return $return;
6551
    }
6552
6553
    /**
6554
     * @param array $elements
6555
     * @param array $default_data
6556
     * @param array $default_content
6557
     *
6558
     * @return string
6559
     */
6560
    public function print_recursive($elements, $default_data, $default_content)
6561
    {
6562
        $return = '';
6563
        foreach ($elements as $key => $item) {
6564
            if (isset($item['load_data']) || empty($item['data'])) {
6565
                $item['data'] = $default_data[$item['load_data']];
6566
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6567
            }
6568
            $sub_list = '';
6569
            if (isset($item['type']) && $item['type'] === 'dir') {
6570
                // empty value
6571
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6572
            }
6573
            if (empty($item['children'])) {
6574
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6575
                $active = null;
6576
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6577
                    $active = 'active';
6578
                }
6579
                $return .= Display::tag(
6580
                    'li',
6581
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6582
                    ['id' => $key, 'class' => 'record li_container']
6583
                );
6584
            } else {
6585
                // Sections
6586
                $data = '';
6587
                if (isset($item['children'])) {
6588
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6589
                }
6590
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6591
                $return .= Display::tag(
6592
                    'li',
6593
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6594
                    ['id' => $key, 'class' => 'record li_container']
6595
                );
6596
            }
6597
        }
6598
6599
        return $return;
6600
    }
6601
6602
    /**
6603
     * This function builds the action menu.
6604
     *
6605
     * @param bool   $returnString           Optional
6606
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6607
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6608
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6609
     * @param string $action
6610
     * @param array  $extraField
6611
     *
6612
     * @return string
6613
     */
6614
    public function build_action_menu(
6615
        $returnString = false,
6616
        $showRequirementButtons = true,
6617
        $isConfigPage = false,
6618
        $allowExpand = true,
6619
        $action = '',
6620
        $extraField = []
6621
    ) {
6622
        $actionsRight = '';
6623
        $lpId = $this->lp_id;
6624
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6625
            $back = Display::url(
6626
                Display::return_icon(
6627
                    'back.png',
6628
                    get_lang('ReturnToLearningPaths'),
6629
                    '',
6630
                    ICON_SIZE_MEDIUM
6631
                ),
6632
                'lp_controller.php?'.api_get_cidreq()
6633
            );
6634
        } else {
6635
            $back = Display::url(
6636
                Display::return_icon(
6637
                    'back.png',
6638
                    get_lang('Back'),
6639
                    '',
6640
                    ICON_SIZE_MEDIUM
6641
                ),
6642
                $extraField['backTo']
6643
            );
6644
        }
6645
6646
        /*if ($backToBuild) {
6647
            $back = Display::url(
6648
                Display::return_icon(
6649
                    'back.png',
6650
                    get_lang('GoBack'),
6651
                    '',
6652
                    ICON_SIZE_MEDIUM
6653
                ),
6654
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6655
            );
6656
        }*/
6657
6658
        $actionsLeft = $back;
6659
6660
        $actionsLeft .= Display::url(
6661
            Display::return_icon(
6662
                'preview_view.png',
6663
                get_lang('Preview'),
6664
                '',
6665
                ICON_SIZE_MEDIUM
6666
            ),
6667
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6668
                'action' => 'view',
6669
                'lp_id' => $lpId,
6670
                'isStudentView' => 'true',
6671
            ])
6672
        );
6673
6674
        $actionsLeft .= Display::url(
6675
            Display::return_icon(
6676
                'upload_audio.png',
6677
                get_lang('UpdateAllAudioFragments'),
6678
                '',
6679
                ICON_SIZE_MEDIUM
6680
            ),
6681
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6682
                'action' => 'admin_view',
6683
                'lp_id' => $lpId,
6684
                'updateaudio' => 'true',
6685
            ])
6686
        );
6687
6688
        $subscriptionSettings = self::getSubscriptionSettings();
6689
6690
        $request = api_request_uri();
6691
        if (strpos($request, 'edit') === false) {
6692
            $actionsLeft .= Display::url(
6693
                Display::return_icon(
6694
                    'settings.png',
6695
                    get_lang('CourseSettings'),
6696
                    '',
6697
                    ICON_SIZE_MEDIUM
6698
                ),
6699
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6700
                    'action' => 'edit',
6701
                    'lp_id' => $lpId,
6702
                ])
6703
            );
6704
        }
6705
6706
        if ((strpos($request, 'build') === false &&
6707
            strpos($request, 'add_item') === false) ||
6708
            in_array($action, ['add_audio'])
6709
        ) {
6710
            $actionsLeft .= Display::url(
6711
                Display::return_icon(
6712
                    'edit.png',
6713
                    get_lang('Edit'),
6714
                    '',
6715
                    ICON_SIZE_MEDIUM
6716
                ),
6717
                'lp_controller.php?'.http_build_query([
6718
                    'action' => 'build',
6719
                    'lp_id' => $lpId,
6720
                ]).'&'.api_get_cidreq()
6721
            );
6722
        }
6723
6724
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6725
            if ($this->subscribeUsers == 1 &&
6726
                $subscriptionSettings['allow_add_users_to_lp']) {
6727
                $actionsLeft .= Display::url(
6728
                    Display::return_icon(
6729
                        'user.png',
6730
                        get_lang('SubscribeUsersToLp'),
6731
                        '',
6732
                        ICON_SIZE_MEDIUM
6733
                    ),
6734
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6735
                );
6736
            }
6737
        }
6738
6739
        if ($allowExpand) {
6740
            $actionsLeft .= Display::url(
6741
                Display::return_icon(
6742
                    'expand.png',
6743
                    get_lang('Expand'),
6744
                    ['id' => 'expand'],
6745
                    ICON_SIZE_MEDIUM
6746
                ).
6747
                Display::return_icon(
6748
                    'contract.png',
6749
                    get_lang('Collapse'),
6750
                    ['id' => 'contract', 'class' => 'hide'],
6751
                    ICON_SIZE_MEDIUM
6752
                ),
6753
                '#',
6754
                ['role' => 'button', 'id' => 'hide_bar_template']
6755
            );
6756
        }
6757
6758
        if ($showRequirementButtons) {
6759
            $buttons = [
6760
                [
6761
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6762
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6763
                        'action' => 'set_previous_step_as_prerequisite',
6764
                        'lp_id' => $lpId,
6765
                    ]),
6766
                ],
6767
                [
6768
                    'title' => get_lang('ClearAllPrerequisites'),
6769
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6770
                        'action' => 'clear_prerequisites',
6771
                        'lp_id' => $lpId,
6772
                    ]),
6773
                ],
6774
            ];
6775
            $actionsRight = Display::groupButtonWithDropDown(
6776
                get_lang('PrerequisitesOptions'),
6777
                $buttons,
6778
                true
6779
            );
6780
        }
6781
6782
        // see  BT#17943
6783
        if (api_is_platform_admin()) {
6784
            if (isset($extraField['authorlp'])) {
6785
                $actionsLeft .= Display::url(
6786
                    Display::return_icon(
6787
                        'add-groups.png',
6788
                        get_lang('Author'),
6789
                        '',
6790
                        ICON_SIZE_MEDIUM
6791
                    ),
6792
                    'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6793
                        'action' => 'author_view',
6794
                        'lp_id' => $lpId,
6795
                    ])
6796
                );
6797
            }
6798
        }
6799
6800
        $toolbar = Display::toolbarAction(
6801
            'actions-lp-controller',
6802
            [$actionsLeft, $actionsRight]
6803
        );
6804
6805
        if ($returnString) {
6806
            return $toolbar;
6807
        }
6808
6809
        echo $toolbar;
6810
    }
6811
6812
    /**
6813
     * Creates the default learning path folder.
6814
     *
6815
     * @param array $course
6816
     * @param int   $creatorId
6817
     *
6818
     * @return bool
6819
     */
6820
    public static function generate_learning_path_folder($course, $creatorId = 0)
6821
    {
6822
        // Creating learning_path folder
6823
        $dir = '/learning_path';
6824
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6825
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6826
6827
        $folder = false;
6828
        if (!is_dir($filepath.'/'.$dir)) {
6829
            $folderData = create_unexisting_directory(
6830
                $course,
6831
                $creatorId,
6832
                0,
6833
                null,
6834
                0,
6835
                $filepath,
6836
                $dir,
6837
                get_lang('LearningPaths'),
6838
                0
6839
            );
6840
            if (!empty($folderData)) {
6841
                $folder = true;
6842
            }
6843
        } else {
6844
            $folder = true;
6845
        }
6846
6847
        return $folder;
6848
    }
6849
6850
    /**
6851
     * @param array  $course
6852
     * @param string $lp_name
6853
     * @param int    $creatorId
6854
     *
6855
     * @return array
6856
     */
6857
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6858
    {
6859
        $filepath = '';
6860
        $dir = '/learning_path/';
6861
6862
        if (empty($lp_name)) {
6863
            $lp_name = $this->name;
6864
        }
6865
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6866
        $folder = self::generate_learning_path_folder($course, $creatorId);
6867
6868
        // Limits title size
6869
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6870
        $dir = $dir.$title;
6871
6872
        // Creating LP folder
6873
        $documentId = null;
6874
        if ($folder) {
6875
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6876
            if (!is_dir($filepath.'/'.$dir)) {
6877
                $folderData = create_unexisting_directory(
6878
                    $course,
6879
                    $creatorId,
6880
                    0,
6881
                    0,
6882
                    0,
6883
                    $filepath,
6884
                    $dir,
6885
                    $lp_name
6886
                );
6887
                if (!empty($folderData)) {
6888
                    $folder = true;
6889
                }
6890
6891
                $documentId = $folderData['id'];
6892
            } else {
6893
                $folder = true;
6894
            }
6895
            $dir = $dir.'/';
6896
            if ($folder) {
6897
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6898
            }
6899
        }
6900
6901
        if (empty($documentId)) {
6902
            $dir = api_remove_trailing_slash($dir);
6903
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6904
        }
6905
6906
        $array = [
6907
            'dir' => $dir,
6908
            'filepath' => $filepath,
6909
            'folder' => $folder,
6910
            'id' => $documentId,
6911
        ];
6912
6913
        return $array;
6914
    }
6915
6916
    /**
6917
     * Create a new document //still needs some finetuning.
6918
     *
6919
     * @param array  $courseInfo
6920
     * @param string $content
6921
     * @param string $title
6922
     * @param string $extension
6923
     * @param int    $parentId
6924
     * @param int    $creatorId  creator id
6925
     *
6926
     * @return int
6927
     */
6928
    public function create_document(
6929
        $courseInfo,
6930
        $content = '',
6931
        $title = '',
6932
        $extension = 'html',
6933
        $parentId = 0,
6934
        $creatorId = 0
6935
    ) {
6936
        if (!empty($courseInfo)) {
6937
            $course_id = $courseInfo['real_id'];
6938
        } else {
6939
            $course_id = api_get_course_int_id();
6940
        }
6941
6942
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6943
        $sessionId = api_get_session_id();
6944
6945
        // Generates folder
6946
        $result = $this->generate_lp_folder($courseInfo);
6947
        $dir = $result['dir'];
6948
6949
        if (empty($parentId) || $parentId == '/') {
6950
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6951
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6952
6953
            if ($parentId === '/') {
6954
                $dir = '/';
6955
            }
6956
6957
            // Please, do not modify this dirname formatting.
6958
            if (strstr($dir, '..')) {
6959
                $dir = '/';
6960
            }
6961
6962
            if (!empty($dir[0]) && $dir[0] == '.') {
6963
                $dir = substr($dir, 1);
6964
            }
6965
            if (!empty($dir[0]) && $dir[0] != '/') {
6966
                $dir = '/'.$dir;
6967
            }
6968
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6969
                $dir .= '/';
6970
            }
6971
        } else {
6972
            $parentInfo = DocumentManager::get_document_data_by_id(
6973
                $parentId,
6974
                $courseInfo['code']
6975
            );
6976
            if (!empty($parentInfo)) {
6977
                $dir = $parentInfo['path'].'/';
6978
            }
6979
        }
6980
6981
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6982
        if (!is_dir($filepath)) {
6983
            $dir = '/';
6984
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6985
        }
6986
6987
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6988
        // is already escaped twice when it gets here.
6989
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6990
        if (!empty($title)) {
6991
            $title = api_replace_dangerous_char(stripslashes($title));
6992
        } else {
6993
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6994
        }
6995
6996
        $title = disable_dangerous_file($title);
6997
        $filename = $title;
6998
        $content = !empty($content) ? $content : $_POST['content_lp'];
6999
        $tmp_filename = $filename;
7000
7001
        $i = 0;
7002
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7003
            $tmp_filename = $filename.'_'.++$i;
7004
        }
7005
7006
        $filename = $tmp_filename.'.'.$extension;
7007
        if ($extension == 'html') {
7008
            $content = stripslashes($content);
7009
            $content = str_replace(
7010
                api_get_path(WEB_COURSE_PATH),
7011
                api_get_path(REL_PATH).'courses/',
7012
                $content
7013
            );
7014
7015
            // Change the path of mp3 to absolute.
7016
            // The first regexp deals with :// urls.
7017
            $content = preg_replace(
7018
                "|(flashvars=\"file=)([^:/]+)/|",
7019
                "$1".api_get_path(
7020
                    REL_COURSE_PATH
7021
                ).$courseInfo['path'].'/document/',
7022
                $content
7023
            );
7024
            // The second regexp deals with audio/ urls.
7025
            $content = preg_replace(
7026
                "|(flashvars=\"file=)([^/]+)/|",
7027
                "$1".api_get_path(
7028
                    REL_COURSE_PATH
7029
                ).$courseInfo['path'].'/document/$2/',
7030
                $content
7031
            );
7032
            // For flv player: To prevent edition problem with firefox,
7033
            // we have to use a strange tip (don't blame me please).
7034
            $content = str_replace(
7035
                '</body>',
7036
                '<style type="text/css">body{}</style></body>',
7037
                $content
7038
            );
7039
        }
7040
7041
        if (!file_exists($filepath.$filename)) {
7042
            if ($fp = @fopen($filepath.$filename, 'w')) {
7043
                fputs($fp, $content);
7044
                fclose($fp);
7045
7046
                $file_size = filesize($filepath.$filename);
7047
                $save_file_path = $dir.$filename;
7048
7049
                $document_id = add_document(
7050
                    $courseInfo,
7051
                    $save_file_path,
7052
                    'file',
7053
                    $file_size,
7054
                    $tmp_filename,
7055
                    '',
7056
                    0, //readonly
7057
                    true,
7058
                    null,
7059
                    $sessionId,
7060
                    $creatorId
7061
                );
7062
7063
                if ($document_id) {
7064
                    api_item_property_update(
7065
                        $courseInfo,
7066
                        TOOL_DOCUMENT,
7067
                        $document_id,
7068
                        'DocumentAdded',
7069
                        $creatorId,
7070
                        null,
7071
                        null,
7072
                        null,
7073
                        null,
7074
                        $sessionId
7075
                    );
7076
7077
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7078
                    $new_title = $originalTitle;
7079
7080
                    if ($new_comment || $new_title) {
7081
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7082
                        $ct = '';
7083
                        if ($new_comment) {
7084
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7085
                        }
7086
                        if ($new_title) {
7087
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7088
                        }
7089
7090
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7091
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7092
                        Database::query($sql);
7093
                    }
7094
                }
7095
7096
                return $document_id;
7097
            }
7098
        }
7099
    }
7100
7101
    /**
7102
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7103
     *
7104
     * @param array $_course array
7105
     */
7106
    public function edit_document($_course)
7107
    {
7108
        $course_id = api_get_course_int_id();
7109
        $urlAppend = api_get_configuration_value('url_append');
7110
        // Please, do not modify this dirname formatting.
7111
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7112
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7113
7114
        if (strstr($dir, '..')) {
7115
            $dir = '/';
7116
        }
7117
7118
        if (isset($dir[0]) && $dir[0] == '.') {
7119
            $dir = substr($dir, 1);
7120
        }
7121
7122
        if (isset($dir[0]) && $dir[0] != '/') {
7123
            $dir = '/'.$dir;
7124
        }
7125
7126
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7127
            $dir .= '/';
7128
        }
7129
7130
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7131
        if (!is_dir($filepath)) {
7132
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7133
        }
7134
7135
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7136
7137
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7138
            $document_id = (int) $_POST['path'];
7139
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7140
            if (empty($documentInfo)) {
7141
                // Try with iid
7142
                $table = Database::get_course_table(TABLE_DOCUMENT);
7143
                $sql = "SELECT id, path FROM $table
7144
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7145
                $res_doc = Database::query($sql);
7146
                $row = Database::fetch_array($res_doc);
7147
                if ($row) {
7148
                    $document_id = $row['id'];
7149
                    $documentPath = $row['path'];
7150
                }
7151
            } else {
7152
                $documentPath = $documentInfo['path'];
7153
            }
7154
7155
            $content = stripslashes($_POST['content_lp']);
7156
            $file = $filepath.$documentPath;
7157
7158
            if (!file_exists($file)) {
7159
                return false;
7160
            }
7161
7162
            if ($fp = @fopen($file, 'w')) {
7163
                $content = str_replace(
7164
                    api_get_path(WEB_COURSE_PATH),
7165
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7166
                    $content
7167
                );
7168
                // Change the path of mp3 to absolute.
7169
                // The first regexp deals with :// urls.
7170
                $content = preg_replace(
7171
                    "|(flashvars=\"file=)([^:/]+)/|",
7172
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7173
                    $content
7174
                );
7175
                // The second regexp deals with audio/ urls.
7176
                $content = preg_replace(
7177
                    "|(flashvars=\"file=)([^:/]+)/|",
7178
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7179
                    $content
7180
                );
7181
                fputs($fp, $content);
7182
                fclose($fp);
7183
7184
                $sql = "UPDATE $table_doc SET
7185
                            title='".Database::escape_string($_POST['title'])."'
7186
                        WHERE c_id = $course_id AND id = ".$document_id;
7187
                Database::query($sql);
7188
            }
7189
        }
7190
    }
7191
7192
    /**
7193
     * Displays the selected item, with a panel for manipulating the item.
7194
     *
7195
     * @param int    $item_id
7196
     * @param string $msg
7197
     * @param bool   $show_actions
7198
     *
7199
     * @return string
7200
     */
7201
    public function display_item($item_id, $msg = null, $show_actions = true)
7202
    {
7203
        $course_id = api_get_course_int_id();
7204
        $return = '';
7205
        if (is_numeric($item_id)) {
7206
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7207
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7208
                    WHERE lp.iid = ".intval($item_id);
7209
            $result = Database::query($sql);
7210
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7211
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7212
7213
                // Prevents wrong parent selection for document, see Bug#1251.
7214
                if ($row['item_type'] != 'dir') {
7215
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7216
                }
7217
7218
                if ($show_actions) {
7219
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7220
                }
7221
                $return .= '<div style="padding:10px;">';
7222
7223
                if ($msg != '') {
7224
                    $return .= $msg;
7225
                }
7226
7227
                $return .= '<h3>'.$row['title'].'</h3>';
7228
7229
                switch ($row['item_type']) {
7230
                    case TOOL_THREAD:
7231
                        $link = $this->rl_get_resource_link_for_learnpath(
7232
                            $course_id,
7233
                            $row['lp_id'],
7234
                            $item_id,
7235
                            0
7236
                        );
7237
                        $return .= Display::url(
7238
                            get_lang('GoToThread'),
7239
                            $link,
7240
                            ['class' => 'btn btn-primary']
7241
                        );
7242
                        break;
7243
                    case TOOL_FORUM:
7244
                        $return .= Display::url(
7245
                            get_lang('GoToForum'),
7246
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7247
                            ['class' => 'btn btn-primary']
7248
                        );
7249
                        break;
7250
                    case TOOL_QUIZ:
7251
                        if (!empty($row['path'])) {
7252
                            $exercise = new Exercise();
7253
                            $exercise->read($row['path']);
7254
                            $return .= $exercise->description.'<br />';
7255
                            $return .= Display::url(
7256
                                get_lang('GoToExercise'),
7257
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7258
                                ['class' => 'btn btn-primary']
7259
                            );
7260
                        }
7261
                        break;
7262
                    case TOOL_LP_FINAL_ITEM:
7263
                        $return .= $this->getSavedFinalItem();
7264
                        break;
7265
                    case TOOL_DOCUMENT:
7266
                    case TOOL_READOUT_TEXT:
7267
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7268
                        $sql_doc = "SELECT path FROM $tbl_doc
7269
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7270
                        $result = Database::query($sql_doc);
7271
                        $path_file = Database::result($result, 0, 0);
7272
                        $path_parts = pathinfo($path_file);
7273
                        // TODO: Correct the following naive comparisons.
7274
                        if (in_array($path_parts['extension'], [
7275
                            'html',
7276
                            'txt',
7277
                            'png',
7278
                            'jpg',
7279
                            'JPG',
7280
                            'jpeg',
7281
                            'JPEG',
7282
                            'gif',
7283
                            'swf',
7284
                            'pdf',
7285
                            'htm',
7286
                        ])) {
7287
                            $return .= $this->display_document($row['path'], true, true);
7288
                        }
7289
                        break;
7290
                    case TOOL_HOTPOTATOES:
7291
                        $return .= $this->display_document($row['path'], false, true);
7292
                        break;
7293
                }
7294
                $return .= '</div>';
7295
            }
7296
        }
7297
7298
        return $return;
7299
    }
7300
7301
    /**
7302
     * Shows the needed forms for editing a specific item.
7303
     *
7304
     * @param int $item_id
7305
     *
7306
     * @throws Exception
7307
     * @throws HTML_QuickForm_Error
7308
     *
7309
     * @return string
7310
     */
7311
    public function display_edit_item($item_id)
7312
    {
7313
        $course_id = api_get_course_int_id();
7314
        $return = '';
7315
        $item_id = (int) $item_id;
7316
7317
        if (empty($item_id)) {
7318
            return '';
7319
        }
7320
7321
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7322
        $sql = "SELECT * FROM $tbl_lp_item
7323
                WHERE iid = ".$item_id;
7324
        $res = Database::query($sql);
7325
        $row = Database::fetch_array($res);
7326
        switch ($row['item_type']) {
7327
            case 'dir':
7328
            case 'asset':
7329
            case 'sco':
7330
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7331
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7332
                    $return .= $this->display_item_form(
7333
                        $row['item_type'],
7334
                        get_lang('EditCurrentChapter').' :',
7335
                        'edit',
7336
                        $item_id,
7337
                        $row
7338
                    );
7339
                } else {
7340
                    $return .= $this->display_item_form(
7341
                        $row['item_type'],
7342
                        get_lang('EditCurrentChapter').' :',
7343
                        'edit_item',
7344
                        $item_id,
7345
                        $row
7346
                    );
7347
                }
7348
                break;
7349
            case TOOL_DOCUMENT:
7350
            case TOOL_READOUT_TEXT:
7351
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7352
                $sql = "SELECT lp.*, doc.path as dir
7353
                        FROM $tbl_lp_item as lp
7354
                        LEFT JOIN $tbl_doc as doc
7355
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7356
                        WHERE
7357
                            doc.c_id = $course_id AND
7358
                            lp.iid = ".$item_id;
7359
                $res_step = Database::query($sql);
7360
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7361
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7362
7363
                if ($row['item_type'] === TOOL_DOCUMENT) {
7364
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7365
                }
7366
7367
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7368
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7369
                }
7370
                break;
7371
            case TOOL_LINK:
7372
                $linkId = (int) $row['path'];
7373
                if (!empty($linkId)) {
7374
                    $table = Database::get_course_table(TABLE_LINK);
7375
                    $sql = 'SELECT url FROM '.$table.'
7376
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7377
                    $res_link = Database::query($sql);
7378
                    $row_link = Database::fetch_array($res_link);
7379
                    if (empty($row_link)) {
7380
                        // Try with id
7381
                        $sql = 'SELECT url FROM '.$table.'
7382
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7383
                        $res_link = Database::query($sql);
7384
                        $row_link = Database::fetch_array($res_link);
7385
                    }
7386
7387
                    if (is_array($row_link)) {
7388
                        $row['url'] = $row_link['url'];
7389
                    }
7390
                }
7391
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7392
                $return .= $this->display_link_form('edit', $item_id, $row);
7393
                break;
7394
            case TOOL_LP_FINAL_ITEM:
7395
                Session::write('finalItem', true);
7396
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7397
                $sql = "SELECT lp.*, doc.path as dir
7398
                        FROM $tbl_lp_item as lp
7399
                        LEFT JOIN $tbl_doc as doc
7400
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7401
                        WHERE
7402
                            doc.c_id = $course_id AND
7403
                            lp.iid = ".$item_id;
7404
                $res_step = Database::query($sql);
7405
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7406
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7407
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7408
                break;
7409
            case TOOL_QUIZ:
7410
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7411
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7412
                break;
7413
            case TOOL_HOTPOTATOES:
7414
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7415
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7416
                break;
7417
            case TOOL_STUDENTPUBLICATION:
7418
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7419
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7420
                break;
7421
            case TOOL_FORUM:
7422
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7423
                $return .= $this->display_forum_form('edit', $item_id, $row);
7424
                break;
7425
            case TOOL_THREAD:
7426
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7427
                $return .= $this->display_thread_form('edit', $item_id, $row);
7428
                break;
7429
        }
7430
7431
        return $return;
7432
    }
7433
7434
    /**
7435
     * Function that displays a list with al the resources that
7436
     * could be added to the learning path.
7437
     *
7438
     * @throws Exception
7439
     * @throws HTML_QuickForm_Error
7440
     *
7441
     * @return bool
7442
     */
7443
    public function display_resources()
7444
    {
7445
        $course_code = api_get_course_id();
7446
7447
        // Get all the docs.
7448
        $documents = $this->get_documents(true);
7449
7450
        // Get all the exercises.
7451
        $exercises = $this->get_exercises();
7452
7453
        // Get all the links.
7454
        $links = $this->get_links();
7455
7456
        // Get all the student publications.
7457
        $works = $this->get_student_publications();
7458
7459
        // Get all the forums.
7460
        $forums = $this->get_forums(null, $course_code);
7461
7462
        // Get the final item form (see BT#11048) .
7463
        $finish = $this->getFinalItemForm();
7464
7465
        $headers = [
7466
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7467
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7468
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7469
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7470
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7471
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7472
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7473
        ];
7474
7475
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7476
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7477
7478
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7479
7480
        echo Display::tabs(
7481
            $headers,
7482
            [
7483
                $documents,
7484
                $exercises,
7485
                $links,
7486
                $works,
7487
                $forums,
7488
                $dir,
7489
                $finish,
7490
            ],
7491
            'resource_tab',
7492
            [],
7493
            [],
7494
            $selected
7495
        );
7496
7497
        return true;
7498
    }
7499
7500
    /**
7501
     * Returns the extension of a document.
7502
     *
7503
     * @param string $filename
7504
     *
7505
     * @return string Extension (part after the last dot)
7506
     */
7507
    public function get_extension($filename)
7508
    {
7509
        $explode = explode('.', $filename);
7510
7511
        return $explode[count($explode) - 1];
7512
    }
7513
7514
    /**
7515
     * Displays a document by id.
7516
     *
7517
     * @param int  $id
7518
     * @param bool $show_title
7519
     * @param bool $iframe
7520
     * @param bool $edit_link
7521
     *
7522
     * @return string
7523
     */
7524
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7525
    {
7526
        $_course = api_get_course_info();
7527
        $course_id = api_get_course_int_id();
7528
        $id = (int) $id;
7529
        $return = '';
7530
        $table = Database::get_course_table(TABLE_DOCUMENT);
7531
        $sql_doc = "SELECT * FROM $table
7532
                    WHERE c_id = $course_id AND iid = $id";
7533
        $res_doc = Database::query($sql_doc);
7534
        $row_doc = Database::fetch_array($res_doc);
7535
7536
        // TODO: Add a path filter.
7537
        if ($iframe) {
7538
            $return .= '<iframe id="learnpath_preview_frame" frameborder="0" height="400" width="100%" scrolling="auto" src="'.api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq().'"></iframe>';
7539
        } else {
7540
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7541
        }
7542
7543
        return $return;
7544
    }
7545
7546
    /**
7547
     * Return HTML form to add/edit a quiz.
7548
     *
7549
     * @param string $action     Action (add/edit)
7550
     * @param int    $id         Item ID if already exists
7551
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7552
     *
7553
     * @throws Exception
7554
     *
7555
     * @return string HTML form
7556
     */
7557
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7558
    {
7559
        $course_id = api_get_course_int_id();
7560
        $id = (int) $id;
7561
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7562
7563
        if ($id != 0 && is_array($extra_info)) {
7564
            $item_title = $extra_info['title'];
7565
            $item_description = $extra_info['description'];
7566
        } elseif (is_numeric($extra_info)) {
7567
            $sql = "SELECT title, description
7568
                    FROM $tbl_quiz
7569
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7570
7571
            $result = Database::query($sql);
7572
            $row = Database::fetch_array($result);
7573
            $item_title = $row['title'];
7574
            $item_description = $row['description'];
7575
        } else {
7576
            $item_title = '';
7577
            $item_description = '';
7578
        }
7579
        $item_title = Security::remove_XSS($item_title);
7580
        $item_description = Security::remove_XSS($item_description);
7581
7582
        $parent = 0;
7583
        if ($id != 0 && is_array($extra_info)) {
7584
            $parent = $extra_info['parent_item_id'];
7585
        }
7586
7587
        $arrLP = $this->getItemsForForm();
7588
        $this->tree_array($arrLP);
7589
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7590
        unset($this->arrMenu);
7591
7592
        $form = new FormValidator(
7593
            'quiz_form',
7594
            'POST',
7595
            $this->getCurrentBuildingModeURL()
7596
        );
7597
        $defaults = [];
7598
7599
        if ($action === 'add') {
7600
            $legend = get_lang('CreateTheExercise');
7601
        } elseif ($action === 'move') {
7602
            $legend = get_lang('MoveTheCurrentExercise');
7603
        } else {
7604
            $legend = get_lang('EditCurrentExecice');
7605
        }
7606
7607
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7608
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7609
        }
7610
7611
        $form->addHeader($legend);
7612
7613
        if ($action != 'move') {
7614
            $this->setItemTitle($form);
7615
            $defaults['title'] = $item_title;
7616
        }
7617
7618
        // Select for Parent item, root or chapter
7619
        $selectParent = $form->addSelect(
7620
            'parent',
7621
            get_lang('Parent'),
7622
            [],
7623
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7624
        );
7625
        $selectParent->addOption($this->name, 0);
7626
7627
        $arrHide = [
7628
            $id,
7629
        ];
7630
        for ($i = 0; $i < count($arrLP); $i++) {
7631
            if ($action != 'add') {
7632
                if (
7633
                    ($arrLP[$i]['item_type'] == 'dir') &&
7634
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7635
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7636
                ) {
7637
                    $selectParent->addOption(
7638
                        $arrLP[$i]['title'],
7639
                        $arrLP[$i]['id'],
7640
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7641
                    );
7642
7643
                    if ($parent == $arrLP[$i]['id']) {
7644
                        $selectParent->setSelected($arrLP[$i]['id']);
7645
                    }
7646
                } else {
7647
                    $arrHide[] = $arrLP[$i]['id'];
7648
                }
7649
            } else {
7650
                if ($arrLP[$i]['item_type'] == 'dir') {
7651
                    $selectParent->addOption(
7652
                        $arrLP[$i]['title'],
7653
                        $arrLP[$i]['id'],
7654
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7655
                    );
7656
7657
                    if ($parent == $arrLP[$i]['id']) {
7658
                        $selectParent->setSelected($arrLP[$i]['id']);
7659
                    }
7660
                }
7661
            }
7662
        }
7663
7664
        if (is_array($arrLP)) {
7665
            reset($arrLP);
7666
        }
7667
7668
        $selectPrevious = $form->addSelect(
7669
            'previous',
7670
            get_lang('Position'),
7671
            [],
7672
            ['id' => 'previous']
7673
        );
7674
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7675
7676
        for ($i = 0; $i < count($arrLP); $i++) {
7677
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7678
                $arrLP[$i]['id'] != $id
7679
            ) {
7680
                $selectPrevious->addOption(
7681
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7682
                    $arrLP[$i]['id']
7683
                );
7684
7685
                if (is_array($extra_info)) {
7686
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7687
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7688
                    }
7689
                } elseif ($action == 'add') {
7690
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7691
                }
7692
            }
7693
        }
7694
7695
        if ($action != 'move') {
7696
            $arrHide = [];
7697
            for ($i = 0; $i < count($arrLP); $i++) {
7698
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7699
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7700
                }
7701
            }
7702
        }
7703
7704
        if ('edit' === $action) {
7705
            $extraField = new ExtraField('lp_item');
7706
            $extraField->addElements($form, $id);
7707
        }
7708
7709
        if ($action === 'add') {
7710
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7711
        } else {
7712
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7713
        }
7714
7715
        if ($action === 'move') {
7716
            $form->addHidden('title', $item_title);
7717
            $form->addHidden('description', $item_description);
7718
        }
7719
7720
        if (is_numeric($extra_info)) {
7721
            $form->addHidden('path', $extra_info);
7722
        } elseif (is_array($extra_info)) {
7723
            $form->addHidden('path', $extra_info['path']);
7724
        }
7725
7726
        $form->addHidden('type', TOOL_QUIZ);
7727
        $form->addHidden('post_time', time());
7728
        $form->setDefaults($defaults);
7729
7730
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7731
    }
7732
7733
    /**
7734
     * Addition of Hotpotatoes tests.
7735
     *
7736
     * @param string $action
7737
     * @param int    $id         Internal ID of the item
7738
     * @param string $extra_info
7739
     *
7740
     * @return string HTML structure to display the hotpotatoes addition formular
7741
     */
7742
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7743
    {
7744
        $course_id = api_get_course_int_id();
7745
        $uploadPath = DIR_HOTPOTATOES;
7746
7747
        if ($id != 0 && is_array($extra_info)) {
7748
            $item_title = stripslashes($extra_info['title']);
7749
            $item_description = stripslashes($extra_info['description']);
7750
        } elseif (is_numeric($extra_info)) {
7751
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7752
7753
            $sql = "SELECT * FROM $TBL_DOCUMENT
7754
                    WHERE
7755
                        c_id = $course_id AND
7756
                        path LIKE '".$uploadPath."/%/%htm%' AND
7757
                        iid = ".(int) $extra_info."
7758
                    ORDER BY iid ASC";
7759
7760
            $res_hot = Database::query($sql);
7761
            $row = Database::fetch_array($res_hot);
7762
7763
            $item_title = $row['title'];
7764
            $item_description = $row['description'];
7765
7766
            if (!empty($row['comment'])) {
7767
                $item_title = $row['comment'];
7768
            }
7769
        } else {
7770
            $item_title = '';
7771
            $item_description = '';
7772
        }
7773
7774
        $parent = 0;
7775
        if ($id != 0 && is_array($extra_info)) {
7776
            $parent = $extra_info['parent_item_id'];
7777
        }
7778
7779
        $arrLP = $this->getItemsForForm();
7780
        $legend = '<legend>';
7781
        if ($action == 'add') {
7782
            $legend .= get_lang('CreateTheExercise');
7783
        } elseif ($action == 'move') {
7784
            $legend .= get_lang('MoveTheCurrentExercise');
7785
        } else {
7786
            $legend .= get_lang('EditCurrentExecice');
7787
        }
7788
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7789
            $legend .= Display:: return_message(
7790
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7791
            );
7792
        }
7793
        $legend .= '</legend>';
7794
7795
        $return = '<form method="POST">';
7796
        $return .= $legend;
7797
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7798
        $return .= '<tr>';
7799
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7800
        $return .= '<td class="input">';
7801
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7802
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7803
        $arrHide = [$id];
7804
7805
        if (count($arrLP) > 0) {
7806
            for ($i = 0; $i < count($arrLP); $i++) {
7807
                if ($action != 'add') {
7808
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7809
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7810
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7811
                    ) {
7812
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7813
                    } else {
7814
                        $arrHide[] = $arrLP[$i]['id'];
7815
                    }
7816
                } else {
7817
                    if ($arrLP[$i]['item_type'] == 'dir') {
7818
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7819
                    }
7820
                }
7821
            }
7822
            reset($arrLP);
7823
        }
7824
7825
        $return .= '</select>';
7826
        $return .= '</td>';
7827
        $return .= '</tr>';
7828
        $return .= '<tr>';
7829
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7830
        $return .= '<td class="input">';
7831
        $return .= '<select id="previous" name="previous" size="1">';
7832
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7833
7834
        for ($i = 0; $i < count($arrLP); $i++) {
7835
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7836
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7837
                    $selected = 'selected="selected" ';
7838
                } elseif ($action == 'add') {
7839
                    $selected = 'selected="selected" ';
7840
                } else {
7841
                    $selected = '';
7842
                }
7843
7844
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7845
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7846
            }
7847
        }
7848
7849
        $return .= '</select>';
7850
        $return .= '</td>';
7851
        $return .= '</tr>';
7852
7853
        if ($action != 'move') {
7854
            $return .= '<tr>';
7855
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7856
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7857
            $return .= '</tr>';
7858
            $id_prerequisite = 0;
7859
            if (is_array($arrLP) && count($arrLP) > 0) {
7860
                foreach ($arrLP as $key => $value) {
7861
                    if ($value['id'] == $id) {
7862
                        $id_prerequisite = $value['prerequisite'];
7863
                        break;
7864
                    }
7865
                }
7866
7867
                $arrHide = [];
7868
                for ($i = 0; $i < count($arrLP); $i++) {
7869
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7870
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7871
                    }
7872
                }
7873
            }
7874
        }
7875
7876
        $return .= '<tr>';
7877
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7878
            get_lang('SaveHotpotatoes').'</button></td>';
7879
        $return .= '</tr>';
7880
        $return .= '</table>';
7881
7882
        if ($action == 'move') {
7883
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7884
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7885
        }
7886
7887
        if (is_numeric($extra_info)) {
7888
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7889
        } elseif (is_array($extra_info)) {
7890
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7891
        }
7892
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7893
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7894
        $return .= '</form>';
7895
7896
        return $return;
7897
    }
7898
7899
    /**
7900
     * Return the form to display the forum edit/add option.
7901
     *
7902
     * @param string $action
7903
     * @param int    $id         ID of the lp_item if already exists
7904
     * @param string $extra_info
7905
     *
7906
     * @throws Exception
7907
     *
7908
     * @return string HTML form
7909
     */
7910
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7911
    {
7912
        $course_id = api_get_course_int_id();
7913
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7914
7915
        $item_title = '';
7916
        $item_description = '';
7917
7918
        if ($id != 0 && is_array($extra_info)) {
7919
            $item_title = stripslashes($extra_info['title']);
7920
        } elseif (is_numeric($extra_info)) {
7921
            $sql = "SELECT forum_title as title, forum_comment as comment
7922
                    FROM $tbl_forum
7923
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7924
7925
            $result = Database::query($sql);
7926
            $row = Database::fetch_array($result);
7927
7928
            $item_title = $row['title'];
7929
            $item_description = $row['comment'];
7930
        }
7931
        $parent = 0;
7932
        if ($id != 0 && is_array($extra_info)) {
7933
            $parent = $extra_info['parent_item_id'];
7934
        }
7935
        $arrLP = $this->getItemsForForm();
7936
        $this->tree_array($arrLP);
7937
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7938
        unset($this->arrMenu);
7939
7940
        if ($action == 'add') {
7941
            $legend = get_lang('CreateTheForum');
7942
        } elseif ($action == 'move') {
7943
            $legend = get_lang('MoveTheCurrentForum');
7944
        } else {
7945
            $legend = get_lang('EditCurrentForum');
7946
        }
7947
7948
        $form = new FormValidator(
7949
            'forum_form',
7950
            'POST',
7951
            $this->getCurrentBuildingModeURL()
7952
        );
7953
        $defaults = [];
7954
7955
        $form->addHeader($legend);
7956
7957
        if ($action != 'move') {
7958
            $this->setItemTitle($form);
7959
            $defaults['title'] = $item_title;
7960
        }
7961
7962
        $selectParent = $form->addSelect(
7963
            'parent',
7964
            get_lang('Parent'),
7965
            [],
7966
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7967
        );
7968
        $selectParent->addOption($this->name, 0);
7969
        $arrHide = [
7970
            $id,
7971
        ];
7972
        for ($i = 0; $i < count($arrLP); $i++) {
7973
            if ($action != 'add') {
7974
                if ($arrLP[$i]['item_type'] == 'dir' &&
7975
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7976
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7977
                ) {
7978
                    $selectParent->addOption(
7979
                        $arrLP[$i]['title'],
7980
                        $arrLP[$i]['id'],
7981
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7982
                    );
7983
7984
                    if ($parent == $arrLP[$i]['id']) {
7985
                        $selectParent->setSelected($arrLP[$i]['id']);
7986
                    }
7987
                } else {
7988
                    $arrHide[] = $arrLP[$i]['id'];
7989
                }
7990
            } else {
7991
                if ($arrLP[$i]['item_type'] == 'dir') {
7992
                    $selectParent->addOption(
7993
                        $arrLP[$i]['title'],
7994
                        $arrLP[$i]['id'],
7995
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7996
                    );
7997
7998
                    if ($parent == $arrLP[$i]['id']) {
7999
                        $selectParent->setSelected($arrLP[$i]['id']);
8000
                    }
8001
                }
8002
            }
8003
        }
8004
8005
        if (is_array($arrLP)) {
8006
            reset($arrLP);
8007
        }
8008
8009
        $selectPrevious = $form->addSelect(
8010
            'previous',
8011
            get_lang('Position'),
8012
            [],
8013
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8014
        );
8015
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8016
8017
        for ($i = 0; $i < count($arrLP); $i++) {
8018
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8019
                $arrLP[$i]['id'] != $id
8020
            ) {
8021
                $selectPrevious->addOption(
8022
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8023
                    $arrLP[$i]['id']
8024
                );
8025
8026
                if (isset($extra_info['previous_item_id']) &&
8027
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8028
                ) {
8029
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8030
                } elseif ($action == 'add') {
8031
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8032
                }
8033
            }
8034
        }
8035
8036
        if ($action != 'move') {
8037
            $id_prerequisite = 0;
8038
            if (is_array($arrLP)) {
8039
                foreach ($arrLP as $key => $value) {
8040
                    if ($value['id'] == $id) {
8041
                        $id_prerequisite = $value['prerequisite'];
8042
                        break;
8043
                    }
8044
                }
8045
            }
8046
8047
            $arrHide = [];
8048
            for ($i = 0; $i < count($arrLP); $i++) {
8049
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8050
                    if (isset($extra_info['previous_item_id']) &&
8051
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8052
                    ) {
8053
                        $s_selected_position = $arrLP[$i]['id'];
8054
                    } elseif ($action == 'add') {
8055
                        $s_selected_position = 0;
8056
                    }
8057
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8058
                }
8059
            }
8060
        }
8061
8062
        if ('edit' === $action) {
8063
            $extraField = new ExtraField('lp_item');
8064
            $extraField->addElements($form, $id);
8065
        }
8066
8067
        if ($action == 'add') {
8068
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8069
        } else {
8070
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8071
        }
8072
8073
        if ($action == 'move') {
8074
            $form->addHidden('title', $item_title);
8075
            $form->addHidden('description', $item_description);
8076
        }
8077
8078
        if (is_numeric($extra_info)) {
8079
            $form->addHidden('path', $extra_info);
8080
        } elseif (is_array($extra_info)) {
8081
            $form->addHidden('path', $extra_info['path']);
8082
        }
8083
        $form->addHidden('type', TOOL_FORUM);
8084
        $form->addHidden('post_time', time());
8085
        $form->setDefaults($defaults);
8086
8087
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8088
    }
8089
8090
    /**
8091
     * Return HTML form to add/edit forum threads.
8092
     *
8093
     * @param string $action
8094
     * @param int    $id         Item ID if already exists in learning path
8095
     * @param string $extra_info
8096
     *
8097
     * @throws Exception
8098
     *
8099
     * @return string HTML form
8100
     */
8101
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8102
    {
8103
        $course_id = api_get_course_int_id();
8104
        if (empty($course_id)) {
8105
            return null;
8106
        }
8107
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8108
8109
        $item_title = '';
8110
        $item_description = '';
8111
        if ($id != 0 && is_array($extra_info)) {
8112
            $item_title = stripslashes($extra_info['title']);
8113
        } elseif (is_numeric($extra_info)) {
8114
            $sql = "SELECT thread_title as title FROM $tbl_forum
8115
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8116
8117
            $result = Database::query($sql);
8118
            $row = Database::fetch_array($result);
8119
8120
            $item_title = $row['title'];
8121
            $item_description = '';
8122
        }
8123
8124
        $parent = 0;
8125
        if ($id != 0 && is_array($extra_info)) {
8126
            $parent = $extra_info['parent_item_id'];
8127
        }
8128
8129
        $arrLP = $this->getItemsForForm();
8130
        $this->tree_array($arrLP);
8131
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8132
        unset($this->arrMenu);
8133
8134
        $form = new FormValidator(
8135
            'thread_form',
8136
            'POST',
8137
            $this->getCurrentBuildingModeURL()
8138
        );
8139
        $defaults = [];
8140
8141
        if ($action == 'add') {
8142
            $legend = get_lang('CreateTheForum');
8143
        } elseif ($action == 'move') {
8144
            $legend = get_lang('MoveTheCurrentForum');
8145
        } else {
8146
            $legend = get_lang('EditCurrentForum');
8147
        }
8148
8149
        $form->addHeader($legend);
8150
        $selectParent = $form->addSelect(
8151
            'parent',
8152
            get_lang('Parent'),
8153
            [],
8154
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8155
        );
8156
        $selectParent->addOption($this->name, 0);
8157
8158
        $arrHide = [
8159
            $id,
8160
        ];
8161
8162
        for ($i = 0; $i < count($arrLP); $i++) {
8163
            if ($action != 'add') {
8164
                if (
8165
                    ($arrLP[$i]['item_type'] == 'dir') &&
8166
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8167
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8168
                ) {
8169
                    $selectParent->addOption(
8170
                        $arrLP[$i]['title'],
8171
                        $arrLP[$i]['id'],
8172
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8173
                    );
8174
8175
                    if ($parent == $arrLP[$i]['id']) {
8176
                        $selectParent->setSelected($arrLP[$i]['id']);
8177
                    }
8178
                } else {
8179
                    $arrHide[] = $arrLP[$i]['id'];
8180
                }
8181
            } else {
8182
                if ($arrLP[$i]['item_type'] == 'dir') {
8183
                    $selectParent->addOption(
8184
                        $arrLP[$i]['title'],
8185
                        $arrLP[$i]['id'],
8186
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8187
                    );
8188
8189
                    if ($parent == $arrLP[$i]['id']) {
8190
                        $selectParent->setSelected($arrLP[$i]['id']);
8191
                    }
8192
                }
8193
            }
8194
        }
8195
8196
        if ($arrLP != null) {
8197
            reset($arrLP);
8198
        }
8199
8200
        $selectPrevious = $form->addSelect(
8201
            'previous',
8202
            get_lang('Position'),
8203
            [],
8204
            ['id' => 'previous']
8205
        );
8206
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8207
8208
        for ($i = 0; $i < count($arrLP); $i++) {
8209
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8210
                $selectPrevious->addOption(
8211
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8212
                    $arrLP[$i]['id']
8213
                );
8214
8215
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8216
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8217
                } elseif ($action == 'add') {
8218
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8219
                }
8220
            }
8221
        }
8222
8223
        if ($action != 'move') {
8224
            $this->setItemTitle($form);
8225
            $defaults['title'] = $item_title;
8226
8227
            $id_prerequisite = 0;
8228
            if ($arrLP != null) {
8229
                foreach ($arrLP as $key => $value) {
8230
                    if ($value['id'] == $id) {
8231
                        $id_prerequisite = $value['prerequisite'];
8232
                        break;
8233
                    }
8234
                }
8235
            }
8236
8237
            $arrHide = [];
8238
            $s_selected_position = 0;
8239
            for ($i = 0; $i < count($arrLP); $i++) {
8240
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8241
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8242
                        $s_selected_position = $arrLP[$i]['id'];
8243
                    } elseif ($action == 'add') {
8244
                        $s_selected_position = 0;
8245
                    }
8246
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8247
                }
8248
            }
8249
8250
            $selectPrerequisites = $form->addSelect(
8251
                'prerequisites',
8252
                get_lang('LearnpathPrerequisites'),
8253
                [],
8254
                ['id' => 'prerequisites']
8255
            );
8256
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8257
8258
            foreach ($arrHide as $key => $value) {
8259
                $selectPrerequisites->addOption($value['value'], $key);
8260
8261
                if ($key == $s_selected_position && $action == 'add') {
8262
                    $selectPrerequisites->setSelected($key);
8263
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8264
                    $selectPrerequisites->setSelected($key);
8265
                }
8266
            }
8267
        }
8268
8269
        if ('edit' === $action) {
8270
            $extraField = new ExtraField('lp_item');
8271
            $extraField->addElements($form, $id);
8272
        }
8273
8274
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8275
8276
        if ($action == 'move') {
8277
            $form->addHidden('title', $item_title);
8278
            $form->addHidden('description', $item_description);
8279
        }
8280
8281
        if (is_numeric($extra_info)) {
8282
            $form->addHidden('path', $extra_info);
8283
        } elseif (is_array($extra_info)) {
8284
            $form->addHidden('path', $extra_info['path']);
8285
        }
8286
8287
        $form->addHidden('type', TOOL_THREAD);
8288
        $form->addHidden('post_time', time());
8289
        $form->setDefaults($defaults);
8290
8291
        return $form->returnForm();
8292
    }
8293
8294
    /**
8295
     * Return the HTML form to display an item (generally a dir item).
8296
     *
8297
     * @param string $item_type
8298
     * @param string $title
8299
     * @param string $action
8300
     * @param int    $id
8301
     * @param string $extra_info
8302
     *
8303
     * @throws Exception
8304
     * @throws HTML_QuickForm_Error
8305
     *
8306
     * @return string HTML form
8307
     */
8308
    public function display_item_form(
8309
        $item_type,
8310
        $title = '',
8311
        $action = 'add_item',
8312
        $id = 0,
8313
        $extra_info = 'new'
8314
    ) {
8315
        $_course = api_get_course_info();
8316
8317
        global $charset;
8318
8319
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8320
        $item_title = '';
8321
        $item_description = '';
8322
        $item_path_fck = '';
8323
8324
        $parent = 0;
8325
        $previousId = null;
8326
        if ($id != 0 && is_array($extra_info)) {
8327
            $item_title = $extra_info['title'];
8328
            $item_description = $extra_info['description'];
8329
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8330
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8331
            $parent = $extra_info['parent_item_id'];
8332
            $previousId = $extra_info['previous_item_id'];
8333
        }
8334
8335
        if ($extra_info instanceof learnpathItem) {
8336
            $item_title = $extra_info->get_title();
8337
            $item_description = $extra_info->get_description();
8338
            $path = $extra_info->get_path();
8339
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8340
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8341
            $parent = $extra_info->get_parent();
8342
            $previousId = $extra_info->previous;
8343
        }
8344
8345
        $id = (int) $id;
8346
        $sql = "SELECT * FROM $tbl_lp_item
8347
                WHERE
8348
                    lp_id = ".$this->lp_id." AND
8349
                    iid != $id";
8350
8351
        if ($item_type === 'dir') {
8352
            $sql .= " AND parent_item_id = 0";
8353
        }
8354
8355
        $result = Database::query($sql);
8356
        $arrLP = [];
8357
        while ($row = Database::fetch_array($result)) {
8358
            $arrLP[] = [
8359
                'id' => $row['iid'],
8360
                'item_type' => $row['item_type'],
8361
                'title' => $this->cleanItemTitle($row['title']),
8362
                'title_raw' => $row['title'],
8363
                'path' => $row['path'],
8364
                'description' => $row['description'],
8365
                'parent_item_id' => $row['parent_item_id'],
8366
                'previous_item_id' => $row['previous_item_id'],
8367
                'next_item_id' => $row['next_item_id'],
8368
                'max_score' => $row['max_score'],
8369
                'min_score' => $row['min_score'],
8370
                'mastery_score' => $row['mastery_score'],
8371
                'prerequisite' => $row['prerequisite'],
8372
                'display_order' => $row['display_order'],
8373
            ];
8374
        }
8375
8376
        $this->tree_array($arrLP);
8377
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8378
        unset($this->arrMenu);
8379
8380
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8381
8382
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8383
        $defaults['title'] = api_html_entity_decode(
8384
            $item_title,
8385
            ENT_QUOTES,
8386
            $charset
8387
        );
8388
        $defaults['description'] = $item_description;
8389
8390
        $form->addHeader($title);
8391
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8392
        $arrHide[0]['padding'] = 20;
8393
        $charset = api_get_system_encoding();
8394
        for ($i = 0; $i < count($arrLP); $i++) {
8395
            if ($action != 'add') {
8396
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8397
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8398
                ) {
8399
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8400
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8401
                    if ($parent == $arrLP[$i]['id']) {
8402
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8403
                    }
8404
                }
8405
            } else {
8406
                if ($arrLP[$i]['item_type'] === 'dir') {
8407
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8408
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8409
                    if ($parent == $arrLP[$i]['id']) {
8410
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8411
                    }
8412
                }
8413
            }
8414
        }
8415
8416
        if ($action !== 'move') {
8417
            $this->setItemTitle($form);
8418
        } else {
8419
            $form->addElement('hidden', 'title');
8420
        }
8421
8422
        $parentSelect = $form->addElement(
8423
            'select',
8424
            'parent',
8425
            get_lang('Parent'),
8426
            '',
8427
            [
8428
                'id' => 'idParent',
8429
                'onchange' => 'javascript: load_cbo(this.value);',
8430
            ]
8431
        );
8432
8433
        foreach ($arrHide as $key => $value) {
8434
            $parentSelect->addOption(
8435
                $value['value'],
8436
                $key,
8437
                'style="padding-left:'.$value['padding'].'px;"'
8438
            );
8439
            $lastPosition = $key;
8440
        }
8441
8442
        if (!empty($s_selected_parent)) {
8443
            $parentSelect->setSelected($s_selected_parent);
8444
        }
8445
8446
        if (is_array($arrLP)) {
8447
            reset($arrLP);
8448
        }
8449
8450
        $arrHide = [];
8451
        // POSITION
8452
        for ($i = 0; $i < count($arrLP); $i++) {
8453
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8454
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8455
                //this is the same!
8456
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8457
                    $s_selected_position = $arrLP[$i]['id'];
8458
                } elseif ($action === 'add') {
8459
                    $s_selected_position = $arrLP[$i]['id'];
8460
                }
8461
8462
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8463
            }
8464
        }
8465
8466
        $position = $form->addElement(
8467
            'select',
8468
            'previous',
8469
            get_lang('Position'),
8470
            '',
8471
            ['id' => 'previous']
8472
        );
8473
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8474
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8475
8476
        $lastPosition = null;
8477
        foreach ($arrHide as $key => $value) {
8478
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8479
            $lastPosition = $key;
8480
        }
8481
8482
        if (!empty($s_selected_position)) {
8483
            $position->setSelected($s_selected_position);
8484
        }
8485
8486
        // When new chapter add at the end
8487
        if ($action === 'add_item') {
8488
            $position->setSelected($lastPosition);
8489
        }
8490
8491
        if (is_array($arrLP)) {
8492
            reset($arrLP);
8493
        }
8494
8495
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8496
8497
        //fix in order to use the tab
8498
        if ($item_type === 'dir') {
8499
            $form->addElement('hidden', 'type', 'dir');
8500
        }
8501
8502
        $extension = null;
8503
        if (!empty($item_path)) {
8504
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8505
        }
8506
8507
        //assets can't be modified
8508
        //$item_type == 'asset' ||
8509
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8510
            if ($item_type === 'sco') {
8511
                $form->addElement(
8512
                    'html',
8513
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8514
                );
8515
            }
8516
            $renderer = $form->defaultRenderer();
8517
            $renderer->setElementTemplate(
8518
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8519
                'content_lp'
8520
            );
8521
8522
            $relative_prefix = '';
8523
            $editor_config = [
8524
                'ToolbarSet' => 'LearningPathDocuments',
8525
                'Width' => '100%',
8526
                'Height' => '500',
8527
                'FullPage' => true,
8528
                'CreateDocumentDir' => $relative_prefix,
8529
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8530
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8531
            ];
8532
8533
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8534
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8535
            $defaults['content_lp'] = file_get_contents($content_path);
8536
        }
8537
8538
        if (!empty($id)) {
8539
            $form->addHidden('id', $id);
8540
        }
8541
8542
        $form->addElement('hidden', 'type', $item_type);
8543
        $form->addElement('hidden', 'post_time', time());
8544
        $form->setDefaults($defaults);
8545
8546
        return $form->returnForm();
8547
    }
8548
8549
    /**
8550
     * @return string
8551
     */
8552
    public function getCurrentBuildingModeURL()
8553
    {
8554
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8555
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8556
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8557
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8558
8559
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8560
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8561
8562
        return $currentUrl;
8563
    }
8564
8565
    /**
8566
     * Returns the form to update or create a document.
8567
     *
8568
     * @param string $action     (add/edit)
8569
     * @param int    $id         ID of the lp_item (if already exists)
8570
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8571
     *
8572
     * @throws Exception
8573
     * @throws HTML_QuickForm_Error
8574
     *
8575
     * @return string HTML form
8576
     */
8577
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8578
    {
8579
        $course_id = api_get_course_int_id();
8580
        $_course = api_get_course_info();
8581
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8582
8583
        $no_display_edit_textarea = false;
8584
        $item_description = '';
8585
        //If action==edit document
8586
        //We don't display the document form if it's not an editable document (html or txt file)
8587
        if ($action === 'edit') {
8588
            if (is_array($extra_info)) {
8589
                $path_parts = pathinfo($extra_info['dir']);
8590
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8591
                    $no_display_edit_textarea = true;
8592
                }
8593
            }
8594
        }
8595
        $no_display_add = false;
8596
8597
        // If action==add an existing document
8598
        // We don't display the document form if it's not an editable document (html or txt file).
8599
        if ($action === 'add') {
8600
            if (is_numeric($extra_info)) {
8601
                $extra_info = (int) $extra_info;
8602
                $sql_doc = "SELECT path FROM $tbl_doc
8603
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8604
                $result = Database::query($sql_doc);
8605
                $path_file = Database::result($result, 0, 0);
8606
                $path_parts = pathinfo($path_file);
8607
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8608
                    $no_display_add = true;
8609
                }
8610
            }
8611
        }
8612
8613
        $item_title = '';
8614
        $item_description = '';
8615
        if ($id != 0 && is_array($extra_info)) {
8616
            $item_title = stripslashes($extra_info['title']);
8617
            $item_description = stripslashes($extra_info['description']);
8618
            if (empty($item_title)) {
8619
                $path_parts = pathinfo($extra_info['path']);
8620
                $item_title = stripslashes($path_parts['filename']);
8621
            }
8622
        } elseif (is_numeric($extra_info)) {
8623
            $sql = "SELECT path, title FROM $tbl_doc
8624
                    WHERE
8625
                        c_id = ".$course_id." AND
8626
                        iid = ".intval($extra_info);
8627
            $result = Database::query($sql);
8628
            $row = Database::fetch_array($result);
8629
            $item_title = $row['title'];
8630
            $item_title = str_replace('_', ' ', $item_title);
8631
            if (empty($item_title)) {
8632
                $path_parts = pathinfo($row['path']);
8633
                $item_title = stripslashes($path_parts['filename']);
8634
            }
8635
        }
8636
8637
        $return = '<legend>';
8638
        $parent = 0;
8639
        if ($id != 0 && is_array($extra_info)) {
8640
            $parent = $extra_info['parent_item_id'];
8641
        }
8642
8643
        $arrLP = $this->getItemsForForm();
8644
        $this->tree_array($arrLP);
8645
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8646
        unset($this->arrMenu);
8647
8648
        if ($action === 'add') {
8649
            $return .= get_lang('CreateTheDocument');
8650
        } elseif ($action === 'move') {
8651
            $return .= get_lang('MoveTheCurrentDocument');
8652
        } else {
8653
            $return .= get_lang('EditTheCurrentDocument');
8654
        }
8655
        $return .= '</legend>';
8656
8657
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8658
            $return .= Display::return_message(
8659
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8660
                false
8661
            );
8662
        }
8663
        $form = new FormValidator(
8664
            'form',
8665
            'POST',
8666
            $this->getCurrentBuildingModeURL(),
8667
            '',
8668
            ['enctype' => 'multipart/form-data']
8669
        );
8670
        $defaults['title'] = Security::remove_XSS($item_title);
8671
        if (empty($item_title)) {
8672
            $defaults['title'] = Security::remove_XSS($item_title);
8673
        }
8674
        $defaults['description'] = $item_description;
8675
        $form->addElement('html', $return);
8676
8677
        if ($action !== 'move') {
8678
            $data = $this->generate_lp_folder($_course);
8679
            if ($action !== 'edit') {
8680
                $folders = DocumentManager::get_all_document_folders(
8681
                    $_course,
8682
                    0,
8683
                    true
8684
                );
8685
                DocumentManager::build_directory_selector(
8686
                    $folders,
8687
                    '',
8688
                    [],
8689
                    true,
8690
                    $form,
8691
                    'directory_parent_id'
8692
                );
8693
            }
8694
8695
            if (isset($data['id'])) {
8696
                $defaults['directory_parent_id'] = $data['id'];
8697
            }
8698
            $this->setItemTitle($form);
8699
        }
8700
8701
        $arrHide[0]['value'] = $this->name;
8702
        $arrHide[0]['padding'] = 20;
8703
8704
        for ($i = 0; $i < count($arrLP); $i++) {
8705
            if ($action !== 'add') {
8706
                if ($arrLP[$i]['item_type'] === 'dir' &&
8707
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8708
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8709
                ) {
8710
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8711
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8712
                }
8713
            } else {
8714
                if ($arrLP[$i]['item_type'] == 'dir') {
8715
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8716
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8717
                }
8718
            }
8719
        }
8720
8721
        $parentSelect = $form->addSelect(
8722
            'parent',
8723
            get_lang('Parent'),
8724
            [],
8725
            [
8726
                'id' => 'idParent',
8727
                'onchange' => 'javascript: load_cbo(this.value);',
8728
            ]
8729
        );
8730
8731
        $my_count = 0;
8732
        foreach ($arrHide as $key => $value) {
8733
            if ($my_count != 0) {
8734
                // The LP name is also the first section and is not in the same charset like the other sections.
8735
                $value['value'] = Security::remove_XSS($value['value']);
8736
                $parentSelect->addOption(
8737
                    $value['value'],
8738
                    $key,
8739
                    'style="padding-left:'.$value['padding'].'px;"'
8740
                );
8741
            } else {
8742
                $value['value'] = Security::remove_XSS($value['value']);
8743
                $parentSelect->addOption(
8744
                    $value['value'],
8745
                    $key,
8746
                    'style="padding-left:'.$value['padding'].'px;"'
8747
                );
8748
            }
8749
            $my_count++;
8750
        }
8751
8752
        if (!empty($id)) {
8753
            $parentSelect->setSelected($parent);
8754
        } else {
8755
            $parent_item_id = Session::read('parent_item_id', 0);
8756
            $parentSelect->setSelected($parent_item_id);
8757
        }
8758
8759
        if (is_array($arrLP)) {
8760
            reset($arrLP);
8761
        }
8762
8763
        $arrHide = [];
8764
        // POSITION
8765
        for ($i = 0; $i < count($arrLP); $i++) {
8766
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8767
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8768
            ) {
8769
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8770
            }
8771
        }
8772
8773
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8774
8775
        $position = $form->addSelect(
8776
            'previous',
8777
            get_lang('Position'),
8778
            [],
8779
            ['id' => 'previous']
8780
        );
8781
8782
        $position->addOption(get_lang('FirstPosition'), 0);
8783
        foreach ($arrHide as $key => $value) {
8784
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8785
            $position->addOption(
8786
                $value['value'],
8787
                $key,
8788
                'style="padding-left:'.$padding.'px;"'
8789
            );
8790
        }
8791
8792
        $position->setSelected($selectedPosition);
8793
8794
        if (is_array($arrLP)) {
8795
            reset($arrLP);
8796
        }
8797
8798
        if ('edit' === $action) {
8799
            $extraField = new ExtraField('lp_item');
8800
            $extraField->addElements($form, $id);
8801
        }
8802
8803
        if ($action !== 'move') {
8804
            $arrHide = [];
8805
            for ($i = 0; $i < count($arrLP); $i++) {
8806
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8807
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8808
                ) {
8809
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8810
                }
8811
            }
8812
8813
            if (!$no_display_add) {
8814
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8815
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8816
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8817
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8818
                ) {
8819
                    if (isset($_POST['content'])) {
8820
                        $content = stripslashes($_POST['content']);
8821
                    } elseif (is_array($extra_info)) {
8822
                        //If it's an html document or a text file
8823
                        if (!$no_display_edit_textarea) {
8824
                            $content = $this->display_document(
8825
                                $extra_info['path'],
8826
                                false,
8827
                                false
8828
                            );
8829
                        }
8830
                    } elseif (is_numeric($extra_info)) {
8831
                        $content = $this->display_document(
8832
                            $extra_info,
8833
                            false,
8834
                            false
8835
                        );
8836
                    } else {
8837
                        $content = '';
8838
                    }
8839
8840
                    if (!$no_display_edit_textarea) {
8841
                        // We need to calculate here some specific settings for the online editor.
8842
                        // The calculated settings work for documents in the Documents tool
8843
                        // (on the root or in subfolders).
8844
                        // For documents in native scorm packages it is unclear whether the
8845
                        // online editor should be activated or not.
8846
8847
                        // A new document, it is in the root of the repository.
8848
                        $relative_path = '';
8849
                        $relative_prefix = '';
8850
                        if (is_array($extra_info) && $extra_info != 'new') {
8851
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8852
                            $relative_path = explode('/', $extra_info['dir']);
8853
                            $cnt = count($relative_path) - 2;
8854
                            if ($cnt < 0) {
8855
                                $cnt = 0;
8856
                            }
8857
                            $relative_prefix = str_repeat('../', $cnt);
8858
                            $relative_path = array_slice($relative_path, 1, $cnt);
8859
                            $relative_path = implode('/', $relative_path);
8860
                            if (strlen($relative_path) > 0) {
8861
                                $relative_path = $relative_path.'/';
8862
                            }
8863
                        } else {
8864
                            $result = $this->generate_lp_folder($_course);
8865
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8866
                            $relative_prefix = '../../';
8867
                        }
8868
8869
                        $editor_config = [
8870
                            'ToolbarSet' => 'LearningPathDocuments',
8871
                            'Width' => '100%',
8872
                            'Height' => '500',
8873
                            'FullPage' => true,
8874
                            'CreateDocumentDir' => $relative_prefix,
8875
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8876
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8877
                        ];
8878
8879
                        if ($_GET['action'] === 'add_item') {
8880
                            $class = 'add';
8881
                            $text = get_lang('LPCreateDocument');
8882
                        } else {
8883
                            if ($_GET['action'] === 'edit_item') {
8884
                                $class = 'save';
8885
                                $text = get_lang('SaveDocument');
8886
                            }
8887
                        }
8888
8889
                        $form->addButtonSave($text, 'submit_button');
8890
                        $renderer = $form->defaultRenderer();
8891
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8892
                        $form->addElement('html', '<div class="editor-lp">');
8893
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8894
                        $form->addElement('html', '</div>');
8895
                        $defaults['content_lp'] = $content;
8896
                    }
8897
                } elseif (is_numeric($extra_info)) {
8898
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8899
8900
                    $return = $this->display_document($extra_info, true, true, true);
8901
                    $form->addElement('html', $return);
8902
                }
8903
            }
8904
        }
8905
        if (isset($extra_info['item_type']) &&
8906
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8907
        ) {
8908
            $parentSelect->freeze();
8909
            $position->freeze();
8910
        }
8911
8912
        if ($action === 'move') {
8913
            $form->addElement('hidden', 'title', $item_title);
8914
            $form->addElement('hidden', 'description', $item_description);
8915
        }
8916
        if (is_numeric($extra_info)) {
8917
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8918
            $form->addElement('hidden', 'path', $extra_info);
8919
        } elseif (is_array($extra_info)) {
8920
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8921
            $form->addElement('hidden', 'path', $extra_info['path']);
8922
        }
8923
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8924
        $form->addElement('hidden', 'post_time', time());
8925
        $form->setDefaults($defaults);
8926
8927
        return $form->returnForm();
8928
    }
8929
8930
    /**
8931
     * Returns the form to update or create a read-out text.
8932
     *
8933
     * @param string $action     "add" or "edit"
8934
     * @param int    $id         ID of the lp_item (if already exists)
8935
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8936
     *
8937
     * @throws Exception
8938
     * @throws HTML_QuickForm_Error
8939
     *
8940
     * @return string HTML form
8941
     */
8942
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8943
    {
8944
        $course_id = api_get_course_int_id();
8945
        $_course = api_get_course_info();
8946
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8947
8948
        $no_display_edit_textarea = false;
8949
        //If action==edit document
8950
        //We don't display the document form if it's not an editable document (html or txt file)
8951
        if ($action == 'edit') {
8952
            if (is_array($extra_info)) {
8953
                $path_parts = pathinfo($extra_info['dir']);
8954
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8955
                    $no_display_edit_textarea = true;
8956
                }
8957
            }
8958
        }
8959
        $no_display_add = false;
8960
8961
        $item_title = '';
8962
        $item_description = '';
8963
        if ($id != 0 && is_array($extra_info)) {
8964
            $item_title = stripslashes($extra_info['title']);
8965
            $item_description = stripslashes($extra_info['description']);
8966
            $item_terms = stripslashes($extra_info['terms']);
8967
            if (empty($item_title)) {
8968
                $path_parts = pathinfo($extra_info['path']);
8969
                $item_title = stripslashes($path_parts['filename']);
8970
            }
8971
        } elseif (is_numeric($extra_info)) {
8972
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8973
            $result = Database::query($sql);
8974
            $row = Database::fetch_array($result);
8975
            $item_title = $row['title'];
8976
            $item_title = str_replace('_', ' ', $item_title);
8977
            if (empty($item_title)) {
8978
                $path_parts = pathinfo($row['path']);
8979
                $item_title = stripslashes($path_parts['filename']);
8980
            }
8981
        }
8982
8983
        $parent = 0;
8984
        if ($id != 0 && is_array($extra_info)) {
8985
            $parent = $extra_info['parent_item_id'];
8986
        }
8987
8988
        $arrLP = $this->getItemsForForm();
8989
        $this->tree_array($arrLP);
8990
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8991
        unset($this->arrMenu);
8992
8993
        if ($action === 'add') {
8994
            $formHeader = get_lang('CreateTheDocument');
8995
        } else {
8996
            $formHeader = get_lang('EditTheCurrentDocument');
8997
        }
8998
8999
        if ('edit' === $action) {
9000
            $urlAudioIcon = Display::url(
9001
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9002
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9003
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9004
            );
9005
        } else {
9006
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9007
        }
9008
9009
        $form = new FormValidator(
9010
            'frm_add_reading',
9011
            'POST',
9012
            $this->getCurrentBuildingModeURL(),
9013
            '',
9014
            ['enctype' => 'multipart/form-data']
9015
        );
9016
        $form->addHeader($formHeader);
9017
        $form->addHtml(
9018
            Display::return_message(
9019
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9020
                'normal',
9021
                false
9022
            )
9023
        );
9024
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9025
        $defaults['description'] = $item_description;
9026
9027
        $data = $this->generate_lp_folder($_course);
9028
9029
        if ($action != 'edit') {
9030
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9031
            DocumentManager::build_directory_selector(
9032
                $folders,
9033
                '',
9034
                [],
9035
                true,
9036
                $form,
9037
                'directory_parent_id'
9038
            );
9039
        }
9040
9041
        if (isset($data['id'])) {
9042
            $defaults['directory_parent_id'] = $data['id'];
9043
        }
9044
        $this->setItemTitle($form);
9045
9046
        $arrHide[0]['value'] = $this->name;
9047
        $arrHide[0]['padding'] = 20;
9048
9049
        for ($i = 0; $i < count($arrLP); $i++) {
9050
            if ($action != 'add') {
9051
                if ($arrLP[$i]['item_type'] == 'dir' &&
9052
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9053
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9054
                ) {
9055
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9056
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9057
                }
9058
            } else {
9059
                if ($arrLP[$i]['item_type'] == 'dir') {
9060
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9061
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9062
                }
9063
            }
9064
        }
9065
9066
        $parent_select = $form->addSelect(
9067
            'parent',
9068
            get_lang('Parent'),
9069
            [],
9070
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9071
        );
9072
9073
        $my_count = 0;
9074
        foreach ($arrHide as $key => $value) {
9075
            if ($my_count != 0) {
9076
                // The LP name is also the first section and is not in the same charset like the other sections.
9077
                $value['value'] = Security::remove_XSS($value['value']);
9078
                $parent_select->addOption(
9079
                    $value['value'],
9080
                    $key,
9081
                    'style="padding-left:'.$value['padding'].'px;"'
9082
                );
9083
            } else {
9084
                $value['value'] = Security::remove_XSS($value['value']);
9085
                $parent_select->addOption(
9086
                    $value['value'],
9087
                    $key,
9088
                    'style="padding-left:'.$value['padding'].'px;"'
9089
                );
9090
            }
9091
            $my_count++;
9092
        }
9093
9094
        if (!empty($id)) {
9095
            $parent_select->setSelected($parent);
9096
        } else {
9097
            $parent_item_id = Session::read('parent_item_id', 0);
9098
            $parent_select->setSelected($parent_item_id);
9099
        }
9100
9101
        if (is_array($arrLP)) {
9102
            reset($arrLP);
9103
        }
9104
9105
        $arrHide = [];
9106
        $s_selected_position = null;
9107
9108
        // POSITION
9109
        $lastPosition = null;
9110
9111
        for ($i = 0; $i < count($arrLP); $i++) {
9112
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9113
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9114
            ) {
9115
                if ((isset($extra_info['previous_item_id']) &&
9116
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9117
                ) {
9118
                    $s_selected_position = $arrLP[$i]['id'];
9119
                }
9120
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9121
            }
9122
            $lastPosition = $arrLP[$i]['id'];
9123
        }
9124
9125
        if (empty($s_selected_position)) {
9126
            $s_selected_position = $lastPosition;
9127
        }
9128
9129
        $position = $form->addSelect(
9130
            'previous',
9131
            get_lang('Position'),
9132
            []
9133
        );
9134
        $position->addOption(get_lang('FirstPosition'), 0);
9135
9136
        foreach ($arrHide as $key => $value) {
9137
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9138
            $position->addOption(
9139
                $value['value'],
9140
                $key,
9141
                'style="padding-left:'.$padding.'px;"'
9142
            );
9143
        }
9144
        $position->setSelected($s_selected_position);
9145
9146
        if (is_array($arrLP)) {
9147
            reset($arrLP);
9148
        }
9149
9150
        if ('edit' === $action) {
9151
            $extraField = new ExtraField('lp_item');
9152
            $extraField->addElements($form, $id);
9153
        }
9154
9155
        $arrHide = [];
9156
9157
        for ($i = 0; $i < count($arrLP); $i++) {
9158
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9159
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9160
            ) {
9161
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9162
            }
9163
        }
9164
9165
        if (!$no_display_add) {
9166
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9167
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9168
9169
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9170
                if (!$no_display_edit_textarea) {
9171
                    $content = '';
9172
9173
                    if (isset($_POST['content'])) {
9174
                        $content = stripslashes($_POST['content']);
9175
                    } elseif (is_array($extra_info)) {
9176
                        $content = $this->display_document($extra_info['path'], false, false);
9177
                    } elseif (is_numeric($extra_info)) {
9178
                        $content = $this->display_document($extra_info, false, false);
9179
                    }
9180
9181
                    // A new document, it is in the root of the repository.
9182
                    if (is_array($extra_info) && $extra_info != 'new') {
9183
                    } else {
9184
                        $this->generate_lp_folder($_course);
9185
                    }
9186
9187
                    if ($_GET['action'] == 'add_item') {
9188
                        $text = get_lang('LPCreateDocument');
9189
                    } else {
9190
                        $text = get_lang('SaveDocument');
9191
                    }
9192
9193
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9194
                    $form
9195
                        ->defaultRenderer()
9196
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9197
                    $form->addButtonSave($text, 'submit_button');
9198
                    $defaults['content_lp'] = $content;
9199
                }
9200
            } elseif (is_numeric($extra_info)) {
9201
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9202
9203
                $return = $this->display_document($extra_info, true, true, true);
9204
                $form->addElement('html', $return);
9205
            }
9206
        }
9207
9208
        if (is_numeric($extra_info)) {
9209
            $form->addElement('hidden', 'path', $extra_info);
9210
        } elseif (is_array($extra_info)) {
9211
            $form->addElement('hidden', 'path', $extra_info['path']);
9212
        }
9213
9214
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9215
        $form->addElement('hidden', 'post_time', time());
9216
        $form->setDefaults($defaults);
9217
9218
        return $form->returnForm();
9219
    }
9220
9221
    /**
9222
     * @param array  $courseInfo
9223
     * @param string $content
9224
     * @param string $title
9225
     * @param int    $parentId
9226
     *
9227
     * @throws \Doctrine\ORM\ORMException
9228
     * @throws \Doctrine\ORM\OptimisticLockException
9229
     * @throws \Doctrine\ORM\TransactionRequiredException
9230
     *
9231
     * @return int
9232
     */
9233
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9234
    {
9235
        $creatorId = api_get_user_id();
9236
        $sessionId = api_get_session_id();
9237
9238
        // Generates folder
9239
        $result = $this->generate_lp_folder($courseInfo);
9240
        $dir = $result['dir'];
9241
9242
        if (empty($parentId) || $parentId == '/') {
9243
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9244
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9245
9246
            if ($parentId === '/') {
9247
                $dir = '/';
9248
            }
9249
9250
            // Please, do not modify this dirname formatting.
9251
            if (strstr($dir, '..')) {
9252
                $dir = '/';
9253
            }
9254
9255
            if (!empty($dir[0]) && $dir[0] == '.') {
9256
                $dir = substr($dir, 1);
9257
            }
9258
            if (!empty($dir[0]) && $dir[0] != '/') {
9259
                $dir = '/'.$dir;
9260
            }
9261
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9262
                $dir .= '/';
9263
            }
9264
        } else {
9265
            $parentInfo = DocumentManager::get_document_data_by_id(
9266
                $parentId,
9267
                $courseInfo['code']
9268
            );
9269
            if (!empty($parentInfo)) {
9270
                $dir = $parentInfo['path'].'/';
9271
            }
9272
        }
9273
9274
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9275
9276
        if (!is_dir($filepath)) {
9277
            $dir = '/';
9278
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9279
        }
9280
9281
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9282
9283
        if (!empty($title)) {
9284
            $title = api_replace_dangerous_char(stripslashes($title));
9285
        } else {
9286
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9287
        }
9288
9289
        $title = disable_dangerous_file($title);
9290
        $filename = $title;
9291
        $content = !empty($content) ? $content : $_POST['content_lp'];
9292
        $tmpFileName = $filename;
9293
9294
        $i = 0;
9295
        while (file_exists($filepath.$tmpFileName.'.html')) {
9296
            $tmpFileName = $filename.'_'.++$i;
9297
        }
9298
9299
        $filename = $tmpFileName.'.html';
9300
        $content = stripslashes($content);
9301
9302
        if (file_exists($filepath.$filename)) {
9303
            return 0;
9304
        }
9305
9306
        $putContent = file_put_contents($filepath.$filename, $content);
9307
9308
        if ($putContent === false) {
9309
            return 0;
9310
        }
9311
9312
        $fileSize = filesize($filepath.$filename);
9313
        $saveFilePath = $dir.$filename;
9314
9315
        $documentId = add_document(
9316
            $courseInfo,
9317
            $saveFilePath,
9318
            'file',
9319
            $fileSize,
9320
            $tmpFileName,
9321
            '',
9322
            0, //readonly
9323
            true,
9324
            null,
9325
            $sessionId,
9326
            $creatorId
9327
        );
9328
9329
        if (!$documentId) {
9330
            return 0;
9331
        }
9332
9333
        api_item_property_update(
9334
            $courseInfo,
9335
            TOOL_DOCUMENT,
9336
            $documentId,
9337
            'DocumentAdded',
9338
            $creatorId,
9339
            null,
9340
            null,
9341
            null,
9342
            null,
9343
            $sessionId
9344
        );
9345
9346
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9347
        $newTitle = $originalTitle;
9348
9349
        if ($newComment || $newTitle) {
9350
            $em = Database::getManager();
9351
9352
            /** @var CDocument $doc */
9353
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9354
9355
            if ($newComment) {
9356
                $doc->setComment($newComment);
9357
            }
9358
9359
            if ($newTitle) {
9360
                $doc->setTitle($newTitle);
9361
            }
9362
9363
            $em->persist($doc);
9364
            $em->flush();
9365
        }
9366
9367
        return $documentId;
9368
    }
9369
9370
    /**
9371
     * Return HTML form to add/edit a link item.
9372
     *
9373
     * @param string $action     (add/edit)
9374
     * @param int    $id         Item ID if exists
9375
     * @param mixed  $extra_info
9376
     *
9377
     * @throws Exception
9378
     * @throws HTML_QuickForm_Error
9379
     *
9380
     * @return string HTML form
9381
     */
9382
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9383
    {
9384
        $course_id = api_get_course_int_id();
9385
        $tbl_link = Database::get_course_table(TABLE_LINK);
9386
9387
        $item_title = '';
9388
        $item_description = '';
9389
        $item_url = '';
9390
9391
        if ($id != 0 && is_array($extra_info)) {
9392
            $item_title = stripslashes($extra_info['title']);
9393
            $item_description = stripslashes($extra_info['description']);
9394
            $item_url = stripslashes($extra_info['url']);
9395
        } elseif (is_numeric($extra_info)) {
9396
            $extra_info = (int) $extra_info;
9397
            $sql = "SELECT title, description, url
9398
                    FROM $tbl_link
9399
                    WHERE c_id = $course_id AND iid = $extra_info";
9400
            $result = Database::query($sql);
9401
            $row = Database::fetch_array($result);
9402
            $item_title = $row['title'];
9403
            $item_description = $row['description'];
9404
            $item_url = $row['url'];
9405
        }
9406
9407
        $form = new FormValidator(
9408
            'edit_link',
9409
            'POST',
9410
            $this->getCurrentBuildingModeURL()
9411
        );
9412
        $defaults = [];
9413
        $parent = 0;
9414
        if ($id != 0 && is_array($extra_info)) {
9415
            $parent = $extra_info['parent_item_id'];
9416
        }
9417
9418
        $arrLP = $this->getItemsForForm();
9419
9420
        $this->tree_array($arrLP);
9421
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9422
        unset($this->arrMenu);
9423
9424
        if ($action == 'add') {
9425
            $legend = get_lang('CreateTheLink');
9426
        } elseif ($action == 'move') {
9427
            $legend = get_lang('MoveCurrentLink');
9428
        } else {
9429
            $legend = get_lang('EditCurrentLink');
9430
        }
9431
9432
        $form->addHeader($legend);
9433
9434
        if ($action != 'move') {
9435
            $this->setItemTitle($form);
9436
            $defaults['title'] = $item_title;
9437
        }
9438
9439
        $selectParent = $form->addSelect(
9440
            'parent',
9441
            get_lang('Parent'),
9442
            [],
9443
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9444
        );
9445
        $selectParent->addOption($this->name, 0);
9446
        $arrHide = [
9447
            $id,
9448
        ];
9449
9450
        $parent_item_id = Session::read('parent_item_id', 0);
9451
9452
        for ($i = 0; $i < count($arrLP); $i++) {
9453
            if ($action != 'add') {
9454
                if (
9455
                    ($arrLP[$i]['item_type'] == 'dir') &&
9456
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9457
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9458
                ) {
9459
                    $selectParent->addOption(
9460
                        $arrLP[$i]['title'],
9461
                        $arrLP[$i]['id'],
9462
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9463
                    );
9464
9465
                    if ($parent == $arrLP[$i]['id']) {
9466
                        $selectParent->setSelected($arrLP[$i]['id']);
9467
                    }
9468
                } else {
9469
                    $arrHide[] = $arrLP[$i]['id'];
9470
                }
9471
            } else {
9472
                if ($arrLP[$i]['item_type'] == 'dir') {
9473
                    $selectParent->addOption(
9474
                        $arrLP[$i]['title'],
9475
                        $arrLP[$i]['id'],
9476
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9477
                    );
9478
9479
                    if ($parent_item_id == $arrLP[$i]['id']) {
9480
                        $selectParent->setSelected($arrLP[$i]['id']);
9481
                    }
9482
                }
9483
            }
9484
        }
9485
9486
        if (is_array($arrLP)) {
9487
            reset($arrLP);
9488
        }
9489
9490
        $selectPrevious = $form->addSelect(
9491
            'previous',
9492
            get_lang('Position'),
9493
            [],
9494
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9495
        );
9496
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9497
9498
        for ($i = 0; $i < count($arrLP); $i++) {
9499
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9500
                $selectPrevious->addOption(
9501
                    $arrLP[$i]['title'],
9502
                    $arrLP[$i]['id']
9503
                );
9504
9505
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9506
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9507
                } elseif ($action == 'add') {
9508
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9509
                }
9510
            }
9511
        }
9512
9513
        if ($action != 'move') {
9514
            $urlAttributes = ['class' => 'learnpath_item_form'];
9515
9516
            if (is_numeric($extra_info)) {
9517
                $urlAttributes['disabled'] = 'disabled';
9518
            }
9519
9520
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9521
            $defaults['url'] = $item_url;
9522
            $arrHide = [];
9523
            for ($i = 0; $i < count($arrLP); $i++) {
9524
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9525
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9526
                }
9527
            }
9528
        }
9529
9530
        if ('edit' === $action) {
9531
            $extraField = new ExtraField('lp_item');
9532
            $extraField->addElements($form, $id);
9533
        }
9534
9535
        if ($action == 'add') {
9536
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9537
        } else {
9538
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9539
        }
9540
9541
        if ($action == 'move') {
9542
            $form->addHidden('title', $item_title);
9543
            $form->addHidden('description', $item_description);
9544
        }
9545
9546
        if (is_numeric($extra_info)) {
9547
            $form->addHidden('path', $extra_info);
9548
        } elseif (is_array($extra_info)) {
9549
            $form->addHidden('path', $extra_info['path']);
9550
        }
9551
        $form->addHidden('type', TOOL_LINK);
9552
        $form->addHidden('post_time', time());
9553
        $form->setDefaults($defaults);
9554
9555
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9556
    }
9557
9558
    /**
9559
     * Return HTML form to add/edit a student publication (work).
9560
     *
9561
     * @param string $action
9562
     * @param int    $id         Item ID if already exists
9563
     * @param string $extra_info
9564
     *
9565
     * @throws Exception
9566
     *
9567
     * @return string HTML form
9568
     */
9569
    public function display_student_publication_form(
9570
        $action = 'add',
9571
        $id = 0,
9572
        $extra_info = ''
9573
    ) {
9574
        $course_id = api_get_course_int_id();
9575
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9576
9577
        $item_title = get_lang('Student_publication');
9578
        if ($id != 0 && is_array($extra_info)) {
9579
            $item_title = stripslashes($extra_info['title']);
9580
            $item_description = stripslashes($extra_info['description']);
9581
        } elseif (is_numeric($extra_info)) {
9582
            $extra_info = (int) $extra_info;
9583
            $sql = "SELECT title, description
9584
                    FROM $tbl_publication
9585
                    WHERE c_id = $course_id AND id = ".$extra_info;
9586
9587
            $result = Database::query($sql);
9588
            $row = Database::fetch_array($result);
9589
            if ($row) {
9590
                $item_title = $row['title'];
9591
            }
9592
        }
9593
9594
        $parent = 0;
9595
        if ($id != 0 && is_array($extra_info)) {
9596
            $parent = $extra_info['parent_item_id'];
9597
        }
9598
9599
        $arrLP = $this->getItemsForForm();
9600
9601
        $this->tree_array($arrLP);
9602
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9603
        unset($this->arrMenu);
9604
9605
        $form = new FormValidator('frm_student_publication', 'post', '#');
9606
9607
        if ($action == 'add') {
9608
            $form->addHeader(get_lang('Student_publication'));
9609
        } elseif ($action == 'move') {
9610
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9611
        } else {
9612
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9613
        }
9614
9615
        if ($action != 'move') {
9616
            $this->setItemTitle($form);
9617
        }
9618
9619
        $parentSelect = $form->addSelect(
9620
            'parent',
9621
            get_lang('Parent'),
9622
            ['0' => $this->name],
9623
            [
9624
                'onchange' => 'javascript: load_cbo(this.value);',
9625
                'class' => 'learnpath_item_form',
9626
                'id' => 'idParent',
9627
            ]
9628
        );
9629
9630
        $arrHide = [$id];
9631
        for ($i = 0; $i < count($arrLP); $i++) {
9632
            if ($action != 'add') {
9633
                if (
9634
                    ($arrLP[$i]['item_type'] == 'dir') &&
9635
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9636
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9637
                ) {
9638
                    $parentSelect->addOption(
9639
                        $arrLP[$i]['title'],
9640
                        $arrLP[$i]['id'],
9641
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9642
                    );
9643
9644
                    if ($parent == $arrLP[$i]['id']) {
9645
                        $parentSelect->setSelected($arrLP[$i]['id']);
9646
                    }
9647
                } else {
9648
                    $arrHide[] = $arrLP[$i]['id'];
9649
                }
9650
            } else {
9651
                if ($arrLP[$i]['item_type'] == 'dir') {
9652
                    $parentSelect->addOption(
9653
                        $arrLP[$i]['title'],
9654
                        $arrLP[$i]['id'],
9655
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9656
                    );
9657
9658
                    if ($parent == $arrLP[$i]['id']) {
9659
                        $parentSelect->setSelected($arrLP[$i]['id']);
9660
                    }
9661
                }
9662
            }
9663
        }
9664
9665
        if (is_array($arrLP)) {
9666
            reset($arrLP);
9667
        }
9668
9669
        $previousSelect = $form->addSelect(
9670
            'previous',
9671
            get_lang('Position'),
9672
            ['0' => get_lang('FirstPosition')],
9673
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9674
        );
9675
9676
        for ($i = 0; $i < count($arrLP); $i++) {
9677
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9678
                $previousSelect->addOption(
9679
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9680
                    $arrLP[$i]['id']
9681
                );
9682
9683
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9684
                    $previousSelect->setSelected($arrLP[$i]['id']);
9685
                } elseif ($action == 'add') {
9686
                    $previousSelect->setSelected($arrLP[$i]['id']);
9687
                }
9688
            }
9689
        }
9690
9691
        if ('edit' === $action) {
9692
            $extraField = new ExtraField('lp_item');
9693
            $extraField->addElements($form, $id);
9694
        }
9695
9696
        if ($action == 'add') {
9697
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9698
        } else {
9699
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9700
        }
9701
9702
        if ($action == 'move') {
9703
            $form->addHidden('title', $item_title);
9704
            $form->addHidden('description', $item_description);
9705
        }
9706
9707
        if (is_numeric($extra_info)) {
9708
            $form->addHidden('path', $extra_info);
9709
        } elseif (is_array($extra_info)) {
9710
            $form->addHidden('path', $extra_info['path']);
9711
        }
9712
9713
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9714
        $form->addHidden('post_time', time());
9715
        $form->setDefaults(['title' => $item_title]);
9716
9717
        $return = '<div class="sectioncomment">';
9718
        $return .= $form->returnForm();
9719
        $return .= '</div>';
9720
9721
        return $return;
9722
    }
9723
9724
    /**
9725
     * Displays the menu for manipulating a step.
9726
     *
9727
     * @param int    $item_id
9728
     * @param string $item_type
9729
     *
9730
     * @return string
9731
     */
9732
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9733
    {
9734
        $_course = api_get_course_info();
9735
        $course_code = api_get_course_id();
9736
        $item_id = (int) $item_id;
9737
9738
        $return = '<div class="actions">';
9739
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9740
        $sql = "SELECT * FROM $tbl_lp_item
9741
                WHERE iid = ".$item_id;
9742
        $result = Database::query($sql);
9743
        $row = Database::fetch_assoc($result);
9744
9745
        $audio_player = null;
9746
        // We display an audio player if needed.
9747
        if (!empty($row['audio'])) {
9748
            $audio = learnpathItem::fixAudio($row['audio']);
9749
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
9750
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9751
                            <audio src="'.$webAudioPath.'" controls>
9752
                            </div><br />';
9753
        }
9754
9755
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9756
9757
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9758
            $return .= Display::url(
9759
                Display::return_icon(
9760
                    'edit.png',
9761
                    get_lang('Edit'),
9762
                    [],
9763
                    ICON_SIZE_SMALL
9764
                ),
9765
                $url.'&action=edit_item&path_item='.$row['path']
9766
            );
9767
9768
            $return .= Display::url(
9769
                Display::return_icon(
9770
                    'move.png',
9771
                    get_lang('Move'),
9772
                    [],
9773
                    ICON_SIZE_SMALL
9774
                ),
9775
                $url.'&action=move_item'
9776
            );
9777
        }
9778
9779
        // Commented for now as prerequisites cannot be added to chapters.
9780
        if ($item_type != 'dir') {
9781
            $return .= Display::url(
9782
                Display::return_icon(
9783
                    'accept.png',
9784
                    get_lang('LearnpathPrerequisites'),
9785
                    [],
9786
                    ICON_SIZE_SMALL
9787
                ),
9788
                $url.'&action=edit_item_prereq'
9789
            );
9790
        }
9791
        $return .= Display::url(
9792
            Display::return_icon(
9793
                'delete.png',
9794
                get_lang('Delete'),
9795
                [],
9796
                ICON_SIZE_SMALL
9797
            ),
9798
            $url.'&action=delete_item'
9799
        );
9800
9801
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9802
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9803
            if (empty($documentData)) {
9804
                // Try with iid
9805
                $table = Database::get_course_table(TABLE_DOCUMENT);
9806
                $sql = "SELECT path FROM $table
9807
                        WHERE
9808
                              c_id = ".api_get_course_int_id()." AND
9809
                              iid = ".$row['path']." AND
9810
                              path NOT LIKE '%_DELETED_%'";
9811
                $result = Database::query($sql);
9812
                $documentData = Database::fetch_array($result);
9813
                if ($documentData) {
9814
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9815
                }
9816
            }
9817
            if (isset($documentData['absolute_path_from_document'])) {
9818
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9819
            }
9820
        }
9821
9822
        $return .= '</div>';
9823
9824
        if (!empty($audio_player)) {
9825
            $return .= $audio_player;
9826
        }
9827
9828
        return $return;
9829
    }
9830
9831
    /**
9832
     * Creates the javascript needed for filling up the checkboxes without page reload.
9833
     *
9834
     * @return string
9835
     */
9836
    public function get_js_dropdown_array()
9837
    {
9838
        $course_id = api_get_course_int_id();
9839
        $return = 'var child_name = new Array();'."\n";
9840
        $return .= 'var child_value = new Array();'."\n\n";
9841
        $return .= 'child_name[0] = new Array();'."\n";
9842
        $return .= 'child_value[0] = new Array();'."\n\n";
9843
9844
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9845
        $sql = "SELECT * FROM ".$tbl_lp_item."
9846
                WHERE
9847
                    c_id = $course_id AND
9848
                    lp_id = ".$this->lp_id." AND
9849
                    parent_item_id = 0
9850
                ORDER BY display_order ASC";
9851
        $res_zero = Database::query($sql);
9852
        $i = 0;
9853
9854
        $list = $this->getItemsForForm(true);
9855
9856
        foreach ($list as $row_zero) {
9857
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9858
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9859
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9860
                }
9861
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9862
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9863
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9864
            }
9865
        }
9866
9867
        $return .= "\n";
9868
        $sql = "SELECT * FROM $tbl_lp_item
9869
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9870
        $res = Database::query($sql);
9871
        while ($row = Database::fetch_array($res)) {
9872
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9873
                           WHERE
9874
                                c_id = ".$course_id." AND
9875
                                parent_item_id = ".$row['iid']."
9876
                           ORDER BY display_order ASC";
9877
            $res_parent = Database::query($sql_parent);
9878
            $i = 0;
9879
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9880
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9881
9882
            while ($row_parent = Database::fetch_array($res_parent)) {
9883
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9884
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9885
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9886
            }
9887
            $return .= "\n";
9888
        }
9889
9890
        $return .= "
9891
            function load_cbo(id) {
9892
                if (!id) {
9893
                    return false;
9894
                }
9895
9896
                var cbo = document.getElementById('previous');
9897
                for(var i = cbo.length - 1; i > 0; i--) {
9898
                    cbo.options[i] = null;
9899
                }
9900
9901
                var k=0;
9902
                for(var i = 1; i <= child_name[id].length; i++){
9903
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9904
                    option.style.paddingLeft = '40px';
9905
                    cbo.options[i] = option;
9906
                    k = i;
9907
                }
9908
9909
                cbo.options[k].selected = true;
9910
                $('#previous').selectpicker('refresh');
9911
            }";
9912
9913
        return $return;
9914
    }
9915
9916
    /**
9917
     * Display the form to allow moving an item.
9918
     *
9919
     * @param learnpathItem $item Item ID
9920
     *
9921
     * @throws Exception
9922
     * @throws HTML_QuickForm_Error
9923
     *
9924
     * @return string HTML form
9925
     */
9926
    public function display_move_item($item)
9927
    {
9928
        $return = '';
9929
        if ($item) {
9930
            $item_id = $item->getIid();
9931
            $type = $item->get_type();
9932
9933
            switch ($type) {
9934
                case 'dir':
9935
                case 'asset':
9936
                    $return .= $this->display_manipulate($item_id, $type);
9937
                    $return .= $this->display_item_form(
9938
                        $type,
9939
                        get_lang('MoveCurrentChapter'),
9940
                        'move',
9941
                        $item_id,
9942
                        $item
9943
                    );
9944
                    break;
9945
                case TOOL_DOCUMENT:
9946
                    $return .= $this->display_manipulate($item_id, $type);
9947
                    $return .= $this->display_document_form('move', $item_id, $item);
9948
                    break;
9949
                case TOOL_LINK:
9950
                    $return .= $this->display_manipulate($item_id, $type);
9951
                    $return .= $this->display_link_form('move', $item_id, $item);
9952
                    break;
9953
                case TOOL_HOTPOTATOES:
9954
                    $return .= $this->display_manipulate($item_id, $type);
9955
                    $return .= $this->display_link_form('move', $item_id, $item);
9956
                    break;
9957
                case TOOL_QUIZ:
9958
                    $return .= $this->display_manipulate($item_id, $type);
9959
                    $return .= $this->display_quiz_form('move', $item_id, $item);
9960
                    break;
9961
                case TOOL_STUDENTPUBLICATION:
9962
                    $return .= $this->display_manipulate($item_id, $type);
9963
                    $return .= $this->display_student_publication_form('move', $item_id, $item);
9964
                    break;
9965
                case TOOL_FORUM:
9966
                    $return .= $this->display_manipulate($item_id, $type);
9967
                    $return .= $this->display_forum_form('move', $item_id, $item);
9968
                    break;
9969
                case TOOL_THREAD:
9970
                    $return .= $this->display_manipulate($item_id, $type);
9971
                    $return .= $this->display_forum_form('move', $item_id, $item);
9972
                    break;
9973
            }
9974
        }
9975
9976
        return $return;
9977
    }
9978
9979
    /**
9980
     * Return HTML form to allow prerequisites selection.
9981
     *
9982
     * @todo use FormValidator
9983
     *
9984
     * @param int Item ID
9985
     *
9986
     * @return string HTML form
9987
     */
9988
    public function display_item_prerequisites_form($item_id = 0)
9989
    {
9990
        $course_id = api_get_course_int_id();
9991
        $item_id = (int) $item_id;
9992
9993
        if (empty($item_id)) {
9994
            return '';
9995
        }
9996
9997
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9998
9999
        /* Current prerequisite */
10000
        $sql = "SELECT * FROM $tbl_lp_item
10001
                WHERE iid = $item_id";
10002
        $result = Database::query($sql);
10003
        $row = Database::fetch_array($result);
10004
        $prerequisiteId = $row['prerequisite'];
10005
10006
        $return = '<legend>';
10007
        $return .= get_lang('AddEditPrerequisites');
10008
        $return .= '</legend>';
10009
        $return .= '<form method="POST">';
10010
        $return .= '<div class="table-responsive">';
10011
        $return .= '<table class="table table-hover">';
10012
        $return .= '<thead>';
10013
        $return .= '<tr>';
10014
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10015
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10016
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10017
        $return .= '</tr>';
10018
        $return .= '</thead>';
10019
10020
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10021
        $return .= '<tbody>';
10022
        $return .= '<tr>';
10023
        $return .= '<td colspan="3">';
10024
        $return .= '<div class="radio learnpath"><label for="idNone">';
10025
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10026
        $return .= get_lang('None').'</label>';
10027
        $return .= '</div>';
10028
        $return .= '</tr>';
10029
10030
        $sql = "SELECT * FROM $tbl_lp_item
10031
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10032
        $result = Database::query($sql);
10033
10034
        $selectedMinScore = [];
10035
        $selectedMaxScore = [];
10036
        $masteryScore = [];
10037
        while ($row = Database::fetch_array($result)) {
10038
            if ($row['iid'] == $item_id) {
10039
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10040
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10041
            }
10042
            $masteryScore[$row['iid']] = $row['mastery_score'];
10043
        }
10044
10045
        $arrLP = $this->getItemsForForm();
10046
        $this->tree_array($arrLP);
10047
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10048
        unset($this->arrMenu);
10049
10050
        for ($i = 0; $i < count($arrLP); $i++) {
10051
            $item = $arrLP[$i];
10052
10053
            if ($item['id'] == $item_id) {
10054
                break;
10055
            }
10056
10057
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10058
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10059
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10060
10061
            $return .= '<tr>';
10062
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10063
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10064
            $return .= '<label for="id'.$item['id'].'">';
10065
10066
            $checked = '';
10067
            if (null !== $prerequisiteId) {
10068
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10069
            }
10070
10071
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10072
10073
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10074
10075
            $icon_name = str_replace(' ', '', $item['item_type']);
10076
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10077
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10078
            } else {
10079
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10080
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10081
                } else {
10082
                    $return .= Display::return_icon('folder_document.png');
10083
                }
10084
            }
10085
10086
            $return .= $item['title'].'</label>';
10087
            $return .= '</div>';
10088
            $return .= '</td>';
10089
10090
            if ($item['item_type'] == TOOL_QUIZ) {
10091
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10092
                $lpItemObj = new LpItem($course_id, $item['id']);
10093
                $exercise = new Exercise($course_id);
10094
                $exercise->read($lpItemObj->path);
10095
                $lpItemObj->max_score = $exercise->get_max_score();
10096
                $lpItemObj->update();
10097
                $item['max_score'] = $lpItemObj->max_score;
10098
10099
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10100
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10101
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10102
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10103
                }
10104
10105
                $return .= '<td>';
10106
                $return .= '<input
10107
                    class="form-control"
10108
                    size="4" maxlength="3"
10109
                    name="min_'.$item['id'].'"
10110
                    type="number"
10111
                    min="0"
10112
                    step="any"
10113
                    max="'.$item['max_score'].'"
10114
                    value="'.$selectedMinScoreValue.'"
10115
                />';
10116
                $return .= '</td>';
10117
                $return .= '<td>';
10118
                $return .= '<input
10119
                    class="form-control"
10120
                    size="4"
10121
                    maxlength="3"
10122
                    name="max_'.$item['id'].'"
10123
                    type="number"
10124
                    min="0"
10125
                    step="any"
10126
                    max="'.$item['max_score'].'"
10127
                    value="'.$selectedMaxScoreValue.'"
10128
                />';
10129
                $return .= '</td>';
10130
            }
10131
10132
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10133
                $return .= '<td>';
10134
                $return .= '<input
10135
                    size="4"
10136
                    maxlength="3"
10137
                    name="min_'.$item['id'].'"
10138
                    type="number"
10139
                    min="0"
10140
                    step="any"
10141
                    max="'.$item['max_score'].'"
10142
                    value="'.$selectedMinScoreValue.'"
10143
                />';
10144
                $return .= '</td>';
10145
                $return .= '<td>';
10146
                $return .= '<input
10147
                    size="4"
10148
                    maxlength="3"
10149
                    name="max_'.$item['id'].'"
10150
                    type="number"
10151
                    min="0"
10152
                    step="any"
10153
                    max="'.$item['max_score'].'"
10154
                    value="'.$selectedMaxScoreValue.'"
10155
                />';
10156
                $return .= '</td>';
10157
            }
10158
            $return .= '</tr>';
10159
        }
10160
        $return .= '<tr>';
10161
        $return .= '</tr>';
10162
        $return .= '</tbody>';
10163
        $return .= '</table>';
10164
        $return .= '</div>';
10165
        $return .= '<div class="form-group">';
10166
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10167
            get_lang('ModifyPrerequisites').'</button>';
10168
        $return .= '</form>';
10169
10170
        return $return;
10171
    }
10172
10173
    /**
10174
     * Return HTML list to allow prerequisites selection for lp.
10175
     *
10176
     * @return string HTML form
10177
     */
10178
    public function display_lp_prerequisites_list()
10179
    {
10180
        $course_id = api_get_course_int_id();
10181
        $lp_id = $this->lp_id;
10182
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10183
10184
        // get current prerequisite
10185
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10186
        $result = Database::query($sql);
10187
        $row = Database::fetch_array($result);
10188
        $prerequisiteId = $row['prerequisite'];
10189
        $session_id = api_get_session_id();
10190
        $session_condition = api_get_session_condition($session_id, true, true);
10191
        $sql = "SELECT * FROM $tbl_lp
10192
                WHERE c_id = $course_id $session_condition
10193
                ORDER BY display_order ";
10194
        $rs = Database::query($sql);
10195
        $return = '';
10196
        $return .= '<select name="prerequisites" class="form-control">';
10197
        $return .= '<option value="0">'.get_lang('None').'</option>';
10198
        if (Database::num_rows($rs) > 0) {
10199
            while ($row = Database::fetch_array($rs)) {
10200
                if ($row['id'] == $lp_id) {
10201
                    continue;
10202
                }
10203
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10204
            }
10205
        }
10206
        $return .= '</select>';
10207
10208
        return $return;
10209
    }
10210
10211
    /**
10212
     * Creates a list with all the documents in it.
10213
     *
10214
     * @param bool $showInvisibleFiles
10215
     *
10216
     * @throws Exception
10217
     * @throws HTML_QuickForm_Error
10218
     *
10219
     * @return string
10220
     */
10221
    public function get_documents($showInvisibleFiles = false)
10222
    {
10223
        $course_info = api_get_course_info();
10224
        $sessionId = api_get_session_id();
10225
        $documentTree = DocumentManager::get_document_preview(
10226
            $course_info,
10227
            $this->lp_id,
10228
            null,
10229
            $sessionId,
10230
            true,
10231
            null,
10232
            null,
10233
            $showInvisibleFiles,
10234
            true
10235
        );
10236
10237
        $headers = [
10238
            get_lang('Files'),
10239
            get_lang('CreateTheDocument'),
10240
            get_lang('CreateReadOutText'),
10241
            get_lang('Upload'),
10242
        ];
10243
10244
        $form = new FormValidator(
10245
            'form_upload',
10246
            'POST',
10247
            $this->getCurrentBuildingModeURL(),
10248
            '',
10249
            ['enctype' => 'multipart/form-data']
10250
        );
10251
10252
        $folders = DocumentManager::get_all_document_folders(
10253
            api_get_course_info(),
10254
            0,
10255
            true
10256
        );
10257
10258
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10259
10260
        DocumentManager::build_directory_selector(
10261
            $folders,
10262
            $lpPathInfo['id'],
10263
            [],
10264
            true,
10265
            $form,
10266
            'directory_parent_id'
10267
        );
10268
10269
        $group = [
10270
            $form->createElement(
10271
                'radio',
10272
                'if_exists',
10273
                get_lang('UplWhatIfFileExists'),
10274
                get_lang('UplDoNothing'),
10275
                'nothing'
10276
            ),
10277
            $form->createElement(
10278
                'radio',
10279
                'if_exists',
10280
                null,
10281
                get_lang('UplOverwriteLong'),
10282
                'overwrite'
10283
            ),
10284
            $form->createElement(
10285
                'radio',
10286
                'if_exists',
10287
                null,
10288
                get_lang('UplRenameLong'),
10289
                'rename'
10290
            ),
10291
        ];
10292
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10293
10294
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10295
        $defaultFileExistsOption = 'rename';
10296
        if (!empty($fileExistsOption)) {
10297
            $defaultFileExistsOption = $fileExistsOption;
10298
        }
10299
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10300
10301
        // Check box options
10302
        $form->addElement(
10303
            'checkbox',
10304
            'unzip',
10305
            get_lang('Options'),
10306
            get_lang('Uncompress')
10307
        );
10308
10309
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10310
        $form->addMultipleUpload($url);
10311
        $new = $this->display_document_form('add', 0);
10312
        $frmReadOutText = $this->displayFrmReadOutText('add');
10313
        $tabs = Display::tabs(
10314
            $headers,
10315
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10316
            'subtab'
10317
        );
10318
10319
        return $tabs;
10320
    }
10321
10322
    /**
10323
     * Creates a list with all the exercises (quiz) in it.
10324
     *
10325
     * @return string
10326
     */
10327
    public function get_exercises()
10328
    {
10329
        $course_id = api_get_course_int_id();
10330
        $session_id = api_get_session_id();
10331
        $userInfo = api_get_user_info();
10332
10333
        // New for hotpotatoes.
10334
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10335
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10336
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10337
        $condition_session = api_get_session_condition($session_id, true, true);
10338
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10339
10340
        $activeCondition = ' active <> -1 ';
10341
        if ($setting) {
10342
            $activeCondition = ' active = 1 ';
10343
        }
10344
10345
        $categoryCondition = '';
10346
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10347
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10348
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10349
        }
10350
10351
        $keywordCondition = '';
10352
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10353
10354
        if (!empty($keyword)) {
10355
            $keyword = Database::escape_string($keyword);
10356
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10357
        }
10358
10359
        $sql_quiz = "SELECT * FROM $tbl_quiz
10360
                     WHERE
10361
                            c_id = $course_id AND
10362
                            $activeCondition
10363
                            $condition_session
10364
                            $categoryCondition
10365
                            $keywordCondition
10366
                     ORDER BY title ASC";
10367
10368
        $sql_hot = "SELECT * FROM $tbl_doc
10369
                    WHERE
10370
                        c_id = $course_id AND
10371
                        path LIKE '".$uploadPath."/%/%htm%'
10372
                        $condition_session
10373
                     ORDER BY id ASC";
10374
10375
        $res_quiz = Database::query($sql_quiz);
10376
        $res_hot = Database::query($sql_hot);
10377
10378
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10379
10380
        // Create a search-box
10381
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10382
        $form->addHidden('action', 'add_item');
10383
        $form->addHidden('type', 'step');
10384
        $form->addHidden('lp_id', $this->lp_id);
10385
        $form->addHidden('lp_build_selected', '2');
10386
10387
        $form->addCourseHiddenParams();
10388
        $form->addText(
10389
            'keyword',
10390
            get_lang('Search'),
10391
            false,
10392
            [
10393
                'aria-label' => get_lang('Search'),
10394
            ]
10395
        );
10396
10397
        if (api_get_configuration_value('allow_exercise_categories')) {
10398
            $manager = new ExerciseCategoryManager();
10399
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10400
            if (!empty($options)) {
10401
                $form->addSelect(
10402
                    'category_id',
10403
                    get_lang('Category'),
10404
                    $options,
10405
                    ['placeholder' => get_lang('SelectAnOption')]
10406
                );
10407
            }
10408
        }
10409
10410
        $form->addButtonSearch(get_lang('Search'));
10411
        $return = $form->returnForm();
10412
10413
        $return .= '<ul class="lp_resource">';
10414
10415
        $return .= '<li class="lp_resource_element">';
10416
        $return .= Display::return_icon('new_exercice.png');
10417
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10418
            get_lang('NewExercise').'</a>';
10419
        $return .= '</li>';
10420
10421
        $previewIcon = Display::return_icon(
10422
            'preview_view.png',
10423
            get_lang('Preview')
10424
        );
10425
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10426
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10427
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10428
10429
        // Display hotpotatoes
10430
        while ($row_hot = Database::fetch_array($res_hot)) {
10431
            $link = Display::url(
10432
                $previewIcon,
10433
                $exerciseUrl.'&file='.$row_hot['path'],
10434
                ['target' => '_blank']
10435
            );
10436
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10437
            $return .= '<a class="moved" href="#">';
10438
            $return .= Display::return_icon(
10439
                'move_everywhere.png',
10440
                get_lang('Move'),
10441
                [],
10442
                ICON_SIZE_TINY
10443
            );
10444
            $return .= '</a> ';
10445
            $return .= Display::return_icon('hotpotatoes_s.png');
10446
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10447
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10448
            $return .= '</li>';
10449
        }
10450
10451
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10452
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10453
            $title = strip_tags(
10454
                api_html_entity_decode($row_quiz['title'])
10455
            );
10456
10457
            $visibility = api_get_item_visibility(
10458
                ['real_id' => $course_id],
10459
                TOOL_QUIZ,
10460
                $row_quiz['iid'],
10461
                $session_id
10462
            );
10463
10464
            $link = Display::url(
10465
                $previewIcon,
10466
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10467
                ['target' => '_blank']
10468
            );
10469
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10470
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10471
            $return .= $quizIcon;
10472
            $sessionStar = api_get_session_image(
10473
                $row_quiz['session_id'],
10474
                $userInfo['status']
10475
            );
10476
            $return .= Display::url(
10477
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10478
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10479
                [
10480
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10481
                ]
10482
            );
10483
            $return .= '</li>';
10484
        }
10485
10486
        $return .= '</ul>';
10487
10488
        return $return;
10489
    }
10490
10491
    /**
10492
     * Creates a list with all the links in it.
10493
     *
10494
     * @return string
10495
     */
10496
    public function get_links()
10497
    {
10498
        $selfUrl = api_get_self();
10499
        $courseIdReq = api_get_cidreq();
10500
        $course = api_get_course_info();
10501
        $userInfo = api_get_user_info();
10502
10503
        $course_id = $course['real_id'];
10504
        $tbl_link = Database::get_course_table(TABLE_LINK);
10505
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10506
        $moveEverywhereIcon = Display::return_icon(
10507
            'move_everywhere.png',
10508
            get_lang('Move'),
10509
            [],
10510
            ICON_SIZE_TINY
10511
        );
10512
10513
        $session_id = api_get_session_id();
10514
        $condition_session = api_get_session_condition(
10515
            $session_id,
10516
            true,
10517
            true,
10518
            'link.session_id'
10519
        );
10520
10521
        $sql = "SELECT
10522
                    link.id as link_id,
10523
                    link.title as link_title,
10524
                    link.session_id as link_session_id,
10525
                    link.category_id as category_id,
10526
                    link_category.category_title as category_title
10527
                FROM $tbl_link as link
10528
                LEFT JOIN $linkCategoryTable as link_category
10529
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10530
                WHERE link.c_id = $course_id $condition_session
10531
                ORDER BY link_category.category_title ASC, link.title ASC";
10532
        $result = Database::query($sql);
10533
        $categorizedLinks = [];
10534
        $categories = [];
10535
10536
        while ($link = Database::fetch_array($result)) {
10537
            if (!$link['category_id']) {
10538
                $link['category_title'] = get_lang('Uncategorized');
10539
            }
10540
            $categories[$link['category_id']] = $link['category_title'];
10541
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10542
        }
10543
10544
        $linksHtmlCode =
10545
            '<script>
10546
            function toggle_tool(tool, id) {
10547
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10548
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10549
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10550
                } else {
10551
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10552
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10553
                }
10554
            }
10555
        </script>
10556
10557
        <ul class="lp_resource">
10558
            <li class="lp_resource_element">
10559
                '.Display::return_icon('linksnew.gif').'
10560
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10561
                get_lang('LinkAdd').'
10562
                </a>
10563
            </li>';
10564
10565
        foreach ($categorizedLinks as $categoryId => $links) {
10566
            $linkNodes = null;
10567
            foreach ($links as $key => $linkInfo) {
10568
                $title = $linkInfo['link_title'];
10569
                $linkSessionId = $linkInfo['link_session_id'];
10570
10571
                $link = Display::url(
10572
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10573
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10574
                    ['target' => '_blank']
10575
                );
10576
10577
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10578
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10579
                    $linkNodes .=
10580
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10581
                        <a class="moved" href="#">'.
10582
                            $moveEverywhereIcon.
10583
                        '</a>
10584
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10585
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10586
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10587
                        Security::remove_XSS($title).$sessionStar.$link.
10588
                        '</a>
10589
                    </li>';
10590
                }
10591
            }
10592
            $linksHtmlCode .=
10593
                '<li>
10594
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10595
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10596
                    align="absbottom" />
10597
                </a>
10598
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10599
            </li>
10600
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10601
        }
10602
        $linksHtmlCode .= '</ul>';
10603
10604
        return $linksHtmlCode;
10605
    }
10606
10607
    /**
10608
     * Creates a list with all the student publications in it.
10609
     *
10610
     * @return string
10611
     */
10612
    public function get_student_publications()
10613
    {
10614
        $return = '<ul class="lp_resource">';
10615
        $return .= '<li class="lp_resource_element">';
10616
        $return .= Display::return_icon('works_new.gif');
10617
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10618
            get_lang('AddAssignmentPage').'</a>';
10619
        $return .= '</li>';
10620
10621
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10622
        $works = getWorkListTeacher(0, 100, null, null, null);
10623
        if (!empty($works)) {
10624
            foreach ($works as $work) {
10625
                $link = Display::url(
10626
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10627
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10628
                    ['target' => '_blank']
10629
                );
10630
10631
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10632
                $return .= '<a class="moved" href="#">';
10633
                $return .= Display::return_icon(
10634
                    'move_everywhere.png',
10635
                    get_lang('Move'),
10636
                    [],
10637
                    ICON_SIZE_TINY
10638
                );
10639
                $return .= '</a> ';
10640
10641
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10642
                $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.'">'.
10643
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10644
                </a>';
10645
10646
                $return .= '</li>';
10647
            }
10648
        }
10649
10650
        $return .= '</ul>';
10651
10652
        return $return;
10653
    }
10654
10655
    /**
10656
     * Creates a list with all the forums in it.
10657
     *
10658
     * @return string
10659
     */
10660
    public function get_forums()
10661
    {
10662
        require_once '../forum/forumfunction.inc.php';
10663
10664
        $forumCategories = get_forum_categories();
10665
        $forumsInNoCategory = get_forums_in_category(0);
10666
        if (!empty($forumsInNoCategory)) {
10667
            $forumCategories = array_merge(
10668
                $forumCategories,
10669
                [
10670
                    [
10671
                        'cat_id' => 0,
10672
                        'session_id' => 0,
10673
                        'visibility' => 1,
10674
                        'cat_comment' => null,
10675
                    ],
10676
                ]
10677
            );
10678
        }
10679
10680
        $forumList = get_forums();
10681
        $a_forums = [];
10682
        foreach ($forumCategories as $forumCategory) {
10683
            // The forums in this category.
10684
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10685
            if (!empty($forumsInCategory)) {
10686
                foreach ($forumList as $forum) {
10687
                    if (isset($forum['forum_category']) &&
10688
                        $forum['forum_category'] == $forumCategory['cat_id']
10689
                    ) {
10690
                        $a_forums[] = $forum;
10691
                    }
10692
                }
10693
            }
10694
        }
10695
10696
        $return = '<ul class="lp_resource">';
10697
10698
        // First add link
10699
        $return .= '<li class="lp_resource_element">';
10700
        $return .= Display::return_icon('new_forum.png');
10701
        $return .= Display::url(
10702
            get_lang('CreateANewForum'),
10703
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10704
                'action' => 'add',
10705
                'content' => 'forum',
10706
                'lp_id' => $this->lp_id,
10707
            ]),
10708
            ['title' => get_lang('CreateANewForum')]
10709
        );
10710
        $return .= '</li>';
10711
10712
        $return .= '<script>
10713
            function toggle_forum(forum_id) {
10714
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10715
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10716
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10717
                } else {
10718
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10719
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10720
                }
10721
            }
10722
        </script>';
10723
10724
        foreach ($a_forums as $forum) {
10725
            if (!empty($forum['forum_id'])) {
10726
                $link = Display::url(
10727
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10728
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10729
                    ['target' => '_blank']
10730
                );
10731
10732
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10733
                $return .= '<a class="moved" href="#">';
10734
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10735
                $return .= ' </a>';
10736
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10737
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10738
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10739
                            </a>
10740
                            <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forum['forum_id'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
10741
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10742
10743
                $return .= '</li>';
10744
10745
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10746
                $a_threads = get_threads($forum['forum_id']);
10747
                if (is_array($a_threads)) {
10748
                    foreach ($a_threads as $thread) {
10749
                        $link = Display::url(
10750
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10751
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10752
                            ['target' => '_blank']
10753
                        );
10754
10755
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10756
                        $return .= '&nbsp;<a class="moved" href="#">';
10757
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10758
                        $return .= ' </a>';
10759
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10760
                        $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$thread['thread_id'].'&lp_id='.$this->lp_id.'">'.
10761
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10762
                        $return .= '</li>';
10763
                    }
10764
                }
10765
                $return .= '</div>';
10766
            }
10767
        }
10768
        $return .= '</ul>';
10769
10770
        return $return;
10771
    }
10772
10773
    /**
10774
     * // TODO: The output encoding should be equal to the system encoding.
10775
     *
10776
     * Exports the learning path as a SCORM package. This is the main function that
10777
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10778
     * whole thing and returns the zip.
10779
     *
10780
     * This method needs to be called in PHP5, as it will fail with non-adequate
10781
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10782
     * you need to call it on a learnpath object.
10783
     *
10784
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10785
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10786
     * path has been modified, it should use the generic method here below.
10787
     *
10788
     * @return string Returns the zip package string, or null if error
10789
     */
10790
    public function scormExport()
10791
    {
10792
        api_set_more_memory_and_time_limits();
10793
10794
        $_course = api_get_course_info();
10795
        $course_id = $_course['real_id'];
10796
        // Create the zip handler (this will remain available throughout the method).
10797
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10798
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10799
        $temp_dir_short = uniqid('scorm_export', true);
10800
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10801
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10802
        $zip_folder = new PclZip($temp_zip_file);
10803
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10804
        $root_path = $main_path = api_get_path(SYS_PATH);
10805
        $files_cleanup = [];
10806
10807
        // Place to temporarily stash the zip file.
10808
        // create the temp dir if it doesn't exist
10809
        // or do a cleanup before creating the zip file.
10810
        if (!is_dir($temp_zip_dir)) {
10811
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10812
        } else {
10813
            // Cleanup: Check the temp dir for old files and delete them.
10814
            $handle = opendir($temp_zip_dir);
10815
            while (false !== ($file = readdir($handle))) {
10816
                if ($file != '.' && $file != '..') {
10817
                    unlink("$temp_zip_dir/$file");
10818
                }
10819
            }
10820
            closedir($handle);
10821
        }
10822
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10823
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10824
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10825
        ) {
10826
            // Remove the possible . at the end of the path.
10827
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10828
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10829
            mkdir(
10830
                $dest_path_to_scorm_folder,
10831
                api_get_permissions_for_new_directories(),
10832
                true
10833
            );
10834
            copyr(
10835
                $current_course_path.'/scorm/'.$this->path,
10836
                $dest_path_to_scorm_folder,
10837
                ['imsmanifest'],
10838
                $zip_files
10839
            );
10840
        }
10841
10842
        // Build a dummy imsmanifest structure.
10843
        // Do not add to the zip yet (we still need it).
10844
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10845
        // Aggregation Model official document, section "2.3 Content Packaging".
10846
        // We are going to build a UTF-8 encoded manifest.
10847
        // Later we will recode it to the desired (and supported) encoding.
10848
        $xmldoc = new DOMDocument('1.0');
10849
        $root = $xmldoc->createElement('manifest');
10850
        $root->setAttribute('identifier', 'SingleCourseManifest');
10851
        $root->setAttribute('version', '1.1');
10852
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10853
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10854
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10855
        $root->setAttribute(
10856
            'xsi:schemaLocation',
10857
            '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'
10858
        );
10859
        // Build mandatory sub-root container elements.
10860
        $metadata = $xmldoc->createElement('metadata');
10861
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10862
        $metadata->appendChild($md_schema);
10863
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10864
        $metadata->appendChild($md_schemaversion);
10865
        $root->appendChild($metadata);
10866
10867
        $organizations = $xmldoc->createElement('organizations');
10868
        $resources = $xmldoc->createElement('resources');
10869
10870
        // Build the only organization we will use in building our learnpaths.
10871
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10872
        $organization = $xmldoc->createElement('organization');
10873
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10874
        // To set the title of the SCORM entity (=organization), we take the name given
10875
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10876
        // learning path charset) as it is the encoding that defines how it is stored
10877
        // in the database. Then we convert it to HTML entities again as the "&" character
10878
        // alone is not authorized in XML (must be &amp;).
10879
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10880
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10881
        $organization->appendChild($org_title);
10882
        $folder_name = 'document';
10883
10884
        // Removes the learning_path/scorm_folder path when exporting see #4841
10885
        $path_to_remove = '';
10886
        $path_to_replace = '';
10887
        $result = $this->generate_lp_folder($_course);
10888
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10889
            $path_to_remove = 'document'.$result['dir'];
10890
            $path_to_replace = $folder_name.'/';
10891
        }
10892
10893
        // Fixes chamilo scorm exports
10894
        if ($this->ref === 'chamilo_scorm_export') {
10895
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10896
        }
10897
10898
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10899
        $link_updates = [];
10900
        $links_to_create = [];
10901
        foreach ($this->ordered_items as $index => $itemId) {
10902
            /** @var learnpathItem $item */
10903
            $item = $this->items[$itemId];
10904
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10905
                // Get included documents from this item.
10906
                if ($item->type === 'sco') {
10907
                    $inc_docs = $item->get_resources_from_source(
10908
                        null,
10909
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10910
                    );
10911
                } else {
10912
                    $inc_docs = $item->get_resources_from_source();
10913
                }
10914
10915
                // Give a child element <item> to the <organization> element.
10916
                $my_item_id = $item->get_id();
10917
                $my_item = $xmldoc->createElement('item');
10918
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10919
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10920
                $my_item->setAttribute('isvisible', 'true');
10921
                // Give a child element <title> to the <item> element.
10922
                $my_title = $xmldoc->createElement(
10923
                    'title',
10924
                    htmlspecialchars(
10925
                        api_utf8_encode($item->get_title()),
10926
                        ENT_QUOTES,
10927
                        'UTF-8'
10928
                    )
10929
                );
10930
                $my_item->appendChild($my_title);
10931
                // Give a child element <adlcp:prerequisites> to the <item> element.
10932
                $my_prereqs = $xmldoc->createElement(
10933
                    'adlcp:prerequisites',
10934
                    $this->get_scorm_prereq_string($my_item_id)
10935
                );
10936
                $my_prereqs->setAttribute('type', 'aicc_script');
10937
                $my_item->appendChild($my_prereqs);
10938
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10939
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10940
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10941
                //$xmldoc->createElement('adlcp:timelimitaction','');
10942
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10943
                //$xmldoc->createElement('adlcp:datafromlms','');
10944
                // Give a child element <adlcp:masteryscore> to the <item> element.
10945
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10946
                $my_item->appendChild($my_masteryscore);
10947
10948
                // Attach this item to the organization element or hits parent if there is one.
10949
                if (!empty($item->parent) && $item->parent != 0) {
10950
                    $children = $organization->childNodes;
10951
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10952
                    if (is_object($possible_parent)) {
10953
                        $possible_parent->appendChild($my_item);
10954
                    } else {
10955
                        if ($this->debug > 0) {
10956
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10957
                        }
10958
                    }
10959
                } else {
10960
                    if ($this->debug > 0) {
10961
                        error_log('No parent');
10962
                    }
10963
                    $organization->appendChild($my_item);
10964
                }
10965
10966
                // Get the path of the file(s) from the course directory root.
10967
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10968
                $my_xml_file_path = $my_file_path;
10969
                if (!empty($path_to_remove)) {
10970
                    // From docs
10971
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10972
10973
                    // From quiz
10974
                    if ($this->ref === 'chamilo_scorm_export') {
10975
                        $path_to_remove = 'scorm/'.$this->path.'/';
10976
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10977
                    }
10978
                }
10979
10980
                $my_sub_dir = dirname($my_file_path);
10981
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10982
                $my_xml_sub_dir = $my_sub_dir;
10983
                // Give a <resource> child to the <resources> element
10984
                $my_resource = $xmldoc->createElement('resource');
10985
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10986
                $my_resource->setAttribute('type', 'webcontent');
10987
                $my_resource->setAttribute('href', $my_xml_file_path);
10988
                // adlcp:scormtype can be either 'sco' or 'asset'.
10989
                if ($item->type === 'sco') {
10990
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10991
                } else {
10992
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10993
                }
10994
                // xml:base is the base directory to find the files declared in this resource.
10995
                $my_resource->setAttribute('xml:base', '');
10996
                // Give a <file> child to the <resource> element.
10997
                $my_file = $xmldoc->createElement('file');
10998
                $my_file->setAttribute('href', $my_xml_file_path);
10999
                $my_resource->appendChild($my_file);
11000
11001
                // Dependency to other files - not yet supported.
11002
                $i = 1;
11003
                if ($inc_docs) {
11004
                    foreach ($inc_docs as $doc_info) {
11005
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11006
                            continue;
11007
                        }
11008
                        $my_dep = $xmldoc->createElement('resource');
11009
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11010
                        $my_dep->setAttribute('identifier', $res_id);
11011
                        $my_dep->setAttribute('type', 'webcontent');
11012
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11013
                        $my_dep_file = $xmldoc->createElement('file');
11014
                        // Check type of URL.
11015
                        if ($doc_info[1] == 'remote') {
11016
                            // Remote file. Save url as is.
11017
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11018
                            $my_dep->setAttribute('xml:base', '');
11019
                        } elseif ($doc_info[1] === 'local') {
11020
                            switch ($doc_info[2]) {
11021
                                case 'url':
11022
                                    // Local URL - save path as url for now, don't zip file.
11023
                                    $abs_path = api_get_path(SYS_PATH).
11024
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11025
                                    $current_dir = dirname($abs_path);
11026
                                    $current_dir = str_replace('\\', '/', $current_dir);
11027
                                    $file_path = realpath($abs_path);
11028
                                    $file_path = str_replace('\\', '/', $file_path);
11029
                                    $my_dep_file->setAttribute('href', $file_path);
11030
                                    $my_dep->setAttribute('xml:base', '');
11031
                                    if (strstr($file_path, $main_path) !== false) {
11032
                                        // The calculated real path is really inside Chamilo's root path.
11033
                                        // Reduce file path to what's under the DocumentRoot.
11034
                                        $replace = $file_path;
11035
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11036
                                        $destinationFile = $file_path;
11037
11038
                                        if (strstr($file_path, 'upload/users') !== false) {
11039
                                            $pos = strpos($file_path, 'my_files/');
11040
                                            if ($pos !== false) {
11041
                                                $onlyDirectory = str_replace(
11042
                                                    'upload/users/',
11043
                                                    '',
11044
                                                    substr($file_path, $pos, strlen($file_path))
11045
                                                );
11046
                                            }
11047
                                            $replace = $onlyDirectory;
11048
                                            $destinationFile = $replace;
11049
                                        }
11050
                                        $zip_files_abs[] = $file_path;
11051
                                        $link_updates[$my_file_path][] = [
11052
                                            'orig' => $doc_info[0],
11053
                                            'dest' => $destinationFile,
11054
                                            'replace' => $replace,
11055
                                        ];
11056
                                        $my_dep_file->setAttribute('href', $file_path);
11057
                                        $my_dep->setAttribute('xml:base', '');
11058
                                    } elseif (empty($file_path)) {
11059
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11060
                                        $file_path = str_replace('//', '/', $file_path);
11061
                                        if (file_exists($file_path)) {
11062
                                            // We get the relative path.
11063
                                            $file_path = substr($file_path, strlen($current_dir));
11064
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11065
                                            $link_updates[$my_file_path][] = [
11066
                                                'orig' => $doc_info[0],
11067
                                                'dest' => $file_path,
11068
                                            ];
11069
                                            $my_dep_file->setAttribute('href', $file_path);
11070
                                            $my_dep->setAttribute('xml:base', '');
11071
                                        }
11072
                                    }
11073
                                    break;
11074
                                case 'abs':
11075
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11076
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11077
                                    $my_dep->setAttribute('xml:base', '');
11078
11079
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11080
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11081
                                    $abs_img_path_without_subdir = $doc_info[0];
11082
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11083
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11084
                                    if ($pos === 0) {
11085
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11086
                                    }
11087
11088
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11089
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11090
11091
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11092
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11093
                                    // Check if the current document is in that path.
11094
                                    if (strstr($file_path, $cur_path) !== false) {
11095
                                        $destinationFile = substr($file_path, strlen($cur_path));
11096
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11097
11098
                                        $fileToTest = $cur_path.$my_file_path;
11099
                                        if (!empty($path_to_remove)) {
11100
                                            $fileToTest = str_replace(
11101
                                                $path_to_remove.'/',
11102
                                                $path_to_replace,
11103
                                                $cur_path.$my_file_path
11104
                                            );
11105
                                        }
11106
11107
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11108
11109
                                        // Put the current document in the zip (this array is the array
11110
                                        // that will manage documents already in the course folder - relative).
11111
                                        $zip_files[] = $filePathNoCoursePart;
11112
                                        // Update the links to the current document in the
11113
                                        // containing document (make them relative).
11114
                                        $link_updates[$my_file_path][] = [
11115
                                            'orig' => $doc_info[0],
11116
                                            'dest' => $destinationFile,
11117
                                            'replace' => $relative_path,
11118
                                        ];
11119
11120
                                        $my_dep_file->setAttribute('href', $file_path);
11121
                                        $my_dep->setAttribute('xml:base', '');
11122
                                    } elseif (strstr($file_path, $main_path) !== false) {
11123
                                        // The calculated real path is really inside Chamilo's root path.
11124
                                        // Reduce file path to what's under the DocumentRoot.
11125
                                        $file_path = substr($file_path, strlen($root_path));
11126
                                        $zip_files_abs[] = $file_path;
11127
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11128
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11129
                                        $my_dep->setAttribute('xml:base', '');
11130
                                    } elseif (empty($file_path)) {
11131
                                        // Probably this is an image inside "/main" directory
11132
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11133
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11134
11135
                                        if (file_exists($file_path)) {
11136
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11137
                                                // We get the relative path.
11138
                                                $pos = strpos($file_path, 'main/default_course_document/');
11139
                                                if ($pos !== false) {
11140
                                                    $onlyDirectory = str_replace(
11141
                                                        'main/default_course_document/',
11142
                                                        '',
11143
                                                        substr($file_path, $pos, strlen($file_path))
11144
                                                    );
11145
                                                }
11146
11147
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11148
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11149
                                                $zip_files_abs[] = $fileAbs;
11150
                                                $link_updates[$my_file_path][] = [
11151
                                                    'orig' => $doc_info[0],
11152
                                                    'dest' => $destinationFile,
11153
                                                ];
11154
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11155
                                                $my_dep->setAttribute('xml:base', '');
11156
                                            }
11157
                                        }
11158
                                    }
11159
                                    break;
11160
                                case 'rel':
11161
                                    // Path relative to the current document.
11162
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11163
                                    if (substr($doc_info[0], 0, 2) === '..') {
11164
                                        // Relative path going up.
11165
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11166
                                        $current_dir = str_replace('\\', '/', $current_dir);
11167
                                        $file_path = realpath($current_dir.$doc_info[0]);
11168
                                        $file_path = str_replace('\\', '/', $file_path);
11169
                                        if (strstr($file_path, $main_path) !== false) {
11170
                                            // The calculated real path is really inside Chamilo's root path.
11171
                                            // Reduce file path to what's under the DocumentRoot.
11172
                                            $file_path = substr($file_path, strlen($root_path));
11173
                                            $zip_files_abs[] = $file_path;
11174
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11175
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11176
                                            $my_dep->setAttribute('xml:base', '');
11177
                                        }
11178
                                    } else {
11179
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11180
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11181
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11182
                                    }
11183
                                    break;
11184
                                default:
11185
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11186
                                    $my_dep->setAttribute('xml:base', '');
11187
                                    break;
11188
                            }
11189
                        }
11190
                        $my_dep->appendChild($my_dep_file);
11191
                        $resources->appendChild($my_dep);
11192
                        $dependency = $xmldoc->createElement('dependency');
11193
                        $dependency->setAttribute('identifierref', $res_id);
11194
                        $my_resource->appendChild($dependency);
11195
                        $i++;
11196
                    }
11197
                }
11198
                $resources->appendChild($my_resource);
11199
                $zip_files[] = $my_file_path;
11200
            } else {
11201
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11202
                switch ($item->type) {
11203
                    case TOOL_LINK:
11204
                        $my_item = $xmldoc->createElement('item');
11205
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11206
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11207
                        $my_item->setAttribute('isvisible', 'true');
11208
                        // Give a child element <title> to the <item> element.
11209
                        $my_title = $xmldoc->createElement(
11210
                            'title',
11211
                            htmlspecialchars(
11212
                                api_utf8_encode($item->get_title()),
11213
                                ENT_QUOTES,
11214
                                'UTF-8'
11215
                            )
11216
                        );
11217
                        $my_item->appendChild($my_title);
11218
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11219
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11220
                        $my_prereqs->setAttribute('type', 'aicc_script');
11221
                        $my_item->appendChild($my_prereqs);
11222
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11223
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11224
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11225
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11226
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11227
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11228
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11229
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11230
                        $my_item->appendChild($my_masteryscore);
11231
11232
                        // Attach this item to the organization element or its parent if there is one.
11233
                        if (!empty($item->parent) && $item->parent != 0) {
11234
                            $children = $organization->childNodes;
11235
                            for ($i = 0; $i < $children->length; $i++) {
11236
                                $item_temp = $children->item($i);
11237
                                if ($item_temp->nodeName == 'item') {
11238
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11239
                                        $item_temp->appendChild($my_item);
11240
                                    }
11241
                                }
11242
                            }
11243
                        } else {
11244
                            $organization->appendChild($my_item);
11245
                        }
11246
11247
                        $my_file_path = 'link_'.$item->get_id().'.html';
11248
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11249
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11250
                        $rs = Database::query($sql);
11251
                        if ($link = Database::fetch_array($rs)) {
11252
                            $url = $link['url'];
11253
                            $title = stripslashes($link['title']);
11254
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11255
                            $my_xml_file_path = $my_file_path;
11256
                            $my_sub_dir = dirname($my_file_path);
11257
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11258
                            $my_xml_sub_dir = $my_sub_dir;
11259
                            // Give a <resource> child to the <resources> element.
11260
                            $my_resource = $xmldoc->createElement('resource');
11261
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11262
                            $my_resource->setAttribute('type', 'webcontent');
11263
                            $my_resource->setAttribute('href', $my_xml_file_path);
11264
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11265
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11266
                            // xml:base is the base directory to find the files declared in this resource.
11267
                            $my_resource->setAttribute('xml:base', '');
11268
                            // give a <file> child to the <resource> element.
11269
                            $my_file = $xmldoc->createElement('file');
11270
                            $my_file->setAttribute('href', $my_xml_file_path);
11271
                            $my_resource->appendChild($my_file);
11272
                            $resources->appendChild($my_resource);
11273
                        }
11274
                        break;
11275
                    case TOOL_QUIZ:
11276
                        $exe_id = $item->path;
11277
                        // Should be using ref when everything will be cleaned up in this regard.
11278
                        $exe = new Exercise();
11279
                        $exe->read($exe_id);
11280
                        $my_item = $xmldoc->createElement('item');
11281
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11282
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11283
                        $my_item->setAttribute('isvisible', 'true');
11284
                        // Give a child element <title> to the <item> element.
11285
                        $my_title = $xmldoc->createElement(
11286
                            'title',
11287
                            htmlspecialchars(
11288
                                api_utf8_encode($item->get_title()),
11289
                                ENT_QUOTES,
11290
                                'UTF-8'
11291
                            )
11292
                        );
11293
                        $my_item->appendChild($my_title);
11294
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11295
                        $my_item->appendChild($my_max_score);
11296
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11297
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11298
                        $my_prereqs->setAttribute('type', 'aicc_script');
11299
                        $my_item->appendChild($my_prereqs);
11300
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11301
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11302
                        $my_item->appendChild($my_masteryscore);
11303
11304
                        // Attach this item to the organization element or hits parent if there is one.
11305
                        if (!empty($item->parent) && $item->parent != 0) {
11306
                            $children = $organization->childNodes;
11307
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11308
                            if ($possible_parent) {
11309
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11310
                                    $possible_parent->appendChild($my_item);
11311
                                }
11312
                            }
11313
                        } else {
11314
                            $organization->appendChild($my_item);
11315
                        }
11316
11317
                        // Get the path of the file(s) from the course directory root
11318
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11319
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11320
                        // Write the contents of the exported exercise into a (big) html file
11321
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11322
                        $scormExercise = new ScormExercise($exe, true);
11323
                        $contents = $scormExercise->export();
11324
11325
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11326
                        $res = file_put_contents($tmp_file_path, $contents);
11327
                        if ($res === false) {
11328
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11329
                        }
11330
                        $files_cleanup[] = $tmp_file_path;
11331
                        $my_xml_file_path = $my_file_path;
11332
                        $my_sub_dir = dirname($my_file_path);
11333
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11334
                        $my_xml_sub_dir = $my_sub_dir;
11335
                        // Give a <resource> child to the <resources> element.
11336
                        $my_resource = $xmldoc->createElement('resource');
11337
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11338
                        $my_resource->setAttribute('type', 'webcontent');
11339
                        $my_resource->setAttribute('href', $my_xml_file_path);
11340
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11341
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11342
                        // xml:base is the base directory to find the files declared in this resource.
11343
                        $my_resource->setAttribute('xml:base', '');
11344
                        // Give a <file> child to the <resource> element.
11345
                        $my_file = $xmldoc->createElement('file');
11346
                        $my_file->setAttribute('href', $my_xml_file_path);
11347
                        $my_resource->appendChild($my_file);
11348
11349
                        // Get included docs.
11350
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11351
11352
                        // Dependency to other files - not yet supported.
11353
                        $i = 1;
11354
                        foreach ($inc_docs as $doc_info) {
11355
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11356
                                continue;
11357
                            }
11358
                            $my_dep = $xmldoc->createElement('resource');
11359
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11360
                            $my_dep->setAttribute('identifier', $res_id);
11361
                            $my_dep->setAttribute('type', 'webcontent');
11362
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11363
                            $my_dep_file = $xmldoc->createElement('file');
11364
                            // Check type of URL.
11365
                            if ($doc_info[1] == 'remote') {
11366
                                // Remote file. Save url as is.
11367
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11368
                                $my_dep->setAttribute('xml:base', '');
11369
                            } elseif ($doc_info[1] == 'local') {
11370
                                switch ($doc_info[2]) {
11371
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11372
                                        // Save file but as local file (retrieve from URL).
11373
                                        $abs_path = api_get_path(SYS_PATH).
11374
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11375
                                        $current_dir = dirname($abs_path);
11376
                                        $current_dir = str_replace('\\', '/', $current_dir);
11377
                                        $file_path = realpath($abs_path);
11378
                                        $file_path = str_replace('\\', '/', $file_path);
11379
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11380
                                        $my_dep->setAttribute('xml:base', '');
11381
                                        if (strstr($file_path, $main_path) !== false) {
11382
                                            // The calculated real path is really inside the chamilo root path.
11383
                                            // Reduce file path to what's under the DocumentRoot.
11384
                                            $file_path = substr($file_path, strlen($root_path));
11385
                                            $zip_files_abs[] = $file_path;
11386
                                            $link_updates[$my_file_path][] = [
11387
                                                'orig' => $doc_info[0],
11388
                                                'dest' => 'document/'.$file_path,
11389
                                            ];
11390
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11391
                                            $my_dep->setAttribute('xml:base', '');
11392
                                        } elseif (empty($file_path)) {
11393
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11394
                                            $file_path = str_replace('//', '/', $file_path);
11395
                                            if (file_exists($file_path)) {
11396
                                                $file_path = substr($file_path, strlen($current_dir));
11397
                                                // We get the relative path.
11398
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11399
                                                $link_updates[$my_file_path][] = [
11400
                                                    'orig' => $doc_info[0],
11401
                                                    'dest' => 'document/'.$file_path,
11402
                                                ];
11403
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11404
                                                $my_dep->setAttribute('xml:base', '');
11405
                                            }
11406
                                        }
11407
                                        break;
11408
                                    case 'abs':
11409
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11410
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11411
                                        $current_dir = str_replace('\\', '/', $current_dir);
11412
                                        $file_path = realpath($doc_info[0]);
11413
                                        $file_path = str_replace('\\', '/', $file_path);
11414
                                        $my_dep_file->setAttribute('href', $file_path);
11415
                                        $my_dep->setAttribute('xml:base', '');
11416
11417
                                        if (strstr($file_path, $main_path) !== false) {
11418
                                            // The calculated real path is really inside the chamilo root path.
11419
                                            // Reduce file path to what's under the DocumentRoot.
11420
                                            $file_path = substr($file_path, strlen($root_path));
11421
                                            $zip_files_abs[] = $file_path;
11422
                                            $link_updates[$my_file_path][] = [
11423
                                                'orig' => $doc_info[0],
11424
                                                'dest' => $file_path,
11425
                                            ];
11426
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11427
                                            $my_dep->setAttribute('xml:base', '');
11428
                                        } elseif (empty($file_path)) {
11429
                                            $docSysPartPath = str_replace(
11430
                                                api_get_path(REL_COURSE_PATH),
11431
                                                '',
11432
                                                $doc_info[0]
11433
                                            );
11434
11435
                                            $docSysPartPathNoCourseCode = str_replace(
11436
                                                $_course['directory'].'/',
11437
                                                '',
11438
                                                $docSysPartPath
11439
                                            );
11440
11441
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11442
                                            if (file_exists($docSysPath)) {
11443
                                                $file_path = $docSysPartPathNoCourseCode;
11444
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11445
                                                $link_updates[$my_file_path][] = [
11446
                                                    'orig' => $doc_info[0],
11447
                                                    'dest' => $file_path,
11448
                                                ];
11449
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11450
                                                $my_dep->setAttribute('xml:base', '');
11451
                                            }
11452
                                        }
11453
                                        break;
11454
                                    case 'rel':
11455
                                        // Path relative to the current document. Save xml:base as current document's
11456
                                        // directory and save file in zip as subdir.file_path
11457
                                        if (substr($doc_info[0], 0, 2) === '..') {
11458
                                            // Relative path going up.
11459
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11460
                                            $current_dir = str_replace('\\', '/', $current_dir);
11461
                                            $file_path = realpath($current_dir.$doc_info[0]);
11462
                                            $file_path = str_replace('\\', '/', $file_path);
11463
                                            if (strstr($file_path, $main_path) !== false) {
11464
                                                // The calculated real path is really inside Chamilo's root path.
11465
                                                // Reduce file path to what's under the DocumentRoot.
11466
11467
                                                $file_path = substr($file_path, strlen($root_path));
11468
                                                $file_path_dest = $file_path;
11469
11470
                                                // File path is courses/CHAMILO/document/....
11471
                                                $info_file_path = explode('/', $file_path);
11472
                                                if ($info_file_path[0] == 'courses') {
11473
                                                    // Add character "/" in file path.
11474
                                                    $file_path_dest = 'document/'.$file_path;
11475
                                                }
11476
                                                $zip_files_abs[] = $file_path;
11477
11478
                                                $link_updates[$my_file_path][] = [
11479
                                                    'orig' => $doc_info[0],
11480
                                                    'dest' => $file_path_dest,
11481
                                                ];
11482
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11483
                                                $my_dep->setAttribute('xml:base', '');
11484
                                            }
11485
                                        } else {
11486
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11487
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11488
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11489
                                        }
11490
                                        break;
11491
                                    default:
11492
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11493
                                        $my_dep->setAttribute('xml:base', '');
11494
                                        break;
11495
                                }
11496
                            }
11497
                            $my_dep->appendChild($my_dep_file);
11498
                            $resources->appendChild($my_dep);
11499
                            $dependency = $xmldoc->createElement('dependency');
11500
                            $dependency->setAttribute('identifierref', $res_id);
11501
                            $my_resource->appendChild($dependency);
11502
                            $i++;
11503
                        }
11504
                        $resources->appendChild($my_resource);
11505
                        $zip_files[] = $my_file_path;
11506
                        break;
11507
                    default:
11508
                        // Get the path of the file(s) from the course directory root
11509
                        $my_file_path = 'non_exportable.html';
11510
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11511
                        $my_xml_file_path = $my_file_path;
11512
                        $my_sub_dir = dirname($my_file_path);
11513
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11514
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11515
                        $my_xml_sub_dir = $my_sub_dir;
11516
                        // Give a <resource> child to the <resources> element.
11517
                        $my_resource = $xmldoc->createElement('resource');
11518
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11519
                        $my_resource->setAttribute('type', 'webcontent');
11520
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11521
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11522
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11523
                        // xml:base is the base directory to find the files declared in this resource.
11524
                        $my_resource->setAttribute('xml:base', '');
11525
                        // Give a <file> child to the <resource> element.
11526
                        $my_file = $xmldoc->createElement('file');
11527
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11528
                        $my_resource->appendChild($my_file);
11529
                        $resources->appendChild($my_resource);
11530
                        break;
11531
                }
11532
            }
11533
        }
11534
        $organizations->appendChild($organization);
11535
        $root->appendChild($organizations);
11536
        $root->appendChild($resources);
11537
        $xmldoc->appendChild($root);
11538
11539
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11540
11541
        // then add the file to the zip, then destroy the file (this is done automatically).
11542
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11543
        foreach ($zip_files as $file_path) {
11544
            if (empty($file_path)) {
11545
                continue;
11546
            }
11547
11548
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11549
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11550
11551
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11552
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11553
            }
11554
11555
            $this->create_path($dest_file);
11556
            @copy($filePath, $dest_file);
11557
11558
            // Check if the file needs a link update.
11559
            if (in_array($file_path, array_keys($link_updates))) {
11560
                $string = file_get_contents($dest_file);
11561
                unlink($dest_file);
11562
                foreach ($link_updates[$file_path] as $old_new) {
11563
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11564
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11565
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11566
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11567
                    if (substr($old_new['dest'], -3) === 'flv' &&
11568
                        substr($old_new['dest'], 0, 5) === 'main/'
11569
                    ) {
11570
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11571
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11572
                        substr($old_new['dest'], 0, 6) === 'video/'
11573
                    ) {
11574
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11575
                    }
11576
11577
                    // Fix to avoid problems with default_course_document
11578
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11579
                        $newDestination = $old_new['dest'];
11580
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11581
                            $newDestination = $old_new['replace'];
11582
                        }
11583
                    } else {
11584
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11585
                    }
11586
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11587
11588
                    // Add files inside the HTMLs
11589
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11590
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11591
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11592
                        copy(
11593
                            $sys_course_path.$new_path,
11594
                            $destinationFile
11595
                        );
11596
                    }
11597
                }
11598
                file_put_contents($dest_file, $string);
11599
            }
11600
11601
            if (file_exists($filePath) && $copyAll) {
11602
                $extension = $this->get_extension($filePath);
11603
                if (in_array($extension, ['html', 'html'])) {
11604
                    $containerOrigin = dirname($filePath);
11605
                    $containerDestination = dirname($dest_file);
11606
11607
                    $finder = new Finder();
11608
                    $finder->files()->in($containerOrigin)
11609
                        ->notName('*_DELETED_*')
11610
                        ->exclude('share_folder')
11611
                        ->exclude('chat_files')
11612
                        ->exclude('certificates')
11613
                    ;
11614
11615
                    if (is_dir($containerOrigin) &&
11616
                        is_dir($containerDestination)
11617
                    ) {
11618
                        $fs = new Filesystem();
11619
                        $fs->mirror(
11620
                            $containerOrigin,
11621
                            $containerDestination,
11622
                            $finder
11623
                        );
11624
                    }
11625
                }
11626
            }
11627
        }
11628
11629
        foreach ($zip_files_abs as $file_path) {
11630
            if (empty($file_path)) {
11631
                continue;
11632
            }
11633
11634
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11635
                continue;
11636
            }
11637
11638
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11639
            if (strstr($file_path, 'upload/users') !== false) {
11640
                $pos = strpos($file_path, 'my_files/');
11641
                if ($pos !== false) {
11642
                    $onlyDirectory = str_replace(
11643
                        'upload/users/',
11644
                        '',
11645
                        substr($file_path, $pos, strlen($file_path))
11646
                    );
11647
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11648
                }
11649
            }
11650
11651
            if (strstr($file_path, 'default_course_document/') !== false) {
11652
                $replace = str_replace('/main', '', $file_path);
11653
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11654
            }
11655
11656
            if (empty($dest_file)) {
11657
                continue;
11658
            }
11659
11660
            $this->create_path($dest_file);
11661
            copy($main_path.$file_path, $dest_file);
11662
            // Check if the file needs a link update.
11663
            if (in_array($file_path, array_keys($link_updates))) {
11664
                $string = file_get_contents($dest_file);
11665
                unlink($dest_file);
11666
                foreach ($link_updates[$file_path] as $old_new) {
11667
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11668
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11669
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11670
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11671
                    if (substr($old_new['dest'], -3) == 'flv' &&
11672
                        substr($old_new['dest'], 0, 5) == 'main/'
11673
                    ) {
11674
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11675
                    }
11676
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11677
                }
11678
                file_put_contents($dest_file, $string);
11679
            }
11680
        }
11681
11682
        if (is_array($links_to_create)) {
11683
            foreach ($links_to_create as $file => $link) {
11684
                $content = '<!DOCTYPE html><head>
11685
                            <meta charset="'.api_get_language_isocode().'" />
11686
                            <title>'.$link['title'].'</title>
11687
                            </head>
11688
                            <body dir="'.api_get_text_direction().'">
11689
                            <div style="text-align:center">
11690
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11691
                            </body>
11692
                            </html>';
11693
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11694
            }
11695
        }
11696
11697
        // Add non exportable message explanation.
11698
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11699
        $file_content = '<!DOCTYPE html><head>
11700
                        <meta charset="'.api_get_language_isocode().'" />
11701
                        <title>'.$lang_not_exportable.'</title>
11702
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11703
                        </head>
11704
                        <body dir="'.api_get_text_direction().'">';
11705
        $file_content .=
11706
            <<<EOD
11707
                    <style>
11708
            .error-message {
11709
                font-family: arial, verdana, helvetica, sans-serif;
11710
                border-width: 1px;
11711
                border-style: solid;
11712
                left: 50%;
11713
                margin: 10px auto;
11714
                min-height: 30px;
11715
                padding: 5px;
11716
                right: 50%;
11717
                width: 500px;
11718
                background-color: #FFD1D1;
11719
                border-color: #FF0000;
11720
                color: #000;
11721
            }
11722
        </style>
11723
    <body>
11724
        <div class="error-message">
11725
            $lang_not_exportable
11726
        </div>
11727
    </body>
11728
</html>
11729
EOD;
11730
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11731
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11732
        }
11733
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11734
11735
        // Add the extra files that go along with a SCORM package.
11736
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11737
11738
        $fs = new Filesystem();
11739
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11740
11741
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11742
        $manifest = @$xmldoc->saveXML();
11743
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11744
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11745
        $zip_folder->add(
11746
            $archivePath.'/'.$temp_dir_short,
11747
            PCLZIP_OPT_REMOVE_PATH,
11748
            $archivePath.'/'.$temp_dir_short.'/'
11749
        );
11750
11751
        // Clean possible temporary files.
11752
        foreach ($files_cleanup as $file) {
11753
            $res = unlink($file);
11754
            if ($res === false) {
11755
                error_log(
11756
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11757
                    0
11758
                );
11759
            }
11760
        }
11761
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11762
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11763
    }
11764
11765
    /**
11766
     * @param int $lp_id
11767
     *
11768
     * @return bool
11769
     */
11770
    public function scorm_export_to_pdf($lp_id)
11771
    {
11772
        $lp_id = (int) $lp_id;
11773
        $files_to_export = [];
11774
11775
        $sessionId = api_get_session_id();
11776
        $course_data = api_get_course_info($this->cc);
11777
11778
        if (!empty($course_data)) {
11779
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11780
            $list = self::get_flat_ordered_items_list($lp_id);
11781
            if (!empty($list)) {
11782
                foreach ($list as $item_id) {
11783
                    $item = $this->items[$item_id];
11784
                    switch ($item->type) {
11785
                        case 'document':
11786
                            // Getting documents from a LP with chamilo documents
11787
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11788
                            // Try loading document from the base course.
11789
                            if (empty($file_data) && !empty($sessionId)) {
11790
                                $file_data = DocumentManager::get_document_data_by_id(
11791
                                    $item->path,
11792
                                    $this->cc,
11793
                                    false,
11794
                                    0
11795
                                );
11796
                            }
11797
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11798
                            if (file_exists($file_path)) {
11799
                                $files_to_export[] = [
11800
                                    'title' => $item->get_title(),
11801
                                    'path' => $file_path,
11802
                                ];
11803
                            }
11804
                            break;
11805
                        case 'asset': //commes from a scorm package generated by chamilo
11806
                        case 'sco':
11807
                            $file_path = $scorm_path.'/'.$item->path;
11808
                            if (file_exists($file_path)) {
11809
                                $files_to_export[] = [
11810
                                    'title' => $item->get_title(),
11811
                                    'path' => $file_path,
11812
                                ];
11813
                            }
11814
                            break;
11815
                        case 'dir':
11816
                            $files_to_export[] = [
11817
                                'title' => $item->get_title(),
11818
                                'path' => null,
11819
                            ];
11820
                            break;
11821
                    }
11822
                }
11823
            }
11824
11825
            $pdf = new PDF();
11826
            $result = $pdf->html_to_pdf(
11827
                $files_to_export,
11828
                $this->name,
11829
                $this->cc,
11830
                true,
11831
                true,
11832
                true,
11833
                $this->get_name()
11834
            );
11835
11836
            return $result;
11837
        }
11838
11839
        return false;
11840
    }
11841
11842
    /**
11843
     * Temp function to be moved in main_api or the best place around for this.
11844
     * Creates a file path if it doesn't exist.
11845
     *
11846
     * @param string $path
11847
     */
11848
    public function create_path($path)
11849
    {
11850
        $path_bits = explode('/', dirname($path));
11851
11852
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11853
        $path_built = IS_WINDOWS_OS ? '' : '/';
11854
        foreach ($path_bits as $bit) {
11855
            if (!empty($bit)) {
11856
                $new_path = $path_built.$bit;
11857
                if (is_dir($new_path)) {
11858
                    $path_built = $new_path.'/';
11859
                } else {
11860
                    mkdir($new_path, api_get_permissions_for_new_directories());
11861
                    $path_built = $new_path.'/';
11862
                }
11863
            }
11864
        }
11865
    }
11866
11867
    /**
11868
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11869
     *
11870
     * @return bool The results of the unlink function, or false if there was no image to start with
11871
     */
11872
    public function delete_lp_image()
11873
    {
11874
        $img = $this->get_preview_image();
11875
        if ($img != '') {
11876
            $del_file = $this->get_preview_image_path(null, 'sys');
11877
            if (isset($del_file) && file_exists($del_file)) {
11878
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11879
                if (file_exists($del_file_2)) {
11880
                    unlink($del_file_2);
11881
                }
11882
                $this->set_preview_image('');
11883
11884
                return @unlink($del_file);
11885
            }
11886
        }
11887
11888
        return false;
11889
    }
11890
11891
    /**
11892
     * Uploads an author image to the upload/learning_path/images directory.
11893
     *
11894
     * @param array    The image array, coming from the $_FILES superglobal
11895
     *
11896
     * @return bool True on success, false on error
11897
     */
11898
    public function upload_image($image_array)
11899
    {
11900
        if (!empty($image_array['name'])) {
11901
            $upload_ok = process_uploaded_file($image_array);
11902
            $has_attachment = true;
11903
        }
11904
11905
        if ($upload_ok && $has_attachment) {
11906
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11907
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11908
            $updir = $sys_course_path.$courseDir;
11909
            // Try to add an extension to the file if it hasn't one.
11910
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11911
11912
            if (filter_extension($new_file_name)) {
11913
                $file_extension = explode('.', $image_array['name']);
11914
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11915
                $filename = uniqid('');
11916
                $new_file_name = $filename.'.'.$file_extension;
11917
                $new_path = $updir.'/'.$new_file_name;
11918
11919
                // Resize the image.
11920
                $temp = new Image($image_array['tmp_name']);
11921
                $temp->resize(104);
11922
                $result = $temp->send_image($new_path);
11923
11924
                // Storing the image filename.
11925
                if ($result) {
11926
                    $this->set_preview_image($new_file_name);
11927
11928
                    //Resize to 64px to use on course homepage
11929
                    $temp->resize(64);
11930
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11931
11932
                    return true;
11933
                }
11934
            }
11935
        }
11936
11937
        return false;
11938
    }
11939
11940
    /**
11941
     * @param int    $lp_id
11942
     * @param string $status
11943
     */
11944
    public function set_autolaunch($lp_id, $status)
11945
    {
11946
        $course_id = api_get_course_int_id();
11947
        $lp_id = (int) $lp_id;
11948
        $status = (int) $status;
11949
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11950
11951
        // Setting everything to autolaunch = 0
11952
        $attributes['autolaunch'] = 0;
11953
        $where = [
11954
            'session_id = ? AND c_id = ? ' => [
11955
                api_get_session_id(),
11956
                $course_id,
11957
            ],
11958
        ];
11959
        Database::update($lp_table, $attributes, $where);
11960
        if ($status == 1) {
11961
            //Setting my lp_id to autolaunch = 1
11962
            $attributes['autolaunch'] = 1;
11963
            $where = [
11964
                'iid = ? AND session_id = ? AND c_id = ?' => [
11965
                    $lp_id,
11966
                    api_get_session_id(),
11967
                    $course_id,
11968
                ],
11969
            ];
11970
            Database::update($lp_table, $attributes, $where);
11971
        }
11972
    }
11973
11974
    /**
11975
     * Gets previous_item_id for the next element of the lp_item table.
11976
     *
11977
     * @author Isaac flores paz
11978
     *
11979
     * @return int Previous item ID
11980
     */
11981
    public function select_previous_item_id()
11982
    {
11983
        $course_id = api_get_course_int_id();
11984
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11985
11986
        // Get the max order of the items
11987
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11988
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11989
        $rs_max_order = Database::query($sql);
11990
        $row_max_order = Database::fetch_object($rs_max_order);
11991
        $max_order = $row_max_order->display_order;
11992
        // Get the previous item ID
11993
        $sql = "SELECT iid as previous FROM $table_lp_item
11994
                WHERE
11995
                    c_id = $course_id AND
11996
                    lp_id = ".$this->lp_id." AND
11997
                    display_order = '$max_order' ";
11998
        $rs_max = Database::query($sql);
11999
        $row_max = Database::fetch_object($rs_max);
12000
12001
        // Return the previous item ID
12002
        return $row_max->previous;
12003
    }
12004
12005
    /**
12006
     * Copies an LP.
12007
     */
12008
    public function copy()
12009
    {
12010
        // Course builder
12011
        $cb = new CourseBuilder();
12012
12013
        //Setting tools that will be copied
12014
        $cb->set_tools_to_build(['learnpaths']);
12015
12016
        //Setting elements that will be copied
12017
        $cb->set_tools_specific_id_list(
12018
            ['learnpaths' => [$this->lp_id]]
12019
        );
12020
12021
        $course = $cb->build();
12022
12023
        //Course restorer
12024
        $course_restorer = new CourseRestorer($course);
12025
        $course_restorer->set_add_text_in_items(true);
12026
        $course_restorer->set_tool_copy_settings(
12027
            ['learnpaths' => ['reset_dates' => true]]
12028
        );
12029
        $course_restorer->restore(
12030
            api_get_course_id(),
12031
            api_get_session_id(),
12032
            false,
12033
            false
12034
        );
12035
    }
12036
12037
    /**
12038
     * Verify document size.
12039
     *
12040
     * @param string $s
12041
     *
12042
     * @return bool
12043
     */
12044
    public static function verify_document_size($s)
12045
    {
12046
        $post_max = ini_get('post_max_size');
12047
        if (substr($post_max, -1, 1) == 'M') {
12048
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12049
        } elseif (substr($post_max, -1, 1) == 'G') {
12050
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12051
        }
12052
        $upl_max = ini_get('upload_max_filesize');
12053
        if (substr($upl_max, -1, 1) == 'M') {
12054
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12055
        } elseif (substr($upl_max, -1, 1) == 'G') {
12056
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12057
        }
12058
        $documents_total_space = DocumentManager::documents_total_space();
12059
        $course_max_space = DocumentManager::get_course_quota();
12060
        $total_size = filesize($s) + $documents_total_space;
12061
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12062
            return true;
12063
        }
12064
12065
        return false;
12066
    }
12067
12068
    /**
12069
     * Clear LP prerequisites.
12070
     */
12071
    public function clear_prerequisites()
12072
    {
12073
        $course_id = $this->get_course_int_id();
12074
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12075
        $lp_id = $this->get_id();
12076
        // Cleaning prerequisites
12077
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12078
                WHERE c_id = $course_id AND lp_id = $lp_id";
12079
        Database::query($sql);
12080
12081
        // Cleaning mastery score for exercises
12082
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12083
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12084
        Database::query($sql);
12085
    }
12086
12087
    public function set_previous_step_as_prerequisite_for_all_items()
12088
    {
12089
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12090
        $course_id = $this->get_course_int_id();
12091
        $lp_id = $this->get_id();
12092
12093
        if (!empty($this->items)) {
12094
            $previous_item_id = null;
12095
            $previous_item_max = 0;
12096
            $previous_item_type = null;
12097
            $last_item_not_dir = null;
12098
            $last_item_not_dir_type = null;
12099
            $last_item_not_dir_max = null;
12100
12101
            foreach ($this->ordered_items as $itemId) {
12102
                $item = $this->getItem($itemId);
12103
                // if there was a previous item... (otherwise jump to set it)
12104
                if (!empty($previous_item_id)) {
12105
                    $current_item_id = $item->get_id(); //save current id
12106
                    if ($item->get_type() != 'dir') {
12107
                        // Current item is not a folder, so it qualifies to get a prerequisites
12108
                        if ($last_item_not_dir_type == 'quiz') {
12109
                            // if previous is quiz, mark its max score as default score to be achieved
12110
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12111
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12112
                            Database::query($sql);
12113
                        }
12114
                        // now simply update the prerequisite to set it to the last non-chapter item
12115
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12116
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12117
                        Database::query($sql);
12118
                        // record item as 'non-chapter' reference
12119
                        $last_item_not_dir = $item->get_id();
12120
                        $last_item_not_dir_type = $item->get_type();
12121
                        $last_item_not_dir_max = $item->get_max();
12122
                    }
12123
                } else {
12124
                    if ($item->get_type() != 'dir') {
12125
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12126
                        $last_item_not_dir = $item->get_id();
12127
                        $last_item_not_dir_type = $item->get_type();
12128
                        $last_item_not_dir_max = $item->get_max();
12129
                    }
12130
                }
12131
                // Saving the item as "previous item" for the next loop
12132
                $previous_item_id = $item->get_id();
12133
                $previous_item_max = $item->get_max();
12134
                $previous_item_type = $item->get_type();
12135
            }
12136
        }
12137
    }
12138
12139
    /**
12140
     * @param array $params
12141
     *
12142
     * @throws \Doctrine\ORM\OptimisticLockException
12143
     *
12144
     * @return int
12145
     */
12146
    public static function createCategory($params)
12147
    {
12148
        $em = Database::getManager();
12149
        $item = new CLpCategory();
12150
        $item->setName($params['name']);
12151
        $item->setCId($params['c_id']);
12152
        $em->persist($item);
12153
        $em->flush();
12154
12155
        $id = $item->getId();
12156
12157
        $sessionId = api_get_session_id();
12158
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12159
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12160
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12161
            Database::query($sql);
12162
        }
12163
12164
        api_item_property_update(
12165
            api_get_course_info(),
12166
            TOOL_LEARNPATH_CATEGORY,
12167
            $id,
12168
            'visible',
12169
            api_get_user_id()
12170
        );
12171
12172
        return $item->getId();
12173
    }
12174
12175
    /**
12176
     * @param array $params
12177
     */
12178
    public static function updateCategory($params)
12179
    {
12180
        $em = Database::getManager();
12181
        $item = self::getCategory($params['id']);
12182
12183
        if ($item) {
12184
            $item->setName($params['name']);
12185
            $em->merge($item);
12186
            $em->flush();
12187
        }
12188
    }
12189
12190
    /**
12191
     * @param int $id
12192
     */
12193
    public static function moveUpCategory($id)
12194
    {
12195
        $item = self::getCategory($id);
12196
        if ($item) {
12197
            $em = Database::getManager();
12198
            $position = $item->getPosition() - 1;
12199
            $item->setPosition($position);
12200
            $em->persist($item);
12201
            $em->flush();
12202
        }
12203
    }
12204
12205
    /**
12206
     * @param int $id
12207
     *
12208
     * @throws \Doctrine\ORM\ORMException
12209
     * @throws \Doctrine\ORM\OptimisticLockException
12210
     * @throws \Doctrine\ORM\TransactionRequiredException
12211
     */
12212
    public static function moveDownCategory($id)
12213
    {
12214
        $item = self::getCategory($id);
12215
        if ($item) {
12216
            $em = Database::getManager();
12217
            $position = $item->getPosition() + 1;
12218
            $item->setPosition($position);
12219
            $em->persist($item);
12220
            $em->flush();
12221
        }
12222
    }
12223
12224
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12225
    {
12226
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12227
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12228
        $courseId = (int) $courseId;
12229
        $sessionId = (int) $sessionId;
12230
12231
        $sql = "SELECT lp.id, lp.name
12232
                FROM $TABLE_LP lp
12233
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12234
                ON lp.id = ip.ref
12235
                WHERE lp.c_id = $courseId ";
12236
12237
        if (!empty($sessionId)) {
12238
            $sql .= "AND ip.session_id = $sessionId ";
12239
        }
12240
12241
        if ($onlyActiveLp) {
12242
            $sql .= "AND ip.tool = 'learnpath' ";
12243
            $sql .= "AND ip.visibility = 1 ";
12244
        }
12245
12246
        $sql .= "GROUP BY lp.id";
12247
12248
        $result = Database::query($sql);
12249
12250
        return Database::store_result($result, 'ASSOC');
12251
    }
12252
12253
    /**
12254
     * @param int $courseId
12255
     *
12256
     * @throws \Doctrine\ORM\Query\QueryException
12257
     *
12258
     * @return int|mixed
12259
     */
12260
    public static function getCountCategories($courseId)
12261
    {
12262
        if (empty($courseId)) {
12263
            return 0;
12264
        }
12265
        $em = Database::getManager();
12266
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12267
        $query->setParameter('id', $courseId);
12268
12269
        return $query->getSingleScalarResult();
12270
    }
12271
12272
    /**
12273
     * @param int $courseId
12274
     *
12275
     * @return CLpCategory[]
12276
     */
12277
    public static function getCategories($courseId)
12278
    {
12279
        $em = Database::getManager();
12280
12281
        // Using doctrine extensions
12282
        /** @var SortableRepository $repo */
12283
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12284
12285
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12286
    }
12287
12288
    public static function getCategorySessionId($id)
12289
    {
12290
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12291
            return 0;
12292
        }
12293
12294
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12295
        $id = (int) $id;
12296
12297
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12298
        $result = Database::query($sql);
12299
        $result = Database::fetch_array($result, 'ASSOC');
12300
12301
        if ($result) {
12302
            return (int) $result['session_id'];
12303
        }
12304
12305
        return 0;
12306
    }
12307
12308
    /**
12309
     * @param int $id
12310
     *
12311
     * @return CLpCategory
12312
     */
12313
    public static function getCategory($id)
12314
    {
12315
        $id = (int) $id;
12316
        $em = Database::getManager();
12317
12318
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12319
    }
12320
12321
    /**
12322
     * @param int $courseId
12323
     *
12324
     * @return array
12325
     */
12326
    public static function getCategoryByCourse($courseId)
12327
    {
12328
        $em = Database::getManager();
12329
12330
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12331
    }
12332
12333
    /**
12334
     * @param int $id
12335
     *
12336
     * @return bool
12337
     */
12338
    public static function deleteCategory($id)
12339
    {
12340
        $em = Database::getManager();
12341
        $id = (int) $id;
12342
        $item = self::getCategory($id);
12343
        if ($item) {
12344
            $courseId = $item->getCId();
12345
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12346
            $query->setParameter('id', $courseId);
12347
            $query->setParameter('catId', $item->getId());
12348
            $lps = $query->getResult();
12349
12350
            // Setting category = 0.
12351
            if ($lps) {
12352
                foreach ($lps as $lpItem) {
12353
                    $lpItem->setCategoryId(0);
12354
                }
12355
            }
12356
12357
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
12358
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
12359
                $sql = "DELETE FROM $table
12360
                        WHERE
12361
                            lp_category_id = $id AND
12362
                            c_id = $courseId ";
12363
                Database::query($sql);
12364
            }
12365
12366
            // Removing category.
12367
            $em->remove($item);
12368
            $em->flush();
12369
12370
            $courseInfo = api_get_course_info_by_id($courseId);
12371
            $sessionId = api_get_session_id();
12372
12373
            // Delete link tool
12374
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12375
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12376
            // Delete tools
12377
            $sql = "DELETE FROM $tbl_tool
12378
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12379
            Database::query($sql);
12380
12381
            return true;
12382
        }
12383
12384
        return false;
12385
    }
12386
12387
    /**
12388
     * @param int  $courseId
12389
     * @param bool $addSelectOption
12390
     *
12391
     * @return mixed
12392
     */
12393
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12394
    {
12395
        $items = self::getCategoryByCourse($courseId);
12396
        $cats = [];
12397
        if ($addSelectOption) {
12398
            $cats = [get_lang('SelectACategory')];
12399
        }
12400
12401
        if (!empty($items)) {
12402
            foreach ($items as $cat) {
12403
                $cats[$cat->getId()] = $cat->getName();
12404
            }
12405
        }
12406
12407
        return $cats;
12408
    }
12409
12410
    /**
12411
     * @param string $courseCode
12412
     * @param int    $lpId
12413
     * @param int    $user_id
12414
     *
12415
     * @return learnpath
12416
     */
12417
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12418
    {
12419
        $debug = 0;
12420
        $learnPath = null;
12421
        $lpObject = Session::read('lpobject');
12422
        if ($lpObject !== null) {
12423
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12424
            if ($debug) {
12425
                error_log('getLpFromSession: unserialize');
12426
                error_log('------getLpFromSession------');
12427
                error_log('------unserialize------');
12428
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12429
                error_log("api_get_sessionid: ".api_get_session_id());
12430
            }
12431
        }
12432
12433
        if (!is_object($learnPath)) {
12434
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12435
            if ($debug) {
12436
                error_log('------getLpFromSession------');
12437
                error_log('getLpFromSession: create new learnpath');
12438
                error_log("create new LP with $courseCode - $lpId - $user_id");
12439
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12440
                error_log("api_get_sessionid: ".api_get_session_id());
12441
            }
12442
        }
12443
12444
        return $learnPath;
12445
    }
12446
12447
    /**
12448
     * @param int $itemId
12449
     *
12450
     * @return learnpathItem|false
12451
     */
12452
    public function getItem($itemId)
12453
    {
12454
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12455
            return $this->items[$itemId];
12456
        }
12457
12458
        return false;
12459
    }
12460
12461
    /**
12462
     * @return int
12463
     */
12464
    public function getCurrentAttempt()
12465
    {
12466
        $attempt = $this->getItem($this->get_current_item_id());
12467
        if ($attempt) {
12468
            $attemptId = $attempt->get_attempt_id();
12469
12470
            return $attemptId;
12471
        }
12472
12473
        return 0;
12474
    }
12475
12476
    /**
12477
     * @return int
12478
     */
12479
    public function getCategoryId()
12480
    {
12481
        return (int) $this->categoryId;
12482
    }
12483
12484
    /**
12485
     * @param int $categoryId
12486
     *
12487
     * @return bool
12488
     */
12489
    public function setCategoryId($categoryId)
12490
    {
12491
        $this->categoryId = (int) $categoryId;
12492
        $table = Database::get_course_table(TABLE_LP_MAIN);
12493
        $lp_id = $this->get_id();
12494
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12495
                WHERE iid = $lp_id";
12496
        Database::query($sql);
12497
12498
        return true;
12499
    }
12500
12501
    /**
12502
     * Get whether this is a learning path with the possibility to subscribe
12503
     * users or not.
12504
     *
12505
     * @return int
12506
     */
12507
    public function getSubscribeUsers()
12508
    {
12509
        return $this->subscribeUsers;
12510
    }
12511
12512
    /**
12513
     * Set whether this is a learning path with the possibility to subscribe
12514
     * users or not.
12515
     *
12516
     * @param int $value (0 = false, 1 = true)
12517
     *
12518
     * @return bool
12519
     */
12520
    public function setSubscribeUsers($value)
12521
    {
12522
        $this->subscribeUsers = (int) $value;
12523
        $table = Database::get_course_table(TABLE_LP_MAIN);
12524
        $lp_id = $this->get_id();
12525
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12526
                WHERE iid = $lp_id";
12527
        Database::query($sql);
12528
12529
        return true;
12530
    }
12531
12532
    /**
12533
     * Calculate the count of stars for a user in this LP
12534
     * This calculation is based on the following rules:
12535
     * - the student gets one star when he gets to 50% of the learning path
12536
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12537
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12538
     * - the student gets the final star when the score for the *last* test is >= 80%.
12539
     *
12540
     * @param int $sessionId Optional. The session ID
12541
     *
12542
     * @return int The count of stars
12543
     */
12544
    public function getCalculateStars($sessionId = 0)
12545
    {
12546
        $stars = 0;
12547
        $progress = self::getProgress(
12548
            $this->lp_id,
12549
            $this->user_id,
12550
            $this->course_int_id,
12551
            $sessionId
12552
        );
12553
12554
        if ($progress >= 50) {
12555
            $stars++;
12556
        }
12557
12558
        // Calculate stars chapters evaluation
12559
        $exercisesItems = $this->getExercisesItems();
12560
12561
        if (!empty($exercisesItems)) {
12562
            $totalResult = 0;
12563
12564
            foreach ($exercisesItems as $exerciseItem) {
12565
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12566
                    $this->user_id,
12567
                    $exerciseItem->path,
12568
                    $this->course_int_id,
12569
                    $sessionId,
12570
                    $this->lp_id,
12571
                    $exerciseItem->db_id
12572
                );
12573
12574
                $exerciseResultInfo = end($exerciseResultInfo);
12575
12576
                if (!$exerciseResultInfo) {
12577
                    continue;
12578
                }
12579
12580
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12581
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12582
                } else {
12583
                    $exerciseResult = 0;
12584
                }
12585
                $totalResult += $exerciseResult;
12586
            }
12587
12588
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12589
12590
            if ($totalExerciseAverage >= 50) {
12591
                $stars++;
12592
            }
12593
12594
            if ($totalExerciseAverage >= 80) {
12595
                $stars++;
12596
            }
12597
        }
12598
12599
        // Calculate star for final evaluation
12600
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12601
12602
        if (!empty($finalEvaluationItem)) {
12603
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12604
                $this->user_id,
12605
                $finalEvaluationItem->path,
12606
                $this->course_int_id,
12607
                $sessionId,
12608
                $this->lp_id,
12609
                $finalEvaluationItem->db_id
12610
            );
12611
12612
            $evaluationResultInfo = end($evaluationResultInfo);
12613
12614
            if ($evaluationResultInfo) {
12615
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12616
12617
                if ($evaluationResult >= 80) {
12618
                    $stars++;
12619
                }
12620
            }
12621
        }
12622
12623
        return $stars;
12624
    }
12625
12626
    /**
12627
     * Get the items of exercise type.
12628
     *
12629
     * @return array The items. Otherwise return false
12630
     */
12631
    public function getExercisesItems()
12632
    {
12633
        $exercises = [];
12634
        foreach ($this->items as $item) {
12635
            if ($item->type != 'quiz') {
12636
                continue;
12637
            }
12638
            $exercises[] = $item;
12639
        }
12640
12641
        array_pop($exercises);
12642
12643
        return $exercises;
12644
    }
12645
12646
    /**
12647
     * Get the item of exercise type (evaluation type).
12648
     *
12649
     * @return array The final evaluation. Otherwise return false
12650
     */
12651
    public function getFinalEvaluationItem()
12652
    {
12653
        $exercises = [];
12654
        foreach ($this->items as $item) {
12655
            if ($item->type != 'quiz') {
12656
                continue;
12657
            }
12658
12659
            $exercises[] = $item;
12660
        }
12661
12662
        return array_pop($exercises);
12663
    }
12664
12665
    /**
12666
     * Calculate the total points achieved for the current user in this learning path.
12667
     *
12668
     * @param int $sessionId Optional. The session Id
12669
     *
12670
     * @return int
12671
     */
12672
    public function getCalculateScore($sessionId = 0)
12673
    {
12674
        // Calculate stars chapters evaluation
12675
        $exercisesItems = $this->getExercisesItems();
12676
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12677
        $totalExercisesResult = 0;
12678
        $totalEvaluationResult = 0;
12679
12680
        if ($exercisesItems !== false) {
12681
            foreach ($exercisesItems as $exerciseItem) {
12682
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12683
                    $this->user_id,
12684
                    $exerciseItem->path,
12685
                    $this->course_int_id,
12686
                    $sessionId,
12687
                    $this->lp_id,
12688
                    $exerciseItem->db_id
12689
                );
12690
12691
                $exerciseResultInfo = end($exerciseResultInfo);
12692
12693
                if (!$exerciseResultInfo) {
12694
                    continue;
12695
                }
12696
12697
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12698
            }
12699
        }
12700
12701
        if (!empty($finalEvaluationItem)) {
12702
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12703
                $this->user_id,
12704
                $finalEvaluationItem->path,
12705
                $this->course_int_id,
12706
                $sessionId,
12707
                $this->lp_id,
12708
                $finalEvaluationItem->db_id
12709
            );
12710
12711
            $evaluationResultInfo = end($evaluationResultInfo);
12712
12713
            if ($evaluationResultInfo) {
12714
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12715
            }
12716
        }
12717
12718
        return $totalExercisesResult + $totalEvaluationResult;
12719
    }
12720
12721
    /**
12722
     * Check if URL is not allowed to be show in a iframe.
12723
     *
12724
     * @param string $src
12725
     *
12726
     * @return string
12727
     */
12728
    public function fixBlockedLinks($src)
12729
    {
12730
        $urlInfo = parse_url($src);
12731
12732
        $platformProtocol = 'https';
12733
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12734
            $platformProtocol = 'http';
12735
        }
12736
12737
        $protocolFixApplied = false;
12738
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12739
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12740
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12741
12742
        if ($platformProtocol != $scheme) {
12743
            Session::write('x_frame_source', $src);
12744
            $src = 'blank.php?error=x_frames_options';
12745
            $protocolFixApplied = true;
12746
        }
12747
12748
        if ($protocolFixApplied == false) {
12749
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12750
                // Check X-Frame-Options
12751
                $ch = curl_init();
12752
                $options = [
12753
                    CURLOPT_URL => $src,
12754
                    CURLOPT_RETURNTRANSFER => true,
12755
                    CURLOPT_HEADER => true,
12756
                    CURLOPT_FOLLOWLOCATION => true,
12757
                    CURLOPT_ENCODING => "",
12758
                    CURLOPT_AUTOREFERER => true,
12759
                    CURLOPT_CONNECTTIMEOUT => 120,
12760
                    CURLOPT_TIMEOUT => 120,
12761
                    CURLOPT_MAXREDIRS => 10,
12762
                ];
12763
12764
                $proxySettings = api_get_configuration_value('proxy_settings');
12765
                if (!empty($proxySettings) &&
12766
                    isset($proxySettings['curl_setopt_array'])
12767
                ) {
12768
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12769
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12770
                }
12771
12772
                curl_setopt_array($ch, $options);
12773
                $response = curl_exec($ch);
12774
                $httpCode = curl_getinfo($ch);
12775
                $headers = substr($response, 0, $httpCode['header_size']);
12776
12777
                $error = false;
12778
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12779
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12780
                ) {
12781
                    $error = true;
12782
                }
12783
12784
                if ($error) {
12785
                    Session::write('x_frame_source', $src);
12786
                    $src = 'blank.php?error=x_frames_options';
12787
                }
12788
            }
12789
        }
12790
12791
        return $src;
12792
    }
12793
12794
    /**
12795
     * Check if this LP has a created forum in the basis course.
12796
     *
12797
     * @return bool
12798
     */
12799
    public function lpHasForum()
12800
    {
12801
        $forumTable = Database::get_course_table(TABLE_FORUM);
12802
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12803
12804
        $fakeFrom = "
12805
            $forumTable f
12806
            INNER JOIN $itemProperty ip
12807
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12808
        ";
12809
12810
        $resultData = Database::select(
12811
            'COUNT(f.iid) AS qty',
12812
            $fakeFrom,
12813
            [
12814
                'where' => [
12815
                    'ip.visibility != ? AND ' => 2,
12816
                    'ip.tool = ? AND ' => TOOL_FORUM,
12817
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12818
                    'f.lp_id = ?' => intval($this->lp_id),
12819
                ],
12820
            ],
12821
            'first'
12822
        );
12823
12824
        return $resultData['qty'] > 0;
12825
    }
12826
12827
    /**
12828
     * Get the forum for this learning path.
12829
     *
12830
     * @param int $sessionId
12831
     *
12832
     * @return bool
12833
     */
12834
    public function getForum($sessionId = 0)
12835
    {
12836
        $forumTable = Database::get_course_table(TABLE_FORUM);
12837
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12838
12839
        $fakeFrom = "$forumTable f
12840
            INNER JOIN $itemProperty ip ";
12841
12842
        if ($this->lp_session_id == 0) {
12843
            $fakeFrom .= "
12844
                ON (
12845
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12846
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12847
                    )
12848
                )
12849
            ";
12850
        } else {
12851
            $fakeFrom .= "
12852
                ON (
12853
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12854
                )
12855
            ";
12856
        }
12857
12858
        $resultData = Database::select(
12859
            'f.*',
12860
            $fakeFrom,
12861
            [
12862
                'where' => [
12863
                    'ip.visibility != ? AND ' => 2,
12864
                    'ip.tool = ? AND ' => TOOL_FORUM,
12865
                    'f.session_id = ? AND ' => $sessionId,
12866
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12867
                    'f.lp_id = ?' => intval($this->lp_id),
12868
                ],
12869
            ],
12870
            'first'
12871
        );
12872
12873
        if (empty($resultData)) {
12874
            return false;
12875
        }
12876
12877
        return $resultData;
12878
    }
12879
12880
    /**
12881
     * Create a forum for this learning path.
12882
     *
12883
     * @param int $forumCategoryId
12884
     *
12885
     * @return int The forum ID if was created. Otherwise return false
12886
     */
12887
    public function createForum($forumCategoryId)
12888
    {
12889
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12890
12891
        $forumId = store_forum(
12892
            [
12893
                'lp_id' => $this->lp_id,
12894
                'forum_title' => $this->name,
12895
                'forum_comment' => null,
12896
                'forum_category' => (int) $forumCategoryId,
12897
                'students_can_edit_group' => ['students_can_edit' => 0],
12898
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12899
                'default_view_type_group' => ['default_view_type' => 'flat'],
12900
                'group_forum' => 0,
12901
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12902
            ],
12903
            [],
12904
            true
12905
        );
12906
12907
        return $forumId;
12908
    }
12909
12910
    /**
12911
     * Get the LP Final Item form.
12912
     *
12913
     * @throws Exception
12914
     * @throws HTML_QuickForm_Error
12915
     *
12916
     * @return string
12917
     */
12918
    public function getFinalItemForm()
12919
    {
12920
        $finalItem = $this->getFinalItem();
12921
        $title = '';
12922
12923
        if ($finalItem) {
12924
            $title = $finalItem->get_title();
12925
            $buttonText = get_lang('Save');
12926
            $content = $this->getSavedFinalItem();
12927
        } else {
12928
            $buttonText = get_lang('LPCreateDocument');
12929
            $content = $this->getFinalItemTemplate();
12930
        }
12931
12932
        $courseInfo = api_get_course_info();
12933
        $result = $this->generate_lp_folder($courseInfo);
12934
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12935
        $relative_prefix = '../../';
12936
12937
        $editorConfig = [
12938
            'ToolbarSet' => 'LearningPathDocuments',
12939
            'Width' => '100%',
12940
            'Height' => '500',
12941
            'FullPage' => true,
12942
            'CreateDocumentDir' => $relative_prefix,
12943
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12944
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12945
        ];
12946
12947
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12948
            'type' => 'document',
12949
            'lp_id' => $this->lp_id,
12950
        ]);
12951
12952
        $form = new FormValidator('final_item', 'POST', $url);
12953
        $form->addText('title', get_lang('Title'));
12954
        $form->addButtonSave($buttonText);
12955
        $form->addHtml(
12956
            Display::return_message(
12957
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12958
                'normal',
12959
                false
12960
            )
12961
        );
12962
12963
        $renderer = $form->defaultRenderer();
12964
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12965
12966
        $form->addHtmlEditor(
12967
            'content_lp_certificate',
12968
            null,
12969
            true,
12970
            false,
12971
            $editorConfig,
12972
            true
12973
        );
12974
        $form->addHidden('action', 'add_final_item');
12975
        $form->addHidden('path', Session::read('pathItem'));
12976
        $form->addHidden('previous', $this->get_last());
12977
        $form->setDefaults(
12978
            ['title' => $title, 'content_lp_certificate' => $content]
12979
        );
12980
12981
        if ($form->validate()) {
12982
            $values = $form->exportValues();
12983
            $lastItemId = $this->getLastInFirstLevel();
12984
12985
            if (!$finalItem) {
12986
                $documentId = $this->create_document(
12987
                    $this->course_info,
12988
                    $values['content_lp_certificate'],
12989
                    $values['title']
12990
                );
12991
                $this->add_item(
12992
                    0,
12993
                    $lastItemId,
12994
                    'final_item',
12995
                    $documentId,
12996
                    $values['title'],
12997
                    ''
12998
                );
12999
13000
                Display::addFlash(
13001
                    Display::return_message(get_lang('Added'))
13002
                );
13003
            } else {
13004
                $this->edit_document($this->course_info);
13005
            }
13006
        }
13007
13008
        return $form->returnForm();
13009
    }
13010
13011
    /**
13012
     * Check if the current lp item is first, both, last or none from lp list.
13013
     *
13014
     * @param int $currentItemId
13015
     *
13016
     * @return string
13017
     */
13018
    public function isFirstOrLastItem($currentItemId)
13019
    {
13020
        $lpItemId = [];
13021
        $typeListNotToVerify = self::getChapterTypes();
13022
13023
        // Using get_toc() function instead $this->items because returns the correct order of the items
13024
        foreach ($this->get_toc() as $item) {
13025
            if (!in_array($item['type'], $typeListNotToVerify)) {
13026
                $lpItemId[] = $item['id'];
13027
            }
13028
        }
13029
13030
        $lastLpItemIndex = count($lpItemId) - 1;
13031
        $position = array_search($currentItemId, $lpItemId);
13032
13033
        switch ($position) {
13034
            case 0:
13035
                if (!$lastLpItemIndex) {
13036
                    $answer = 'both';
13037
                    break;
13038
                }
13039
13040
                $answer = 'first';
13041
                break;
13042
            case $lastLpItemIndex:
13043
                $answer = 'last';
13044
                break;
13045
            default:
13046
                $answer = 'none';
13047
        }
13048
13049
        return $answer;
13050
    }
13051
13052
    /**
13053
     * Get whether this is a learning path with the accumulated SCORM time or not.
13054
     *
13055
     * @return int
13056
     */
13057
    public function getAccumulateScormTime()
13058
    {
13059
        return $this->accumulateScormTime;
13060
    }
13061
13062
    /**
13063
     * Set whether this is a learning path with the accumulated SCORM time or not.
13064
     *
13065
     * @param int $value (0 = false, 1 = true)
13066
     *
13067
     * @return bool Always returns true
13068
     */
13069
    public function setAccumulateScormTime($value)
13070
    {
13071
        $this->accumulateScormTime = (int) $value;
13072
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13073
        $lp_id = $this->get_id();
13074
        $sql = "UPDATE $lp_table
13075
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13076
                WHERE iid = $lp_id";
13077
        Database::query($sql);
13078
13079
        return true;
13080
    }
13081
13082
    /**
13083
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13084
     * the new learning path tool.
13085
     *
13086
     * The function is a big switch on tool type.
13087
     * In each case, we query the corresponding table for information and build the link
13088
     * with that information.
13089
     *
13090
     * @author Yannick Warnier <[email protected]> - rebranding based on
13091
     * previous work (display_addedresource_link_in_learnpath())
13092
     *
13093
     * @param int $course_id      Course code
13094
     * @param int $learningPathId The learning path ID (in lp table)
13095
     * @param int $id_in_path     the unique index in the items table
13096
     * @param int $lpViewId
13097
     * @param int $lpSessionId
13098
     *
13099
     * @return string
13100
     */
13101
    public static function rl_get_resource_link_for_learnpath(
13102
        $course_id,
13103
        $learningPathId,
13104
        $id_in_path,
13105
        $lpViewId,
13106
        $lpSessionId = 0
13107
    ) {
13108
        $session_id = api_get_session_id();
13109
        $course_info = api_get_course_info_by_id($course_id);
13110
13111
        $learningPathId = (int) $learningPathId;
13112
        $id_in_path = (int) $id_in_path;
13113
        $lpViewId = (int) $lpViewId;
13114
13115
        $em = Database::getManager();
13116
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13117
13118
        /** @var CLpItem $rowItem */
13119
        $rowItem = $lpItemRepo->findOneBy([
13120
            'cId' => $course_id,
13121
            'lpId' => $learningPathId,
13122
            'iid' => $id_in_path,
13123
        ]);
13124
13125
        if (!$rowItem) {
13126
            // Try one more time with "id"
13127
            /** @var CLpItem $rowItem */
13128
            $rowItem = $lpItemRepo->findOneBy([
13129
                'cId' => $course_id,
13130
                'lpId' => $learningPathId,
13131
                'id' => $id_in_path,
13132
            ]);
13133
13134
            if (!$rowItem) {
13135
                return -1;
13136
            }
13137
        }
13138
13139
        $course_code = $course_info['code'];
13140
        $type = $rowItem->getItemType();
13141
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13142
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13143
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13144
        $link = '';
13145
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13146
13147
        switch ($type) {
13148
            case 'dir':
13149
                return $main_dir_path.'lp/blank.php';
13150
            case TOOL_CALENDAR_EVENT:
13151
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13152
            case TOOL_ANNOUNCEMENT:
13153
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13154
            case TOOL_LINK:
13155
                $linkInfo = Link::getLinkInfo($id);
13156
                if (isset($linkInfo['url'])) {
13157
                    return $linkInfo['url'];
13158
                }
13159
13160
                return '';
13161
            case TOOL_QUIZ:
13162
                if (empty($id)) {
13163
                    return '';
13164
                }
13165
13166
                // Get the lp_item_view with the highest view_count.
13167
                $learnpathItemViewResult = $em
13168
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13169
                    ->findBy(
13170
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13171
                        ['viewCount' => 'DESC'],
13172
                        1
13173
                    );
13174
                /** @var CLpItemView $learnpathItemViewData */
13175
                $learnpathItemViewData = current($learnpathItemViewResult);
13176
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13177
13178
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13179
                    .http_build_query([
13180
                        'lp_init' => 1,
13181
                        'learnpath_item_view_id' => $learnpathItemViewId,
13182
                        'learnpath_id' => $learningPathId,
13183
                        'learnpath_item_id' => $id_in_path,
13184
                        'exerciseId' => $id,
13185
                    ]);
13186
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13187
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13188
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13189
                $myrow = Database::fetch_array($result);
13190
                $path = $myrow['path'];
13191
13192
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13193
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13194
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13195
            case TOOL_FORUM:
13196
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13197
            case TOOL_THREAD:
13198
                // forum post
13199
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13200
                if (empty($id)) {
13201
                    return '';
13202
                }
13203
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13204
                $result = Database::query($sql);
13205
                $myrow = Database::fetch_array($result);
13206
13207
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13208
                    .$extraParams;
13209
            case TOOL_POST:
13210
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13211
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13212
                $myrow = Database::fetch_array($result);
13213
13214
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13215
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13216
            case TOOL_READOUT_TEXT:
13217
                return api_get_path(WEB_CODE_PATH).
13218
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13219
            case TOOL_DOCUMENT:
13220
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13221
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13222
13223
                if (empty($document)) {
13224
                    // Try with normal id
13225
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13226
13227
                    if (empty($document)) {
13228
                        return '';
13229
                    }
13230
                }
13231
13232
                $documentPathInfo = pathinfo($document->getPath());
13233
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13234
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13235
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13236
13237
                $openmethod = 2;
13238
                $officedoc = false;
13239
                Session::write('openmethod', $openmethod);
13240
                Session::write('officedoc', $officedoc);
13241
13242
                if ($showDirectUrl) {
13243
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13244
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13245
                        if (Link::isPdfLink($file)) {
13246
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13247
13248
                            return $pdfUrl;
13249
                        }
13250
                    }
13251
13252
                    return $file;
13253
                }
13254
13255
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13256
            case TOOL_LP_FINAL_ITEM:
13257
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13258
                    .$extraParams;
13259
            case 'assignments':
13260
                return $main_dir_path.'work/work.php?'.$extraParams;
13261
            case TOOL_DROPBOX:
13262
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13263
            case 'introduction_text': //DEPRECATED
13264
                return '';
13265
            case TOOL_COURSE_DESCRIPTION:
13266
                return $main_dir_path.'course_description?'.$extraParams;
13267
            case TOOL_GROUP:
13268
                return $main_dir_path.'group/group.php?'.$extraParams;
13269
            case TOOL_USER:
13270
                return $main_dir_path.'user/user.php?'.$extraParams;
13271
            case TOOL_STUDENTPUBLICATION:
13272
                if (!empty($rowItem->getPath())) {
13273
                    $workId = $rowItem->getPath();
13274
                    if (empty($lpSessionId) && !empty($session_id)) {
13275
                        // Check if a student publication with the same name exists in this session see BT#17700
13276
                        $title = Database::escape_string($rowItem->getTitle());
13277
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13278
                        $sql = "SELECT * FROM $table
13279
                                WHERE
13280
                                    active = 1 AND
13281
                                    parent_id = 0 AND
13282
                                    c_id = $course_id AND
13283
                                    session_id = $session_id AND
13284
                                    title = '$title'
13285
                                LIMIT 1";
13286
                        $result = Database::query($sql);
13287
                        if (Database::num_rows($result)) {
13288
                            $work = Database::fetch_array($result, 'ASSOC');
13289
                            if ($work) {
13290
                                $workId = $work['iid'];
13291
                            }
13292
                        }
13293
                    }
13294
13295
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13296
                }
13297
13298
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13299
        }
13300
13301
        return $link;
13302
    }
13303
13304
    /**
13305
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13306
     *
13307
     * @author Yannick Warnier <[email protected]>
13308
     *
13309
     * @param string $course_code    Course code
13310
     * @param int    $learningPathId
13311
     * @param int    $id_in_path     The resource ID
13312
     *
13313
     * @return string
13314
     */
13315
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13316
    {
13317
        $_course = api_get_course_info($course_code);
13318
        if (empty($_course)) {
13319
            return '';
13320
        }
13321
        $course_id = $_course['real_id'];
13322
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13323
        $learningPathId = (int) $learningPathId;
13324
        $id_in_path = (int) $id_in_path;
13325
13326
        $sql = "SELECT item_type, title, ref
13327
                FROM $tbl_lp_item
13328
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13329
        $res_item = Database::query($sql);
13330
13331
        if (Database::num_rows($res_item) < 1) {
13332
            return '';
13333
        }
13334
        $row_item = Database::fetch_array($res_item);
13335
        $type = strtolower($row_item['item_type']);
13336
        $id = $row_item['ref'];
13337
        $output = '';
13338
13339
        switch ($type) {
13340
            case TOOL_CALENDAR_EVENT:
13341
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13342
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13343
                $myrow = Database::fetch_array($result);
13344
                $output = $myrow['title'];
13345
                break;
13346
            case TOOL_ANNOUNCEMENT:
13347
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13348
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13349
                $myrow = Database::fetch_array($result);
13350
                $output = $myrow['title'];
13351
                break;
13352
            case TOOL_LINK:
13353
                // Doesn't take $target into account.
13354
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13355
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13356
                $myrow = Database::fetch_array($result);
13357
                $output = $myrow['title'];
13358
                break;
13359
            case TOOL_QUIZ:
13360
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13361
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13362
                $myrow = Database::fetch_array($result);
13363
                $output = $myrow['title'];
13364
                break;
13365
            case TOOL_FORUM:
13366
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13367
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13368
                $myrow = Database::fetch_array($result);
13369
                $output = $myrow['forum_name'];
13370
                break;
13371
            case TOOL_THREAD:
13372
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13373
                // Grabbing the title of the post.
13374
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13375
                $result_title = Database::query($sql_title);
13376
                $myrow_title = Database::fetch_array($result_title);
13377
                $output = $myrow_title['post_title'];
13378
                break;
13379
            case TOOL_POST:
13380
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13381
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13382
                $result = Database::query($sql);
13383
                $post = Database::fetch_array($result);
13384
                $output = $post['post_title'];
13385
                break;
13386
            case 'dir':
13387
            case TOOL_DOCUMENT:
13388
                $title = $row_item['title'];
13389
                $output = '-';
13390
                if (!empty($title)) {
13391
                    $output = $title;
13392
                }
13393
                break;
13394
            case 'hotpotatoes':
13395
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13396
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13397
                $myrow = Database::fetch_array($result);
13398
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13399
                $last = count($pathname) - 1; // Making a correct name for the link.
13400
                $filename = $pathname[$last]; // Making a correct name for the link.
13401
                $myrow['path'] = rawurlencode($myrow['path']);
13402
                $output = $filename;
13403
                break;
13404
        }
13405
13406
        return stripslashes($output);
13407
    }
13408
13409
    /**
13410
     * Get the parent names for the current item.
13411
     *
13412
     * @param int $newItemId Optional. The item ID
13413
     *
13414
     * @return array
13415
     */
13416
    public function getCurrentItemParentNames($newItemId = 0)
13417
    {
13418
        $newItemId = $newItemId ?: $this->get_current_item_id();
13419
        $return = [];
13420
        $item = $this->getItem($newItemId);
13421
        $parent = $this->getItem($item->get_parent());
13422
13423
        while ($parent) {
13424
            $return[] = $parent->get_title();
13425
            $parent = $this->getItem($parent->get_parent());
13426
        }
13427
13428
        return array_reverse($return);
13429
    }
13430
13431
    /**
13432
     * Reads and process "lp_subscription_settings" setting.
13433
     *
13434
     * @return array
13435
     */
13436
    public static function getSubscriptionSettings()
13437
    {
13438
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13439
        if (empty($subscriptionSettings)) {
13440
            // By default allow both settings
13441
            $subscriptionSettings = [
13442
                'allow_add_users_to_lp' => true,
13443
                'allow_add_users_to_lp_category' => true,
13444
            ];
13445
        } else {
13446
            $subscriptionSettings = $subscriptionSettings['options'];
13447
        }
13448
13449
        return $subscriptionSettings;
13450
    }
13451
13452
    /**
13453
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13454
     */
13455
    public function exportToCourseBuildFormat()
13456
    {
13457
        if (!api_is_allowed_to_edit()) {
13458
            return false;
13459
        }
13460
13461
        $courseBuilder = new CourseBuilder();
13462
        $itemList = [];
13463
        /** @var learnpathItem $item */
13464
        foreach ($this->items as $item) {
13465
            $itemList[$item->get_type()][] = $item->get_path();
13466
        }
13467
13468
        if (empty($itemList)) {
13469
            return false;
13470
        }
13471
13472
        if (isset($itemList['document'])) {
13473
            // Get parents
13474
            foreach ($itemList['document'] as $documentId) {
13475
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13476
                if (!empty($documentInfo['parents'])) {
13477
                    foreach ($documentInfo['parents'] as $parentInfo) {
13478
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13479
                            continue;
13480
                        }
13481
                        $itemList['document'][] = $parentInfo['iid'];
13482
                    }
13483
                }
13484
            }
13485
13486
            $courseInfo = api_get_course_info();
13487
            foreach ($itemList['document'] as $documentId) {
13488
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13489
                $items = DocumentManager::get_resources_from_source_html(
13490
                    $documentInfo['absolute_path'],
13491
                    true,
13492
                    TOOL_DOCUMENT
13493
                );
13494
13495
                if (!empty($items)) {
13496
                    foreach ($items as $item) {
13497
                        // Get information about source url
13498
                        $url = $item[0]; // url
13499
                        $scope = $item[1]; // scope (local, remote)
13500
                        $type = $item[2]; // type (rel, abs, url)
13501
13502
                        $origParseUrl = parse_url($url);
13503
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13504
13505
                        if ($scope == 'local') {
13506
                            if ($type == 'abs' || $type == 'rel') {
13507
                                $documentFile = strstr($realOrigPath, 'document');
13508
                                if (strpos($realOrigPath, $documentFile) !== false) {
13509
                                    $documentFile = str_replace('document', '', $documentFile);
13510
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13511
                                    // Document found! Add it to the list
13512
                                    if ($itemDocumentId) {
13513
                                        $itemList['document'][] = $itemDocumentId;
13514
                                    }
13515
                                }
13516
                            }
13517
                        }
13518
                    }
13519
                }
13520
            }
13521
13522
            $courseBuilder->build_documents(
13523
                api_get_session_id(),
13524
                $this->get_course_int_id(),
13525
                true,
13526
                $itemList['document']
13527
            );
13528
        }
13529
13530
        if (isset($itemList['quiz'])) {
13531
            $courseBuilder->build_quizzes(
13532
                api_get_session_id(),
13533
                $this->get_course_int_id(),
13534
                true,
13535
                $itemList['quiz']
13536
            );
13537
        }
13538
13539
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13540
13541
        /*if (!empty($itemList['thread'])) {
13542
            $postList = [];
13543
            foreach ($itemList['thread'] as $postId) {
13544
                $post = get_post_information($postId);
13545
                if ($post) {
13546
                    if (!isset($itemList['forum'])) {
13547
                        $itemList['forum'] = [];
13548
                    }
13549
                    $itemList['forum'][] = $post['forum_id'];
13550
                    $postList[] = $postId;
13551
                }
13552
            }
13553
13554
            if (!empty($postList)) {
13555
                $courseBuilder->build_forum_posts(
13556
                    $this->get_course_int_id(),
13557
                    null,
13558
                    null,
13559
                    $postList
13560
                );
13561
            }
13562
        }*/
13563
13564
        if (!empty($itemList['thread'])) {
13565
            $threadList = [];
13566
            $em = Database::getManager();
13567
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13568
            foreach ($itemList['thread'] as $threadId) {
13569
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13570
                $thread = $repo->find($threadId);
13571
                if ($thread) {
13572
                    $itemList['forum'][] = $thread->getForumId();
13573
                    $threadList[] = $thread->getIid();
13574
                }
13575
            }
13576
13577
            if (!empty($threadList)) {
13578
                $courseBuilder->build_forum_topics(
13579
                    api_get_session_id(),
13580
                    $this->get_course_int_id(),
13581
                    null,
13582
                    $threadList
13583
                );
13584
            }
13585
        }
13586
13587
        $forumCategoryList = [];
13588
        if (isset($itemList['forum'])) {
13589
            foreach ($itemList['forum'] as $forumId) {
13590
                $forumInfo = get_forums($forumId);
13591
                $forumCategoryList[] = $forumInfo['forum_category'];
13592
            }
13593
        }
13594
13595
        if (!empty($forumCategoryList)) {
13596
            $courseBuilder->build_forum_category(
13597
                api_get_session_id(),
13598
                $this->get_course_int_id(),
13599
                true,
13600
                $forumCategoryList
13601
            );
13602
        }
13603
13604
        if (!empty($itemList['forum'])) {
13605
            $courseBuilder->build_forums(
13606
                api_get_session_id(),
13607
                $this->get_course_int_id(),
13608
                true,
13609
                $itemList['forum']
13610
            );
13611
        }
13612
13613
        if (isset($itemList['link'])) {
13614
            $courseBuilder->build_links(
13615
                api_get_session_id(),
13616
                $this->get_course_int_id(),
13617
                true,
13618
                $itemList['link']
13619
            );
13620
        }
13621
13622
        if (!empty($itemList['student_publication'])) {
13623
            $courseBuilder->build_works(
13624
                api_get_session_id(),
13625
                $this->get_course_int_id(),
13626
                true,
13627
                $itemList['student_publication']
13628
            );
13629
        }
13630
13631
        $courseBuilder->build_learnpaths(
13632
            api_get_session_id(),
13633
            $this->get_course_int_id(),
13634
            true,
13635
            [$this->get_id()],
13636
            false
13637
        );
13638
13639
        $courseBuilder->restoreDocumentsFromList();
13640
13641
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13642
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13643
        $result = DocumentManager::file_send_for_download(
13644
            $zipPath,
13645
            true,
13646
            $this->get_name().'.zip'
13647
        );
13648
13649
        if ($result) {
13650
            api_not_allowed();
13651
        }
13652
13653
        return true;
13654
    }
13655
13656
    /**
13657
     * Get whether this is a learning path with the accumulated work time or not.
13658
     *
13659
     * @return int
13660
     */
13661
    public function getAccumulateWorkTime()
13662
    {
13663
        return (int) $this->accumulateWorkTime;
13664
    }
13665
13666
    /**
13667
     * Get whether this is a learning path with the accumulated work time or not.
13668
     *
13669
     * @return int
13670
     */
13671
    public function getAccumulateWorkTimeTotalCourse()
13672
    {
13673
        $table = Database::get_course_table(TABLE_LP_MAIN);
13674
        $sql = "SELECT SUM(accumulate_work_time) AS total
13675
                FROM $table
13676
                WHERE c_id = ".$this->course_int_id;
13677
        $result = Database::query($sql);
13678
        $row = Database::fetch_array($result);
13679
13680
        return (int) $row['total'];
13681
    }
13682
13683
    /**
13684
     * Set whether this is a learning path with the accumulated work time or not.
13685
     *
13686
     * @param int $value (0 = false, 1 = true)
13687
     *
13688
     * @return bool
13689
     */
13690
    public function setAccumulateWorkTime($value)
13691
    {
13692
        if (!api_get_configuration_value('lp_minimum_time')) {
13693
            return false;
13694
        }
13695
13696
        $this->accumulateWorkTime = (int) $value;
13697
        $table = Database::get_course_table(TABLE_LP_MAIN);
13698
        $lp_id = $this->get_id();
13699
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13700
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13701
        Database::query($sql);
13702
13703
        return true;
13704
    }
13705
13706
    /**
13707
     * @param int $lpId
13708
     * @param int $courseId
13709
     *
13710
     * @return mixed
13711
     */
13712
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13713
    {
13714
        $lpId = (int) $lpId;
13715
        $courseId = (int) $courseId;
13716
13717
        $table = Database::get_course_table(TABLE_LP_MAIN);
13718
        $sql = "SELECT accumulate_work_time
13719
                FROM $table
13720
                WHERE c_id = $courseId AND id = $lpId";
13721
        $result = Database::query($sql);
13722
        $row = Database::fetch_array($result);
13723
13724
        return $row['accumulate_work_time'];
13725
    }
13726
13727
    /**
13728
     * @param int $courseId
13729
     *
13730
     * @return int
13731
     */
13732
    public static function getAccumulateWorkTimeTotal($courseId)
13733
    {
13734
        $table = Database::get_course_table(TABLE_LP_MAIN);
13735
        $courseId = (int) $courseId;
13736
        $sql = "SELECT SUM(accumulate_work_time) AS total
13737
                FROM $table
13738
                WHERE c_id = $courseId";
13739
        $result = Database::query($sql);
13740
        $row = Database::fetch_array($result);
13741
13742
        return (int) $row['total'];
13743
    }
13744
13745
    /**
13746
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13747
     * and put the images in.
13748
     *
13749
     * @return array
13750
     */
13751
    public static function getIconSelect()
13752
    {
13753
        $theme = api_get_visual_theme();
13754
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13755
        $icons = ['' => get_lang('SelectAnOption')];
13756
13757
        if (is_dir($path)) {
13758
            $finder = new Finder();
13759
            $finder->files()->in($path);
13760
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13761
            /** @var SplFileInfo $file */
13762
            foreach ($finder as $file) {
13763
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13764
                    $icons[$file->getFilename()] = $file->getFilename();
13765
                }
13766
            }
13767
        }
13768
13769
        return $icons;
13770
    }
13771
13772
    /**
13773
     * @param int $lpId
13774
     *
13775
     * @return string
13776
     */
13777
    public static function getSelectedIcon($lpId)
13778
    {
13779
        $extraFieldValue = new ExtraFieldValue('lp');
13780
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13781
        $icon = '';
13782
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13783
            $icon = $lpIcon['value'];
13784
        }
13785
13786
        return $icon;
13787
    }
13788
13789
    /**
13790
     * @param int $lpId
13791
     *
13792
     * @return string
13793
     */
13794
    public static function getSelectedIconHtml($lpId)
13795
    {
13796
        $icon = self::getSelectedIcon($lpId);
13797
13798
        if (empty($icon)) {
13799
            return '';
13800
        }
13801
13802
        $theme = api_get_visual_theme();
13803
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13804
13805
        return Display::img($path);
13806
    }
13807
13808
    /**
13809
     * @param string $value
13810
     *
13811
     * @return string
13812
     */
13813
    public function cleanItemTitle($value)
13814
    {
13815
        $value = Security::remove_XSS(strip_tags($value));
13816
13817
        return $value;
13818
    }
13819
13820
    public function setItemTitle(FormValidator $form)
13821
    {
13822
        if (api_get_configuration_value('save_titles_as_html')) {
13823
            $form->addHtmlEditor(
13824
                'title',
13825
                get_lang('Title'),
13826
                true,
13827
                false,
13828
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13829
            );
13830
        } else {
13831
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13832
            $form->applyFilter('title', 'trim');
13833
            $form->applyFilter('title', 'html_filter');
13834
        }
13835
    }
13836
13837
    /**
13838
     * @return array
13839
     */
13840
    public function getItemsForForm($addParentCondition = false)
13841
    {
13842
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13843
        $course_id = api_get_course_int_id();
13844
13845
        $sql = "SELECT * FROM $tbl_lp_item
13846
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13847
13848
        if ($addParentCondition) {
13849
            $sql .= ' AND parent_item_id = 0 ';
13850
        }
13851
        $sql .= ' ORDER BY display_order ASC';
13852
13853
        $result = Database::query($sql);
13854
        $arrLP = [];
13855
        while ($row = Database::fetch_array($result)) {
13856
            $arrLP[] = [
13857
                'iid' => $row['iid'],
13858
                'id' => $row['iid'],
13859
                'item_type' => $row['item_type'],
13860
                'title' => $this->cleanItemTitle($row['title']),
13861
                'title_raw' => $row['title'],
13862
                'path' => $row['path'],
13863
                'description' => Security::remove_XSS($row['description']),
13864
                'parent_item_id' => $row['parent_item_id'],
13865
                'previous_item_id' => $row['previous_item_id'],
13866
                'next_item_id' => $row['next_item_id'],
13867
                'display_order' => $row['display_order'],
13868
                'max_score' => $row['max_score'],
13869
                'min_score' => $row['min_score'],
13870
                'mastery_score' => $row['mastery_score'],
13871
                'prerequisite' => $row['prerequisite'],
13872
                'max_time_allowed' => $row['max_time_allowed'],
13873
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13874
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13875
            ];
13876
        }
13877
13878
        return $arrLP;
13879
    }
13880
13881
    /**
13882
     * Gets whether this SCORM learning path has been marked to use the score
13883
     * as progress. Takes into account whether the learnpath matches (SCORM
13884
     * content + less than 2 items).
13885
     *
13886
     * @return bool True if the score should be used as progress, false otherwise
13887
     */
13888
    public function getUseScoreAsProgress()
13889
    {
13890
        // If not a SCORM, we don't care about the setting
13891
        if ($this->get_type() != 2) {
13892
            return false;
13893
        }
13894
        // If more than one step in the SCORM, we don't care about the setting
13895
        if ($this->get_total_items_count() > 1) {
13896
            return false;
13897
        }
13898
        $extraFieldValue = new ExtraFieldValue('lp');
13899
        $doUseScore = false;
13900
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
13901
        if (!empty($useScore) && isset($useScore['value'])) {
13902
            $doUseScore = $useScore['value'];
13903
        }
13904
13905
        return $doUseScore;
13906
    }
13907
13908
    /**
13909
     * Get the user identifier (user_id or username
13910
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
13911
     *
13912
     * @return string User ID or username, depending on configuration setting
13913
     */
13914
    public static function getUserIdentifierForExternalServices()
13915
    {
13916
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
13917
            return api_get_user_info(api_get_user_id())['username'];
13918
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
13919
            $extraFieldValue = new ExtraFieldValue('user');
13920
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(api_get_user_id(), api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id'));
13921
13922
            return $extrafield['value'];
13923
        } else {
13924
            return api_get_user_id();
13925
        }
13926
    }
13927
13928
    /**
13929
     * Save the new order for learning path items.
13930
     *
13931
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
13932
     *
13933
     * @param array $orderList A associative array with item ID as key and parent ID as value.
13934
     * @param int   $courseId
13935
     */
13936
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
13937
    {
13938
        $courseId = $courseId ?: api_get_course_int_id();
13939
        $itemList = new LpItemOrderList();
13940
13941
        foreach ($orderList as $id => $parentId) {
13942
            $item = new LpOrderItem($id, $parentId);
13943
            $itemList->add($item);
13944
        }
13945
13946
        $parents = $itemList->getListOfParents();
13947
13948
        foreach ($parents as $parentId) {
13949
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
13950
            $previous_item_id = 0;
13951
            for ($i = 0; $i < count($sameParentLpItemList->list); $i++) {
13952
                $item_id = $sameParentLpItemList->list[$i]->id;
13953
                // display_order
13954
                $display_order = $i + 1;
13955
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
13956
                // previous_item_id
13957
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
13958
                $previous_item_id = $item_id;
13959
                // next_item_id
13960
                $next_item_id = 0;
13961
                if ($i < count($sameParentLpItemList->list) - 1) {
13962
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
13963
                }
13964
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
13965
            }
13966
        }
13967
13968
        $table = Database::get_course_table(TABLE_LP_ITEM);
13969
13970
        foreach ($itemList->list as $item) {
13971
            $params = [];
13972
            $params['display_order'] = $item->display_order;
13973
            $params['previous_item_id'] = $item->previous_item_id;
13974
            $params['next_item_id'] = $item->next_item_id;
13975
            $params['parent_item_id'] = $item->parent_item_id;
13976
13977
            Database::update(
13978
                $table,
13979
                $params,
13980
                [
13981
                    'iid = ? AND c_id = ? ' => [
13982
                        (int) $item->id,
13983
                        (int) $courseId,
13984
                    ],
13985
                ]
13986
            );
13987
        }
13988
    }
13989
13990
    /**
13991
     * Get the depth level of LP item.
13992
     *
13993
     * @param array $items
13994
     * @param int   $currentItemId
13995
     *
13996
     * @return int
13997
     */
13998
    private static function get_level_for_item($items, $currentItemId)
13999
    {
14000
        $parentItemId = 0;
14001
        if (isset($items[$currentItemId])) {
14002
            $parentItemId = $items[$currentItemId]->parent;
14003
        }
14004
14005
        if ($parentItemId == 0) {
14006
            return 0;
14007
        } else {
14008
            return self::get_level_for_item($items, $parentItemId) + 1;
14009
        }
14010
    }
14011
14012
    /**
14013
     * Generate the link for a learnpath category as course tool.
14014
     *
14015
     * @param int $categoryId
14016
     *
14017
     * @return string
14018
     */
14019
    private static function getCategoryLinkForTool($categoryId)
14020
    {
14021
        $categoryId = (int) $categoryId;
14022
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14023
            .http_build_query(
14024
                [
14025
                    'action' => 'view_category',
14026
                    'id' => $categoryId,
14027
                ]
14028
            );
14029
14030
        return $link;
14031
    }
14032
14033
    /**
14034
     * Return the scorm item type object with spaces replaced with _
14035
     * The return result is use to build a css classname like scorm_type_$return.
14036
     *
14037
     * @param $in_type
14038
     *
14039
     * @return mixed
14040
     */
14041
    private static function format_scorm_type_item($in_type)
14042
    {
14043
        return str_replace(' ', '_', $in_type);
14044
    }
14045
14046
    /**
14047
     * Check and obtain the lp final item if exist.
14048
     *
14049
     * @return learnpathItem
14050
     */
14051
    private function getFinalItem()
14052
    {
14053
        if (empty($this->items)) {
14054
            return null;
14055
        }
14056
14057
        foreach ($this->items as $item) {
14058
            if ($item->type !== 'final_item') {
14059
                continue;
14060
            }
14061
14062
            return $item;
14063
        }
14064
    }
14065
14066
    /**
14067
     * Get the LP Final Item Template.
14068
     *
14069
     * @return string
14070
     */
14071
    private function getFinalItemTemplate()
14072
    {
14073
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14074
    }
14075
14076
    /**
14077
     * Get the LP Final Item Url.
14078
     *
14079
     * @return string
14080
     */
14081
    private function getSavedFinalItem()
14082
    {
14083
        $finalItem = $this->getFinalItem();
14084
        $doc = DocumentManager::get_document_data_by_id(
14085
            $finalItem->path,
14086
            $this->cc
14087
        );
14088
        if ($doc && file_exists($doc['absolute_path'])) {
14089
            return file_get_contents($doc['absolute_path']);
14090
        }
14091
14092
        return '';
14093
    }
14094
}
14095