Passed
Push — 1.11.x ( a5a0b8...4f166c )
by Julito
13:24
created

learnpath::update_display_order()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 3
nop 0
dl 0
loc 29
rs 9.3888
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
5
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
9
use Chamilo\CourseBundle\Entity\CDocument;
10
use Chamilo\CourseBundle\Entity\CItemProperty;
11
use Chamilo\CourseBundle\Entity\CLp;
12
use Chamilo\CourseBundle\Entity\CLpCategory;
13
use Chamilo\CourseBundle\Entity\CLpItem;
14
use Chamilo\CourseBundle\Entity\CLpItemView;
15
use Chamilo\CourseBundle\Entity\CTool;
16
use Chamilo\UserBundle\Entity\User;
17
use ChamiloSession as Session;
18
use Gedmo\Sortable\Entity\Repository\SortableRepository;
19
use Symfony\Component\Filesystem\Filesystem;
20
use Symfony\Component\Finder\Finder;
21
22
/**
23
 * Class learnpath
24
 * This class defines the parent attributes and methods for Chamilo learnpaths
25
 * and SCORM learnpaths. It is used by the scorm class.
26
 *
27
 * @todo decouple class
28
 *
29
 * @package chamilo.learnpath
30
 *
31
 * @author  Yannick Warnier <[email protected]>
32
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
33
 */
34
class learnpath
35
{
36
    const MAX_LP_ITEM_TITLE_LENGTH = 32;
37
    const STATUS_CSS_CLASS_NAME = [
38
        'not attempted' => 'scorm_not_attempted',
39
        'incomplete' => 'scorm_not_attempted',
40
        'failed' => 'scorm_failed',
41
        'completed' => 'scorm_completed',
42
        'passed' => 'scorm_completed',
43
        'succeeded' => 'scorm_completed',
44
        'browsed' => 'scorm_completed',
45
    ];
46
47
    public $attempt = 0; // The number for the current ID view.
48
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
49
    public $current; // Id of the current item the user is viewing.
50
    public $current_score; // The score of the current item.
51
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
52
    public $current_time_stop; // The time the user closed this resource.
53
    public $default_status = 'not attempted';
54
    public $encoding = 'UTF-8';
55
    public $error = '';
56
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
57
    public $index; // The index of the active learnpath_item in $ordered_items array.
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') {
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
                // Getting the filename only.
712
                $file_components = explode('/', $file_path);
713
                $file = $file_components[count($file_components) - 1];
714
715
                // Store the mp3 file in the lp_item table.
716
                $sql = "UPDATE $tbl_lp_item SET
717
                          audio = '".Database::escape_string($file)."'
718
                        WHERE iid = '".intval($new_item_id)."'";
719
                Database::query($sql);
720
            }
721
        }
722
723
        return $new_item_id;
724
    }
725
726
    /**
727
     * Static admin function allowing addition of a learnpath to a course.
728
     *
729
     * @param string $courseCode
730
     * @param string $name
731
     * @param string $description
732
     * @param string $learnpath
733
     * @param string $origin
734
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
735
     * @param string $publicated_on
736
     * @param string $expired_on
737
     * @param int    $categoryId
738
     * @param int    $userId
739
     *
740
     * @return int The new learnpath ID on success, 0 on failure
741
     */
742
    public static function add_lp(
743
        $courseCode,
744
        $name,
745
        $description = '',
746
        $learnpath = 'guess',
747
        $origin = 'zip',
748
        $zipname = '',
749
        $publicated_on = '',
750
        $expired_on = '',
751
        $categoryId = 0,
752
        $userId = 0
753
    ) {
754
        global $charset;
755
756
        if (!empty($courseCode)) {
757
            $courseInfo = api_get_course_info($courseCode);
758
            $course_id = $courseInfo['real_id'];
759
        } else {
760
            $course_id = api_get_course_int_id();
761
            $courseInfo = api_get_course_info();
762
        }
763
764
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
765
        // Check course code exists.
766
        // Check lp_name doesn't exist, otherwise append something.
767
        $i = 0;
768
        $categoryId = (int) $categoryId;
769
        // Session id.
770
        $session_id = api_get_session_id();
771
        $userId = empty($userId) ? api_get_user_id() : $userId;
772
773
        if (empty($publicated_on)) {
774
            $publicated_on = null;
775
        } else {
776
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
777
        }
778
779
        if (empty($expired_on)) {
780
            $expired_on = null;
781
        } else {
782
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
783
        }
784
785
        $check_name = "SELECT * FROM $tbl_lp
786
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
787
        $res_name = Database::query($check_name);
788
789
        while (Database::num_rows($res_name)) {
790
            // There is already one such name, update the current one a bit.
791
            $i++;
792
            $name = $name.' - '.$i;
793
            $check_name = "SELECT * FROM $tbl_lp
794
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
795
            $res_name = Database::query($check_name);
796
        }
797
        // New name does not exist yet; keep it.
798
        // Escape description.
799
        // Kevin: added htmlentities().
800
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
801
        $type = 1;
802
        switch ($learnpath) {
803
            case 'guess':
804
                break;
805
            case 'dokeos':
806
            case 'chamilo':
807
                $type = 1;
808
                break;
809
            case 'aicc':
810
                break;
811
        }
812
813
        switch ($origin) {
814
            case 'zip':
815
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
816
                break;
817
            case 'manual':
818
            default:
819
                $get_max = "SELECT MAX(display_order)
820
                            FROM $tbl_lp WHERE c_id = $course_id";
821
                $res_max = Database::query($get_max);
822
                if (Database::num_rows($res_max) < 1) {
823
                    $dsp = 1;
824
                } else {
825
                    $row = Database::fetch_array($res_max);
826
                    $dsp = $row[0] + 1;
827
                }
828
829
                $params = [
830
                    'c_id' => $course_id,
831
                    'lp_type' => $type,
832
                    'name' => $name,
833
                    'description' => $description,
834
                    'path' => '',
835
                    'default_view_mod' => 'embedded',
836
                    'default_encoding' => 'UTF-8',
837
                    'display_order' => $dsp,
838
                    'content_maker' => 'Chamilo',
839
                    'content_local' => 'local',
840
                    'js_lib' => '',
841
                    'session_id' => $session_id,
842
                    'created_on' => api_get_utc_datetime(),
843
                    'modified_on' => api_get_utc_datetime(),
844
                    'publicated_on' => $publicated_on,
845
                    'expired_on' => $expired_on,
846
                    'category_id' => $categoryId,
847
                    'force_commit' => 0,
848
                    'content_license' => '',
849
                    'debug' => 0,
850
                    'theme' => '',
851
                    'preview_image' => '',
852
                    'author' => '',
853
                    'prerequisite' => 0,
854
                    'hide_toc_frame' => 0,
855
                    'seriousgame_mode' => 0,
856
                    'autolaunch' => 0,
857
                    'max_attempts' => 0,
858
                    'subscribe_users' => 0,
859
                    'accumulate_scorm_time' => 1,
860
                ];
861
                $id = Database::insert($tbl_lp, $params);
862
863
                if ($id > 0) {
864
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
865
                    Database::query($sql);
866
867
                    // Insert into item_property.
868
                    api_item_property_update(
869
                        $courseInfo,
870
                        TOOL_LEARNPATH,
871
                        $id,
872
                        'LearnpathAdded',
873
                        $userId
874
                    );
875
                    api_set_default_visibility(
876
                        $id,
877
                        TOOL_LEARNPATH,
878
                        0,
879
                        $courseInfo,
880
                        $session_id,
881
                        $userId
882
                    );
883
884
                    return $id;
885
                }
886
                break;
887
        }
888
    }
889
890
    /**
891
     * Auto completes the parents of an item in case it's been completed or passed.
892
     *
893
     * @param int $item Optional ID of the item from which to look for parents
894
     */
895
    public function autocomplete_parents($item)
896
    {
897
        $debug = $this->debug;
898
899
        if (empty($item)) {
900
            $item = $this->current;
901
        }
902
903
        $currentItem = $this->getItem($item);
904
        if ($currentItem) {
905
            $parent_id = $currentItem->get_parent();
906
            $parent = $this->getItem($parent_id);
907
            if ($parent) {
908
                // if $item points to an object and there is a parent.
909
                if ($debug) {
910
                    error_log(
911
                        'Autocompleting parent of item '.$item.' '.
912
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
913
                        0
914
                    );
915
                }
916
917
                // New experiment including failed and browsed in completed status.
918
                //$current_status = $currentItem->get_status();
919
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
920
                // Fixes chapter auto complete
921
                if (true) {
922
                    // If the current item is completed or passes or succeeded.
923
                    $updateParentStatus = true;
924
                    if ($debug) {
925
                        error_log('Status of current item is alright');
926
                    }
927
928
                    foreach ($parent->get_children() as $childItemId) {
929
                        $childItem = $this->getItem($childItemId);
930
931
                        // If children was not set try to get the info
932
                        if (empty($childItem->db_item_view_id)) {
933
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
934
                        }
935
936
                        // Check all his brothers (parent's children) for completion status.
937
                        if ($childItemId != $item) {
938
                            if ($debug) {
939
                                error_log(
940
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
941
                                    0
942
                                );
943
                            }
944
                            // Trying completing parents of failed and browsed items as well.
945
                            if ($childItem->status_is(
946
                                [
947
                                    'completed',
948
                                    'passed',
949
                                    'succeeded',
950
                                    'browsed',
951
                                    'failed',
952
                                ]
953
                            )
954
                            ) {
955
                                // Keep completion status to true.
956
                                continue;
957
                            } else {
958
                                if ($debug > 2) {
959
                                    error_log(
960
                                        '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,
961
                                        0
962
                                    );
963
                                }
964
                                $updateParentStatus = false;
965
                                break;
966
                            }
967
                        }
968
                    }
969
970
                    if ($updateParentStatus) {
971
                        // If all the children were completed:
972
                        $parent->set_status('completed');
973
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
974
                        // Force the status to "completed"
975
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
976
                        $this->update_queue[$parent->get_id()] = 'completed';
977
                        if ($debug) {
978
                            error_log(
979
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
980
                                print_r($this->update_queue, 1),
981
                                0
982
                            );
983
                        }
984
                        // Recursive call.
985
                        $this->autocomplete_parents($parent->get_id());
986
                    }
987
                }
988
            } else {
989
                if ($debug) {
990
                    error_log("Parent #$parent_id does not exists");
991
                }
992
            }
993
        } else {
994
            if ($debug) {
995
                error_log("#$item is an item that doesn't have parents");
996
            }
997
        }
998
    }
999
1000
    /**
1001
     * Closes the current resource.
1002
     *
1003
     * Stops the timer
1004
     * Saves into the database if required
1005
     * Clears the current resource data from this object
1006
     *
1007
     * @return bool True on success, false on failure
1008
     */
1009
    public function close()
1010
    {
1011
        if (empty($this->lp_id)) {
1012
            $this->error = 'Trying to close this learnpath but no ID is set';
1013
1014
            return false;
1015
        }
1016
        $this->current_time_stop = time();
1017
        $this->ordered_items = [];
1018
        $this->index = 0;
1019
        unset($this->lp_id);
1020
        //unset other stuff
1021
        return true;
1022
    }
1023
1024
    /**
1025
     * Static admin function allowing removal of a learnpath.
1026
     *
1027
     * @param array  $courseInfo
1028
     * @param int    $id         Learnpath ID
1029
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1030
     *
1031
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1032
     */
1033
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1034
    {
1035
        $course_id = api_get_course_int_id();
1036
        if (!empty($courseInfo)) {
1037
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1038
        }
1039
1040
        // TODO: Implement a way of getting this to work when the current object is not set.
1041
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1042
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1043
        if (!empty($id) && ($id != $this->lp_id)) {
1044
            return false;
1045
        }
1046
1047
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1048
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1049
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1050
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1051
1052
        // Delete lp item id.
1053
        foreach ($this->items as $lpItemId => $dummy) {
1054
            $sql = "DELETE FROM $lp_item_view
1055
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1056
            Database::query($sql);
1057
        }
1058
1059
        // Proposed by Christophe (nickname: clefevre)
1060
        $sql = "DELETE FROM $lp_item
1061
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1062
        Database::query($sql);
1063
1064
        $sql = "DELETE FROM $lp_view
1065
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1066
        Database::query($sql);
1067
1068
        self::toggle_publish($this->lp_id, 'i');
1069
1070
        if ($this->type == 2 || $this->type == 3) {
1071
            // This is a scorm learning path, delete the files as well.
1072
            $sql = "SELECT path FROM $lp
1073
                    WHERE iid = ".$this->lp_id;
1074
            $res = Database::query($sql);
1075
            if (Database::num_rows($res) > 0) {
1076
                $row = Database::fetch_array($res);
1077
                $path = $row['path'];
1078
                $sql = "SELECT id FROM $lp
1079
                        WHERE
1080
                            c_id = $course_id AND
1081
                            path = '$path' AND
1082
                            iid != ".$this->lp_id;
1083
                $res = Database::query($sql);
1084
                if (Database::num_rows($res) > 0) {
1085
                    // Another learning path uses this directory, so don't delete it.
1086
                    if ($this->debug > 2) {
1087
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1088
                    }
1089
                } else {
1090
                    // No other LP uses that directory, delete it.
1091
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1092
                    // The absolute system path for this course.
1093
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1094
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1095
                        if ($this->debug > 2) {
1096
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1097
                        }
1098
                        // Proposed by Christophe (clefevre).
1099
                        if (strcmp(substr($path, -2), "/.") == 0) {
1100
                            $path = substr($path, 0, -1); // Remove "." at the end.
1101
                        }
1102
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1103
                        rmdirr($course_scorm_dir.$path);
1104
                    }
1105
                }
1106
            }
1107
        }
1108
1109
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1110
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1111
        // Delete tools
1112
        $sql = "DELETE FROM $tbl_tool
1113
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1114
        Database::query($sql);
1115
1116
        $sql = "DELETE FROM $lp
1117
                WHERE iid = ".$this->lp_id;
1118
        Database::query($sql);
1119
        // Updates the display order of all lps.
1120
        $this->update_display_order();
1121
1122
        api_item_property_update(
1123
            api_get_course_info(),
1124
            TOOL_LEARNPATH,
1125
            $this->lp_id,
1126
            'delete',
1127
            api_get_user_id()
1128
        );
1129
1130
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1131
            api_get_course_id(),
1132
            4,
1133
            $id,
1134
            api_get_session_id()
1135
        );
1136
1137
        if ($link_info !== false) {
1138
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1139
        }
1140
1141
        if (api_get_setting('search_enabled') == 'true') {
1142
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1143
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1144
        }
1145
    }
1146
1147
    /**
1148
     * Removes all the children of one item - dangerous!
1149
     *
1150
     * @param int $id Element ID of which children have to be removed
1151
     *
1152
     * @return int Total number of children removed
1153
     */
1154
    public function delete_children_items($id)
1155
    {
1156
        $course_id = $this->course_info['real_id'];
1157
1158
        $num = 0;
1159
        $id = (int) $id;
1160
        if (empty($id) || empty($course_id)) {
1161
            return false;
1162
        }
1163
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1164
        $sql = "SELECT * FROM $lp_item
1165
                WHERE c_id = $course_id AND parent_item_id = $id";
1166
        $res = Database::query($sql);
1167
        while ($row = Database::fetch_array($res)) {
1168
            $num += $this->delete_children_items($row['iid']);
1169
            $sql = "DELETE FROM $lp_item
1170
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1171
            Database::query($sql);
1172
            $num++;
1173
        }
1174
1175
        return $num;
1176
    }
1177
1178
    /**
1179
     * Removes an item from the current learnpath.
1180
     *
1181
     * @param int $id Elem ID (0 if first)
1182
     *
1183
     * @return int Number of elements moved
1184
     *
1185
     * @todo implement resource removal
1186
     */
1187
    public function delete_item($id)
1188
    {
1189
        $course_id = api_get_course_int_id();
1190
        $id = (int) $id;
1191
        // TODO: Implement the resource removal.
1192
        if (empty($id) || empty($course_id)) {
1193
            return false;
1194
        }
1195
        // First select item to get previous, next, and display order.
1196
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1197
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1198
        $res_sel = Database::query($sql_sel);
1199
        if (Database::num_rows($res_sel) < 1) {
1200
            return false;
1201
        }
1202
        $row = Database::fetch_array($res_sel);
1203
        $previous = $row['previous_item_id'];
1204
        $next = $row['next_item_id'];
1205
        $display = $row['display_order'];
1206
        $parent = $row['parent_item_id'];
1207
        $lp = $row['lp_id'];
1208
        // Delete children items.
1209
        $this->delete_children_items($id);
1210
        // Now delete the item.
1211
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1212
        Database::query($sql_del);
1213
        // Now update surrounding items.
1214
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1215
                    WHERE iid = $previous";
1216
        Database::query($sql_upd);
1217
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1218
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1219
        Database::query($sql_upd);
1220
        // Now update all following items with new display order.
1221
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1222
                    WHERE
1223
                        c_id = $course_id AND
1224
                        lp_id = $lp AND
1225
                        parent_item_id = $parent AND
1226
                        display_order > $display";
1227
        Database::query($sql_all);
1228
1229
        //Removing prerequisites since the item will not longer exist
1230
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1231
                    WHERE c_id = $course_id AND prerequisite = $id";
1232
        Database::query($sql_all);
1233
1234
        $sql = "UPDATE $lp_item
1235
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1236
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1237
        Database::query($sql);
1238
1239
        // Remove from search engine if enabled.
1240
        if (api_get_setting('search_enabled') === 'true') {
1241
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1242
            $sql = 'SELECT * FROM %s
1243
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1244
                    LIMIT 1';
1245
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1246
            $res = Database::query($sql);
1247
            if (Database::num_rows($res) > 0) {
1248
                $row2 = Database::fetch_array($res);
1249
                $di = new ChamiloIndexer();
1250
                $di->remove_document($row2['search_did']);
1251
            }
1252
            $sql = 'DELETE FROM %s
1253
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1254
                    LIMIT 1';
1255
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1256
            Database::query($sql);
1257
        }
1258
    }
1259
1260
    /**
1261
     * Updates an item's content in place.
1262
     *
1263
     * @param int    $id               Element ID
1264
     * @param int    $parent           Parent item ID
1265
     * @param int    $previous         Previous item ID
1266
     * @param string $title            Item title
1267
     * @param string $description      Item description
1268
     * @param string $prerequisites    Prerequisites (optional)
1269
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1270
     * @param int    $max_time_allowed
1271
     * @param string $url
1272
     *
1273
     * @return bool True on success, false on error
1274
     */
1275
    public function edit_item(
1276
        $id,
1277
        $parent,
1278
        $previous,
1279
        $title,
1280
        $description,
1281
        $prerequisites = '0',
1282
        $audio = [],
1283
        $max_time_allowed = 0,
1284
        $url = ''
1285
    ) {
1286
        $course_id = api_get_course_int_id();
1287
        $_course = api_get_course_info();
1288
        $id = (int) $id;
1289
1290
        if (empty($max_time_allowed)) {
1291
            $max_time_allowed = 0;
1292
        }
1293
1294
        if (empty($id) || empty($_course)) {
1295
            return false;
1296
        }
1297
1298
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1299
        $sql = "SELECT * FROM $tbl_lp_item
1300
                WHERE iid = $id";
1301
        $res_select = Database::query($sql);
1302
        $row_select = Database::fetch_array($res_select);
1303
        $audio_update_sql = '';
1304
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1305
            // Create the audio folder if it does not exist yet.
1306
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1307
            if (!is_dir($filepath.'audio')) {
1308
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1309
                $audio_id = add_document(
1310
                    $_course,
1311
                    '/audio',
1312
                    'folder',
1313
                    0,
1314
                    'audio'
1315
                );
1316
                api_item_property_update(
1317
                    $_course,
1318
                    TOOL_DOCUMENT,
1319
                    $audio_id,
1320
                    'FolderCreated',
1321
                    api_get_user_id(),
1322
                    null,
1323
                    null,
1324
                    null,
1325
                    null,
1326
                    api_get_session_id()
1327
                );
1328
                api_item_property_update(
1329
                    $_course,
1330
                    TOOL_DOCUMENT,
1331
                    $audio_id,
1332
                    'invisible',
1333
                    api_get_user_id(),
1334
                    null,
1335
                    null,
1336
                    null,
1337
                    null,
1338
                    api_get_session_id()
1339
                );
1340
            }
1341
1342
            // Upload file in documents.
1343
            $pi = pathinfo($audio['name']);
1344
            if ($pi['extension'] === 'mp3') {
1345
                $c_det = api_get_course_info($this->cc);
1346
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1347
                $path = handle_uploaded_document(
1348
                    $c_det,
1349
                    $audio,
1350
                    $bp,
1351
                    '/audio',
1352
                    api_get_user_id(),
1353
                    0,
1354
                    null,
1355
                    0,
1356
                    'rename',
1357
                    false,
1358
                    0
1359
                );
1360
                $path = substr($path, 7);
1361
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1362
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1363
            }
1364
        }
1365
1366
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1367
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1368
1369
        // TODO: htmlspecialchars to be checked for encoding related problems.
1370
        if ($same_parent && $same_previous) {
1371
            // Only update title and description.
1372
            $sql = "UPDATE $tbl_lp_item
1373
                    SET title = '".Database::escape_string($title)."',
1374
                        prerequisite = '".$prerequisites."',
1375
                        description = '".Database::escape_string($description)."'
1376
                        ".$audio_update_sql.",
1377
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1378
                    WHERE iid = $id";
1379
            Database::query($sql);
1380
        } else {
1381
            $old_parent = $row_select['parent_item_id'];
1382
            $old_previous = $row_select['previous_item_id'];
1383
            $old_next = $row_select['next_item_id'];
1384
            $old_order = $row_select['display_order'];
1385
            $old_prerequisite = $row_select['prerequisite'];
1386
            $old_max_time_allowed = $row_select['max_time_allowed'];
1387
1388
            /* BEGIN -- virtually remove the current item id */
1389
            /* for the next and previous item it is like the current item doesn't exist anymore */
1390
            if ($old_previous != 0) {
1391
                // Next
1392
                $sql = "UPDATE $tbl_lp_item
1393
                        SET next_item_id = $old_next
1394
                        WHERE iid = $old_previous";
1395
                Database::query($sql);
1396
            }
1397
1398
            if (!empty($old_next)) {
1399
                // Previous
1400
                $sql = "UPDATE $tbl_lp_item
1401
                        SET previous_item_id = $old_previous
1402
                        WHERE iid = $old_next";
1403
                Database::query($sql);
1404
            }
1405
1406
            // display_order - 1 for every item with a display_order
1407
            // bigger then the display_order of the current item.
1408
            $sql = "UPDATE $tbl_lp_item
1409
                    SET display_order = display_order - 1
1410
                    WHERE
1411
                        c_id = $course_id AND
1412
                        display_order > $old_order AND
1413
                        lp_id = ".$this->lp_id." AND
1414
                        parent_item_id = $old_parent";
1415
            Database::query($sql);
1416
            /* END -- virtually remove the current item id */
1417
1418
            /* BEGIN -- update the current item id to his new location */
1419
            if ($previous == 0) {
1420
                // Select the data of the item that should come after the current item.
1421
                $sql = "SELECT id, display_order
1422
                        FROM $tbl_lp_item
1423
                        WHERE
1424
                            c_id = $course_id AND
1425
                            lp_id = ".$this->lp_id." AND
1426
                            parent_item_id = $parent AND
1427
                            previous_item_id = $previous";
1428
                $res_select_old = Database::query($sql);
1429
                $row_select_old = Database::fetch_array($res_select_old);
1430
1431
                // If the new parent didn't have children before.
1432
                if (Database::num_rows($res_select_old) == 0) {
1433
                    $new_next = 0;
1434
                    $new_order = 1;
1435
                } else {
1436
                    $new_next = $row_select_old['id'];
1437
                    $new_order = $row_select_old['display_order'];
1438
                }
1439
            } else {
1440
                // Select the data of the item that should come before the current item.
1441
                $sql = "SELECT next_item_id, display_order
1442
                        FROM $tbl_lp_item
1443
                        WHERE iid = $previous";
1444
                $res_select_old = Database::query($sql);
1445
                $row_select_old = Database::fetch_array($res_select_old);
1446
                $new_next = $row_select_old['next_item_id'];
1447
                $new_order = $row_select_old['display_order'] + 1;
1448
            }
1449
1450
            // TODO: htmlspecialchars to be checked for encoding related problems.
1451
            // Update the current item with the new data.
1452
            $sql = "UPDATE $tbl_lp_item
1453
                    SET
1454
                        title = '".Database::escape_string($title)."',
1455
                        description = '".Database::escape_string($description)."',
1456
                        parent_item_id = $parent,
1457
                        previous_item_id = $previous,
1458
                        next_item_id = $new_next,
1459
                        display_order = $new_order
1460
                        $audio_update_sql
1461
                    WHERE iid = $id";
1462
            Database::query($sql);
1463
1464
            if ($previous != 0) {
1465
                // Update the previous item's next_item_id.
1466
                $sql = "UPDATE $tbl_lp_item
1467
                        SET next_item_id = $id
1468
                        WHERE iid = $previous";
1469
                Database::query($sql);
1470
            }
1471
1472
            if (!empty($new_next)) {
1473
                // Update the next item's previous_item_id.
1474
                $sql = "UPDATE $tbl_lp_item
1475
                        SET previous_item_id = $id
1476
                        WHERE iid = $new_next";
1477
                Database::query($sql);
1478
            }
1479
1480
            if ($old_prerequisite != $prerequisites) {
1481
                $sql = "UPDATE $tbl_lp_item
1482
                        SET prerequisite = '$prerequisites'
1483
                        WHERE iid = $id";
1484
                Database::query($sql);
1485
            }
1486
1487
            if ($old_max_time_allowed != $max_time_allowed) {
1488
                // update max time allowed
1489
                $sql = "UPDATE $tbl_lp_item
1490
                        SET max_time_allowed = $max_time_allowed
1491
                        WHERE iid = $id";
1492
                Database::query($sql);
1493
            }
1494
1495
            // Update all the items with the same or a bigger display_order than the current item.
1496
            $sql = "UPDATE $tbl_lp_item
1497
                    SET display_order = display_order + 1
1498
                    WHERE
1499
                       c_id = $course_id AND
1500
                       lp_id = ".$this->get_id()." AND
1501
                       iid <> $id AND
1502
                       parent_item_id = $parent AND
1503
                       display_order >= $new_order";
1504
            Database::query($sql);
1505
        }
1506
1507
        if ($row_select['item_type'] == 'link') {
1508
            $link = new Link();
1509
            $linkId = $row_select['path'];
1510
            $link->updateLink($linkId, $url);
1511
        }
1512
    }
1513
1514
    /**
1515
     * Updates an item's prereq in place.
1516
     *
1517
     * @param int    $id              Element ID
1518
     * @param string $prerequisite_id Prerequisite Element ID
1519
     * @param int    $minScore        Prerequisite min score
1520
     * @param int    $maxScore        Prerequisite max score
1521
     *
1522
     * @return bool True on success, false on error
1523
     */
1524
    public function edit_item_prereq(
1525
        $id,
1526
        $prerequisite_id,
1527
        $minScore = 0,
1528
        $maxScore = 100
1529
    ) {
1530
        $id = (int) $id;
1531
        $prerequisite_id = (int) $prerequisite_id;
1532
1533
        if (empty($id)) {
1534
            return false;
1535
        }
1536
1537
        if (empty($minScore) || $minScore < 0) {
1538
            $minScore = 0;
1539
        }
1540
1541
        if (empty($maxScore) || $maxScore < 0) {
1542
            $maxScore = 100;
1543
        }
1544
1545
        $minScore = floatval($minScore);
1546
        $maxScore = floatval($maxScore);
1547
1548
        if (empty($prerequisite_id)) {
1549
            $prerequisite_id = 'NULL';
1550
            $minScore = 0;
1551
            $maxScore = 100;
1552
        }
1553
1554
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1555
        $sql = " UPDATE $tbl_lp_item
1556
                 SET
1557
                    prerequisite = $prerequisite_id ,
1558
                    prerequisite_min_score = $minScore ,
1559
                    prerequisite_max_score = $maxScore
1560
                 WHERE iid = $id";
1561
1562
        Database::query($sql);
1563
1564
        return true;
1565
    }
1566
1567
    /**
1568
     * Get the specific prefix index terms of this learning path.
1569
     *
1570
     * @param string $prefix
1571
     *
1572
     * @return array Array of terms
1573
     */
1574
    public function get_common_index_terms_by_prefix($prefix)
1575
    {
1576
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1577
        $terms = get_specific_field_values_list_by_prefix(
1578
            $prefix,
1579
            $this->cc,
1580
            TOOL_LEARNPATH,
1581
            $this->lp_id
1582
        );
1583
        $prefix_terms = [];
1584
        if (!empty($terms)) {
1585
            foreach ($terms as $term) {
1586
                $prefix_terms[] = $term['value'];
1587
            }
1588
        }
1589
1590
        return $prefix_terms;
1591
    }
1592
1593
    /**
1594
     * Gets the number of items currently completed.
1595
     *
1596
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1597
     *
1598
     * @return int The number of items currently completed
1599
     */
1600
    public function get_complete_items_count($failedStatusException = false)
1601
    {
1602
        $i = 0;
1603
        $completedStatusList = [
1604
            'completed',
1605
            'passed',
1606
            'succeeded',
1607
            'browsed',
1608
        ];
1609
1610
        if (!$failedStatusException) {
1611
            $completedStatusList[] = 'failed';
1612
        }
1613
1614
        foreach ($this->items as $id => $dummy) {
1615
            // Trying failed and browsed considered "progressed" as well.
1616
            if ($this->items[$id]->status_is($completedStatusList) &&
1617
                $this->items[$id]->get_type() != 'dir'
1618
            ) {
1619
                $i++;
1620
            }
1621
        }
1622
1623
        return $i;
1624
    }
1625
1626
    /**
1627
     * Gets the current item ID.
1628
     *
1629
     * @return int The current learnpath item id
1630
     */
1631
    public function get_current_item_id()
1632
    {
1633
        $current = 0;
1634
        if (!empty($this->current)) {
1635
            $current = (int) $this->current;
1636
        }
1637
1638
        return $current;
1639
    }
1640
1641
    /**
1642
     * Force to get the first learnpath item id.
1643
     *
1644
     * @return int The current learnpath item id
1645
     */
1646
    public function get_first_item_id()
1647
    {
1648
        $current = 0;
1649
        if (is_array($this->ordered_items)) {
1650
            $current = $this->ordered_items[0];
1651
        }
1652
1653
        return $current;
1654
    }
1655
1656
    /**
1657
     * Gets the total number of items available for viewing in this SCORM.
1658
     *
1659
     * @return int The total number of items
1660
     */
1661
    public function get_total_items_count()
1662
    {
1663
        return count($this->items);
1664
    }
1665
1666
    /**
1667
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1668
     *
1669
     * @return int The total no-chapters number of items
1670
     */
1671
    public function getTotalItemsCountWithoutDirs()
1672
    {
1673
        $total = 0;
1674
        $typeListNotToCount = self::getChapterTypes();
1675
        foreach ($this->items as $temp2) {
1676
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1677
                $total++;
1678
            }
1679
        }
1680
1681
        return $total;
1682
    }
1683
1684
    /**
1685
     *  Sets the first element URL.
1686
     */
1687
    public function first()
1688
    {
1689
        if ($this->debug > 0) {
1690
            error_log('In learnpath::first()', 0);
1691
            error_log('$this->last_item_seen '.$this->last_item_seen);
1692
        }
1693
1694
        // Test if the last_item_seen exists and is not a dir.
1695
        if (count($this->ordered_items) == 0) {
1696
            $this->index = 0;
1697
        }
1698
1699
        if (!empty($this->last_item_seen) &&
1700
            !empty($this->items[$this->last_item_seen]) &&
1701
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1702
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1703
            //&& !$this->items[$this->last_item_seen]->is_done()
1704
        ) {
1705
            if ($this->debug > 2) {
1706
                error_log(
1707
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1708
                    $this->items[$this->last_item_seen]->get_type()
1709
                );
1710
            }
1711
            $index = -1;
1712
            foreach ($this->ordered_items as $myindex => $item_id) {
1713
                if ($item_id == $this->last_item_seen) {
1714
                    $index = $myindex;
1715
                    break;
1716
                }
1717
            }
1718
            if ($index == -1) {
1719
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1720
                if ($this->debug > 2) {
1721
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1722
                }
1723
1724
                return false;
1725
            } else {
1726
                $this->last = $this->last_item_seen;
1727
                $this->current = $this->last_item_seen;
1728
                $this->index = $index;
1729
            }
1730
        } else {
1731
            if ($this->debug > 2) {
1732
                error_log('In learnpath::first() - No last item seen', 0);
1733
            }
1734
            $index = 0;
1735
            // Loop through all ordered items and stop at the first item that is
1736
            // not a directory *and* that has not been completed yet.
1737
            while (!empty($this->ordered_items[$index]) &&
1738
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1739
                (
1740
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1741
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1742
                ) && $index < $this->max_ordered_items) {
1743
                $index++;
1744
            }
1745
1746
            $this->last = $this->current;
1747
            // current is
1748
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1749
            $this->index = $index;
1750
            if ($this->debug > 2) {
1751
                error_log('$index '.$index);
1752
                error_log('In learnpath::first() - No last item seen');
1753
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1754
            }
1755
        }
1756
        if ($this->debug > 2) {
1757
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1758
        }
1759
    }
1760
1761
    /**
1762
     * Gets the js library from the database.
1763
     *
1764
     * @return string The name of the javascript library to be used
1765
     */
1766
    public function get_js_lib()
1767
    {
1768
        $lib = '';
1769
        if (!empty($this->js_lib)) {
1770
            $lib = $this->js_lib;
1771
        }
1772
1773
        return $lib;
1774
    }
1775
1776
    /**
1777
     * Gets the learnpath database ID.
1778
     *
1779
     * @return int Learnpath ID in the lp table
1780
     */
1781
    public function get_id()
1782
    {
1783
        if (!empty($this->lp_id)) {
1784
            return (int) $this->lp_id;
1785
        }
1786
1787
        return 0;
1788
    }
1789
1790
    /**
1791
     * Gets the last element URL.
1792
     *
1793
     * @return string URL to load into the viewer
1794
     */
1795
    public function get_last()
1796
    {
1797
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1798
        if (count($this->ordered_items) > 0) {
1799
            $this->index = count($this->ordered_items) - 1;
1800
1801
            return $this->ordered_items[$this->index];
1802
        }
1803
1804
        return false;
1805
    }
1806
1807
    /**
1808
     * Get the last element in the first level.
1809
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1810
     *
1811
     * @return mixed
1812
     */
1813
    public function getLastInFirstLevel()
1814
    {
1815
        try {
1816
            $lastId = Database::getManager()
1817
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1818
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1819
                ->setMaxResults(1)
1820
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1821
                ->getSingleScalarResult();
1822
1823
            return $lastId;
1824
        } catch (Exception $exception) {
1825
            return 0;
1826
        }
1827
    }
1828
1829
    /**
1830
     * Gets the navigation bar for the learnpath display screen.
1831
     *
1832
     * @param string $barId
1833
     *
1834
     * @return string The HTML string to use as a navigation bar
1835
     */
1836
    public function get_navigation_bar($barId = '')
1837
    {
1838
        if (empty($barId)) {
1839
            $barId = 'control-top';
1840
        }
1841
        $lpId = $this->lp_id;
1842
        $mycurrentitemid = $this->get_current_item_id();
1843
1844
        $reportingText = get_lang('Reporting');
1845
        $previousText = get_lang('ScormPrevious');
1846
        $nextText = get_lang('ScormNext');
1847
        $fullScreenText = get_lang('ScormExitFullScreen');
1848
1849
        $settings = api_get_configuration_value('lp_view_settings');
1850
        $display = isset($settings['display']) ? $settings['display'] : false;
1851
        $reportingIcon = '
1852
            <a class="icon-toolbar"
1853
                id="stats_link"
1854
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1855
                onclick="window.parent.API.save_asset(); return true;"
1856
                target="content_name" title="'.$reportingText.'">
1857
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1858
            </a>';
1859
1860
        if (!empty($display)) {
1861
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1862
            if ($showReporting === false) {
1863
                $reportingIcon = '';
1864
            }
1865
        }
1866
1867
        $hideArrows = false;
1868
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1869
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1870
        }
1871
1872
        $previousIcon = '';
1873
        $nextIcon = '';
1874
        if ($hideArrows === false) {
1875
            $previousIcon = '
1876
                <a class="icon-toolbar" id="scorm-previous" href="#"
1877
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1878
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1879
                </a>';
1880
1881
            $nextIcon = '
1882
                <a class="icon-toolbar" id="scorm-next" href="#"
1883
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1884
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1885
                </a>';
1886
        }
1887
1888
        if ($this->mode === 'fullscreen') {
1889
            $navbar = '
1890
                  <span id="'.$barId.'" class="buttons">
1891
                    '.$reportingIcon.'
1892
                    '.$previousIcon.'
1893
                    '.$nextIcon.'
1894
                    <a class="icon-toolbar" id="view-embedded"
1895
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1896
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1897
                    </a>
1898
                  </span>';
1899
        } else {
1900
            $navbar = '
1901
                 <span id="'.$barId.'" class="buttons text-right">
1902
                    '.$reportingIcon.'
1903
                    '.$previousIcon.'
1904
                    '.$nextIcon.'
1905
                </span>';
1906
        }
1907
1908
        return $navbar;
1909
    }
1910
1911
    /**
1912
     * Gets the next resource in queue (url).
1913
     *
1914
     * @return string URL to load into the viewer
1915
     */
1916
    public function get_next_index()
1917
    {
1918
        // TODO
1919
        $index = $this->index;
1920
        $index++;
1921
        while (
1922
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1923
            $index < $this->max_ordered_items
1924
        ) {
1925
            $index++;
1926
            if ($index == $this->max_ordered_items) {
1927
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1928
                    return $this->index;
1929
                }
1930
1931
                return $index;
1932
            }
1933
        }
1934
        if (empty($this->ordered_items[$index])) {
1935
            return $this->index;
1936
        }
1937
1938
        return $index;
1939
    }
1940
1941
    /**
1942
     * Gets item_id for the next element.
1943
     *
1944
     * @return int Next item (DB) ID
1945
     */
1946
    public function get_next_item_id()
1947
    {
1948
        $new_index = $this->get_next_index();
1949
        if (!empty($new_index)) {
1950
            if (isset($this->ordered_items[$new_index])) {
1951
                return $this->ordered_items[$new_index];
1952
            }
1953
        }
1954
1955
        return 0;
1956
    }
1957
1958
    /**
1959
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1960
     *
1961
     * Generally, the package provided is in the form of a zip file, so the function
1962
     * has been written to test a zip file. If not a zip, the function will return the
1963
     * default return value: ''
1964
     *
1965
     * @param string $file_path the path to the file
1966
     * @param string $file_name the original name of the file
1967
     *
1968
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1969
     */
1970
    public static function get_package_type($file_path, $file_name)
1971
    {
1972
        // Get name of the zip file without the extension.
1973
        $file_info = pathinfo($file_name);
1974
        $extension = $file_info['extension']; // Extension only.
1975
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1976
                'dll',
1977
                'exe',
1978
            ])) {
1979
            return 'oogie';
1980
        }
1981
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1982
                'dll',
1983
                'exe',
1984
            ])) {
1985
            return 'woogie';
1986
        }
1987
1988
        $zipFile = new PclZip($file_path);
1989
        // Check the zip content (real size and file extension).
1990
        $zipContentArray = $zipFile->listContent();
1991
        $package_type = '';
1992
        $manifest = '';
1993
        $aicc_match_crs = 0;
1994
        $aicc_match_au = 0;
1995
        $aicc_match_des = 0;
1996
        $aicc_match_cst = 0;
1997
1998
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1999
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2000
            foreach ($zipContentArray as $thisContent) {
2001
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2002
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2003
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2004
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2005
                    $package_type = 'scorm';
2006
                    break; // Exit the foreach loop.
2007
                } elseif (
2008
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2009
                    in_array(
2010
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2011
                        ['crs', 'au', 'des', 'cst']
2012
                    )
2013
                ) {
2014
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2015
                    switch ($ext) {
2016
                        case 'crs':
2017
                            $aicc_match_crs = 1;
2018
                            break;
2019
                        case 'au':
2020
                            $aicc_match_au = 1;
2021
                            break;
2022
                        case 'des':
2023
                            $aicc_match_des = 1;
2024
                            break;
2025
                        case 'cst':
2026
                            $aicc_match_cst = 1;
2027
                            break;
2028
                        default:
2029
                            break;
2030
                    }
2031
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2032
                } else {
2033
                    $package_type = '';
2034
                }
2035
            }
2036
        }
2037
2038
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2039
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2040
            $package_type = 'aicc';
2041
        }
2042
2043
        // Try with chamilo course builder
2044
        if (empty($package_type)) {
2045
            $package_type = 'chamilo';
2046
        }
2047
2048
        return $package_type;
2049
    }
2050
2051
    /**
2052
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2053
     *
2054
     * @return string URL to load into the viewer
2055
     */
2056
    public function get_previous_index()
2057
    {
2058
        $index = $this->index;
2059
        if (isset($this->ordered_items[$index - 1])) {
2060
            $index--;
2061
            while (isset($this->ordered_items[$index]) &&
2062
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2063
            ) {
2064
                $index--;
2065
                if ($index < 0) {
2066
                    return $this->index;
2067
                }
2068
            }
2069
        }
2070
2071
        return $index;
2072
    }
2073
2074
    /**
2075
     * Gets item_id for the next element.
2076
     *
2077
     * @return int Previous item (DB) ID
2078
     */
2079
    public function get_previous_item_id()
2080
    {
2081
        $index = $this->get_previous_index();
2082
2083
        return $this->ordered_items[$index];
2084
    }
2085
2086
    /**
2087
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2088
     *
2089
     * @param int    $lpItemId
2090
     * @param string $autostart
2091
     *
2092
     * @return string The mediaplayer HTML
2093
     */
2094
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2095
    {
2096
        $course_id = api_get_course_int_id();
2097
        $_course = api_get_course_info();
2098
        if (empty($_course)) {
2099
            return '';
2100
        }
2101
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2102
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2103
        $lpItemId = (int) $lpItemId;
2104
2105
        /** @var learnpathItem $item */
2106
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2107
        $itemViewId = 0;
2108
        if ($item) {
2109
            $itemViewId = (int) $item->db_item_view_id;
2110
        }
2111
2112
        // Getting all the information about the item.
2113
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2114
                FROM $tbl_lp_item as lpi
2115
                INNER JOIN $tbl_lp_item_view as lp_view
2116
                ON (lpi.iid = lp_view.lp_item_id)
2117
                WHERE
2118
                    lp_view.iid = $itemViewId AND
2119
                    lpi.iid = $lpItemId AND
2120
                    lp_view.c_id = $course_id";
2121
        $result = Database::query($sql);
2122
        $row = Database::fetch_assoc($result);
2123
        $output = '';
2124
2125
        if (!empty($row['audio'])) {
2126
            $list = $_SESSION['oLP']->get_toc();
2127
2128
            switch ($row['item_type']) {
2129
                case 'quiz':
2130
                    $type_quiz = false;
2131
                    foreach ($list as $toc) {
2132
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2133
                            $type_quiz = true;
2134
                        }
2135
                    }
2136
2137
                    if ($type_quiz) {
2138
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2139
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2140
                        } else {
2141
                            $autostart_audio = $autostart;
2142
                        }
2143
                    }
2144
                    break;
2145
                case TOOL_READOUT_TEXT:;
2146
                    $autostart_audio = 'false';
2147
                    break;
2148
                default:
2149
                    $autostart_audio = 'true';
2150
            }
2151
2152
            $courseInfo = api_get_course_info();
2153
            $audio = $row['audio'];
2154
2155
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2156
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2157
2158
            if (!file_exists($file)) {
2159
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2160
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2161
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2162
            }
2163
2164
            $player = Display::getMediaPlayer(
2165
                $file,
2166
                [
2167
                    'id' => 'lp_audio_media_player',
2168
                    'url' => $url,
2169
                    'autoplay' => $autostart_audio,
2170
                    'width' => '100%',
2171
                ]
2172
            );
2173
2174
            // The mp3 player.
2175
            $output = '<div id="container">';
2176
            $output .= $player;
2177
            $output .= '</div>';
2178
        }
2179
2180
        return $output;
2181
    }
2182
2183
    /**
2184
     * @param int   $studentId
2185
     * @param int   $prerequisite
2186
     * @param array $courseInfo
2187
     * @param int   $sessionId
2188
     *
2189
     * @return bool
2190
     */
2191
    public static function isBlockedByPrerequisite(
2192
        $studentId,
2193
        $prerequisite,
2194
        $courseInfo,
2195
        $sessionId
2196
    ) {
2197
        if (empty($courseInfo)) {
2198
            return false;
2199
        }
2200
2201
        $courseId = $courseInfo['real_id'];
2202
2203
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2204
        if ($allow) {
2205
            if (api_is_allowed_to_edit() ||
2206
                api_is_platform_admin(true) ||
2207
                api_is_drh() ||
2208
                api_is_coach($sessionId, $courseId, false)
2209
            ) {
2210
                return false;
2211
            }
2212
        }
2213
2214
        $isBlocked = false;
2215
        if (!empty($prerequisite)) {
2216
            $progress = self::getProgress(
2217
                $prerequisite,
2218
                $studentId,
2219
                $courseId,
2220
                $sessionId
2221
            );
2222
            if ($progress < 100) {
2223
                $isBlocked = true;
2224
            }
2225
2226
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2227
                // Block if it does not exceed minimum time
2228
                // Minimum time (in minutes) to pass the learning path
2229
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2230
2231
                if ($accumulateWorkTime > 0) {
2232
                    // Total time in course (sum of times in learning paths from course)
2233
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2234
2235
                    // Connect with the plugin_licences_course_session table
2236
                    // which indicates what percentage of the time applies
2237
                    // Minimum connection percentage
2238
                    $perc = 100;
2239
                    // Time from the course
2240
                    $tc = $accumulateWorkTimeTotal;
2241
2242
                    // Percentage of the learning paths
2243
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2244
                    // Minimum time for each learning path
2245
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2246
2247
                    // Spent time (in seconds) so far in the learning path
2248
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2249
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2250
2251
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2252
                        $isBlocked = true;
2253
                    }
2254
                }
2255
            }
2256
        }
2257
2258
        return $isBlocked;
2259
    }
2260
2261
    /**
2262
     * Checks if the learning path is visible for student after the progress
2263
     * of its prerequisite is completed, considering the time availability and
2264
     * the LP visibility.
2265
     *
2266
     * @param int   $lp_id
2267
     * @param int   $student_id
2268
     * @param array $courseInfo
2269
     * @param int   $sessionId
2270
     *
2271
     * @return bool
2272
     */
2273
    public static function is_lp_visible_for_student(
2274
        $lp_id,
2275
        $student_id,
2276
        $courseInfo = [],
2277
        $sessionId = 0
2278
    ) {
2279
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2280
        $lp_id = (int) $lp_id;
2281
        $sessionId = (int) $sessionId;
2282
2283
        if (empty($courseInfo)) {
2284
            return false;
2285
        }
2286
2287
        if (empty($sessionId)) {
2288
            $sessionId = api_get_session_id();
2289
        }
2290
2291
        $courseId = $courseInfo['real_id'];
2292
2293
        $itemInfo = api_get_item_property_info(
2294
            $courseId,
2295
            TOOL_LEARNPATH,
2296
            $lp_id,
2297
            $sessionId
2298
        );
2299
2300
        // If the item was deleted.
2301
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2302
            return false;
2303
        }
2304
2305
        // @todo remove this query and load the row info as a parameter
2306
        $table = Database::get_course_table(TABLE_LP_MAIN);
2307
        // Get current prerequisite
2308
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2309
                FROM $table
2310
                WHERE iid = $lp_id";
2311
        $rs = Database::query($sql);
2312
        $now = time();
2313
        if (Database::num_rows($rs) > 0) {
2314
            $row = Database::fetch_array($rs, 'ASSOC');
2315
2316
            if (!empty($row['category_id'])) {
2317
                $em = Database::getManager();
2318
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2319
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2320
                    return false;
2321
                }
2322
            }
2323
2324
            $prerequisite = $row['prerequisite'];
2325
            $is_visible = true;
2326
2327
            $isBlocked = self::isBlockedByPrerequisite(
2328
                $student_id,
2329
                $prerequisite,
2330
                $courseInfo,
2331
                $sessionId
2332
            );
2333
2334
            if ($isBlocked) {
2335
                $is_visible = false;
2336
            }
2337
2338
            // Also check the time availability of the LP
2339
            if ($is_visible) {
2340
                // Adding visibility restrictions
2341
                if (!empty($row['publicated_on'])) {
2342
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2343
                        $is_visible = false;
2344
                    }
2345
                }
2346
                // Blocking empty start times see BT#2800
2347
                global $_custom;
2348
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2349
                    $_custom['lps_hidden_when_no_start_date']
2350
                ) {
2351
                    if (empty($row['publicated_on'])) {
2352
                        $is_visible = false;
2353
                    }
2354
                }
2355
2356
                if (!empty($row['expired_on'])) {
2357
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2358
                        $is_visible = false;
2359
                    }
2360
                }
2361
            }
2362
2363
            if ($is_visible) {
2364
                $subscriptionSettings = self::getSubscriptionSettings();
2365
2366
                // Check if the subscription users/group to a LP is ON
2367
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2368
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2369
                ) {
2370
                    // Try group
2371
                    $is_visible = false;
2372
                    // Checking only the user visibility
2373
                    $userVisibility = api_get_item_visibility(
2374
                        $courseInfo,
2375
                        'learnpath',
2376
                        $row['id'],
2377
                        $sessionId,
2378
                        $student_id,
2379
                        'LearnpathSubscription'
2380
                    );
2381
2382
                    if ($userVisibility == 1) {
2383
                        $is_visible = true;
2384
                    } else {
2385
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2386
                        if (!empty($userGroups)) {
2387
                            foreach ($userGroups as $groupInfo) {
2388
                                $groupId = $groupInfo['iid'];
2389
                                $userVisibility = api_get_item_visibility(
2390
                                    $courseInfo,
2391
                                    'learnpath',
2392
                                    $row['id'],
2393
                                    $sessionId,
2394
                                    null,
2395
                                    'LearnpathSubscription',
2396
                                    $groupId
2397
                                );
2398
2399
                                if ($userVisibility == 1) {
2400
                                    $is_visible = true;
2401
                                    break;
2402
                                }
2403
                            }
2404
                        }
2405
                    }
2406
                }
2407
            }
2408
2409
            return $is_visible;
2410
        }
2411
2412
        return false;
2413
    }
2414
2415
    /**
2416
     * @param int $lpId
2417
     * @param int $userId
2418
     * @param int $courseId
2419
     * @param int $sessionId
2420
     *
2421
     * @return int
2422
     */
2423
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2424
    {
2425
        $lpId = (int) $lpId;
2426
        $userId = (int) $userId;
2427
        $courseId = (int) $courseId;
2428
        $sessionId = (int) $sessionId;
2429
2430
        $sessionCondition = api_get_session_condition($sessionId);
2431
        $table = Database::get_course_table(TABLE_LP_VIEW);
2432
        $sql = "SELECT progress FROM $table
2433
                WHERE
2434
                    c_id = $courseId AND
2435
                    lp_id = $lpId AND
2436
                    user_id = $userId $sessionCondition ";
2437
        $res = Database::query($sql);
2438
2439
        $progress = 0;
2440
        if (Database::num_rows($res) > 0) {
2441
            $row = Database::fetch_array($res);
2442
            $progress = (int) $row['progress'];
2443
        }
2444
2445
        return $progress;
2446
    }
2447
2448
    /**
2449
     * @param array $lpList
2450
     * @param int   $userId
2451
     * @param int   $courseId
2452
     * @param int   $sessionId
2453
     *
2454
     * @return array
2455
     */
2456
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2457
    {
2458
        $lpList = array_map('intval', $lpList);
2459
        if (empty($lpList)) {
2460
            return [];
2461
        }
2462
2463
        $lpList = implode("','", $lpList);
2464
2465
        $userId = (int) $userId;
2466
        $courseId = (int) $courseId;
2467
        $sessionId = (int) $sessionId;
2468
2469
        $sessionCondition = api_get_session_condition($sessionId);
2470
        $table = Database::get_course_table(TABLE_LP_VIEW);
2471
        $sql = "SELECT lp_id, progress FROM $table
2472
                WHERE
2473
                    c_id = $courseId AND
2474
                    lp_id IN ('".$lpList."') AND
2475
                    user_id = $userId $sessionCondition ";
2476
        $res = Database::query($sql);
2477
2478
        if (Database::num_rows($res) > 0) {
2479
            $list = [];
2480
            while ($row = Database::fetch_array($res)) {
2481
                $list[$row['lp_id']] = $row['progress'];
2482
            }
2483
2484
            return $list;
2485
        }
2486
2487
        return [];
2488
    }
2489
2490
    /**
2491
     * Displays a progress bar
2492
     * completed so far.
2493
     *
2494
     * @param int    $percentage Progress value to display
2495
     * @param string $text_add   Text to display near the progress value
2496
     *
2497
     * @return string HTML string containing the progress bar
2498
     */
2499
    public static function get_progress_bar($percentage = -1, $text_add = '')
2500
    {
2501
        $text = $percentage.$text_add;
2502
        $output = '<div class="progress">
2503
            <div id="progress_bar_value"
2504
                class="progress-bar progress-bar-success" role="progressbar"
2505
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2506
            '.$text.'
2507
            </div>
2508
        </div>';
2509
2510
        return $output;
2511
    }
2512
2513
    /**
2514
     * @param string $mode can be '%' or 'abs'
2515
     *                     otherwise this value will be used $this->progress_bar_mode
2516
     *
2517
     * @return string
2518
     */
2519
    public function getProgressBar($mode = null)
2520
    {
2521
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2522
2523
        return self::get_progress_bar($percentage, $text_add);
2524
    }
2525
2526
    /**
2527
     * Gets the progress bar info to display inside the progress bar.
2528
     * Also used by scorm_api.php.
2529
     *
2530
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2531
     *                     we display a number of completed elements per total elements
2532
     * @param int    $add  Additional steps to fake as completed
2533
     *
2534
     * @return array Percentage or number and symbol (% or /xx)
2535
     */
2536
    public function get_progress_bar_text($mode = '', $add = 0)
2537
    {
2538
        if (empty($mode)) {
2539
            $mode = $this->progress_bar_mode;
2540
        }
2541
        $total_items = $this->getTotalItemsCountWithoutDirs();
2542
        $completeItems = $this->get_complete_items_count();
2543
        if ($add != 0) {
2544
            $completeItems += $add;
2545
        }
2546
        $text = '';
2547
        if ($completeItems > $total_items) {
2548
            $completeItems = $total_items;
2549
        }
2550
        $percentage = 0;
2551
        if ($mode == '%') {
2552
            if ($total_items > 0) {
2553
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2554
            }
2555
            $percentage = number_format($percentage, 0);
2556
            $text = '%';
2557
        } elseif ($mode === 'abs') {
2558
            $percentage = $completeItems;
2559
            $text = '/'.$total_items;
2560
        }
2561
2562
        return [
2563
            $percentage,
2564
            $text,
2565
        ];
2566
    }
2567
2568
    /**
2569
     * Gets the progress bar mode.
2570
     *
2571
     * @return string The progress bar mode attribute
2572
     */
2573
    public function get_progress_bar_mode()
2574
    {
2575
        if (!empty($this->progress_bar_mode)) {
2576
            return $this->progress_bar_mode;
2577
        }
2578
2579
        return '%';
2580
    }
2581
2582
    /**
2583
     * Gets the learnpath theme (remote or local).
2584
     *
2585
     * @return string Learnpath theme
2586
     */
2587
    public function get_theme()
2588
    {
2589
        if (!empty($this->theme)) {
2590
            return $this->theme;
2591
        }
2592
2593
        return '';
2594
    }
2595
2596
    /**
2597
     * Gets the learnpath session id.
2598
     *
2599
     * @return int
2600
     */
2601
    public function get_lp_session_id()
2602
    {
2603
        if (!empty($this->lp_session_id)) {
2604
            return (int) $this->lp_session_id;
2605
        }
2606
2607
        return 0;
2608
    }
2609
2610
    /**
2611
     * Gets the learnpath image.
2612
     *
2613
     * @return string Web URL of the LP image
2614
     */
2615
    public function get_preview_image()
2616
    {
2617
        if (!empty($this->preview_image)) {
2618
            return $this->preview_image;
2619
        }
2620
2621
        return '';
2622
    }
2623
2624
    /**
2625
     * @param string $size
2626
     * @param string $path_type
2627
     *
2628
     * @return bool|string
2629
     */
2630
    public function get_preview_image_path($size = null, $path_type = 'web')
2631
    {
2632
        $preview_image = $this->get_preview_image();
2633
        if (isset($preview_image) && !empty($preview_image)) {
2634
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2635
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2636
2637
            if (isset($size)) {
2638
                $info = pathinfo($preview_image);
2639
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2640
2641
                if (file_exists($image_sys_path.$image_custom_size)) {
2642
                    if ($path_type == 'web') {
2643
                        return $image_path.$image_custom_size;
2644
                    } else {
2645
                        return $image_sys_path.$image_custom_size;
2646
                    }
2647
                }
2648
            } else {
2649
                if ($path_type == 'web') {
2650
                    return $image_path.$preview_image;
2651
                } else {
2652
                    return $image_sys_path.$preview_image;
2653
                }
2654
            }
2655
        }
2656
2657
        return false;
2658
    }
2659
2660
    /**
2661
     * Gets the learnpath author.
2662
     *
2663
     * @return string LP's author
2664
     */
2665
    public function get_author()
2666
    {
2667
        if (!empty($this->author)) {
2668
            return $this->author;
2669
        }
2670
2671
        return '';
2672
    }
2673
2674
    /**
2675
     * Gets hide table of contents.
2676
     *
2677
     * @return int
2678
     */
2679
    public function getHideTableOfContents()
2680
    {
2681
        return (int) $this->hide_toc_frame;
2682
    }
2683
2684
    /**
2685
     * Generate a new prerequisites string for a given item. If this item was a sco and
2686
     * its prerequisites were strings (instead of IDs), then transform those strings into
2687
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2688
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2689
     * same rule as the scormExport() method.
2690
     *
2691
     * @param int $item_id Item ID
2692
     *
2693
     * @return string Prerequisites string ready for the export as SCORM
2694
     */
2695
    public function get_scorm_prereq_string($item_id)
2696
    {
2697
        if ($this->debug > 0) {
2698
            error_log('In learnpath::get_scorm_prereq_string()');
2699
        }
2700
        if (!is_object($this->items[$item_id])) {
2701
            return false;
2702
        }
2703
        /** @var learnpathItem $oItem */
2704
        $oItem = $this->items[$item_id];
2705
        $prereq = $oItem->get_prereq_string();
2706
2707
        if (empty($prereq)) {
2708
            return '';
2709
        }
2710
        if (preg_match('/^\d+$/', $prereq) &&
2711
            isset($this->items[$prereq]) &&
2712
            is_object($this->items[$prereq])
2713
        ) {
2714
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2715
            // then simply return it (with the ITEM_ prefix).
2716
            //return 'ITEM_' . $prereq;
2717
            return $this->items[$prereq]->ref;
2718
        } else {
2719
            if (isset($this->refs_list[$prereq])) {
2720
                // It's a simple string item from which the ID can be found in the refs list,
2721
                // so we can transform it directly to an ID for export.
2722
                return $this->items[$this->refs_list[$prereq]]->ref;
2723
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2724
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2725
            } else {
2726
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2727
                // and replace them, one by one, by the internal IDs (chamilo db)
2728
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2729
                // by a space as well.
2730
                $find = [
2731
                    '&',
2732
                    '|',
2733
                    '~',
2734
                    '=',
2735
                    '<>',
2736
                    '{',
2737
                    '}',
2738
                    '*',
2739
                    '(',
2740
                    ')',
2741
                ];
2742
                $replace = [
2743
                    ' ',
2744
                    ' ',
2745
                    ' ',
2746
                    ' ',
2747
                    ' ',
2748
                    ' ',
2749
                    ' ',
2750
                    ' ',
2751
                    ' ',
2752
                    ' ',
2753
                ];
2754
                $prereq_mod = str_replace($find, $replace, $prereq);
2755
                $ids = explode(' ', $prereq_mod);
2756
                foreach ($ids as $id) {
2757
                    $id = trim($id);
2758
                    if (isset($this->refs_list[$id])) {
2759
                        $prereq = preg_replace(
2760
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2761
                            'ITEM_'.$this->refs_list[$id],
2762
                            $prereq
2763
                        );
2764
                    }
2765
                }
2766
2767
                return $prereq;
2768
            }
2769
        }
2770
    }
2771
2772
    /**
2773
     * Returns the XML DOM document's node.
2774
     *
2775
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2776
     * @param string   $id       The identifier to look for
2777
     *
2778
     * @return mixed The reference to the element found with that identifier. False if not found
2779
     */
2780
    public function get_scorm_xml_node(&$children, $id)
2781
    {
2782
        for ($i = 0; $i < $children->length; $i++) {
2783
            $item_temp = $children->item($i);
2784
            if ($item_temp->nodeName == 'item') {
2785
                if ($item_temp->getAttribute('identifier') == $id) {
2786
                    return $item_temp;
2787
                }
2788
            }
2789
            $subchildren = $item_temp->childNodes;
2790
            if ($subchildren && $subchildren->length > 0) {
2791
                $val = $this->get_scorm_xml_node($subchildren, $id);
2792
                if (is_object($val)) {
2793
                    return $val;
2794
                }
2795
            }
2796
        }
2797
2798
        return false;
2799
    }
2800
2801
    /**
2802
     * Gets the status list for all LP's items.
2803
     *
2804
     * @return array Array of [index] => [item ID => current status]
2805
     */
2806
    public function get_items_status_list()
2807
    {
2808
        $list = [];
2809
        foreach ($this->ordered_items as $item_id) {
2810
            $list[] = [
2811
                $item_id => $this->items[$item_id]->get_status(),
2812
            ];
2813
        }
2814
2815
        return $list;
2816
    }
2817
2818
    /**
2819
     * Return the number of interactions for the given learnpath Item View ID.
2820
     * This method can be used as static.
2821
     *
2822
     * @param int $lp_iv_id  Item View ID
2823
     * @param int $course_id course id
2824
     *
2825
     * @return int
2826
     */
2827
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2828
    {
2829
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2830
        $lp_iv_id = (int) $lp_iv_id;
2831
        $course_id = (int) $course_id;
2832
2833
        $sql = "SELECT count(*) FROM $table
2834
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2835
        $res = Database::query($sql);
2836
        $num = 0;
2837
        if (Database::num_rows($res)) {
2838
            $row = Database::fetch_array($res);
2839
            $num = $row[0];
2840
        }
2841
2842
        return $num;
2843
    }
2844
2845
    /**
2846
     * Return the interactions as an array for the given lp_iv_id.
2847
     * This method can be used as static.
2848
     *
2849
     * @param int $lp_iv_id Learnpath Item View ID
2850
     *
2851
     * @return array
2852
     *
2853
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2854
     */
2855
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2856
    {
2857
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2858
        $list = [];
2859
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2860
        $lp_iv_id = (int) $lp_iv_id;
2861
2862
        if (empty($lp_iv_id) || empty($course_id)) {
2863
            return [];
2864
        }
2865
2866
        $sql = "SELECT * FROM $table
2867
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2868
                ORDER BY order_id ASC";
2869
        $res = Database::query($sql);
2870
        $num = Database::num_rows($res);
2871
        if ($num > 0) {
2872
            $list[] = [
2873
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2874
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2875
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2876
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2877
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2878
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2879
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2880
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2881
                'student_response_formatted' => '',
2882
            ];
2883
            while ($row = Database::fetch_array($res)) {
2884
                $studentResponseFormatted = urldecode($row['student_response']);
2885
                $content_student_response = explode('__|', $studentResponseFormatted);
2886
                if (count($content_student_response) > 0) {
2887
                    if (count($content_student_response) >= 3) {
2888
                        // Pop the element off the end of array.
2889
                        array_pop($content_student_response);
2890
                    }
2891
                    $studentResponseFormatted = implode(',', $content_student_response);
2892
                }
2893
2894
                $list[] = [
2895
                    'order_id' => $row['order_id'] + 1,
2896
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2897
                    'type' => $row['interaction_type'],
2898
                    'time' => $row['completion_time'],
2899
                    'correct_responses' => '', // Hide correct responses from students.
2900
                    'student_response' => $row['student_response'],
2901
                    'result' => $row['result'],
2902
                    'latency' => $row['latency'],
2903
                    'student_response_formatted' => $studentResponseFormatted,
2904
                ];
2905
            }
2906
        }
2907
2908
        return $list;
2909
    }
2910
2911
    /**
2912
     * Return the number of objectives for the given learnpath Item View ID.
2913
     * This method can be used as static.
2914
     *
2915
     * @param int $lp_iv_id  Item View ID
2916
     * @param int $course_id Course ID
2917
     *
2918
     * @return int Number of objectives
2919
     */
2920
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2921
    {
2922
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2923
        $course_id = (int) $course_id;
2924
        $lp_iv_id = (int) $lp_iv_id;
2925
        $sql = "SELECT count(*) FROM $table
2926
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2927
        //@todo seems that this always returns 0
2928
        $res = Database::query($sql);
2929
        $num = 0;
2930
        if (Database::num_rows($res)) {
2931
            $row = Database::fetch_array($res);
2932
            $num = $row[0];
2933
        }
2934
2935
        return $num;
2936
    }
2937
2938
    /**
2939
     * Return the objectives as an array for the given lp_iv_id.
2940
     * This method can be used as static.
2941
     *
2942
     * @param int $lpItemViewId Learnpath Item View ID
2943
     * @param int $course_id
2944
     *
2945
     * @return array
2946
     *
2947
     * @todo    Translate labels
2948
     */
2949
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2950
    {
2951
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2952
        $lpItemViewId = (int) $lpItemViewId;
2953
2954
        if (empty($course_id) || empty($lpItemViewId)) {
2955
            return [];
2956
        }
2957
2958
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2959
        $sql = "SELECT * FROM $table
2960
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2961
                ORDER BY order_id ASC";
2962
        $res = Database::query($sql);
2963
        $num = Database::num_rows($res);
2964
        $list = [];
2965
        if ($num > 0) {
2966
            $list[] = [
2967
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2968
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
2969
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
2970
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
2971
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
2972
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
2973
            ];
2974
            while ($row = Database::fetch_array($res)) {
2975
                $list[] = [
2976
                    'order_id' => $row['order_id'] + 1,
2977
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2978
                    'score_raw' => $row['score_raw'],
2979
                    'score_max' => $row['score_max'],
2980
                    'score_min' => $row['score_min'],
2981
                    'status' => $row['status'],
2982
                ];
2983
            }
2984
        }
2985
2986
        return $list;
2987
    }
2988
2989
    /**
2990
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2991
     * used by get_html_toc() to be ready to display.
2992
     *
2993
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2994
     */
2995
    public function get_toc()
2996
    {
2997
        $toc = [];
2998
        foreach ($this->ordered_items as $item_id) {
2999
            // TODO: Change this link generation and use new function instead.
3000
            $toc[] = [
3001
                'id' => $item_id,
3002
                'title' => $this->items[$item_id]->get_title(),
3003
                'status' => $this->items[$item_id]->get_status(),
3004
                'level' => $this->items[$item_id]->get_level(),
3005
                'type' => $this->items[$item_id]->get_type(),
3006
                'description' => $this->items[$item_id]->get_description(),
3007
                'path' => $this->items[$item_id]->get_path(),
3008
                'parent' => $this->items[$item_id]->get_parent(),
3009
            ];
3010
        }
3011
3012
        return $toc;
3013
    }
3014
3015
    /**
3016
     * Returns the CSS class name associated with a given item status.
3017
     *
3018
     * @param $status string an item status
3019
     *
3020
     * @return string CSS class name
3021
     */
3022
    public static function getStatusCSSClassName($status)
3023
    {
3024
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3025
            return self::STATUS_CSS_CLASS_NAME[$status];
3026
        }
3027
3028
        return '';
3029
    }
3030
3031
    /**
3032
     * Generate the tree of contents for this learnpath as an associative array tree
3033
     * with keys id, title, status, type, description, path, parent_id, children
3034
     * (title and descriptions as secured)
3035
     * and clues for CSS class composition:
3036
     *  - booleans is_current, is_parent_of_current, is_chapter
3037
     *  - string status_css_class_name.
3038
     *
3039
     * @param $parentId int restrict returned list to children of this parent
3040
     *
3041
     * @return array TOC as a table
3042
     */
3043
    public function getTOCTree($parentId = 0)
3044
    {
3045
        $toc = [];
3046
        $currentItemId = $this->get_current_item_id();
3047
3048
        foreach ($this->ordered_items as $itemId) {
3049
            $item = $this->items[$itemId];
3050
            if ($item->get_parent() == $parentId) {
3051
                $title = $item->get_title();
3052
                if (empty($title)) {
3053
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3054
                }
3055
3056
                $itemData = [
3057
                    'id' => $itemId,
3058
                    'title' => Security::remove_XSS($title),
3059
                    'status' => $item->get_status(),
3060
                    'level' => $item->get_level(), // FIXME should not be needed
3061
                    'type' => $item->get_type(),
3062
                    'description' => Security::remove_XSS($item->get_description()),
3063
                    'path' => $item->get_path(),
3064
                    'parent_id' => $item->get_parent(),
3065
                    'children' => $this->getTOCTree($itemId),
3066
                    'is_current' => ($itemId == $currentItemId),
3067
                    'is_parent_of_current' => false,
3068
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3069
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3070
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3071
                ];
3072
3073
                if (!empty($itemData['children'])) {
3074
                    foreach ($itemData['children'] as $child) {
3075
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3076
                            $itemData['is_parent_of_current'] = true;
3077
                            break;
3078
                        }
3079
                    }
3080
                }
3081
3082
                $toc[] = $itemData;
3083
            }
3084
        }
3085
3086
        return $toc;
3087
    }
3088
3089
    /**
3090
     * Generate and return the table of contents for this learnpath. The JS
3091
     * table returned is used inside of scorm_api.php.
3092
     *
3093
     * @param string $varname
3094
     *
3095
     * @return string A JS array variable construction
3096
     */
3097
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3098
    {
3099
        $toc = $varname.' = new Array();';
3100
        foreach ($this->ordered_items as $item_id) {
3101
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3102
        }
3103
3104
        return $toc;
3105
    }
3106
3107
    /**
3108
     * Gets the learning path type.
3109
     *
3110
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3111
     *
3112
     * @return mixed Type ID or name, depending on the parameter
3113
     */
3114
    public function get_type($get_name = false)
3115
    {
3116
        $res = false;
3117
        if (!empty($this->type) && (!$get_name)) {
3118
            $res = $this->type;
3119
        }
3120
3121
        return $res;
3122
    }
3123
3124
    /**
3125
     * Gets the learning path type as static method.
3126
     *
3127
     * @param int $lp_id
3128
     *
3129
     * @return mixed Type ID or name, depending on the parameter
3130
     */
3131
    public static function get_type_static($lp_id = 0)
3132
    {
3133
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3134
        $lp_id = (int) $lp_id;
3135
        $sql = "SELECT lp_type FROM $tbl_lp
3136
                WHERE iid = $lp_id";
3137
        $res = Database::query($sql);
3138
        if ($res === false) {
3139
            return null;
3140
        }
3141
        if (Database::num_rows($res) <= 0) {
3142
            return null;
3143
        }
3144
        $row = Database::fetch_array($res);
3145
3146
        return $row['lp_type'];
3147
    }
3148
3149
    /**
3150
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3151
     * This method can be used as abstract and is recursive.
3152
     *
3153
     * @param int $lp        Learnpath ID
3154
     * @param int $parent    Parent ID of the items to look for
3155
     * @param int $course_id
3156
     *
3157
     * @return array Ordered list of item IDs (empty array on error)
3158
     */
3159
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3160
    {
3161
        if (empty($course_id)) {
3162
            $course_id = api_get_course_int_id();
3163
        } else {
3164
            $course_id = (int) $course_id;
3165
        }
3166
        $list = [];
3167
3168
        if (empty($lp)) {
3169
            return $list;
3170
        }
3171
3172
        $lp = (int) $lp;
3173
        $parent = (int) $parent;
3174
3175
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3176
        $sql = "SELECT iid FROM $tbl_lp_item
3177
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3178
                ORDER BY display_order";
3179
3180
        $res = Database::query($sql);
3181
        while ($row = Database::fetch_array($res)) {
3182
            $sublist = self::get_flat_ordered_items_list(
3183
                $lp,
3184
                $row['iid'],
3185
                $course_id
3186
            );
3187
            $list[] = $row['iid'];
3188
            foreach ($sublist as $item) {
3189
                $list[] = $item;
3190
            }
3191
        }
3192
3193
        return $list;
3194
    }
3195
3196
    /**
3197
     * @return array
3198
     */
3199
    public static function getChapterTypes()
3200
    {
3201
        return [
3202
            'dir',
3203
        ];
3204
    }
3205
3206
    /**
3207
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3208
     *
3209
     * @param $tree
3210
     *
3211
     * @return array HTML TOC ready to display
3212
     */
3213
    public function getParentToc($tree)
3214
    {
3215
        if (empty($tree)) {
3216
            $tree = $this->get_toc();
3217
        }
3218
        $dirTypes = self::getChapterTypes();
3219
        $myCurrentId = $this->get_current_item_id();
3220
        $listParent = [];
3221
        $listChildren = [];
3222
        $listNotParent = [];
3223
        $list = [];
3224
        foreach ($tree as $subtree) {
3225
            if (in_array($subtree['type'], $dirTypes)) {
3226
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3227
                $subtree['children'] = $listChildren;
3228
                if (!empty($subtree['children'])) {
3229
                    foreach ($subtree['children'] as $subItem) {
3230
                        if ($subItem['id'] == $this->current) {
3231
                            $subtree['parent_current'] = 'in';
3232
                            $subtree['current'] = 'on';
3233
                        }
3234
                    }
3235
                }
3236
                $listParent[] = $subtree;
3237
            }
3238
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3239
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3240
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3241
                }
3242
3243
                $title = Security::remove_XSS($subtree['title']);
3244
                unset($subtree['title']);
3245
3246
                if (empty($title)) {
3247
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3248
                }
3249
                $classStyle = null;
3250
                if ($subtree['id'] == $this->current) {
3251
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3252
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3253
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3254
                }
3255
                $subtree['title'] = $title;
3256
                $subtree['class'] = $classStyle.' '.$cssStatus;
3257
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3258
                $subtree['current_id'] = $myCurrentId;
3259
                $listNotParent[] = $subtree;
3260
            }
3261
        }
3262
3263
        $list['are_parents'] = $listParent;
3264
        $list['not_parents'] = $listNotParent;
3265
3266
        return $list;
3267
    }
3268
3269
    /**
3270
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3271
     *
3272
     * @param array $tree
3273
     * @param int   $id
3274
     * @param bool  $parent
3275
     *
3276
     * @return array HTML TOC ready to display
3277
     */
3278
    public function getChildrenToc($tree, $id, $parent = true)
3279
    {
3280
        if (empty($tree)) {
3281
            $tree = $this->get_toc();
3282
        }
3283
3284
        $dirTypes = self::getChapterTypes();
3285
        $currentItemId = $this->get_current_item_id();
3286
        $list = [];
3287
3288
        foreach ($tree as $subtree) {
3289
            $subtree['tree'] = null;
3290
3291
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3292
                if ($subtree['id'] == $this->current) {
3293
                    $subtree['current'] = 'active';
3294
                } else {
3295
                    $subtree['current'] = null;
3296
                }
3297
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3298
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3299
                }
3300
3301
                $title = Security::remove_XSS($subtree['title']);
3302
                unset($subtree['title']);
3303
                if (empty($title)) {
3304
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3305
                }
3306
3307
                $classStyle = null;
3308
                if ($subtree['id'] == $this->current) {
3309
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3310
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3311
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3312
                }
3313
3314
                if (in_array($subtree['type'], $dirTypes)) {
3315
                    $subtree['title'] = stripslashes($title);
3316
                } else {
3317
                    $subtree['title'] = $title;
3318
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3319
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3320
                    $subtree['current_id'] = $currentItemId;
3321
                }
3322
                $list[] = $subtree;
3323
            }
3324
        }
3325
3326
        return $list;
3327
    }
3328
3329
    /**
3330
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3331
     *
3332
     * @param array $toc_list
3333
     *
3334
     * @return array HTML TOC ready to display
3335
     */
3336
    public function getListArrayToc($toc_list = [])
3337
    {
3338
        if (empty($toc_list)) {
3339
            $toc_list = $this->get_toc();
3340
        }
3341
        // Temporary variables.
3342
        $currentItemId = $this->get_current_item_id();
3343
        $list = [];
3344
        $arrayList = [];
3345
3346
        foreach ($toc_list as $item) {
3347
            $list['id'] = $item['id'];
3348
            $list['status'] = $item['status'];
3349
            $cssStatus = null;
3350
3351
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3352
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3353
            }
3354
3355
            $classStyle = ' ';
3356
            $dirTypes = self::getChapterTypes();
3357
3358
            if (in_array($item['type'], $dirTypes)) {
3359
                $classStyle = 'scorm_item_section ';
3360
            }
3361
            if ($item['id'] == $this->current) {
3362
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3363
            } elseif (!in_array($item['type'], $dirTypes)) {
3364
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3365
            }
3366
            $title = $item['title'];
3367
            if (empty($title)) {
3368
                $title = self::rl_get_resource_name(
3369
                    api_get_course_id(),
3370
                    $this->get_id(),
3371
                    $item['id']
3372
                );
3373
            }
3374
            $title = Security::remove_XSS($item['title']);
3375
3376
            if (empty($item['description'])) {
3377
                $list['description'] = $title;
3378
            } else {
3379
                $list['description'] = $item['description'];
3380
            }
3381
3382
            $list['class'] = $classStyle.' '.$cssStatus;
3383
            $list['level'] = $item['level'];
3384
            $list['type'] = $item['type'];
3385
3386
            if (in_array($item['type'], $dirTypes)) {
3387
                $list['css_level'] = 'level_'.$item['level'];
3388
            } else {
3389
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3390
            }
3391
3392
            if (in_array($item['type'], $dirTypes)) {
3393
                $list['title'] = stripslashes($title);
3394
            } else {
3395
                $list['title'] = stripslashes($title);
3396
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3397
                $list['current_id'] = $currentItemId;
3398
            }
3399
            $arrayList[] = $list;
3400
        }
3401
3402
        return $arrayList;
3403
    }
3404
3405
    /**
3406
     * Returns an HTML-formatted string ready to display with teacher buttons
3407
     * in LP view menu.
3408
     *
3409
     * @return string HTML TOC ready to display
3410
     */
3411
    public function get_teacher_toc_buttons()
3412
    {
3413
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3414
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3415
        $html = '';
3416
        if ($isAllow && $hideIcons == false) {
3417
            if ($this->get_lp_session_id() == api_get_session_id()) {
3418
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3419
                $html .= '<div class="btn-group">';
3420
                $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'>".
3421
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3422
                $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'>".
3423
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3424
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3425
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3426
                $html .= '</div>';
3427
                $html .= '</div>';
3428
            }
3429
        }
3430
3431
        return $html;
3432
    }
3433
3434
    /**
3435
     * Gets the learnpath maker name - generally the editor's name.
3436
     *
3437
     * @return string Learnpath maker name
3438
     */
3439
    public function get_maker()
3440
    {
3441
        if (!empty($this->maker)) {
3442
            return $this->maker;
3443
        }
3444
3445
        return '';
3446
    }
3447
3448
    /**
3449
     * Gets the learnpath name/title.
3450
     *
3451
     * @return string Learnpath name/title
3452
     */
3453
    public function get_name()
3454
    {
3455
        if (!empty($this->name)) {
3456
            return $this->name;
3457
        }
3458
3459
        return 'N/A';
3460
    }
3461
3462
    /**
3463
     * @return string
3464
     */
3465
    public function getNameNoTags()
3466
    {
3467
        return strip_tags($this->get_name());
3468
    }
3469
3470
    /**
3471
     * Gets a link to the resource from the present location, depending on item ID.
3472
     *
3473
     * @param string $type         Type of link expected
3474
     * @param int    $item_id      Learnpath item ID
3475
     * @param bool   $provided_toc
3476
     *
3477
     * @return string $provided_toc Link to the lp_item resource
3478
     */
3479
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3480
    {
3481
        $course_id = $this->get_course_int_id();
3482
        $item_id = (int) $item_id;
3483
3484
        if (empty($item_id)) {
3485
            $item_id = $this->get_current_item_id();
3486
3487
            if (empty($item_id)) {
3488
                //still empty, this means there was no item_id given and we are not in an object context or
3489
                //the object property is empty, return empty link
3490
                $this->first();
3491
3492
                return '';
3493
            }
3494
        }
3495
3496
        $file = '';
3497
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3498
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3499
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3500
3501
        $sql = "SELECT
3502
                    l.lp_type as ltype,
3503
                    l.path as lpath,
3504
                    li.item_type as litype,
3505
                    li.path as lipath,
3506
                    li.parameters as liparams
3507
        		FROM $lp_table l
3508
                INNER JOIN $lp_item_table li
3509
                ON (li.lp_id = l.iid)
3510
        		WHERE
3511
        		    li.iid = $item_id
3512
        		";
3513
        $res = Database::query($sql);
3514
        if (Database::num_rows($res) > 0) {
3515
            $row = Database::fetch_array($res);
3516
            $lp_type = $row['ltype'];
3517
            $lp_path = $row['lpath'];
3518
            $lp_item_type = $row['litype'];
3519
            $lp_item_path = $row['lipath'];
3520
            $lp_item_params = $row['liparams'];
3521
3522
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3523
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3524
            }
3525
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3526
            if ($type === 'http') {
3527
                //web path
3528
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3529
            } else {
3530
                $course_path = $sys_course_path; //system path
3531
            }
3532
3533
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3534
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3535
            if (in_array(
3536
                $lp_item_type,
3537
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3538
            )
3539
            ) {
3540
                $lp_type = 1;
3541
            }
3542
3543
            // Now go through the specific cases to get the end of the path
3544
            // @todo Use constants instead of int values.
3545
            switch ($lp_type) {
3546
                case 1:
3547
                    $file = self::rl_get_resource_link_for_learnpath(
3548
                        $course_id,
3549
                        $this->get_id(),
3550
                        $item_id,
3551
                        $this->get_view_id()
3552
                    );
3553
                    switch ($lp_item_type) {
3554
                        case 'document':
3555
                            // Shows a button to download the file instead of just downloading the file directly.
3556
                            $documentPathInfo = pathinfo($file);
3557
                            if (isset($documentPathInfo['extension'])) {
3558
                                $parsed = parse_url($documentPathInfo['extension']);
3559
                                if (isset($parsed['path'])) {
3560
                                    $extension = $parsed['path'];
3561
                                    $extensionsToDownload = [
3562
                                        'zip',
3563
                                        'ppt',
3564
                                        'pptx',
3565
                                        'ods',
3566
                                        'xlsx',
3567
                                        'xls',
3568
                                        'csv',
3569
                                        'doc',
3570
                                        'docx',
3571
                                        'dot',
3572
                                    ];
3573
3574
                                    if (in_array($extension, $extensionsToDownload)) {
3575
                                        $file = api_get_path(WEB_CODE_PATH).
3576
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3577
                                    }
3578
                                }
3579
                            }
3580
                            break;
3581
                        case 'dir':
3582
                            $file = 'lp_content.php?type=dir';
3583
                            break;
3584
                        case 'link':
3585
                            if (Link::is_youtube_link($file)) {
3586
                                $src = Link::get_youtube_video_id($file);
3587
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3588
                            } elseif (Link::isVimeoLink($file)) {
3589
                                $src = Link::getVimeoLinkId($file);
3590
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3591
                            } else {
3592
                                // If the current site is HTTPS and the link is
3593
                                // HTTP, browsers will refuse opening the link
3594
                                $urlId = api_get_current_access_url_id();
3595
                                $url = api_get_access_url($urlId, false);
3596
                                $protocol = substr($url['url'], 0, 5);
3597
                                if ($protocol === 'https') {
3598
                                    $linkProtocol = substr($file, 0, 5);
3599
                                    if ($linkProtocol === 'http:') {
3600
                                        //this is the special intervention case
3601
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3602
                                    }
3603
                                }
3604
                            }
3605
                            break;
3606
                        case 'quiz':
3607
                            // Check how much attempts of a exercise exits in lp
3608
                            $lp_item_id = $this->get_current_item_id();
3609
                            $lp_view_id = $this->get_view_id();
3610
3611
                            $prevent_reinit = null;
3612
                            if (isset($this->items[$this->current])) {
3613
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3614
                            }
3615
3616
                            if (empty($provided_toc)) {
3617
                                if ($this->debug > 0) {
3618
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3619
                                }
3620
                                $list = $this->get_toc();
3621
                            } else {
3622
                                if ($this->debug > 0) {
3623
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3624
                                }
3625
                                $list = $provided_toc;
3626
                            }
3627
3628
                            $type_quiz = false;
3629
                            foreach ($list as $toc) {
3630
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3631
                                    $type_quiz = true;
3632
                                }
3633
                            }
3634
3635
                            if ($type_quiz) {
3636
                                $lp_item_id = (int) $lp_item_id;
3637
                                $lp_view_id = (int) $lp_view_id;
3638
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3639
                                        WHERE
3640
                                            c_id = $course_id AND
3641
                                            lp_item_id='".$lp_item_id."' AND
3642
                                            lp_view_id ='".$lp_view_id."' AND
3643
                                            status='completed'";
3644
                                $result = Database::query($sql);
3645
                                $row_count = Database:: fetch_row($result);
3646
                                $count_item_view = (int) $row_count[0];
3647
                                $not_multiple_attempt = 0;
3648
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3649
                                    $not_multiple_attempt = 1;
3650
                                }
3651
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3652
                            }
3653
                            break;
3654
                    }
3655
3656
                    $tmp_array = explode('/', $file);
3657
                    $document_name = $tmp_array[count($tmp_array) - 1];
3658
                    if (strpos($document_name, '_DELETED_')) {
3659
                        $file = 'blank.php?error=document_deleted';
3660
                    }
3661
                    break;
3662
                case 2:
3663
                    if ($this->debug > 2) {
3664
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3665
                    }
3666
3667
                    if ($lp_item_type != 'dir') {
3668
                        // Quite complex here:
3669
                        // We want to make sure 'http://' (and similar) links can
3670
                        // be loaded as is (withouth the Chamilo path in front) but
3671
                        // some contents use this form: resource.htm?resource=http://blablabla
3672
                        // which means we have to find a protocol at the path's start, otherwise
3673
                        // it should not be considered as an external URL.
3674
                        // if ($this->prerequisites_match($item_id)) {
3675
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3676
                            if ($this->debug > 2) {
3677
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3678
                            }
3679
                            // Distant url, return as is.
3680
                            $file = $lp_item_path;
3681
                        } else {
3682
                            if ($this->debug > 2) {
3683
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3684
                            }
3685
                            // Prevent getting untranslatable urls.
3686
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3687
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3688
                            // Prepare the path.
3689
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3690
                            // TODO: Fix this for urls with protocol header.
3691
                            $file = str_replace('//', '/', $file);
3692
                            $file = str_replace(':/', '://', $file);
3693
                            if (substr($lp_path, -1) == '/') {
3694
                                $lp_path = substr($lp_path, 0, -1);
3695
                            }
3696
3697
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3698
                                // if file not found.
3699
                                $decoded = html_entity_decode($lp_item_path);
3700
                                list($decoded) = explode('?', $decoded);
3701
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3702
                                    $file = self::rl_get_resource_link_for_learnpath(
3703
                                        $course_id,
3704
                                        $this->get_id(),
3705
                                        $item_id,
3706
                                        $this->get_view_id()
3707
                                    );
3708
                                    if (empty($file)) {
3709
                                        $file = 'blank.php?error=document_not_found';
3710
                                    } else {
3711
                                        $tmp_array = explode('/', $file);
3712
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3713
                                        if (strpos($document_name, '_DELETED_')) {
3714
                                            $file = 'blank.php?error=document_deleted';
3715
                                        } else {
3716
                                            $file = 'blank.php?error=document_not_found';
3717
                                        }
3718
                                    }
3719
                                } else {
3720
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3721
                                }
3722
                            }
3723
                        }
3724
3725
                        // We want to use parameters if they were defined in the imsmanifest
3726
                        if (strpos($file, 'blank.php') === false) {
3727
                            $lp_item_params = ltrim($lp_item_params, '?');
3728
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3729
                        }
3730
                    } else {
3731
                        $file = 'lp_content.php?type=dir';
3732
                    }
3733
                    break;
3734
                case 3:
3735
                    if ($this->debug > 2) {
3736
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3737
                    }
3738
                    // Formatting AICC HACP append URL.
3739
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3740
                    if (!empty($lp_item_params)) {
3741
                        $aicc_append .= $lp_item_params.'&';
3742
                    }
3743
                    if ($lp_item_type != 'dir') {
3744
                        // Quite complex here:
3745
                        // We want to make sure 'http://' (and similar) links can
3746
                        // be loaded as is (withouth the Chamilo path in front) but
3747
                        // some contents use this form: resource.htm?resource=http://blablabla
3748
                        // which means we have to find a protocol at the path's start, otherwise
3749
                        // it should not be considered as an external URL.
3750
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3751
                            if ($this->debug > 2) {
3752
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3753
                            }
3754
                            // Distant url, return as is.
3755
                            $file = $lp_item_path;
3756
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3757
                            /*
3758
                            if (stristr($file,'<servername>') !== false) {
3759
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3760
                            }
3761
                            */
3762
                            if (stripos($file, '<servername>') !== false) {
3763
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3764
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3765
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3766
                            }
3767
3768
                            $file .= $aicc_append;
3769
                        } else {
3770
                            if ($this->debug > 2) {
3771
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3772
                            }
3773
                            // Prevent getting untranslatable urls.
3774
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3775
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3776
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3777
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3778
                            // TODO: Fix this for urls with protocol header.
3779
                            $file = str_replace('//', '/', $file);
3780
                            $file = str_replace(':/', '://', $file);
3781
                            $file .= $aicc_append;
3782
                        }
3783
                    } else {
3784
                        $file = 'lp_content.php?type=dir';
3785
                    }
3786
                    break;
3787
                case 4:
3788
                    break;
3789
                default:
3790
                    break;
3791
            }
3792
            // Replace &amp; by & because &amp; will break URL with params
3793
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3794
        }
3795
        if ($this->debug > 2) {
3796
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3797
        }
3798
3799
        return $file;
3800
    }
3801
3802
    /**
3803
     * Gets the latest usable view or generate a new one.
3804
     *
3805
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3806
     *
3807
     * @return int DB lp_view id
3808
     */
3809
    public function get_view($attempt_num = 0)
3810
    {
3811
        $search = '';
3812
        // Use $attempt_num to enable multi-views management (disabled so far).
3813
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3814
            $search = 'AND view_count = '.$attempt_num;
3815
        }
3816
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3817
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3818
3819
        $course_id = api_get_course_int_id();
3820
        $sessionId = api_get_session_id();
3821
3822
        $sql = "SELECT iid, view_count FROM $lp_view_table
3823
        		WHERE
3824
        		    c_id = $course_id AND
3825
        		    lp_id = ".$this->get_id()." AND
3826
        		    user_id = ".$this->get_user_id()." AND
3827
        		    session_id = $sessionId
3828
        		    $search
3829
                ORDER BY view_count DESC";
3830
        $res = Database::query($sql);
3831
        if (Database::num_rows($res) > 0) {
3832
            $row = Database::fetch_array($res);
3833
            $this->lp_view_id = $row['iid'];
3834
        } elseif (!api_is_invitee()) {
3835
            // There is no database record, create one.
3836
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3837
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3838
            Database::query($sql);
3839
            $id = Database::insert_id();
3840
            $this->lp_view_id = $id;
3841
3842
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3843
            Database::query($sql);
3844
        }
3845
3846
        return $this->lp_view_id;
3847
    }
3848
3849
    /**
3850
     * Gets the current view id.
3851
     *
3852
     * @return int View ID (from lp_view)
3853
     */
3854
    public function get_view_id()
3855
    {
3856
        if (!empty($this->lp_view_id)) {
3857
            return (int) $this->lp_view_id;
3858
        }
3859
3860
        return 0;
3861
    }
3862
3863
    /**
3864
     * Gets the update queue.
3865
     *
3866
     * @return array Array containing IDs of items to be updated by JavaScript
3867
     */
3868
    public function get_update_queue()
3869
    {
3870
        return $this->update_queue;
3871
    }
3872
3873
    /**
3874
     * Gets the user ID.
3875
     *
3876
     * @return int User ID
3877
     */
3878
    public function get_user_id()
3879
    {
3880
        if (!empty($this->user_id)) {
3881
            return (int) $this->user_id;
3882
        }
3883
3884
        return false;
3885
    }
3886
3887
    /**
3888
     * Checks if any of the items has an audio element attached.
3889
     *
3890
     * @return bool True or false
3891
     */
3892
    public function has_audio()
3893
    {
3894
        $has = false;
3895
        foreach ($this->items as $i => $item) {
3896
            if (!empty($this->items[$i]->audio)) {
3897
                $has = true;
3898
                break;
3899
            }
3900
        }
3901
3902
        return $has;
3903
    }
3904
3905
    /**
3906
     * Moves an item up and down at its level.
3907
     *
3908
     * @param int    $id        Item to move up and down
3909
     * @param string $direction Direction 'up' or 'down'
3910
     *
3911
     * @return bool|int
3912
     */
3913
    public function move_item($id, $direction)
3914
    {
3915
        $course_id = api_get_course_int_id();
3916
        if (empty($id) || empty($direction)) {
3917
            return false;
3918
        }
3919
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3920
        $sql_sel = "SELECT *
3921
                    FROM $tbl_lp_item
3922
                    WHERE
3923
                        iid = $id
3924
                    ";
3925
        $res_sel = Database::query($sql_sel);
3926
        // Check if elem exists.
3927
        if (Database::num_rows($res_sel) < 1) {
3928
            return false;
3929
        }
3930
        // Gather data.
3931
        $row = Database::fetch_array($res_sel);
3932
        $previous = $row['previous_item_id'];
3933
        $next = $row['next_item_id'];
3934
        $display = $row['display_order'];
3935
        $parent = $row['parent_item_id'];
3936
        $lp = $row['lp_id'];
3937
        // Update the item (switch with previous/next one).
3938
        switch ($direction) {
3939
            case 'up':
3940
                if ($display > 1) {
3941
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3942
                                 WHERE iid = $previous";
3943
                    $res_sel2 = Database::query($sql_sel2);
3944
                    if (Database::num_rows($res_sel2) < 1) {
3945
                        $previous_previous = 0;
3946
                    }
3947
                    // Gather data.
3948
                    $row2 = Database::fetch_array($res_sel2);
3949
                    $previous_previous = $row2['previous_item_id'];
3950
                    // Update previous_previous item (switch "next" with current).
3951
                    if ($previous_previous != 0) {
3952
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3953
                                        next_item_id = $id
3954
                                    WHERE iid = $previous_previous";
3955
                        Database::query($sql_upd2);
3956
                    }
3957
                    // Update previous item (switch with current).
3958
                    if ($previous != 0) {
3959
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3960
                                    next_item_id = $next,
3961
                                    previous_item_id = $id,
3962
                                    display_order = display_order +1
3963
                                    WHERE iid = $previous";
3964
                        Database::query($sql_upd2);
3965
                    }
3966
3967
                    // Update current item (switch with previous).
3968
                    if ($id != 0) {
3969
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3970
                                        next_item_id = $previous,
3971
                                        previous_item_id = $previous_previous,
3972
                                        display_order = display_order-1
3973
                                    WHERE c_id = ".$course_id." AND id = $id";
3974
                        Database::query($sql_upd2);
3975
                    }
3976
                    // Update next item (new previous item).
3977
                    if (!empty($next)) {
3978
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3979
                                     WHERE iid = $next";
3980
                        Database::query($sql_upd2);
3981
                    }
3982
                    $display = $display - 1;
3983
                }
3984
                break;
3985
            case 'down':
3986
                if ($next != 0) {
3987
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3988
                                 WHERE iid = $next";
3989
                    $res_sel2 = Database::query($sql_sel2);
3990
                    if (Database::num_rows($res_sel2) < 1) {
3991
                        $next_next = 0;
3992
                    }
3993
                    // Gather data.
3994
                    $row2 = Database::fetch_array($res_sel2);
3995
                    $next_next = $row2['next_item_id'];
3996
                    // Update previous item (switch with current).
3997
                    if ($previous != 0) {
3998
                        $sql_upd2 = "UPDATE $tbl_lp_item
3999
                                     SET next_item_id = $next
4000
                                     WHERE iid = $previous";
4001
                        Database::query($sql_upd2);
4002
                    }
4003
                    // Update current item (switch with previous).
4004
                    if ($id != 0) {
4005
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4006
                                     previous_item_id = $next,
4007
                                     next_item_id = $next_next,
4008
                                     display_order = display_order + 1
4009
                                     WHERE iid = $id";
4010
                        Database::query($sql_upd2);
4011
                    }
4012
4013
                    // Update next item (new previous item).
4014
                    if ($next != 0) {
4015
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4016
                                     previous_item_id = $previous,
4017
                                     next_item_id = $id,
4018
                                     display_order = display_order-1
4019
                                     WHERE iid = $next";
4020
                        Database::query($sql_upd2);
4021
                    }
4022
4023
                    // Update next_next item (switch "previous" with current).
4024
                    if ($next_next != 0) {
4025
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4026
                                     previous_item_id = $id
4027
                                     WHERE iid = $next_next";
4028
                        Database::query($sql_upd2);
4029
                    }
4030
                    $display = $display + 1;
4031
                }
4032
                break;
4033
            default:
4034
                return false;
4035
        }
4036
4037
        return $display;
4038
    }
4039
4040
    /**
4041
     * Move a LP up (display_order).
4042
     *
4043
     * @param int $lp_id      Learnpath ID
4044
     * @param int $categoryId Category ID
4045
     *
4046
     * @return bool
4047
     */
4048
    public static function move_up($lp_id, $categoryId = 0)
4049
    {
4050
        $courseId = api_get_course_int_id();
4051
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4052
4053
        $categoryCondition = '';
4054
        if (!empty($categoryId)) {
4055
            $categoryId = (int) $categoryId;
4056
            $categoryCondition = " AND category_id = $categoryId";
4057
        }
4058
        $sql = "SELECT * FROM $lp_table
4059
                WHERE c_id = $courseId
4060
                $categoryCondition
4061
                ORDER BY display_order";
4062
        $res = Database::query($sql);
4063
        if ($res === false) {
4064
            return false;
4065
        }
4066
4067
        $lps = [];
4068
        $lp_order = [];
4069
        $num = Database::num_rows($res);
4070
        // First check the order is correct, globally (might be wrong because
4071
        // of versions < 1.8.4)
4072
        if ($num > 0) {
4073
            $i = 1;
4074
            while ($row = Database::fetch_array($res)) {
4075
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4076
                    $sql = "UPDATE $lp_table SET display_order = $i
4077
                            WHERE iid = ".$row['iid'];
4078
                    Database::query($sql);
4079
                }
4080
                $row['display_order'] = $i;
4081
                $lps[$row['iid']] = $row;
4082
                $lp_order[$i] = $row['iid'];
4083
                $i++;
4084
            }
4085
        }
4086
        if ($num > 1) { // If there's only one element, no need to sort.
4087
            $order = $lps[$lp_id]['display_order'];
4088
            if ($order > 1) { // If it's the first element, no need to move up.
4089
                $sql = "UPDATE $lp_table SET display_order = $order
4090
                        WHERE iid = ".$lp_order[$order - 1];
4091
                Database::query($sql);
4092
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4093
                        WHERE iid = $lp_id";
4094
                Database::query($sql);
4095
            }
4096
        }
4097
4098
        return true;
4099
    }
4100
4101
    /**
4102
     * Move a learnpath down (display_order).
4103
     *
4104
     * @param int $lp_id      Learnpath ID
4105
     * @param int $categoryId Category ID
4106
     *
4107
     * @return bool
4108
     */
4109
    public static function move_down($lp_id, $categoryId = 0)
4110
    {
4111
        $courseId = api_get_course_int_id();
4112
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4113
4114
        $categoryCondition = '';
4115
        if (!empty($categoryId)) {
4116
            $categoryId = (int) $categoryId;
4117
            $categoryCondition = " AND category_id = $categoryId";
4118
        }
4119
4120
        $sql = "SELECT * FROM $lp_table
4121
                WHERE c_id = $courseId
4122
                $categoryCondition
4123
                ORDER BY display_order";
4124
        $res = Database::query($sql);
4125
        if ($res === false) {
4126
            return false;
4127
        }
4128
        $lps = [];
4129
        $lp_order = [];
4130
        $num = Database::num_rows($res);
4131
        $max = 0;
4132
        // First check the order is correct, globally (might be wrong because
4133
        // of versions < 1.8.4).
4134
        if ($num > 0) {
4135
            $i = 1;
4136
            while ($row = Database::fetch_array($res)) {
4137
                $max = $i;
4138
                if ($row['display_order'] != $i) {
4139
                    // 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 < $max) { // 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
     * Updates learnpath attributes to point to the next element
4167
     * The last part is similar to set_current_item but processing the other way around.
4168
     */
4169
    public function next()
4170
    {
4171
        if ($this->debug > 0) {
4172
            error_log('In learnpath::next()', 0);
4173
        }
4174
        $this->last = $this->get_current_item_id();
4175
        $this->items[$this->last]->save(
4176
            false,
4177
            $this->prerequisites_match($this->last)
4178
        );
4179
        $this->autocomplete_parents($this->last);
4180
        $new_index = $this->get_next_index();
4181
        if ($this->debug > 2) {
4182
            error_log('New index: '.$new_index, 0);
4183
        }
4184
        $this->index = $new_index;
4185
        if ($this->debug > 2) {
4186
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4187
        }
4188
        $this->current = $this->ordered_items[$new_index];
4189
        if ($this->debug > 2) {
4190
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4191
        }
4192
    }
4193
4194
    /**
4195
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4196
     * class, this might be redefined to allow several behaviours depending on the document type.
4197
     *
4198
     * @param int $id Resource ID
4199
     */
4200
    public function open($id)
4201
    {
4202
        // TODO:
4203
        // set the current resource attribute to this resource
4204
        // switch on element type (redefine in child class?)
4205
        // set status for this item to "opened"
4206
        // start timer
4207
        // initialise score
4208
        $this->index = 0; //or = the last item seen (see $this->last)
4209
    }
4210
4211
    /**
4212
     * Check that all prerequisites are fulfilled. Returns true and an
4213
     * empty string on success, returns false
4214
     * and the prerequisite string on error.
4215
     * This function is based on the rules for aicc_script language as
4216
     * described in the SCORM 1.2 CAM documentation page 108.
4217
     *
4218
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4219
     *
4220
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4221
     *              string otherwise
4222
     */
4223
    public function prerequisites_match($itemId = null)
4224
    {
4225
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4226
        if ($allow) {
4227
            if (api_is_allowed_to_edit() ||
4228
                api_is_platform_admin(true) ||
4229
                api_is_drh() ||
4230
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4231
            ) {
4232
                return true;
4233
            }
4234
        }
4235
4236
        $debug = $this->debug;
4237
        if ($debug > 0) {
4238
            error_log('In learnpath::prerequisites_match()');
4239
        }
4240
4241
        if (empty($itemId)) {
4242
            $itemId = $this->current;
4243
        }
4244
4245
        $currentItem = $this->getItem($itemId);
4246
4247
        if ($currentItem) {
4248
            if ($this->type == 2) {
4249
                // Getting prereq from scorm
4250
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4251
            } else {
4252
                $prereq_string = $currentItem->get_prereq_string();
4253
            }
4254
4255
            if (empty($prereq_string)) {
4256
                if ($debug > 0) {
4257
                    error_log('Found prereq_string is empty return true');
4258
                }
4259
4260
                return true;
4261
            }
4262
4263
            // Clean spaces.
4264
            $prereq_string = str_replace(' ', '', $prereq_string);
4265
            if ($debug > 0) {
4266
                error_log('Found prereq_string: '.$prereq_string, 0);
4267
            }
4268
4269
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4270
            $result = $currentItem->parse_prereq(
4271
                $prereq_string,
4272
                $this->items,
4273
                $this->refs_list,
4274
                $this->get_user_id()
4275
            );
4276
4277
            if ($result === false) {
4278
                $this->set_error_msg($currentItem->prereq_alert);
4279
            }
4280
        } else {
4281
            $result = true;
4282
            if ($debug > 1) {
4283
                error_log('$this->items['.$itemId.'] was not an object', 0);
4284
            }
4285
        }
4286
4287
        if ($debug > 1) {
4288
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4289
        }
4290
4291
        return $result;
4292
    }
4293
4294
    /**
4295
     * Updates learnpath attributes to point to the previous element
4296
     * The last part is similar to set_current_item but processing the other way around.
4297
     */
4298
    public function previous()
4299
    {
4300
        $this->last = $this->get_current_item_id();
4301
        $this->items[$this->last]->save(
4302
            false,
4303
            $this->prerequisites_match($this->last)
4304
        );
4305
        $this->autocomplete_parents($this->last);
4306
        $new_index = $this->get_previous_index();
4307
        $this->index = $new_index;
4308
        $this->current = $this->ordered_items[$new_index];
4309
    }
4310
4311
    /**
4312
     * Publishes a learnpath. This basically means show or hide the learnpath
4313
     * to normal users.
4314
     * Can be used as abstract.
4315
     *
4316
     * @param int $lp_id          Learnpath ID
4317
     * @param int $set_visibility New visibility
4318
     *
4319
     * @return bool
4320
     */
4321
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4322
    {
4323
        $action = 'visible';
4324
        if ($set_visibility != 1) {
4325
            $action = 'invisible';
4326
            self::toggle_publish($lp_id, 'i');
4327
        }
4328
4329
        return api_item_property_update(
4330
            api_get_course_info(),
4331
            TOOL_LEARNPATH,
4332
            $lp_id,
4333
            $action,
4334
            api_get_user_id()
4335
        );
4336
    }
4337
4338
    /**
4339
     * Publishes a learnpath category.
4340
     * This basically means show or hide the learnpath category to normal users.
4341
     *
4342
     * @param int $id
4343
     * @param int $visibility
4344
     *
4345
     * @throws \Doctrine\ORM\NonUniqueResultException
4346
     * @throws \Doctrine\ORM\ORMException
4347
     * @throws \Doctrine\ORM\OptimisticLockException
4348
     * @throws \Doctrine\ORM\TransactionRequiredException
4349
     *
4350
     * @return bool
4351
     */
4352
    public static function toggleCategoryVisibility($id, $visibility = 1)
4353
    {
4354
        $action = 'visible';
4355
        if ($visibility != 1) {
4356
            self::toggleCategoryPublish($id, 0);
4357
            $action = 'invisible';
4358
        }
4359
4360
        return api_item_property_update(
4361
            api_get_course_info(),
4362
            TOOL_LEARNPATH_CATEGORY,
4363
            $id,
4364
            $action,
4365
            api_get_user_id()
4366
        );
4367
    }
4368
4369
    /**
4370
     * Publishes a learnpath. This basically means show or hide the learnpath
4371
     * on the course homepage
4372
     * Can be used as abstract.
4373
     *
4374
     * @param int    $lp_id          Learnpath id
4375
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4376
     *
4377
     * @return bool
4378
     */
4379
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4380
    {
4381
        $course_id = api_get_course_int_id();
4382
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4383
        $lp_id = (int) $lp_id;
4384
        $sql = "SELECT * FROM $tbl_lp
4385
                WHERE iid = $lp_id";
4386
        $result = Database::query($sql);
4387
        if (Database::num_rows($result)) {
4388
            $row = Database::fetch_array($result);
4389
            $name = Database::escape_string($row['name']);
4390
            if ($set_visibility == 'i') {
4391
                $v = 0;
4392
            }
4393
            if ($set_visibility == 'v') {
4394
                $v = 1;
4395
            }
4396
4397
            $session_id = api_get_session_id();
4398
            $session_condition = api_get_session_condition($session_id);
4399
4400
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4401
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4402
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4403
4404
            $sql = "SELECT * FROM $tbl_tool
4405
                    WHERE
4406
                        c_id = $course_id AND
4407
                        (link = '$link' OR link = '$oldLink') AND
4408
                        image = 'scormbuilder.gif' AND
4409
                        (
4410
                            link LIKE '$link%' OR
4411
                            link LIKE '$oldLink%'
4412
                        )
4413
                        $session_condition
4414
                    ";
4415
4416
            $result = Database::query($sql);
4417
            $num = Database::num_rows($result);
4418
            if ($set_visibility == 'i' && $num > 0) {
4419
                $sql = "DELETE FROM $tbl_tool
4420
                        WHERE
4421
                            c_id = $course_id AND
4422
                            (link = '$link' OR link = '$oldLink') AND
4423
                            image='scormbuilder.gif'
4424
                            $session_condition";
4425
                Database::query($sql);
4426
            } elseif ($set_visibility == 'v' && $num == 0) {
4427
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4428
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4429
                Database::query($sql);
4430
                $insertId = Database::insert_id();
4431
                if ($insertId) {
4432
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4433
                    Database::query($sql);
4434
                }
4435
            } elseif ($set_visibility == 'v' && $num > 0) {
4436
                $sql = "UPDATE $tbl_tool SET
4437
                            c_id = $course_id,
4438
                            name = '$name',
4439
                            link = '$link',
4440
                            image = 'scormbuilder.gif',
4441
                            visibility = '$v',
4442
                            admin = '0',
4443
                            address = 'pastillegris.gif',
4444
                            added_tool = 0,
4445
                            session_id = $session_id
4446
                        WHERE
4447
                            c_id = ".$course_id." AND
4448
                            (link = '$link' OR link = '$oldLink') AND
4449
                            image='scormbuilder.gif'
4450
                            $session_condition
4451
                        ";
4452
                Database::query($sql);
4453
            } else {
4454
                // Parameter and database incompatible, do nothing, exit.
4455
                return false;
4456
            }
4457
        } else {
4458
            return false;
4459
        }
4460
    }
4461
4462
    /**
4463
     * Publishes a learnpath.
4464
     * Show or hide the learnpath category on the course homepage.
4465
     *
4466
     * @param int $id
4467
     * @param int $setVisibility
4468
     *
4469
     * @throws \Doctrine\ORM\NonUniqueResultException
4470
     * @throws \Doctrine\ORM\ORMException
4471
     * @throws \Doctrine\ORM\OptimisticLockException
4472
     * @throws \Doctrine\ORM\TransactionRequiredException
4473
     *
4474
     * @return bool
4475
     */
4476
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4477
    {
4478
        $courseId = api_get_course_int_id();
4479
        $sessionId = api_get_session_id();
4480
        $sessionCondition = api_get_session_condition(
4481
            $sessionId,
4482
            true,
4483
            false,
4484
            't.sessionId'
4485
        );
4486
4487
        $em = Database::getManager();
4488
4489
        /** @var CLpCategory $category */
4490
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4491
4492
        if (!$category) {
4493
            return false;
4494
        }
4495
4496
        if (empty($courseId)) {
4497
            return false;
4498
        }
4499
4500
        $link = self::getCategoryLinkForTool($id);
4501
4502
        /** @var CTool $tool */
4503
        $tool = $em->createQuery("
4504
                SELECT t FROM ChamiloCourseBundle:CTool t
4505
                WHERE
4506
                    t.cId = :course AND
4507
                    t.link = :link1 AND
4508
                    t.image = 'lp_category.gif' AND
4509
                    t.link LIKE :link2
4510
                    $sessionCondition
4511
            ")
4512
            ->setParameters([
4513
                'course' => $courseId,
4514
                'link1' => $link,
4515
                'link2' => "$link%",
4516
            ])
4517
            ->getOneOrNullResult();
4518
4519
        if ($setVisibility == 0 && $tool) {
4520
            $em->remove($tool);
4521
            $em->flush();
4522
4523
            return true;
4524
        }
4525
4526
        if ($setVisibility == 1 && !$tool) {
4527
            $tool = new CTool();
4528
            $tool
4529
                ->setCategory('authoring')
4530
                ->setCId($courseId)
4531
                ->setName(strip_tags($category->getName()))
4532
                ->setLink($link)
4533
                ->setImage('lp_category.gif')
4534
                ->setVisibility(1)
4535
                ->setAdmin(0)
4536
                ->setAddress('pastillegris.gif')
4537
                ->setAddedTool(0)
4538
                ->setSessionId($sessionId)
4539
                ->setTarget('_self');
4540
4541
            $em->persist($tool);
4542
            $em->flush();
4543
4544
            $tool->setId($tool->getIid());
4545
4546
            $em->persist($tool);
4547
            $em->flush();
4548
4549
            return true;
4550
        }
4551
4552
        if ($setVisibility == 1 && $tool) {
4553
            $tool
4554
                ->setName(strip_tags($category->getName()))
4555
                ->setVisibility(1);
4556
4557
            $em->persist($tool);
4558
            $em->flush();
4559
4560
            return true;
4561
        }
4562
4563
        return false;
4564
    }
4565
4566
    /**
4567
     * Check if the learnpath category is visible for a user.
4568
     *
4569
     * @param int
4570
     * @param int
4571
     *
4572
     * @return bool
4573
     */
4574
    public static function categoryIsVisibleForStudent(
4575
        CLpCategory $category,
4576
        User $user,
4577
        $courseId = 0,
4578
        $sessionId = 0
4579
    ) {
4580
        if (empty($category)) {
4581
            return false;
4582
        }
4583
4584
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4585
4586
        if ($isAllowedToEdit) {
4587
            return true;
4588
        }
4589
4590
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4591
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4592
4593
        $courseInfo = api_get_course_info_by_id($courseId);
4594
4595
        $categoryVisibility = api_get_item_visibility(
4596
            $courseInfo,
4597
            TOOL_LEARNPATH_CATEGORY,
4598
            $category->getId(),
4599
            $sessionId
4600
        );
4601
4602
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4603
            return false;
4604
        }
4605
4606
        $subscriptionSettings = self::getSubscriptionSettings();
4607
4608
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4609
            return true;
4610
        }
4611
4612
        $noUserSubscribed = false;
4613
        $noGroupSubscribed = true;
4614
        $users = $category->getUsers();
4615
        if (empty($users) || !$users->count()) {
4616
            $noUserSubscribed = true;
4617
        } elseif ($category->hasUserAdded($user)) {
4618
            return true;
4619
        }
4620
4621
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4622
        $em = Database::getManager();
4623
4624
        /** @var ItemPropertyRepository $itemRepo */
4625
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4626
4627
        /** @var CourseRepository $courseRepo */
4628
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4629
        $session = null;
4630
        if (!empty($sessionId)) {
4631
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4632
        }
4633
4634
        $course = $courseRepo->find($courseId);
4635
4636
        if ($courseId != 0) {
4637
            // Subscribed groups to a LP
4638
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4639
                TOOL_LEARNPATH_CATEGORY,
4640
                $category->getId(),
4641
                $course,
4642
                $session
4643
            );
4644
        }
4645
4646
        if (!empty($subscribedGroupsInLp)) {
4647
            $noGroupSubscribed = false;
4648
            if (!empty($groups)) {
4649
                $groups = array_column($groups, 'iid');
4650
                /** @var CItemProperty $item */
4651
                foreach ($subscribedGroupsInLp as $item) {
4652
                    if ($item->getGroup() &&
4653
                        in_array($item->getGroup()->getId(), $groups)
4654
                    ) {
4655
                        return true;
4656
                    }
4657
                }
4658
            }
4659
        }
4660
        $response = $noGroupSubscribed && $noUserSubscribed;
4661
4662
        return $response;
4663
    }
4664
4665
    /**
4666
     * Check if a learnpath category is published as course tool.
4667
     *
4668
     * @param int $courseId
4669
     *
4670
     * @return bool
4671
     */
4672
    public static function categoryIsPublished(
4673
        CLpCategory $category,
4674
        $courseId
4675
    ) {
4676
        $link = self::getCategoryLinkForTool($category->getId());
4677
        $em = Database::getManager();
4678
4679
        $tools = $em
4680
            ->createQuery("
4681
                SELECT t FROM ChamiloCourseBundle:CTool t
4682
                WHERE t.cId = :course AND
4683
                    t.name = :name AND
4684
                    t.image = 'lp_category.gif' AND
4685
                    t.link LIKE :link
4686
            ")
4687
            ->setParameters([
4688
                'course' => $courseId,
4689
                'name' => strip_tags($category->getName()),
4690
                'link' => "$link%",
4691
            ])
4692
            ->getResult();
4693
4694
        /** @var CTool $tool */
4695
        $tool = current($tools);
4696
4697
        return $tool ? $tool->getVisibility() : false;
4698
    }
4699
4700
    /**
4701
     * Restart the whole learnpath. Return the URL of the first element.
4702
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4703
     * To use a similar method  statically, use the create_new_attempt() method.
4704
     *
4705
     * @return bool
4706
     */
4707
    public function restart()
4708
    {
4709
        if ($this->debug > 0) {
4710
            error_log('In learnpath::restart()', 0);
4711
        }
4712
        // TODO
4713
        // Call autosave method to save the current progress.
4714
        //$this->index = 0;
4715
        if (api_is_invitee()) {
4716
            return false;
4717
        }
4718
        $session_id = api_get_session_id();
4719
        $course_id = api_get_course_int_id();
4720
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4721
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4722
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4723
        if ($this->debug > 2) {
4724
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4725
        }
4726
        Database::query($sql);
4727
        $view_id = Database::insert_id();
4728
4729
        if ($view_id) {
4730
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4731
            Database::query($sql);
4732
            $this->lp_view_id = $view_id;
4733
            $this->attempt = $this->attempt + 1;
4734
        } else {
4735
            $this->error = 'Could not insert into item_view table...';
4736
4737
            return false;
4738
        }
4739
        $this->autocomplete_parents($this->current);
4740
        foreach ($this->items as $index => $dummy) {
4741
            $this->items[$index]->restart();
4742
            $this->items[$index]->set_lp_view($this->lp_view_id);
4743
        }
4744
        $this->first();
4745
4746
        return true;
4747
    }
4748
4749
    /**
4750
     * Saves the current item.
4751
     *
4752
     * @return bool
4753
     */
4754
    public function save_current()
4755
    {
4756
        $debug = $this->debug;
4757
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4758
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4759
        if ($debug) {
4760
            error_log('save_current() saving item '.$this->current, 0);
4761
            error_log(''.print_r($this->items, true), 0);
4762
        }
4763
        if (isset($this->items[$this->current]) &&
4764
            is_object($this->items[$this->current])
4765
        ) {
4766
            if ($debug) {
4767
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4768
            }
4769
4770
            $res = $this->items[$this->current]->save(
4771
                false,
4772
                $this->prerequisites_match($this->current)
4773
            );
4774
            $this->autocomplete_parents($this->current);
4775
            $status = $this->items[$this->current]->get_status();
4776
            $this->update_queue[$this->current] = $status;
4777
4778
            if ($debug) {
4779
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4780
            }
4781
4782
            return $res;
4783
        }
4784
4785
        return false;
4786
    }
4787
4788
    /**
4789
     * Saves the given item.
4790
     *
4791
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4792
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4793
     *
4794
     * @return bool
4795
     */
4796
    public function save_item($item_id = null, $from_outside = true)
4797
    {
4798
        $debug = $this->debug;
4799
        if ($debug) {
4800
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4801
        }
4802
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4803
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4804
        if (empty($item_id)) {
4805
            $item_id = (int) $_REQUEST['id'];
4806
        }
4807
4808
        if (empty($item_id)) {
4809
            $item_id = $this->get_current_item_id();
4810
        }
4811
        if (isset($this->items[$item_id]) &&
4812
            is_object($this->items[$item_id])
4813
        ) {
4814
            if ($debug) {
4815
                error_log('Object exists');
4816
            }
4817
4818
            // Saving the item.
4819
            $res = $this->items[$item_id]->save(
4820
                $from_outside,
4821
                $this->prerequisites_match($item_id)
4822
            );
4823
4824
            if ($debug) {
4825
                error_log('update_queue before:');
4826
                error_log(print_r($this->update_queue, 1));
4827
            }
4828
            $this->autocomplete_parents($item_id);
4829
4830
            $status = $this->items[$item_id]->get_status();
4831
            $this->update_queue[$item_id] = $status;
4832
4833
            if ($debug) {
4834
                error_log('get_status(): '.$status);
4835
                error_log('update_queue after:');
4836
                error_log(print_r($this->update_queue, 1));
4837
            }
4838
4839
            return $res;
4840
        }
4841
4842
        return false;
4843
    }
4844
4845
    /**
4846
     * Saves the last item seen's ID only in case.
4847
     */
4848
    public function save_last()
4849
    {
4850
        $course_id = api_get_course_int_id();
4851
        $debug = $this->debug;
4852
        if ($debug) {
4853
            error_log('In learnpath::save_last()', 0);
4854
        }
4855
        $session_condition = api_get_session_condition(
4856
            api_get_session_id(),
4857
            true,
4858
            false
4859
        );
4860
        $table = Database::get_course_table(TABLE_LP_VIEW);
4861
4862
        if (isset($this->current) && !api_is_invitee()) {
4863
            if ($debug) {
4864
                error_log('Saving current item ('.$this->current.') for later review', 0);
4865
            }
4866
            $sql = "UPDATE $table SET
4867
                        last_item = ".$this->get_current_item_id()."
4868
                    WHERE
4869
                        c_id = $course_id AND
4870
                        lp_id = ".$this->get_id()." AND
4871
                        user_id = ".$this->get_user_id()." ".$session_condition;
4872
4873
            if ($debug) {
4874
                error_log('Saving last item seen : '.$sql, 0);
4875
            }
4876
            Database::query($sql);
4877
        }
4878
4879
        if (!api_is_invitee()) {
4880
            // Save progress.
4881
            list($progress) = $this->get_progress_bar_text('%');
4882
            if ($progress >= 0 && $progress <= 100) {
4883
                $progress = (int) $progress;
4884
                $sql = "UPDATE $table SET
4885
                            progress = $progress
4886
                        WHERE
4887
                            c_id = $course_id AND
4888
                            lp_id = ".$this->get_id()." AND
4889
                            user_id = ".$this->get_user_id()." ".$session_condition;
4890
                // Ignore errors as some tables might not have the progress field just yet.
4891
                Database::query($sql);
4892
                $this->progress_db = $progress;
4893
            }
4894
        }
4895
    }
4896
4897
    /**
4898
     * Sets the current item ID (checks if valid and authorized first).
4899
     *
4900
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4901
     */
4902
    public function set_current_item($item_id = null)
4903
    {
4904
        $debug = $this->debug;
4905
        if ($debug) {
4906
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4907
        }
4908
        if (empty($item_id)) {
4909
            if ($debug) {
4910
                error_log('No new current item given, ignore...', 0);
4911
            }
4912
            // Do nothing.
4913
        } else {
4914
            if ($debug) {
4915
                error_log('New current item given is '.$item_id.'...', 0);
4916
            }
4917
            if (is_numeric($item_id)) {
4918
                $item_id = (int) $item_id;
4919
                // TODO: Check in database here.
4920
                $this->last = $this->current;
4921
                $this->current = $item_id;
4922
                // TODO: Update $this->index as well.
4923
                foreach ($this->ordered_items as $index => $item) {
4924
                    if ($item == $this->current) {
4925
                        $this->index = $index;
4926
                        break;
4927
                    }
4928
                }
4929
                if ($debug) {
4930
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4931
                }
4932
            } else {
4933
                if ($debug) {
4934
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4935
                }
4936
            }
4937
        }
4938
    }
4939
4940
    /**
4941
     * Sets the encoding.
4942
     *
4943
     * @param string $enc New encoding
4944
     *
4945
     * @return bool
4946
     *
4947
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4948
     */
4949
    public function set_encoding($enc = 'UTF-8')
4950
    {
4951
        $enc = api_refine_encoding_id($enc);
4952
        if (empty($enc)) {
4953
            $enc = api_get_system_encoding();
4954
        }
4955
        if (api_is_encoding_supported($enc)) {
4956
            $lp = $this->get_id();
4957
            if ($lp != 0) {
4958
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4959
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4960
                        WHERE iid = ".$lp;
4961
                $res = Database::query($sql);
4962
4963
                return $res;
4964
            }
4965
        }
4966
4967
        return false;
4968
    }
4969
4970
    /**
4971
     * Sets the JS lib setting in the database directly.
4972
     * This is the JavaScript library file this lp needs to load on startup.
4973
     *
4974
     * @param string $lib Proximity setting
4975
     *
4976
     * @return bool True on update success. False otherwise.
4977
     */
4978
    public function set_jslib($lib = '')
4979
    {
4980
        $lp = $this->get_id();
4981
4982
        if ($lp != 0) {
4983
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4984
            $lib = Database::escape_string($lib);
4985
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4986
                    WHERE iid = $lp";
4987
            $res = Database::query($sql);
4988
4989
            return $res;
4990
        }
4991
4992
        return false;
4993
    }
4994
4995
    /**
4996
     * Sets the name of the LP maker (publisher) (and save).
4997
     *
4998
     * @param string $name Optional string giving the new content_maker of this learnpath
4999
     *
5000
     * @return bool True
5001
     */
5002
    public function set_maker($name = '')
5003
    {
5004
        if (empty($name)) {
5005
            return false;
5006
        }
5007
        $this->maker = $name;
5008
        $table = Database::get_course_table(TABLE_LP_MAIN);
5009
        $lp_id = $this->get_id();
5010
        $sql = "UPDATE $table SET
5011
                content_maker = '".Database::escape_string($this->maker)."'
5012
                WHERE iid = $lp_id";
5013
        Database::query($sql);
5014
5015
        return true;
5016
    }
5017
5018
    /**
5019
     * Sets the name of the current learnpath (and save).
5020
     *
5021
     * @param string $name Optional string giving the new name of this learnpath
5022
     *
5023
     * @return bool True/False
5024
     */
5025
    public function set_name($name = null)
5026
    {
5027
        if (empty($name)) {
5028
            return false;
5029
        }
5030
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5031
        $name = Database::escape_string($name);
5032
5033
        $this->name = $name;
5034
5035
        $lp_id = $this->get_id();
5036
        $course_id = $this->course_info['real_id'];
5037
        $sql = "UPDATE $lp_table SET
5038
                name = '$name'
5039
                WHERE iid = $lp_id";
5040
        $result = Database::query($sql);
5041
        // If the lp is visible on the homepage, change his name there.
5042
        if (Database::affected_rows($result)) {
5043
            $session_id = api_get_session_id();
5044
            $session_condition = api_get_session_condition($session_id);
5045
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5046
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5047
            $sql = "UPDATE $tbl_tool SET name = '$name'
5048
            	    WHERE
5049
            	        c_id = $course_id AND
5050
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5051
            Database::query($sql);
5052
5053
            return true;
5054
        }
5055
5056
        return false;
5057
    }
5058
5059
    /**
5060
     * Set index specified prefix terms for all items in this path.
5061
     *
5062
     * @param string $terms_string Comma-separated list of terms
5063
     * @param string $prefix       Xapian term prefix
5064
     *
5065
     * @return bool False on error, true otherwise
5066
     */
5067
    public function set_terms_by_prefix($terms_string, $prefix)
5068
    {
5069
        $course_id = api_get_course_int_id();
5070
        if (api_get_setting('search_enabled') !== 'true') {
5071
            return false;
5072
        }
5073
5074
        if (!extension_loaded('xapian')) {
5075
            return false;
5076
        }
5077
5078
        $terms_string = trim($terms_string);
5079
        $terms = explode(',', $terms_string);
5080
        array_walk($terms, 'trim_value');
5081
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5082
5083
        // Don't do anything if no change, verify only at DB, not the search engine.
5084
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5085
            return false;
5086
        }
5087
5088
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5089
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5090
5091
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5092
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5093
        $lp_id = (int) $_POST['lp_id'];
5094
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5095
        $result = Database::query($sql);
5096
        $di = new ChamiloIndexer();
5097
5098
        while ($lp_item = Database::fetch_array($result)) {
5099
            // Get search_did.
5100
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5101
            $sql = 'SELECT * FROM %s
5102
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5103
                    LIMIT 1';
5104
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5105
5106
            //echo $sql; echo '<br>';
5107
            $res = Database::query($sql);
5108
            if (Database::num_rows($res) > 0) {
5109
                $se_ref = Database::fetch_array($res);
5110
                // Compare terms.
5111
                $doc = $di->get_document($se_ref['search_did']);
5112
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5113
                $xterms = [];
5114
                foreach ($xapian_terms as $xapian_term) {
5115
                    $xterms[] = substr($xapian_term['name'], 1);
5116
                }
5117
5118
                $dterms = $terms;
5119
                $missing_terms = array_diff($dterms, $xterms);
5120
                $deprecated_terms = array_diff($xterms, $dterms);
5121
5122
                // Save it to search engine.
5123
                foreach ($missing_terms as $term) {
5124
                    $doc->add_term($prefix.$term, 1);
5125
                }
5126
                foreach ($deprecated_terms as $term) {
5127
                    $doc->remove_term($prefix.$term);
5128
                }
5129
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5130
                $di->getDb()->flush();
5131
            }
5132
        }
5133
5134
        return true;
5135
    }
5136
5137
    /**
5138
     * Sets the theme of the LP (local/remote) (and save).
5139
     *
5140
     * @param string $name Optional string giving the new theme of this learnpath
5141
     *
5142
     * @return bool Returns true if theme name is not empty
5143
     */
5144
    public function set_theme($name = '')
5145
    {
5146
        $this->theme = $name;
5147
        $table = Database::get_course_table(TABLE_LP_MAIN);
5148
        $lp_id = $this->get_id();
5149
        $sql = "UPDATE $table
5150
                SET theme = '".Database::escape_string($this->theme)."'
5151
                WHERE iid = $lp_id";
5152
        Database::query($sql);
5153
5154
        return true;
5155
    }
5156
5157
    /**
5158
     * Sets the image of an LP (and save).
5159
     *
5160
     * @param string $name Optional string giving the new image of this learnpath
5161
     *
5162
     * @return bool Returns true if theme name is not empty
5163
     */
5164
    public function set_preview_image($name = '')
5165
    {
5166
        $this->preview_image = $name;
5167
        $table = Database::get_course_table(TABLE_LP_MAIN);
5168
        $lp_id = $this->get_id();
5169
        $sql = "UPDATE $table SET
5170
                preview_image = '".Database::escape_string($this->preview_image)."'
5171
                WHERE iid = $lp_id";
5172
        Database::query($sql);
5173
5174
        return true;
5175
    }
5176
5177
    /**
5178
     * Sets the author of a LP (and save).
5179
     *
5180
     * @param string $name Optional string giving the new author of this learnpath
5181
     *
5182
     * @return bool Returns true if author's name is not empty
5183
     */
5184
    public function set_author($name = '')
5185
    {
5186
        $this->author = $name;
5187
        $table = Database::get_course_table(TABLE_LP_MAIN);
5188
        $lp_id = $this->get_id();
5189
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5190
                WHERE iid = $lp_id";
5191
        Database::query($sql);
5192
5193
        return true;
5194
    }
5195
5196
    /**
5197
     * Sets the hide_toc_frame parameter of a LP (and save).
5198
     *
5199
     * @param int $hide 1 if frame is hidden 0 then else
5200
     *
5201
     * @return bool Returns true if author's name is not empty
5202
     */
5203
    public function set_hide_toc_frame($hide)
5204
    {
5205
        if (intval($hide) == $hide) {
5206
            $this->hide_toc_frame = $hide;
5207
            $table = Database::get_course_table(TABLE_LP_MAIN);
5208
            $lp_id = $this->get_id();
5209
            $sql = "UPDATE $table SET
5210
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5211
                    WHERE iid = $lp_id";
5212
            Database::query($sql);
5213
5214
            return true;
5215
        }
5216
5217
        return false;
5218
    }
5219
5220
    /**
5221
     * Sets the prerequisite of a LP (and save).
5222
     *
5223
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5224
     *
5225
     * @return bool returns true if prerequisite is not empty
5226
     */
5227
    public function set_prerequisite($prerequisite)
5228
    {
5229
        $this->prerequisite = (int) $prerequisite;
5230
        $table = Database::get_course_table(TABLE_LP_MAIN);
5231
        $lp_id = $this->get_id();
5232
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5233
                WHERE iid = $lp_id";
5234
        Database::query($sql);
5235
5236
        return true;
5237
    }
5238
5239
    /**
5240
     * Sets the location/proximity of the LP (local/remote) (and save).
5241
     *
5242
     * @param string $name Optional string giving the new location of this learnpath
5243
     *
5244
     * @return bool True on success / False on error
5245
     */
5246
    public function set_proximity($name = '')
5247
    {
5248
        if (empty($name)) {
5249
            return false;
5250
        }
5251
5252
        $this->proximity = $name;
5253
        $table = Database::get_course_table(TABLE_LP_MAIN);
5254
        $lp_id = $this->get_id();
5255
        $sql = "UPDATE $table SET
5256
                    content_local = '".Database::escape_string($name)."'
5257
                WHERE iid = $lp_id";
5258
        Database::query($sql);
5259
5260
        return true;
5261
    }
5262
5263
    /**
5264
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5265
     *
5266
     * @param int $id DB ID of the item
5267
     */
5268
    public function set_previous_item($id)
5269
    {
5270
        if ($this->debug > 0) {
5271
            error_log('In learnpath::set_previous_item()', 0);
5272
        }
5273
        $this->last = $id;
5274
    }
5275
5276
    /**
5277
     * Sets use_max_score.
5278
     *
5279
     * @param int $use_max_score Optional string giving the new location of this learnpath
5280
     *
5281
     * @return bool True on success / False on error
5282
     */
5283
    public function set_use_max_score($use_max_score = 1)
5284
    {
5285
        $use_max_score = (int) $use_max_score;
5286
        $this->use_max_score = $use_max_score;
5287
        $table = Database::get_course_table(TABLE_LP_MAIN);
5288
        $lp_id = $this->get_id();
5289
        $sql = "UPDATE $table SET
5290
                    use_max_score = '".$this->use_max_score."'
5291
                WHERE iid = $lp_id";
5292
        Database::query($sql);
5293
5294
        return true;
5295
    }
5296
5297
    /**
5298
     * Sets and saves the expired_on date.
5299
     *
5300
     * @param string $expired_on Optional string giving the new author of this learnpath
5301
     *
5302
     * @throws \Doctrine\ORM\OptimisticLockException
5303
     *
5304
     * @return bool Returns true if author's name is not empty
5305
     */
5306
    public function set_expired_on($expired_on)
5307
    {
5308
        $em = Database::getManager();
5309
        /** @var CLp $lp */
5310
        $lp = $em
5311
            ->getRepository('ChamiloCourseBundle:CLp')
5312
            ->findOneBy(
5313
                [
5314
                    'iid' => $this->get_id(),
5315
                ]
5316
            );
5317
5318
        if (!$lp) {
5319
            return false;
5320
        }
5321
5322
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5323
5324
        $lp->setExpiredOn($this->expired_on);
5325
        $em->persist($lp);
5326
        $em->flush();
5327
5328
        return true;
5329
    }
5330
5331
    /**
5332
     * Sets and saves the publicated_on date.
5333
     *
5334
     * @param string $publicated_on Optional string giving the new author of this learnpath
5335
     *
5336
     * @throws \Doctrine\ORM\OptimisticLockException
5337
     *
5338
     * @return bool Returns true if author's name is not empty
5339
     */
5340
    public function set_publicated_on($publicated_on)
5341
    {
5342
        $em = Database::getManager();
5343
        /** @var CLp $lp */
5344
        $lp = $em
5345
            ->getRepository('ChamiloCourseBundle:CLp')
5346
            ->findOneBy(
5347
                [
5348
                    'iid' => $this->get_id(),
5349
                ]
5350
            );
5351
5352
        if (!$lp) {
5353
            return false;
5354
        }
5355
5356
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5357
        $lp->setPublicatedOn($this->publicated_on);
5358
        $em->persist($lp);
5359
        $em->flush();
5360
5361
        return true;
5362
    }
5363
5364
    /**
5365
     * Sets and saves the expired_on date.
5366
     *
5367
     * @return bool Returns true if author's name is not empty
5368
     */
5369
    public function set_modified_on()
5370
    {
5371
        $this->modified_on = api_get_utc_datetime();
5372
        $table = Database::get_course_table(TABLE_LP_MAIN);
5373
        $lp_id = $this->get_id();
5374
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5375
                WHERE iid = $lp_id";
5376
        Database::query($sql);
5377
5378
        return true;
5379
    }
5380
5381
    /**
5382
     * Sets the object's error message.
5383
     *
5384
     * @param string $error Error message. If empty, reinits the error string
5385
     */
5386
    public function set_error_msg($error = '')
5387
    {
5388
        if ($this->debug > 0) {
5389
            error_log('In learnpath::set_error_msg()', 0);
5390
        }
5391
        if (empty($error)) {
5392
            $this->error = '';
5393
        } else {
5394
            $this->error .= $error;
5395
        }
5396
    }
5397
5398
    /**
5399
     * Launches the current item if not 'sco'
5400
     * (starts timer and make sure there is a record ready in the DB).
5401
     *
5402
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5403
     *
5404
     * @return bool
5405
     */
5406
    public function start_current_item($allow_new_attempt = false)
5407
    {
5408
        $debug = $this->debug;
5409
        if ($debug) {
5410
            error_log('In learnpath::start_current_item()');
5411
            error_log('current: '.$this->current);
5412
        }
5413
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5414
            $type = $this->get_type();
5415
            $item_type = $this->items[$this->current]->get_type();
5416
            if (($type == 2 && $item_type != 'sco') ||
5417
                ($type == 3 && $item_type != 'au') ||
5418
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5419
            ) {
5420
                if ($debug) {
5421
                    error_log('item type: '.$item_type);
5422
                    error_log('lp type: '.$type);
5423
                }
5424
                $this->items[$this->current]->open($allow_new_attempt);
5425
                $this->autocomplete_parents($this->current);
5426
                $prereq_check = $this->prerequisites_match($this->current);
5427
                if ($debug) {
5428
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5429
                }
5430
                $this->items[$this->current]->save(false, $prereq_check);
5431
            }
5432
            // If sco, then it is supposed to have been updated by some other call.
5433
            if ($item_type == 'sco') {
5434
                $this->items[$this->current]->restart();
5435
            }
5436
        }
5437
        if ($debug) {
5438
            error_log('lp_view_session_id');
5439
            error_log($this->lp_view_session_id);
5440
            error_log('api session id');
5441
            error_log(api_get_session_id());
5442
            error_log('End of learnpath::start_current_item()');
5443
        }
5444
5445
        return true;
5446
    }
5447
5448
    /**
5449
     * Stops the processing and counters for the old item (as held in $this->last).
5450
     *
5451
     * @return bool True/False
5452
     */
5453
    public function stop_previous_item()
5454
    {
5455
        $debug = $this->debug;
5456
        if ($debug) {
5457
            error_log('In learnpath::stop_previous_item()', 0);
5458
        }
5459
5460
        if ($this->last != 0 && $this->last != $this->current &&
5461
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5462
        ) {
5463
            if ($debug) {
5464
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5465
            }
5466
            switch ($this->get_type()) {
5467
                case '3':
5468
                    if ($this->items[$this->last]->get_type() != 'au') {
5469
                        if ($debug) {
5470
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5471
                        }
5472
                        $this->items[$this->last]->close();
5473
                    } else {
5474
                        if ($debug) {
5475
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5476
                        }
5477
                    }
5478
                    break;
5479
                case '2':
5480
                    if ($this->items[$this->last]->get_type() != 'sco') {
5481
                        if ($debug) {
5482
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5483
                        }
5484
                        $this->items[$this->last]->close();
5485
                    } else {
5486
                        if ($debug) {
5487
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5488
                        }
5489
                    }
5490
                    break;
5491
                case '1':
5492
                default:
5493
                    if ($debug) {
5494
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5495
                    }
5496
                    $this->items[$this->last]->close();
5497
                    break;
5498
            }
5499
        } else {
5500
            if ($debug) {
5501
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5502
            }
5503
5504
            return false;
5505
        }
5506
5507
        return true;
5508
    }
5509
5510
    /**
5511
     * Updates the default view mode from fullscreen to embedded and inversely.
5512
     *
5513
     * @return string The current default view mode ('fullscreen' or 'embedded')
5514
     */
5515
    public function update_default_view_mode()
5516
    {
5517
        $table = Database::get_course_table(TABLE_LP_MAIN);
5518
        $sql = "SELECT * FROM $table
5519
                WHERE iid = ".$this->get_id();
5520
        $res = Database::query($sql);
5521
        if (Database::num_rows($res) > 0) {
5522
            $row = Database::fetch_array($res);
5523
            $default_view_mode = $row['default_view_mod'];
5524
            $view_mode = $default_view_mode;
5525
            switch ($default_view_mode) {
5526
                case 'fullscreen': // default with popup
5527
                    $view_mode = 'embedded';
5528
                    break;
5529
                case 'embedded': // default view with left menu
5530
                    $view_mode = 'embedframe';
5531
                    break;
5532
                case 'embedframe': //folded menu
5533
                    $view_mode = 'impress';
5534
                    break;
5535
                case 'impress':
5536
                    $view_mode = 'fullscreen';
5537
                    break;
5538
            }
5539
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5540
                    WHERE iid = ".$this->get_id();
5541
            Database::query($sql);
5542
            $this->mode = $view_mode;
5543
5544
            return $view_mode;
5545
        }
5546
5547
        return -1;
5548
    }
5549
5550
    /**
5551
     * Updates the default behaviour about auto-commiting SCORM updates.
5552
     *
5553
     * @return bool True if auto-commit has been set to 'on', false otherwise
5554
     */
5555
    public function update_default_scorm_commit()
5556
    {
5557
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5558
        $sql = "SELECT * FROM $lp_table
5559
                WHERE iid = ".$this->get_id();
5560
        $res = Database::query($sql);
5561
        if (Database::num_rows($res) > 0) {
5562
            $row = Database::fetch_array($res);
5563
            $force = $row['force_commit'];
5564
            if ($force == 1) {
5565
                $force = 0;
5566
                $force_return = false;
5567
            } elseif ($force == 0) {
5568
                $force = 1;
5569
                $force_return = true;
5570
            }
5571
            $sql = "UPDATE $lp_table SET force_commit = $force
5572
                    WHERE iid = ".$this->get_id();
5573
            Database::query($sql);
5574
            $this->force_commit = $force_return;
5575
5576
            return $force_return;
5577
        }
5578
5579
        return -1;
5580
    }
5581
5582
    /**
5583
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5584
     *
5585
     * @return bool True on success, false on failure
5586
     */
5587
    public function update_display_order()
5588
    {
5589
        $course_id = api_get_course_int_id();
5590
        $table = Database::get_course_table(TABLE_LP_MAIN);
5591
        $sql = "SELECT * FROM $table
5592
                WHERE c_id = $course_id
5593
                ORDER BY display_order";
5594
        $res = Database::query($sql);
5595
        if ($res === false) {
5596
            return false;
5597
        }
5598
5599
        $num = Database::num_rows($res);
5600
        // First check the order is correct, globally (might be wrong because
5601
        // of versions < 1.8.4).
5602
        if ($num > 0) {
5603
            $i = 1;
5604
            while ($row = Database::fetch_array($res)) {
5605
                if ($row['display_order'] != $i) {
5606
                    // If we find a gap in the order, we need to fix it.
5607
                    $sql = "UPDATE $table SET display_order = $i
5608
                            WHERE iid = ".$row['iid'];
5609
                    Database::query($sql);
5610
                }
5611
                $i++;
5612
            }
5613
        }
5614
5615
        return true;
5616
    }
5617
5618
    /**
5619
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5620
     *
5621
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5622
     */
5623
    public function update_reinit()
5624
    {
5625
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5626
        $sql = "SELECT * FROM $lp_table
5627
                WHERE iid = ".$this->get_id();
5628
        $res = Database::query($sql);
5629
        if (Database::num_rows($res) > 0) {
5630
            $row = Database::fetch_array($res);
5631
            $force = $row['prevent_reinit'];
5632
            if ($force == 1) {
5633
                $force = 0;
5634
            } elseif ($force == 0) {
5635
                $force = 1;
5636
            }
5637
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5638
                    WHERE iid = ".$this->get_id();
5639
            Database::query($sql);
5640
            $this->prevent_reinit = $force;
5641
5642
            return $force;
5643
        }
5644
5645
        return -1;
5646
    }
5647
5648
    /**
5649
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5650
     *
5651
     * @return string 'single', 'multi' or 'seriousgame'
5652
     *
5653
     * @author ndiechburg <[email protected]>
5654
     */
5655
    public function get_attempt_mode()
5656
    {
5657
        //Set default value for seriousgame_mode
5658
        if (!isset($this->seriousgame_mode)) {
5659
            $this->seriousgame_mode = 0;
5660
        }
5661
        // Set default value for prevent_reinit
5662
        if (!isset($this->prevent_reinit)) {
5663
            $this->prevent_reinit = 1;
5664
        }
5665
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5666
            return 'seriousgame';
5667
        }
5668
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5669
            return 'single';
5670
        }
5671
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5672
            return 'multiple';
5673
        }
5674
5675
        return 'single';
5676
    }
5677
5678
    /**
5679
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5680
     *
5681
     * @param string 'seriousgame', 'single' or 'multiple'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'seriousgame', 'single' at position 0 could not be parsed: Unknown type name ''seriousgame'' at position 0 in 'seriousgame', 'single'.
Loading history...
5682
     *
5683
     * @return bool
5684
     *
5685
     * @author ndiechburg <[email protected]>
5686
     */
5687
    public function set_attempt_mode($mode)
5688
    {
5689
        switch ($mode) {
5690
            case 'seriousgame':
5691
                $sg_mode = 1;
5692
                $prevent_reinit = 1;
5693
                break;
5694
            case 'single':
5695
                $sg_mode = 0;
5696
                $prevent_reinit = 1;
5697
                break;
5698
            case 'multiple':
5699
                $sg_mode = 0;
5700
                $prevent_reinit = 0;
5701
                break;
5702
            default:
5703
                $sg_mode = 0;
5704
                $prevent_reinit = 0;
5705
                break;
5706
        }
5707
        $this->prevent_reinit = $prevent_reinit;
5708
        $this->seriousgame_mode = $sg_mode;
5709
        $table = Database::get_course_table(TABLE_LP_MAIN);
5710
        $sql = "UPDATE $table SET
5711
                prevent_reinit = $prevent_reinit ,
5712
                seriousgame_mode = $sg_mode
5713
                WHERE iid = ".$this->get_id();
5714
        $res = Database::query($sql);
5715
        if ($res) {
5716
            return true;
5717
        } else {
5718
            return false;
5719
        }
5720
    }
5721
5722
    /**
5723
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5724
     *
5725
     * @author ndiechburg <[email protected]>
5726
     */
5727
    public function switch_attempt_mode()
5728
    {
5729
        $mode = $this->get_attempt_mode();
5730
        switch ($mode) {
5731
            case 'single':
5732
                $next_mode = 'multiple';
5733
                break;
5734
            case 'multiple':
5735
                $next_mode = 'seriousgame';
5736
                break;
5737
            case 'seriousgame':
5738
            default:
5739
                $next_mode = 'single';
5740
                break;
5741
        }
5742
        $this->set_attempt_mode($next_mode);
5743
    }
5744
5745
    /**
5746
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5747
     * but possibility to do again a completed item.
5748
     *
5749
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5750
     *
5751
     * @author ndiechburg <[email protected]>
5752
     */
5753
    public function set_seriousgame_mode()
5754
    {
5755
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5756
        $sql = "SELECT * FROM $lp_table
5757
                WHERE iid = ".$this->get_id();
5758
        $res = Database::query($sql);
5759
        if (Database::num_rows($res) > 0) {
5760
            $row = Database::fetch_array($res);
5761
            $force = $row['seriousgame_mode'];
5762
            if ($force == 1) {
5763
                $force = 0;
5764
            } elseif ($force == 0) {
5765
                $force = 1;
5766
            }
5767
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5768
			        WHERE iid = ".$this->get_id();
5769
            Database::query($sql);
5770
            $this->seriousgame_mode = $force;
5771
5772
            return $force;
5773
        }
5774
5775
        return -1;
5776
    }
5777
5778
    /**
5779
     * Updates the "scorm_debug" value that shows or hide the debug window.
5780
     *
5781
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5782
     */
5783
    public function update_scorm_debug()
5784
    {
5785
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5786
        $sql = "SELECT * FROM $lp_table
5787
                WHERE iid = ".$this->get_id();
5788
        $res = Database::query($sql);
5789
        if (Database::num_rows($res) > 0) {
5790
            $row = Database::fetch_array($res);
5791
            $force = $row['debug'];
5792
            if ($force == 1) {
5793
                $force = 0;
5794
            } elseif ($force == 0) {
5795
                $force = 1;
5796
            }
5797
            $sql = "UPDATE $lp_table SET debug = $force
5798
                    WHERE iid = ".$this->get_id();
5799
            Database::query($sql);
5800
            $this->scorm_debug = $force;
5801
5802
            return $force;
5803
        }
5804
5805
        return -1;
5806
    }
5807
5808
    /**
5809
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5810
     *
5811
     * @author Kevin Van Den Haute
5812
     *
5813
     * @param  array
5814
     */
5815
    public function tree_array($array)
5816
    {
5817
        $array = $this->sort_tree_array($array);
5818
        $this->create_tree_array($array);
5819
    }
5820
5821
    /**
5822
     * Creates an array with the elements of the learning path tree in it.
5823
     *
5824
     * @author Kevin Van Den Haute
5825
     *
5826
     * @param array $array
5827
     * @param int   $parent
5828
     * @param int   $depth
5829
     * @param array $tmp
5830
     */
5831
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5832
    {
5833
        if (is_array($array)) {
5834
            for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5835
                if ($array[$i]['parent_item_id'] == $parent) {
5836
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5837
                        $tmp[] = $array[$i]['parent_item_id'];
5838
                        $depth++;
5839
                    }
5840
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5841
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5842
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5843
5844
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5845
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5846
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5847
                    $this->arrMenu[] = [
5848
                        'id' => $array[$i]['id'],
5849
                        'ref' => $ref,
5850
                        'item_type' => $array[$i]['item_type'],
5851
                        'title' => $array[$i]['title'],
5852
                        'title_raw' => $array[$i]['title_raw'],
5853
                        'path' => $path,
5854
                        'description' => $array[$i]['description'],
5855
                        'parent_item_id' => $array[$i]['parent_item_id'],
5856
                        'previous_item_id' => $array[$i]['previous_item_id'],
5857
                        'next_item_id' => $array[$i]['next_item_id'],
5858
                        'min_score' => $array[$i]['min_score'],
5859
                        'max_score' => $array[$i]['max_score'],
5860
                        'mastery_score' => $array[$i]['mastery_score'],
5861
                        'display_order' => $array[$i]['display_order'],
5862
                        'prerequisite' => $preq,
5863
                        'depth' => $depth,
5864
                        'audio' => $audio,
5865
                        'prerequisite_min_score' => $prerequisiteMinScore,
5866
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5867
                    ];
5868
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5869
                }
5870
            }
5871
        }
5872
    }
5873
5874
    /**
5875
     * Sorts a multi dimensional array by parent id and display order.
5876
     *
5877
     * @author Kevin Van Den Haute
5878
     *
5879
     * @param array $array (array with al the learning path items in it)
5880
     *
5881
     * @return array
5882
     */
5883
    public function sort_tree_array($array)
5884
    {
5885
        foreach ($array as $key => $row) {
5886
            $parent[$key] = $row['parent_item_id'];
5887
            $position[$key] = $row['display_order'];
5888
        }
5889
5890
        if (count($array) > 0) {
5891
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5892
        }
5893
5894
        return $array;
5895
    }
5896
5897
    /**
5898
     * Function that creates a html list of learning path items so that we can add audio files to them.
5899
     *
5900
     * @author Kevin Van Den Haute
5901
     *
5902
     * @return string
5903
     */
5904
    public function overview()
5905
    {
5906
        $return = '';
5907
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5908
5909
        // we need to start a form when we want to update all the mp3 files
5910
        if ($update_audio == 'true') {
5911
            $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">';
5912
        }
5913
        $return .= '<div id="message"></div>';
5914
        if (count($this->items) == 0) {
5915
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5916
        } else {
5917
            $return_audio = '<table class="data_table">';
5918
            $return_audio .= '<tr>';
5919
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5920
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5921
            $return_audio .= '</tr>';
5922
5923
            if ($update_audio != 'true') {
5924
                $return .= '<div class="col-md-12">';
5925
                $return .= self::return_new_tree($update_audio);
5926
                $return .= '</div>';
5927
                $return .= Display::div(
5928
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5929
                    ['style' => 'float:left; margin-top:15px;width:100%']
5930
                );
5931
            } else {
5932
                $return_audio .= self::return_new_tree($update_audio);
5933
                $return .= $return_audio.'</table>';
5934
            }
5935
5936
            // We need to close the form when we are updating the mp3 files.
5937
            if ($update_audio == 'true') {
5938
                $return .= '<div class="footer-audio">';
5939
                $return .= Display::button(
5940
                    'save_audio',
5941
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
5942
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5943
                );
5944
                $return .= '</div>';
5945
            }
5946
        }
5947
5948
        // We need to close the form when we are updating the mp3 files.
5949
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5950
            $return .= '</form>';
5951
        }
5952
5953
        return $return;
5954
    }
5955
5956
    /**
5957
     * @param string $update_audio
5958
     *
5959
     * @return array
5960
     */
5961
    public function processBuildMenuElements($update_audio = 'false')
5962
    {
5963
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5964
        $arrLP = $this->getItemsForForm();
5965
5966
        $this->tree_array($arrLP);
5967
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5968
        unset($this->arrMenu);
5969
        $default_data = null;
5970
        $default_content = null;
5971
        $elements = [];
5972
        $return_audio = null;
5973
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5974
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5975
        $countItems = count($arrLP);
5976
5977
        $upIcon = Display::return_icon(
5978
            'up.png',
5979
            get_lang('Up'),
5980
            [],
5981
            ICON_SIZE_TINY
5982
        );
5983
5984
        $disableUpIcon = Display::return_icon(
5985
            'up_na.png',
5986
            get_lang('Up'),
5987
            [],
5988
            ICON_SIZE_TINY
5989
        );
5990
5991
        $downIcon = Display::return_icon(
5992
            'down.png',
5993
            get_lang('Down'),
5994
            [],
5995
            ICON_SIZE_TINY
5996
        );
5997
5998
        $disableDownIcon = Display::return_icon(
5999
            'down_na.png',
6000
            get_lang('Down'),
6001
            [],
6002
            ICON_SIZE_TINY
6003
        );
6004
6005
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6006
6007
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6008
        $plugin = null;
6009
        if ($pluginCalendar) {
6010
            $plugin = LearningCalendarPlugin::create();
6011
        }
6012
6013
        for ($i = 0; $i < $countItems; $i++) {
6014
            $parent_id = $arrLP[$i]['parent_item_id'];
6015
            $title = $arrLP[$i]['title'];
6016
            $title_cut = $arrLP[$i]['title_raw'];
6017
            if ($show === false) {
6018
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6019
            }
6020
            // Link for the documents
6021
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6022
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6023
                $title_cut = Display::url(
6024
                    $title_cut,
6025
                    $url,
6026
                    [
6027
                        'class' => 'ajax moved',
6028
                        'data-title' => $title,
6029
                        'title' => $title,
6030
                    ]
6031
                );
6032
            }
6033
6034
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6035
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6036
                Session::write('pathItem', $arrLP[$i]['path']);
6037
            }
6038
6039
            $oddClass = 'row_even';
6040
            if (($i % 2) == 0) {
6041
                $oddClass = 'row_odd';
6042
            }
6043
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6044
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6045
6046
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6047
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6048
            } else {
6049
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6050
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6051
                } else {
6052
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6053
                        $icon = Display::return_icon('certificate.png');
6054
                    } else {
6055
                        $icon = Display::return_icon('folder_document.gif');
6056
                    }
6057
                }
6058
            }
6059
6060
            // The audio column.
6061
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6062
            $audio = '';
6063
            if (!$update_audio || $update_audio != 'true') {
6064
                if (empty($arrLP[$i]['audio'])) {
6065
                    $audio .= '';
6066
                }
6067
            } else {
6068
                $types = self::getChapterTypes();
6069
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6070
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6071
                    if (!empty($arrLP[$i]['audio'])) {
6072
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6073
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6074
                    }
6075
                }
6076
            }
6077
6078
            $return_audio .= Display::span($icon.' '.$title).
6079
                Display::tag(
6080
                    'td',
6081
                    $audio,
6082
                    ['style' => '']
6083
                );
6084
            $return_audio .= '</td>';
6085
            $move_icon = '';
6086
            $move_item_icon = '';
6087
            $edit_icon = '';
6088
            $delete_icon = '';
6089
            $audio_icon = '';
6090
            $prerequisities_icon = '';
6091
            $forumIcon = '';
6092
            $previewIcon = '';
6093
            $pluginCalendarIcon = '';
6094
            $orderIcons = '';
6095
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6096
6097
            if ($is_allowed_to_edit) {
6098
                if (!$update_audio || $update_audio != 'true') {
6099
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6100
                        $move_icon .= '<a class="moved" href="#">';
6101
                        $move_icon .= Display::return_icon(
6102
                            'move_everywhere.png',
6103
                            get_lang('Move'),
6104
                            [],
6105
                            ICON_SIZE_TINY
6106
                        );
6107
                        $move_icon .= '</a>';
6108
                    }
6109
                }
6110
6111
                // No edit for this item types
6112
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6113
                    if ($arrLP[$i]['item_type'] != 'dir') {
6114
                        $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">';
6115
                        $edit_icon .= Display::return_icon(
6116
                            'edit.png',
6117
                            get_lang('LearnpathEditModule'),
6118
                            [],
6119
                            ICON_SIZE_TINY
6120
                        );
6121
                        $edit_icon .= '</a>';
6122
6123
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6124
                            $forumThread = null;
6125
                            if (isset($this->items[$arrLP[$i]['id']])) {
6126
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6127
                                    $this->course_int_id,
6128
                                    $this->lp_session_id
6129
                                );
6130
                            }
6131
                            if ($forumThread) {
6132
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6133
                                        'action' => 'dissociate_forum',
6134
                                        'id' => $arrLP[$i]['id'],
6135
                                        'lp_id' => $this->lp_id,
6136
                                    ]);
6137
                                $forumIcon = Display::url(
6138
                                    Display::return_icon(
6139
                                        'forum.png',
6140
                                        get_lang('DissociateForumToLPItem'),
6141
                                        [],
6142
                                        ICON_SIZE_TINY
6143
                                    ),
6144
                                    $forumIconUrl,
6145
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6146
                                );
6147
                            } else {
6148
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6149
                                        'action' => 'create_forum',
6150
                                        'id' => $arrLP[$i]['id'],
6151
                                        'lp_id' => $this->lp_id,
6152
                                    ]);
6153
                                $forumIcon = Display::url(
6154
                                    Display::return_icon(
6155
                                        'forum.png',
6156
                                        get_lang('AssociateForumToLPItem'),
6157
                                        [],
6158
                                        ICON_SIZE_TINY
6159
                                    ),
6160
                                    $forumIconUrl,
6161
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6162
                                );
6163
                            }
6164
                        }
6165
                    } else {
6166
                        $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">';
6167
                        $edit_icon .= Display::return_icon(
6168
                            'edit.png',
6169
                            get_lang('LearnpathEditModule'),
6170
                            [],
6171
                            ICON_SIZE_TINY
6172
                        );
6173
                        $edit_icon .= '</a>';
6174
                    }
6175
                } else {
6176
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6177
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6178
                        $edit_icon .= Display::return_icon(
6179
                            'edit.png',
6180
                            get_lang('Edit'),
6181
                            [],
6182
                            ICON_SIZE_TINY
6183
                        );
6184
                        $edit_icon .= '</a>';
6185
                    }
6186
                }
6187
6188
                if ($pluginCalendar) {
6189
                    $pluginLink = $pluginUrl.
6190
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6191
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6192
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6193
                    if ($itemInfo && $itemInfo['value'] == 1) {
6194
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6195
                    }
6196
                    $pluginCalendarIcon = Display::url(
6197
                        $iconCalendar,
6198
                        $pluginLink,
6199
                        ['class' => 'btn btn-default']
6200
                    );
6201
                }
6202
6203
                if ($arrLP[$i]['item_type'] != 'final_item') {
6204
                    $orderIcons = Display::url(
6205
                        $upIcon,
6206
                        'javascript:void(0)',
6207
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6208
                    );
6209
                    $orderIcons .= Display::url(
6210
                        $downIcon,
6211
                        'javascript:void(0)',
6212
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6213
                    );
6214
                }
6215
6216
                $delete_icon .= ' <a
6217
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6218
                    onclick="return confirmation(\''.addslashes($title).'\');"
6219
                    class="btn btn-default">';
6220
                $delete_icon .= Display::return_icon(
6221
                    'delete.png',
6222
                    get_lang('LearnpathDeleteModule'),
6223
                    [],
6224
                    ICON_SIZE_TINY
6225
                );
6226
                $delete_icon .= '</a>';
6227
6228
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6229
                $previewImage = Display::return_icon(
6230
                    'preview_view.png',
6231
                    get_lang('Preview'),
6232
                    [],
6233
                    ICON_SIZE_TINY
6234
                );
6235
6236
                switch ($arrLP[$i]['item_type']) {
6237
                    case TOOL_DOCUMENT:
6238
                    case TOOL_LP_FINAL_ITEM:
6239
                    case TOOL_READOUT_TEXT:
6240
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6241
                        $previewIcon = Display::url(
6242
                            $previewImage,
6243
                            $urlPreviewLink,
6244
                            [
6245
                                'target' => '_blank',
6246
                                'class' => 'btn btn-default',
6247
                                'data-title' => $arrLP[$i]['title'],
6248
                                'title' => $arrLP[$i]['title'],
6249
                            ]
6250
                        );
6251
                        break;
6252
                    case TOOL_THREAD:
6253
                    case TOOL_FORUM:
6254
                    case TOOL_QUIZ:
6255
                    case TOOL_STUDENTPUBLICATION:
6256
                    case TOOL_LP_FINAL_ITEM:
6257
                    case TOOL_LINK:
6258
                        $class = 'btn btn-default';
6259
                        $target = '_blank';
6260
                        $link = self::rl_get_resource_link_for_learnpath(
6261
                            $this->course_int_id,
6262
                            $this->lp_id,
6263
                            $arrLP[$i]['id'],
6264
                            0
6265
                        );
6266
                        $previewIcon = Display::url(
6267
                            $previewImage,
6268
                            $link,
6269
                            [
6270
                                'class' => $class,
6271
                                'data-title' => $arrLP[$i]['title'],
6272
                                'title' => $arrLP[$i]['title'],
6273
                                'target' => $target,
6274
                            ]
6275
                        );
6276
                        break;
6277
                    default:
6278
                        $previewIcon = Display::url(
6279
                            $previewImage,
6280
                            $url.'&action=view_item',
6281
                            ['class' => 'btn btn-default', 'target' => '_blank']
6282
                        );
6283
                        break;
6284
                }
6285
6286
                if ($arrLP[$i]['item_type'] != 'dir') {
6287
                    $prerequisities_icon = Display::url(
6288
                        Display::return_icon(
6289
                            'accept.png',
6290
                            get_lang('LearnpathPrerequisites'),
6291
                            [],
6292
                            ICON_SIZE_TINY
6293
                        ),
6294
                        $url.'&action=edit_item_prereq',
6295
                        ['class' => 'btn btn-default']
6296
                    );
6297
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6298
                        $move_item_icon = Display::url(
6299
                            Display::return_icon(
6300
                                'move.png',
6301
                                get_lang('Move'),
6302
                                [],
6303
                                ICON_SIZE_TINY
6304
                            ),
6305
                            $url.'&action=move_item',
6306
                            ['class' => 'btn btn-default']
6307
                        );
6308
                    }
6309
                    $audio_icon = Display::url(
6310
                        Display::return_icon(
6311
                            'audio.png',
6312
                            get_lang('UplUpload'),
6313
                            [],
6314
                            ICON_SIZE_TINY
6315
                        ),
6316
                        $url.'&action=add_audio',
6317
                        ['class' => 'btn btn-default']
6318
                    );
6319
                }
6320
            }
6321
            if ($update_audio != 'true') {
6322
                $row = $move_icon.' '.$icon.
6323
                    Display::span($title_cut).
6324
                    Display::tag(
6325
                        'div',
6326
                        "<div class=\"btn-group btn-group-xs\">
6327
                                    $previewIcon
6328
                                    $audio
6329
                                    $edit_icon
6330
                                    $pluginCalendarIcon
6331
                                    $forumIcon
6332
                                    $prerequisities_icon
6333
                                    $move_item_icon
6334
                                    $audio_icon
6335
                                    $orderIcons
6336
                                    $delete_icon
6337
                                </div>",
6338
                        ['class' => 'btn-toolbar button_actions']
6339
                    );
6340
            } else {
6341
                $row =
6342
                    Display::span($title.$icon).
6343
                    Display::span($audio, ['class' => 'button_actions']);
6344
            }
6345
6346
            $default_data[$arrLP[$i]['id']] = $row;
6347
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6348
6349
            if (empty($parent_id)) {
6350
                $elements[$arrLP[$i]['id']]['data'] = $row;
6351
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6352
            } else {
6353
                $parent_arrays = [];
6354
                if ($arrLP[$i]['depth'] > 1) {
6355
                    // Getting list of parents
6356
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6357
                        foreach ($arrLP as $item) {
6358
                            if ($item['id'] == $parent_id) {
6359
                                if ($item['parent_item_id'] == 0) {
6360
                                    $parent_id = $item['id'];
6361
                                    break;
6362
                                } else {
6363
                                    $parent_id = $item['parent_item_id'];
6364
                                    if (empty($parent_arrays)) {
6365
                                        $parent_arrays[] = intval($item['id']);
6366
                                    }
6367
                                    $parent_arrays[] = $parent_id;
6368
                                    break;
6369
                                }
6370
                            }
6371
                        }
6372
                    }
6373
                }
6374
6375
                if (!empty($parent_arrays)) {
6376
                    $parent_arrays = array_reverse($parent_arrays);
6377
                    $val = '$elements';
6378
                    $x = 0;
6379
                    foreach ($parent_arrays as $item) {
6380
                        if ($x != count($parent_arrays) - 1) {
6381
                            $val .= '["'.$item.'"]["children"]';
6382
                        } else {
6383
                            $val .= '["'.$item.'"]["children"]';
6384
                        }
6385
                        $x++;
6386
                    }
6387
                    $val .= "";
6388
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6389
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6390
                } else {
6391
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6392
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6393
                }
6394
            }
6395
        }
6396
6397
        return [
6398
            'elements' => $elements,
6399
            'default_data' => $default_data,
6400
            'default_content' => $default_content,
6401
            'return_audio' => $return_audio,
6402
        ];
6403
    }
6404
6405
    /**
6406
     * @param string $updateAudio true/false strings
6407
     *
6408
     * @return string
6409
     */
6410
    public function returnLpItemList($updateAudio)
6411
    {
6412
        $result = $this->processBuildMenuElements($updateAudio);
6413
6414
        $html = self::print_recursive(
6415
            $result['elements'],
6416
            $result['default_data'],
6417
            $result['default_content']
6418
        );
6419
6420
        if (!empty($html)) {
6421
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6422
        }
6423
6424
        return $html;
6425
    }
6426
6427
    /**
6428
     * @param string $update_audio
6429
     * @param bool   $drop_element_here
6430
     *
6431
     * @return string
6432
     */
6433
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6434
    {
6435
        $result = $this->processBuildMenuElements($update_audio);
6436
6437
        $list = '<ul id="lp_item_list">';
6438
        $tree = $this->print_recursive(
6439
            $result['elements'],
6440
            $result['default_data'],
6441
            $result['default_content']
6442
        );
6443
6444
        if (!empty($tree)) {
6445
            $list .= $tree;
6446
        } else {
6447
            if ($drop_element_here) {
6448
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6449
            }
6450
        }
6451
        $list .= '</ul>';
6452
6453
        $return = Display::panelCollapse(
6454
            $this->name,
6455
            $list,
6456
            'scorm-list',
6457
            null,
6458
            'scorm-list-accordion',
6459
            'scorm-list-collapse'
6460
        );
6461
6462
        if ($update_audio === 'true') {
6463
            $return = $result['return_audio'];
6464
        }
6465
6466
        return $return;
6467
    }
6468
6469
    /**
6470
     * @param array $elements
6471
     * @param array $default_data
6472
     * @param array $default_content
6473
     *
6474
     * @return string
6475
     */
6476
    public function print_recursive($elements, $default_data, $default_content)
6477
    {
6478
        $return = '';
6479
        foreach ($elements as $key => $item) {
6480
            if (isset($item['load_data']) || empty($item['data'])) {
6481
                $item['data'] = $default_data[$item['load_data']];
6482
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6483
            }
6484
            $sub_list = '';
6485
            if (isset($item['type']) && $item['type'] === 'dir') {
6486
                // empty value
6487
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6488
            }
6489
            if (empty($item['children'])) {
6490
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6491
                $active = null;
6492
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6493
                    $active = 'active';
6494
                }
6495
                $return .= Display::tag(
6496
                    'li',
6497
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6498
                    ['id' => $key, 'class' => 'record li_container']
6499
                );
6500
            } else {
6501
                // Sections
6502
                $data = '';
6503
                if (isset($item['children'])) {
6504
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6505
                }
6506
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6507
                $return .= Display::tag(
6508
                    'li',
6509
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6510
                    ['id' => $key, 'class' => 'record li_container']
6511
                );
6512
            }
6513
        }
6514
6515
        return $return;
6516
    }
6517
6518
    /**
6519
     * This function builds the action menu.
6520
     *
6521
     * @param bool $returnContent          Optional
6522
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6523
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6524
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6525
     *
6526
     * @return string
6527
     */
6528
    public function build_action_menu(
6529
        $returnContent = false,
6530
        $showRequirementButtons = true,
6531
        $isConfigPage = false,
6532
        $allowExpand = true
6533
    ) {
6534
        $actionsRight = '';
6535
        $actionsLeft = Display::url(
6536
            Display::return_icon(
6537
                'back.png',
6538
                get_lang('ReturnToLearningPaths'),
6539
                '',
6540
                ICON_SIZE_MEDIUM
6541
            ),
6542
            'lp_controller.php?'.api_get_cidreq()
6543
        );
6544
        $actionsLeft .= Display::url(
6545
            Display::return_icon(
6546
                'preview_view.png',
6547
                get_lang('Preview'),
6548
                '',
6549
                ICON_SIZE_MEDIUM
6550
            ),
6551
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6552
                'action' => 'view',
6553
                'lp_id' => $this->lp_id,
6554
                'isStudentView' => 'true',
6555
            ])
6556
        );
6557
6558
        $actionsLeft .= Display::url(
6559
            Display::return_icon(
6560
                'upload_audio.png',
6561
                get_lang('UpdateAllAudioFragments'),
6562
                '',
6563
                ICON_SIZE_MEDIUM
6564
            ),
6565
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6566
                'action' => 'admin_view',
6567
                'lp_id' => $this->lp_id,
6568
                'updateaudio' => 'true',
6569
            ])
6570
        );
6571
6572
        $subscriptionSettings = self::getSubscriptionSettings();
6573
6574
        $request = api_request_uri();
6575
        if (strpos($request, 'edit') === false) {
6576
            $actionsLeft .= Display::url(
6577
                Display::return_icon(
6578
                    'settings.png',
6579
                    get_lang('CourseSettings'),
6580
                    '',
6581
                    ICON_SIZE_MEDIUM
6582
                ),
6583
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6584
                    'action' => 'edit',
6585
                    'lp_id' => $this->lp_id,
6586
                ])
6587
            );
6588
        }
6589
6590
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6591
            $actionsLeft .= Display::url(
6592
                Display::return_icon(
6593
                    'edit.png',
6594
                    get_lang('Edit'),
6595
                    '',
6596
                    ICON_SIZE_MEDIUM
6597
                ),
6598
                'lp_controller.php?'.http_build_query([
6599
                    'action' => 'build',
6600
                    'lp_id' => $this->lp_id,
6601
                ]).'&'.api_get_cidreq()
6602
            );
6603
        }
6604
6605
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6606
            if ($this->subscribeUsers == 1 &&
6607
                $subscriptionSettings['allow_add_users_to_lp']) {
6608
                $actionsLeft .= Display::url(
6609
                    Display::return_icon(
6610
                        'user.png',
6611
                        get_lang('SubscribeUsersToLp'),
6612
                        '',
6613
                        ICON_SIZE_MEDIUM
6614
                    ),
6615
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6616
                );
6617
            }
6618
        }
6619
6620
        if ($allowExpand) {
6621
            $actionsLeft .= Display::url(
6622
                Display::return_icon(
6623
                    'expand.png',
6624
                    get_lang('Expand'),
6625
                    ['id' => 'expand'],
6626
                    ICON_SIZE_MEDIUM
6627
                ).
6628
                Display::return_icon(
6629
                    'contract.png',
6630
                    get_lang('Collapse'),
6631
                    ['id' => 'contract', 'class' => 'hide'],
6632
                    ICON_SIZE_MEDIUM
6633
                ),
6634
                '#',
6635
                ['role' => 'button', 'id' => 'hide_bar_template']
6636
            );
6637
        }
6638
6639
        if ($showRequirementButtons) {
6640
            $buttons = [
6641
                [
6642
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6643
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6644
                        'action' => 'set_previous_step_as_prerequisite',
6645
                        'lp_id' => $this->lp_id,
6646
                    ]),
6647
                ],
6648
                [
6649
                    'title' => get_lang('ClearAllPrerequisites'),
6650
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6651
                        'action' => 'clear_prerequisites',
6652
                        'lp_id' => $this->lp_id,
6653
                    ]),
6654
                ],
6655
            ];
6656
            $actionsRight = Display::groupButtonWithDropDown(
6657
                get_lang('PrerequisitesOptions'),
6658
                $buttons,
6659
                true
6660
            );
6661
        }
6662
6663
        $toolbar = Display::toolbarAction(
6664
            'actions-lp-controller',
6665
            [$actionsLeft, $actionsRight]
6666
        );
6667
6668
        if ($returnContent) {
6669
            return $toolbar;
6670
        }
6671
6672
        echo $toolbar;
6673
    }
6674
6675
    /**
6676
     * Creates the default learning path folder.
6677
     *
6678
     * @param array $course
6679
     * @param int   $creatorId
6680
     *
6681
     * @return bool
6682
     */
6683
    public static function generate_learning_path_folder($course, $creatorId = 0)
6684
    {
6685
        // Creating learning_path folder
6686
        $dir = '/learning_path';
6687
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6688
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6689
6690
        $folder = false;
6691
        if (!is_dir($filepath.'/'.$dir)) {
6692
            $folderData = create_unexisting_directory(
6693
                $course,
6694
                $creatorId,
6695
                0,
6696
                null,
6697
                0,
6698
                $filepath,
6699
                $dir,
6700
                get_lang('LearningPaths'),
6701
                0
6702
            );
6703
            if (!empty($folderData)) {
6704
                $folder = true;
6705
            }
6706
        } else {
6707
            $folder = true;
6708
        }
6709
6710
        return $folder;
6711
    }
6712
6713
    /**
6714
     * @param array  $course
6715
     * @param string $lp_name
6716
     * @param int    $creatorId
6717
     *
6718
     * @return array
6719
     */
6720
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6721
    {
6722
        $filepath = '';
6723
        $dir = '/learning_path/';
6724
6725
        if (empty($lp_name)) {
6726
            $lp_name = $this->name;
6727
        }
6728
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6729
        $folder = self::generate_learning_path_folder($course, $creatorId);
6730
6731
        // Limits title size
6732
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6733
        $dir = $dir.$title;
6734
6735
        // Creating LP folder
6736
        $documentId = null;
6737
        if ($folder) {
6738
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6739
            if (!is_dir($filepath.'/'.$dir)) {
6740
                $folderData = create_unexisting_directory(
6741
                    $course,
6742
                    $creatorId,
6743
                    0,
6744
                    0,
6745
                    0,
6746
                    $filepath,
6747
                    $dir,
6748
                    $lp_name
6749
                );
6750
                if (!empty($folderData)) {
6751
                    $folder = true;
6752
                }
6753
6754
                $documentId = $folderData['id'];
6755
            } else {
6756
                $folder = true;
6757
            }
6758
            $dir = $dir.'/';
6759
            if ($folder) {
6760
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6761
            }
6762
        }
6763
6764
        if (empty($documentId)) {
6765
            $dir = api_remove_trailing_slash($dir);
6766
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6767
        }
6768
6769
        $array = [
6770
            'dir' => $dir,
6771
            'filepath' => $filepath,
6772
            'folder' => $folder,
6773
            'id' => $documentId,
6774
        ];
6775
6776
        return $array;
6777
    }
6778
6779
    /**
6780
     * Create a new document //still needs some finetuning.
6781
     *
6782
     * @param array  $courseInfo
6783
     * @param string $content
6784
     * @param string $title
6785
     * @param string $extension
6786
     * @param int    $parentId
6787
     * @param int    $creatorId  creator id
6788
     *
6789
     * @return int
6790
     */
6791
    public function create_document(
6792
        $courseInfo,
6793
        $content = '',
6794
        $title = '',
6795
        $extension = 'html',
6796
        $parentId = 0,
6797
        $creatorId = 0
6798
    ) {
6799
        if (!empty($courseInfo)) {
6800
            $course_id = $courseInfo['real_id'];
6801
        } else {
6802
            $course_id = api_get_course_int_id();
6803
        }
6804
6805
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6806
        $sessionId = api_get_session_id();
6807
6808
        // Generates folder
6809
        $result = $this->generate_lp_folder($courseInfo);
6810
        $dir = $result['dir'];
6811
6812
        if (empty($parentId) || $parentId == '/') {
6813
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6814
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6815
6816
            if ($parentId === '/') {
6817
                $dir = '/';
6818
            }
6819
6820
            // Please, do not modify this dirname formatting.
6821
            if (strstr($dir, '..')) {
6822
                $dir = '/';
6823
            }
6824
6825
            if (!empty($dir[0]) && $dir[0] == '.') {
6826
                $dir = substr($dir, 1);
6827
            }
6828
            if (!empty($dir[0]) && $dir[0] != '/') {
6829
                $dir = '/'.$dir;
6830
            }
6831
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6832
                $dir .= '/';
6833
            }
6834
        } else {
6835
            $parentInfo = DocumentManager::get_document_data_by_id(
6836
                $parentId,
6837
                $courseInfo['code']
6838
            );
6839
            if (!empty($parentInfo)) {
6840
                $dir = $parentInfo['path'].'/';
6841
            }
6842
        }
6843
6844
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6845
        if (!is_dir($filepath)) {
6846
            $dir = '/';
6847
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6848
        }
6849
6850
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6851
        // is already escaped twice when it gets here.
6852
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6853
        if (!empty($title)) {
6854
            $title = api_replace_dangerous_char(stripslashes($title));
6855
        } else {
6856
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6857
        }
6858
6859
        $title = disable_dangerous_file($title);
6860
        $filename = $title;
6861
        $content = !empty($content) ? $content : $_POST['content_lp'];
6862
        $tmp_filename = $filename;
6863
6864
        $i = 0;
6865
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6866
            $tmp_filename = $filename.'_'.++$i;
6867
        }
6868
6869
        $filename = $tmp_filename.'.'.$extension;
6870
        if ($extension == 'html') {
6871
            $content = stripslashes($content);
6872
            $content = str_replace(
6873
                api_get_path(WEB_COURSE_PATH),
6874
                api_get_path(REL_PATH).'courses/',
6875
                $content
6876
            );
6877
6878
            // Change the path of mp3 to absolute.
6879
            // The first regexp deals with :// urls.
6880
            $content = preg_replace(
6881
                "|(flashvars=\"file=)([^:/]+)/|",
6882
                "$1".api_get_path(
6883
                    REL_COURSE_PATH
6884
                ).$courseInfo['path'].'/document/',
6885
                $content
6886
            );
6887
            // The second regexp deals with audio/ urls.
6888
            $content = preg_replace(
6889
                "|(flashvars=\"file=)([^/]+)/|",
6890
                "$1".api_get_path(
6891
                    REL_COURSE_PATH
6892
                ).$courseInfo['path'].'/document/$2/',
6893
                $content
6894
            );
6895
            // For flv player: To prevent edition problem with firefox,
6896
            // we have to use a strange tip (don't blame me please).
6897
            $content = str_replace(
6898
                '</body>',
6899
                '<style type="text/css">body{}</style></body>',
6900
                $content
6901
            );
6902
        }
6903
6904
        if (!file_exists($filepath.$filename)) {
6905
            if ($fp = @fopen($filepath.$filename, 'w')) {
6906
                fputs($fp, $content);
6907
                fclose($fp);
6908
6909
                $file_size = filesize($filepath.$filename);
6910
                $save_file_path = $dir.$filename;
6911
6912
                $document_id = add_document(
6913
                    $courseInfo,
6914
                    $save_file_path,
6915
                    'file',
6916
                    $file_size,
6917
                    $tmp_filename,
6918
                    '',
6919
                    0, //readonly
6920
                    true,
6921
                    null,
6922
                    $sessionId,
6923
                    $creatorId
6924
                );
6925
6926
                if ($document_id) {
6927
                    api_item_property_update(
6928
                        $courseInfo,
6929
                        TOOL_DOCUMENT,
6930
                        $document_id,
6931
                        'DocumentAdded',
6932
                        $creatorId,
6933
                        null,
6934
                        null,
6935
                        null,
6936
                        null,
6937
                        $sessionId
6938
                    );
6939
6940
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6941
                    $new_title = $originalTitle;
6942
6943
                    if ($new_comment || $new_title) {
6944
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6945
                        $ct = '';
6946
                        if ($new_comment) {
6947
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
6948
                        }
6949
                        if ($new_title) {
6950
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
6951
                        }
6952
6953
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
6954
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
6955
                        Database::query($sql);
6956
                    }
6957
                }
6958
6959
                return $document_id;
6960
            }
6961
        }
6962
    }
6963
6964
    /**
6965
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6966
     *
6967
     * @param array $_course array
6968
     */
6969
    public function edit_document($_course)
6970
    {
6971
        $course_id = api_get_course_int_id();
6972
        $urlAppend = api_get_configuration_value('url_append');
6973
        // Please, do not modify this dirname formatting.
6974
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6975
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6976
6977
        if (strstr($dir, '..')) {
6978
            $dir = '/';
6979
        }
6980
6981
        if (isset($dir[0]) && $dir[0] == '.') {
6982
            $dir = substr($dir, 1);
6983
        }
6984
6985
        if (isset($dir[0]) && $dir[0] != '/') {
6986
            $dir = '/'.$dir;
6987
        }
6988
6989
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6990
            $dir .= '/';
6991
        }
6992
6993
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6994
        if (!is_dir($filepath)) {
6995
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6996
        }
6997
6998
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6999
7000
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7001
            $document_id = (int) $_POST['path'];
7002
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7003
            if (empty($documentInfo)) {
7004
                // Try with iid
7005
                $table = Database::get_course_table(TABLE_DOCUMENT);
7006
                $sql = "SELECT id, path FROM $table
7007
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7008
                $res_doc = Database::query($sql);
7009
                $row = Database::fetch_array($res_doc);
7010
                if ($row) {
7011
                    $document_id = $row['id'];
7012
                    $documentPath = $row['path'];
7013
                }
7014
            } else {
7015
                $documentPath = $documentInfo['path'];
7016
            }
7017
7018
            $content = stripslashes($_POST['content_lp']);
7019
            $file = $filepath.$documentPath;
7020
7021
            if (!file_exists($file)) {
7022
                return false;
7023
            }
7024
7025
            if ($fp = @fopen($file, 'w')) {
7026
                $content = str_replace(
7027
                    api_get_path(WEB_COURSE_PATH),
7028
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7029
                    $content
7030
                );
7031
                // Change the path of mp3 to absolute.
7032
                // The first regexp deals with :// urls.
7033
                $content = preg_replace(
7034
                    "|(flashvars=\"file=)([^:/]+)/|",
7035
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7036
                    $content
7037
                );
7038
                // The second regexp deals with audio/ urls.
7039
                $content = preg_replace(
7040
                    "|(flashvars=\"file=)([^:/]+)/|",
7041
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7042
                    $content
7043
                );
7044
                fputs($fp, $content);
7045
                fclose($fp);
7046
7047
                $sql = "UPDATE $table_doc SET
7048
                            title='".Database::escape_string($_POST['title'])."'
7049
                        WHERE c_id = $course_id AND id = ".$document_id;
7050
                Database::query($sql);
7051
            }
7052
        }
7053
    }
7054
7055
    /**
7056
     * Displays the selected item, with a panel for manipulating the item.
7057
     *
7058
     * @param int    $item_id
7059
     * @param string $msg
7060
     * @param bool   $show_actions
7061
     *
7062
     * @return string
7063
     */
7064
    public function display_item($item_id, $msg = null, $show_actions = true)
7065
    {
7066
        $course_id = api_get_course_int_id();
7067
        $return = '';
7068
        if (is_numeric($item_id)) {
7069
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7070
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7071
                    WHERE lp.iid = ".intval($item_id);
7072
            $result = Database::query($sql);
7073
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7074
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7075
7076
                // Prevents wrong parent selection for document, see Bug#1251.
7077
                if ($row['item_type'] != 'dir') {
7078
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7079
                }
7080
7081
                if ($show_actions) {
7082
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7083
                }
7084
                $return .= '<div style="padding:10px;">';
7085
7086
                if ($msg != '') {
7087
                    $return .= $msg;
7088
                }
7089
7090
                $return .= '<h3>'.$row['title'].'</h3>';
7091
7092
                switch ($row['item_type']) {
7093
                    case TOOL_THREAD:
7094
                        $link = $this->rl_get_resource_link_for_learnpath(
7095
                            $course_id,
7096
                            $row['lp_id'],
7097
                            $item_id,
7098
                            0
7099
                        );
7100
                        $return .= Display::url(
7101
                            get_lang('GoToThread'),
7102
                            $link,
7103
                            ['class' => 'btn btn-primary']
7104
                        );
7105
                        break;
7106
                    case TOOL_FORUM:
7107
                        $return .= Display::url(
7108
                            get_lang('GoToForum'),
7109
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7110
                            ['class' => 'btn btn-primary']
7111
                        );
7112
                        break;
7113
                    case TOOL_QUIZ:
7114
                        if (!empty($row['path'])) {
7115
                            $exercise = new Exercise();
7116
                            $exercise->read($row['path']);
7117
                            $return .= $exercise->description.'<br />';
7118
                            $return .= Display::url(
7119
                                get_lang('GoToExercise'),
7120
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7121
                                ['class' => 'btn btn-primary']
7122
                            );
7123
                        }
7124
                        break;
7125
                    case TOOL_LP_FINAL_ITEM:
7126
                        $return .= $this->getSavedFinalItem();
7127
                        break;
7128
                    case TOOL_DOCUMENT:
7129
                    case TOOL_READOUT_TEXT:
7130
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7131
                        $sql_doc = "SELECT path FROM $tbl_doc
7132
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7133
                        $result = Database::query($sql_doc);
7134
                        $path_file = Database::result($result, 0, 0);
7135
                        $path_parts = pathinfo($path_file);
7136
                        // TODO: Correct the following naive comparisons.
7137
                        if (in_array($path_parts['extension'], [
7138
                            'html',
7139
                            'txt',
7140
                            'png',
7141
                            'jpg',
7142
                            'JPG',
7143
                            'jpeg',
7144
                            'JPEG',
7145
                            'gif',
7146
                            'swf',
7147
                            'pdf',
7148
                            'htm',
7149
                        ])) {
7150
                            $return .= $this->display_document($row['path'], true, true);
7151
                        }
7152
                        break;
7153
                    case TOOL_HOTPOTATOES:
7154
                        $return .= $this->display_document($row['path'], false, true);
7155
                        break;
7156
                }
7157
                $return .= '</div>';
7158
            }
7159
        }
7160
7161
        return $return;
7162
    }
7163
7164
    /**
7165
     * Shows the needed forms for editing a specific item.
7166
     *
7167
     * @param int $item_id
7168
     *
7169
     * @throws Exception
7170
     * @throws HTML_QuickForm_Error
7171
     *
7172
     * @return string
7173
     */
7174
    public function display_edit_item($item_id)
7175
    {
7176
        $course_id = api_get_course_int_id();
7177
        $return = '';
7178
        $item_id = (int) $item_id;
7179
7180
        if (empty($item_id)) {
7181
            return '';
7182
        }
7183
7184
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7185
        $sql = "SELECT * FROM $tbl_lp_item
7186
                WHERE iid = ".$item_id;
7187
        $res = Database::query($sql);
7188
        $row = Database::fetch_array($res);
7189
        switch ($row['item_type']) {
7190
            case 'dir':
7191
            case 'asset':
7192
            case 'sco':
7193
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7194
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7195
                    $return .= $this->display_item_form(
7196
                        $row['item_type'],
7197
                        get_lang('EditCurrentChapter').' :',
7198
                        'edit',
7199
                        $item_id,
7200
                        $row
7201
                    );
7202
                } else {
7203
                    $return .= $this->display_item_form(
7204
                        $row['item_type'],
7205
                        get_lang('EditCurrentChapter').' :',
7206
                        'edit_item',
7207
                        $item_id,
7208
                        $row
7209
                    );
7210
                }
7211
                break;
7212
            case TOOL_DOCUMENT:
7213
            case TOOL_READOUT_TEXT:
7214
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7215
                $sql = "SELECT lp.*, doc.path as dir
7216
                        FROM $tbl_lp_item as lp
7217
                        LEFT JOIN $tbl_doc as doc
7218
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7219
                        WHERE
7220
                            doc.c_id = $course_id AND
7221
                            lp.iid = ".$item_id;
7222
                $res_step = Database::query($sql);
7223
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7224
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7225
7226
                if ($row['item_type'] === TOOL_DOCUMENT) {
7227
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7228
                }
7229
7230
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7231
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7232
                }
7233
                break;
7234
            case TOOL_LINK:
7235
                $linkId = (int) $row['path'];
7236
                if (!empty($linkId)) {
7237
                    $table = Database::get_course_table(TABLE_LINK);
7238
                    $sql = 'SELECT url FROM '.$table.'
7239
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7240
                    $res_link = Database::query($sql);
7241
                    $row_link = Database::fetch_array($res_link);
7242
                    if (empty($row_link)) {
7243
                        // Try with id
7244
                        $sql = 'SELECT url FROM '.$table.'
7245
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7246
                        $res_link = Database::query($sql);
7247
                        $row_link = Database::fetch_array($res_link);
7248
                    }
7249
7250
                    if (is_array($row_link)) {
7251
                        $row['url'] = $row_link['url'];
7252
                    }
7253
                }
7254
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7255
                $return .= $this->display_link_form('edit', $item_id, $row);
7256
                break;
7257
            case TOOL_LP_FINAL_ITEM:
7258
                Session::write('finalItem', true);
7259
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7260
                $sql = "SELECT lp.*, doc.path as dir
7261
                        FROM $tbl_lp_item as lp
7262
                        LEFT JOIN $tbl_doc as doc
7263
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7264
                        WHERE
7265
                            doc.c_id = $course_id AND
7266
                            lp.iid = ".$item_id;
7267
                $res_step = Database::query($sql);
7268
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7269
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7270
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7271
                break;
7272
            case TOOL_QUIZ:
7273
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7274
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7275
                break;
7276
            case TOOL_HOTPOTATOES:
7277
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7278
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7279
                break;
7280
            case TOOL_STUDENTPUBLICATION:
7281
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7282
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7283
                break;
7284
            case TOOL_FORUM:
7285
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7286
                $return .= $this->display_forum_form('edit', $item_id, $row);
7287
                break;
7288
            case TOOL_THREAD:
7289
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7290
                $return .= $this->display_thread_form('edit', $item_id, $row);
7291
                break;
7292
        }
7293
7294
        return $return;
7295
    }
7296
7297
    /**
7298
     * Function that displays a list with al the resources that
7299
     * could be added to the learning path.
7300
     *
7301
     * @throws Exception
7302
     * @throws HTML_QuickForm_Error
7303
     *
7304
     * @return bool
7305
     */
7306
    public function display_resources()
7307
    {
7308
        $course_code = api_get_course_id();
7309
7310
        // Get all the docs.
7311
        $documents = $this->get_documents(true);
7312
7313
        // Get all the exercises.
7314
        $exercises = $this->get_exercises();
7315
7316
        // Get all the links.
7317
        $links = $this->get_links();
7318
7319
        // Get all the student publications.
7320
        $works = $this->get_student_publications();
7321
7322
        // Get all the forums.
7323
        $forums = $this->get_forums(null, $course_code);
7324
7325
        // Get the final item form (see BT#11048) .
7326
        $finish = $this->getFinalItemForm();
7327
7328
        $headers = [
7329
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7330
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7331
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7332
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7333
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7334
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7335
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7336
        ];
7337
7338
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7339
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7340
7341
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7342
7343
        echo Display::tabs(
7344
            $headers,
7345
            [
7346
                $documents,
7347
                $exercises,
7348
                $links,
7349
                $works,
7350
                $forums,
7351
                $dir,
7352
                $finish,
7353
            ],
7354
            'resource_tab',
7355
            [],
7356
            [],
7357
            $selected
7358
        );
7359
7360
        return true;
7361
    }
7362
7363
    /**
7364
     * Returns the extension of a document.
7365
     *
7366
     * @param string $filename
7367
     *
7368
     * @return string Extension (part after the last dot)
7369
     */
7370
    public function get_extension($filename)
7371
    {
7372
        $explode = explode('.', $filename);
7373
7374
        return $explode[count($explode) - 1];
7375
    }
7376
7377
    /**
7378
     * Displays a document by id.
7379
     *
7380
     * @param int  $id
7381
     * @param bool $show_title
7382
     * @param bool $iframe
7383
     * @param bool $edit_link
7384
     *
7385
     * @return string
7386
     */
7387
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7388
    {
7389
        $_course = api_get_course_info();
7390
        $course_id = api_get_course_int_id();
7391
        $id = (int) $id;
7392
        $return = '';
7393
        $table = Database::get_course_table(TABLE_DOCUMENT);
7394
        $sql_doc = "SELECT * FROM $table
7395
                    WHERE c_id = $course_id AND iid = $id";
7396
        $res_doc = Database::query($sql_doc);
7397
        $row_doc = Database::fetch_array($res_doc);
7398
7399
        // TODO: Add a path filter.
7400
        if ($iframe) {
7401
            $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>';
7402
        } else {
7403
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7404
        }
7405
7406
        return $return;
7407
    }
7408
7409
    /**
7410
     * Return HTML form to add/edit a quiz.
7411
     *
7412
     * @param string $action     Action (add/edit)
7413
     * @param int    $id         Item ID if already exists
7414
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7415
     *
7416
     * @throws Exception
7417
     *
7418
     * @return string HTML form
7419
     */
7420
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7421
    {
7422
        $course_id = api_get_course_int_id();
7423
        $id = (int) $id;
7424
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7425
7426
        if ($id != 0 && is_array($extra_info)) {
7427
            $item_title = $extra_info['title'];
7428
            $item_description = $extra_info['description'];
7429
        } elseif (is_numeric($extra_info)) {
7430
            $sql = "SELECT title, description
7431
                    FROM $tbl_quiz
7432
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7433
7434
            $result = Database::query($sql);
7435
            $row = Database::fetch_array($result);
7436
            $item_title = $row['title'];
7437
            $item_description = $row['description'];
7438
        } else {
7439
            $item_title = '';
7440
            $item_description = '';
7441
        }
7442
        $item_title = Security::remove_XSS($item_title);
7443
        $item_description = Security::remove_XSS($item_description);
7444
7445
        $parent = 0;
7446
        if ($id != 0 && is_array($extra_info)) {
7447
            $parent = $extra_info['parent_item_id'];
7448
        }
7449
7450
        $arrLP = $this->getItemsForForm();
7451
        $this->tree_array($arrLP);
7452
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7453
        unset($this->arrMenu);
7454
7455
        $form = new FormValidator(
7456
            'quiz_form',
7457
            'POST',
7458
            $this->getCurrentBuildingModeURL()
7459
        );
7460
        $defaults = [];
7461
7462
        if ($action === 'add') {
7463
            $legend = get_lang('CreateTheExercise');
7464
        } elseif ($action === 'move') {
7465
            $legend = get_lang('MoveTheCurrentExercise');
7466
        } else {
7467
            $legend = get_lang('EditCurrentExecice');
7468
        }
7469
7470
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7471
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7472
        }
7473
7474
        $form->addHeader($legend);
7475
7476
        if ($action != 'move') {
7477
            $this->setItemTitle($form);
7478
            $defaults['title'] = $item_title;
7479
        }
7480
7481
        // Select for Parent item, root or chapter
7482
        $selectParent = $form->addSelect(
7483
            'parent',
7484
            get_lang('Parent'),
7485
            [],
7486
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7487
        );
7488
        $selectParent->addOption($this->name, 0);
7489
7490
        $arrHide = [
7491
            $id,
7492
        ];
7493
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7494
            if ($action != 'add') {
7495
                if (
7496
                    ($arrLP[$i]['item_type'] == 'dir') &&
7497
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7498
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7499
                ) {
7500
                    $selectParent->addOption(
7501
                        $arrLP[$i]['title'],
7502
                        $arrLP[$i]['id'],
7503
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7504
                    );
7505
7506
                    if ($parent == $arrLP[$i]['id']) {
7507
                        $selectParent->setSelected($arrLP[$i]['id']);
7508
                    }
7509
                } else {
7510
                    $arrHide[] = $arrLP[$i]['id'];
7511
                }
7512
            } else {
7513
                if ($arrLP[$i]['item_type'] == 'dir') {
7514
                    $selectParent->addOption(
7515
                        $arrLP[$i]['title'],
7516
                        $arrLP[$i]['id'],
7517
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7518
                    );
7519
7520
                    if ($parent == $arrLP[$i]['id']) {
7521
                        $selectParent->setSelected($arrLP[$i]['id']);
7522
                    }
7523
                }
7524
            }
7525
        }
7526
7527
        if (is_array($arrLP)) {
7528
            reset($arrLP);
7529
        }
7530
7531
        $selectPrevious = $form->addSelect(
7532
            'previous',
7533
            get_lang('Position'),
7534
            [],
7535
            ['id' => 'previous']
7536
        );
7537
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7538
7539
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7540
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7541
                $arrLP[$i]['id'] != $id
7542
            ) {
7543
                $selectPrevious->addOption(
7544
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7545
                    $arrLP[$i]['id']
7546
                );
7547
7548
                if (is_array($extra_info)) {
7549
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7550
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7551
                    }
7552
                } elseif ($action == 'add') {
7553
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7554
                }
7555
            }
7556
        }
7557
7558
        if ($action != 'move') {
7559
            $arrHide = [];
7560
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7561
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7562
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7563
                }
7564
            }
7565
        }
7566
7567
        if ($action === 'add') {
7568
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7569
        } else {
7570
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7571
        }
7572
7573
        if ($action === 'move') {
7574
            $form->addHidden('title', $item_title);
7575
            $form->addHidden('description', $item_description);
7576
        }
7577
7578
        if (is_numeric($extra_info)) {
7579
            $form->addHidden('path', $extra_info);
7580
        } elseif (is_array($extra_info)) {
7581
            $form->addHidden('path', $extra_info['path']);
7582
        }
7583
7584
        $form->addHidden('type', TOOL_QUIZ);
7585
        $form->addHidden('post_time', time());
7586
        $form->setDefaults($defaults);
7587
7588
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7589
    }
7590
7591
    /**
7592
     * Addition of Hotpotatoes tests.
7593
     *
7594
     * @param string $action
7595
     * @param int    $id         Internal ID of the item
7596
     * @param string $extra_info
7597
     *
7598
     * @return string HTML structure to display the hotpotatoes addition formular
7599
     */
7600
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7601
    {
7602
        $course_id = api_get_course_int_id();
7603
        $uploadPath = DIR_HOTPOTATOES;
7604
7605
        if ($id != 0 && is_array($extra_info)) {
7606
            $item_title = stripslashes($extra_info['title']);
7607
            $item_description = stripslashes($extra_info['description']);
7608
        } elseif (is_numeric($extra_info)) {
7609
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7610
7611
            $sql = "SELECT * FROM $TBL_DOCUMENT
7612
                    WHERE
7613
                        c_id = $course_id AND
7614
                        path LIKE '".$uploadPath."/%/%htm%' AND
7615
                        iid = ".(int) $extra_info."
7616
                    ORDER BY iid ASC";
7617
7618
            $res_hot = Database::query($sql);
7619
            $row = Database::fetch_array($res_hot);
7620
7621
            $item_title = $row['title'];
7622
            $item_description = $row['description'];
7623
7624
            if (!empty($row['comment'])) {
7625
                $item_title = $row['comment'];
7626
            }
7627
        } else {
7628
            $item_title = '';
7629
            $item_description = '';
7630
        }
7631
7632
        $parent = 0;
7633
        if ($id != 0 && is_array($extra_info)) {
7634
            $parent = $extra_info['parent_item_id'];
7635
        }
7636
7637
        $arrLP = $this->getItemsForForm();
7638
        $legend = '<legend>';
7639
        if ($action == 'add') {
7640
            $legend .= get_lang('CreateTheExercise');
7641
        } elseif ($action == 'move') {
7642
            $legend .= get_lang('MoveTheCurrentExercise');
7643
        } else {
7644
            $legend .= get_lang('EditCurrentExecice');
7645
        }
7646
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7647
            $legend .= Display:: return_message(
7648
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7649
            );
7650
        }
7651
        $legend .= '</legend>';
7652
7653
        $return = '<form method="POST">';
7654
        $return .= $legend;
7655
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7656
        $return .= '<tr>';
7657
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7658
        $return .= '<td class="input">';
7659
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7660
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7661
        $arrHide = [$id];
7662
7663
        if (count($arrLP) > 0) {
7664
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7665
                if ($action != 'add') {
7666
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7667
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7668
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7669
                    ) {
7670
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7671
                    } else {
7672
                        $arrHide[] = $arrLP[$i]['id'];
7673
                    }
7674
                } else {
7675
                    if ($arrLP[$i]['item_type'] == 'dir') {
7676
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7677
                    }
7678
                }
7679
            }
7680
            reset($arrLP);
7681
        }
7682
7683
        $return .= '</select>';
7684
        $return .= '</td>';
7685
        $return .= '</tr>';
7686
        $return .= '<tr>';
7687
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7688
        $return .= '<td class="input">';
7689
        $return .= '<select id="previous" name="previous" size="1">';
7690
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7691
7692
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7693
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7694
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7695
                    $selected = 'selected="selected" ';
7696
                } elseif ($action == 'add') {
7697
                    $selected = 'selected="selected" ';
7698
                } else {
7699
                    $selected = '';
7700
                }
7701
7702
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7703
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7704
            }
7705
        }
7706
7707
        $return .= '</select>';
7708
        $return .= '</td>';
7709
        $return .= '</tr>';
7710
7711
        if ($action != 'move') {
7712
            $return .= '<tr>';
7713
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7714
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7715
            $return .= '</tr>';
7716
            $id_prerequisite = 0;
7717
            if (is_array($arrLP) && count($arrLP) > 0) {
7718
                foreach ($arrLP as $key => $value) {
7719
                    if ($value['id'] == $id) {
7720
                        $id_prerequisite = $value['prerequisite'];
7721
                        break;
7722
                    }
7723
                }
7724
7725
                $arrHide = [];
7726
                for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7727
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7728
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7729
                    }
7730
                }
7731
            }
7732
        }
7733
7734
        $return .= '<tr>';
7735
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7736
            get_lang('SaveHotpotatoes').'</button></td>';
7737
        $return .= '</tr>';
7738
        $return .= '</table>';
7739
7740
        if ($action == 'move') {
7741
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7742
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7743
        }
7744
7745
        if (is_numeric($extra_info)) {
7746
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7747
        } elseif (is_array($extra_info)) {
7748
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7749
        }
7750
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7751
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7752
        $return .= '</form>';
7753
7754
        return $return;
7755
    }
7756
7757
    /**
7758
     * Return the form to display the forum edit/add option.
7759
     *
7760
     * @param string $action
7761
     * @param int    $id         ID of the lp_item if already exists
7762
     * @param string $extra_info
7763
     *
7764
     * @throws Exception
7765
     *
7766
     * @return string HTML form
7767
     */
7768
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7769
    {
7770
        $course_id = api_get_course_int_id();
7771
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7772
7773
        $item_title = '';
7774
        $item_description = '';
7775
7776
        if ($id != 0 && is_array($extra_info)) {
7777
            $item_title = stripslashes($extra_info['title']);
7778
        } elseif (is_numeric($extra_info)) {
7779
            $sql = "SELECT forum_title as title, forum_comment as comment
7780
                    FROM $tbl_forum
7781
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7782
7783
            $result = Database::query($sql);
7784
            $row = Database::fetch_array($result);
7785
7786
            $item_title = $row['title'];
7787
            $item_description = $row['comment'];
7788
        }
7789
        $parent = 0;
7790
        if ($id != 0 && is_array($extra_info)) {
7791
            $parent = $extra_info['parent_item_id'];
7792
        }
7793
        $arrLP = $this->getItemsForForm();
7794
        $this->tree_array($arrLP);
7795
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7796
        unset($this->arrMenu);
7797
7798
        if ($action == 'add') {
7799
            $legend = get_lang('CreateTheForum');
7800
        } elseif ($action == 'move') {
7801
            $legend = get_lang('MoveTheCurrentForum');
7802
        } else {
7803
            $legend = get_lang('EditCurrentForum');
7804
        }
7805
7806
        $form = new FormValidator(
7807
            'forum_form',
7808
            'POST',
7809
            $this->getCurrentBuildingModeURL()
7810
        );
7811
        $defaults = [];
7812
7813
        $form->addHeader($legend);
7814
7815
        if ($action != 'move') {
7816
            $this->setItemTitle($form);
7817
            $defaults['title'] = $item_title;
7818
        }
7819
7820
        $selectParent = $form->addSelect(
7821
            'parent',
7822
            get_lang('Parent'),
7823
            [],
7824
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7825
        );
7826
        $selectParent->addOption($this->name, 0);
7827
        $arrHide = [
7828
            $id,
7829
        ];
7830
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7831
            if ($action != 'add') {
7832
                if ($arrLP[$i]['item_type'] == 'dir' &&
7833
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7834
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7835
                ) {
7836
                    $selectParent->addOption(
7837
                        $arrLP[$i]['title'],
7838
                        $arrLP[$i]['id'],
7839
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7840
                    );
7841
7842
                    if ($parent == $arrLP[$i]['id']) {
7843
                        $selectParent->setSelected($arrLP[$i]['id']);
7844
                    }
7845
                } else {
7846
                    $arrHide[] = $arrLP[$i]['id'];
7847
                }
7848
            } else {
7849
                if ($arrLP[$i]['item_type'] == 'dir') {
7850
                    $selectParent->addOption(
7851
                        $arrLP[$i]['title'],
7852
                        $arrLP[$i]['id'],
7853
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7854
                    );
7855
7856
                    if ($parent == $arrLP[$i]['id']) {
7857
                        $selectParent->setSelected($arrLP[$i]['id']);
7858
                    }
7859
                }
7860
            }
7861
        }
7862
7863
        if (is_array($arrLP)) {
7864
            reset($arrLP);
7865
        }
7866
7867
        $selectPrevious = $form->addSelect(
7868
            'previous',
7869
            get_lang('Position'),
7870
            [],
7871
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7872
        );
7873
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7874
7875
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7876
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7877
                $arrLP[$i]['id'] != $id
7878
            ) {
7879
                $selectPrevious->addOption(
7880
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7881
                    $arrLP[$i]['id']
7882
                );
7883
7884
                if (isset($extra_info['previous_item_id']) &&
7885
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7886
                ) {
7887
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7888
                } elseif ($action == 'add') {
7889
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7890
                }
7891
            }
7892
        }
7893
7894
        if ($action != 'move') {
7895
            $id_prerequisite = 0;
7896
            if (is_array($arrLP)) {
7897
                foreach ($arrLP as $key => $value) {
7898
                    if ($value['id'] == $id) {
7899
                        $id_prerequisite = $value['prerequisite'];
7900
                        break;
7901
                    }
7902
                }
7903
            }
7904
7905
            $arrHide = [];
7906
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7907
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7908
                    if (isset($extra_info['previous_item_id']) &&
7909
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7910
                    ) {
7911
                        $s_selected_position = $arrLP[$i]['id'];
7912
                    } elseif ($action == 'add') {
7913
                        $s_selected_position = 0;
7914
                    }
7915
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7916
                }
7917
            }
7918
        }
7919
7920
        if ($action == 'add') {
7921
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
7922
        } else {
7923
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
7924
        }
7925
7926
        if ($action == 'move') {
7927
            $form->addHidden('title', $item_title);
7928
            $form->addHidden('description', $item_description);
7929
        }
7930
7931
        if (is_numeric($extra_info)) {
7932
            $form->addHidden('path', $extra_info);
7933
        } elseif (is_array($extra_info)) {
7934
            $form->addHidden('path', $extra_info['path']);
7935
        }
7936
        $form->addHidden('type', TOOL_FORUM);
7937
        $form->addHidden('post_time', time());
7938
        $form->setDefaults($defaults);
7939
7940
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7941
    }
7942
7943
    /**
7944
     * Return HTML form to add/edit forum threads.
7945
     *
7946
     * @param string $action
7947
     * @param int    $id         Item ID if already exists in learning path
7948
     * @param string $extra_info
7949
     *
7950
     * @throws Exception
7951
     *
7952
     * @return string HTML form
7953
     */
7954
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7955
    {
7956
        $course_id = api_get_course_int_id();
7957
        if (empty($course_id)) {
7958
            return null;
7959
        }
7960
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7961
7962
        $item_title = '';
7963
        $item_description = '';
7964
        if ($id != 0 && is_array($extra_info)) {
7965
            $item_title = stripslashes($extra_info['title']);
7966
        } elseif (is_numeric($extra_info)) {
7967
            $sql = "SELECT thread_title as title FROM $tbl_forum
7968
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7969
7970
            $result = Database::query($sql);
7971
            $row = Database::fetch_array($result);
7972
7973
            $item_title = $row['title'];
7974
            $item_description = '';
7975
        }
7976
7977
        $parent = 0;
7978
        if ($id != 0 && is_array($extra_info)) {
7979
            $parent = $extra_info['parent_item_id'];
7980
        }
7981
7982
        $arrLP = $this->getItemsForForm();
7983
        $this->tree_array($arrLP);
7984
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7985
        unset($this->arrMenu);
7986
7987
        $form = new FormValidator(
7988
            'thread_form',
7989
            'POST',
7990
            $this->getCurrentBuildingModeURL()
7991
        );
7992
        $defaults = [];
7993
7994
        if ($action == 'add') {
7995
            $legend = get_lang('CreateTheForum');
7996
        } elseif ($action == 'move') {
7997
            $legend = get_lang('MoveTheCurrentForum');
7998
        } else {
7999
            $legend = get_lang('EditCurrentForum');
8000
        }
8001
8002
        $form->addHeader($legend);
8003
        $selectParent = $form->addSelect(
8004
            'parent',
8005
            get_lang('Parent'),
8006
            [],
8007
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8008
        );
8009
        $selectParent->addOption($this->name, 0);
8010
8011
        $arrHide = [
8012
            $id,
8013
        ];
8014
8015
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8016
            if ($action != 'add') {
8017
                if (
8018
                    ($arrLP[$i]['item_type'] == 'dir') &&
8019
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8020
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8021
                ) {
8022
                    $selectParent->addOption(
8023
                        $arrLP[$i]['title'],
8024
                        $arrLP[$i]['id'],
8025
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8026
                    );
8027
8028
                    if ($parent == $arrLP[$i]['id']) {
8029
                        $selectParent->setSelected($arrLP[$i]['id']);
8030
                    }
8031
                } else {
8032
                    $arrHide[] = $arrLP[$i]['id'];
8033
                }
8034
            } else {
8035
                if ($arrLP[$i]['item_type'] == 'dir') {
8036
                    $selectParent->addOption(
8037
                        $arrLP[$i]['title'],
8038
                        $arrLP[$i]['id'],
8039
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8040
                    );
8041
8042
                    if ($parent == $arrLP[$i]['id']) {
8043
                        $selectParent->setSelected($arrLP[$i]['id']);
8044
                    }
8045
                }
8046
            }
8047
        }
8048
8049
        if ($arrLP != null) {
8050
            reset($arrLP);
8051
        }
8052
8053
        $selectPrevious = $form->addSelect(
8054
            'previous',
8055
            get_lang('Position'),
8056
            [],
8057
            ['id' => 'previous']
8058
        );
8059
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8060
8061
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8062
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8063
                $selectPrevious->addOption(
8064
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8065
                    $arrLP[$i]['id']
8066
                );
8067
8068
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8069
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8070
                } elseif ($action == 'add') {
8071
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8072
                }
8073
            }
8074
        }
8075
8076
        if ($action != 'move') {
8077
            $this->setItemTitle($form);
8078
            $defaults['title'] = $item_title;
8079
8080
            $id_prerequisite = 0;
8081
            if ($arrLP != null) {
8082
                foreach ($arrLP as $key => $value) {
8083
                    if ($value['id'] == $id) {
8084
                        $id_prerequisite = $value['prerequisite'];
8085
                        break;
8086
                    }
8087
                }
8088
            }
8089
8090
            $arrHide = [];
8091
            $s_selected_position = 0;
8092
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8093
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8094
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8095
                        $s_selected_position = $arrLP[$i]['id'];
8096
                    } elseif ($action == 'add') {
8097
                        $s_selected_position = 0;
8098
                    }
8099
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8100
                }
8101
            }
8102
8103
            $selectPrerequisites = $form->addSelect(
8104
                'prerequisites',
8105
                get_lang('LearnpathPrerequisites'),
8106
                [],
8107
                ['id' => 'prerequisites']
8108
            );
8109
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8110
8111
            foreach ($arrHide as $key => $value) {
8112
                $selectPrerequisites->addOption($value['value'], $key);
8113
8114
                if ($key == $s_selected_position && $action == 'add') {
8115
                    $selectPrerequisites->setSelected($key);
8116
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8117
                    $selectPrerequisites->setSelected($key);
8118
                }
8119
            }
8120
        }
8121
8122
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8123
8124
        if ($action == 'move') {
8125
            $form->addHidden('title', $item_title);
8126
            $form->addHidden('description', $item_description);
8127
        }
8128
8129
        if (is_numeric($extra_info)) {
8130
            $form->addHidden('path', $extra_info);
8131
        } elseif (is_array($extra_info)) {
8132
            $form->addHidden('path', $extra_info['path']);
8133
        }
8134
8135
        $form->addHidden('type', TOOL_THREAD);
8136
        $form->addHidden('post_time', time());
8137
        $form->setDefaults($defaults);
8138
8139
        return $form->returnForm();
8140
    }
8141
8142
    /**
8143
     * Return the HTML form to display an item (generally a dir item).
8144
     *
8145
     * @param string $item_type
8146
     * @param string $title
8147
     * @param string $action
8148
     * @param int    $id
8149
     * @param string $extra_info
8150
     *
8151
     * @throws Exception
8152
     * @throws HTML_QuickForm_Error
8153
     *
8154
     * @return string HTML form
8155
     */
8156
    public function display_item_form(
8157
        $item_type,
8158
        $title = '',
8159
        $action = 'add_item',
8160
        $id = 0,
8161
        $extra_info = 'new'
8162
    ) {
8163
        $_course = api_get_course_info();
8164
8165
        global $charset;
8166
8167
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8168
        $item_title = '';
8169
        $item_description = '';
8170
        $item_path_fck = '';
8171
8172
        if ($id != 0 && is_array($extra_info)) {
8173
            $item_title = $extra_info['title'];
8174
            $item_description = $extra_info['description'];
8175
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8176
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8177
        }
8178
        $parent = 0;
8179
        if ($id != 0 && is_array($extra_info)) {
8180
            $parent = $extra_info['parent_item_id'];
8181
        }
8182
8183
        $id = (int) $id;
8184
        $sql = "SELECT * FROM $tbl_lp_item
8185
                WHERE
8186
                    lp_id = ".$this->lp_id." AND
8187
                    iid != $id";
8188
8189
        if ($item_type == 'dir') {
8190
            $sql .= " AND parent_item_id = 0";
8191
        }
8192
8193
        $result = Database::query($sql);
8194
        $arrLP = [];
8195
        while ($row = Database::fetch_array($result)) {
8196
            $arrLP[] = [
8197
                'id' => $row['iid'],
8198
                'item_type' => $row['item_type'],
8199
                'title' => $this->cleanItemTitle($row['title']),
8200
                'title_raw' => $row['title'],
8201
                'path' => $row['path'],
8202
                'description' => $row['description'],
8203
                'parent_item_id' => $row['parent_item_id'],
8204
                'previous_item_id' => $row['previous_item_id'],
8205
                'next_item_id' => $row['next_item_id'],
8206
                'max_score' => $row['max_score'],
8207
                'min_score' => $row['min_score'],
8208
                'mastery_score' => $row['mastery_score'],
8209
                'prerequisite' => $row['prerequisite'],
8210
                'display_order' => $row['display_order'],
8211
            ];
8212
        }
8213
8214
        $this->tree_array($arrLP);
8215
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8216
        unset($this->arrMenu);
8217
8218
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8219
8220
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8221
        $defaults['title'] = api_html_entity_decode(
8222
            $item_title,
8223
            ENT_QUOTES,
8224
            $charset
8225
        );
8226
        $defaults['description'] = $item_description;
8227
8228
        $form->addHeader($title);
8229
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8230
        $arrHide[0]['padding'] = 20;
8231
        $charset = api_get_system_encoding();
8232
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8233
            if ($action != 'add') {
8234
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8235
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8236
                ) {
8237
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8238
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8239
                    if ($parent == $arrLP[$i]['id']) {
8240
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8241
                    }
8242
                }
8243
            } else {
8244
                if ($arrLP[$i]['item_type'] === 'dir') {
8245
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8246
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8247
                    if ($parent == $arrLP[$i]['id']) {
8248
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8249
                    }
8250
                }
8251
            }
8252
        }
8253
8254
        if ($action != 'move') {
8255
            $this->setItemTitle($form);
8256
        } else {
8257
            $form->addElement('hidden', 'title');
8258
        }
8259
8260
        $parentSelect = $form->addElement(
8261
            'select',
8262
            'parent',
8263
            get_lang('Parent'),
8264
            '',
8265
            [
8266
                'id' => 'idParent',
8267
                'onchange' => 'javascript: load_cbo(this.value);',
8268
            ]
8269
        );
8270
8271
        foreach ($arrHide as $key => $value) {
8272
            $parentSelect->addOption(
8273
                $value['value'],
8274
                $key,
8275
                'style="padding-left:'.$value['padding'].'px;"'
8276
            );
8277
            $lastPosition = $key;
8278
        }
8279
8280
        if (!empty($s_selected_parent)) {
8281
            $parentSelect->setSelected($s_selected_parent);
8282
        }
8283
8284
        if (is_array($arrLP)) {
8285
            reset($arrLP);
8286
        }
8287
8288
        $arrHide = [];
8289
        // POSITION
8290
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8291
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8292
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8293
                //this is the same!
8294
                if (isset($extra_info['previous_item_id']) &&
8295
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8296
                ) {
8297
                    $s_selected_position = $arrLP[$i]['id'];
8298
                } elseif ($action == 'add') {
8299
                    $s_selected_position = $arrLP[$i]['id'];
8300
                }
8301
8302
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8303
            }
8304
        }
8305
8306
        $position = $form->addElement(
8307
            'select',
8308
            'previous',
8309
            get_lang('Position'),
8310
            '',
8311
            ['id' => 'previous']
8312
        );
8313
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8314
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8315
8316
        $lastPosition = null;
8317
        foreach ($arrHide as $key => $value) {
8318
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8319
            $lastPosition = $key;
8320
        }
8321
8322
        if (!empty($s_selected_position)) {
8323
            $position->setSelected($s_selected_position);
8324
        }
8325
8326
        // When new chapter add at the end
8327
        if ($action === 'add_item') {
8328
            $position->setSelected($lastPosition);
8329
        }
8330
8331
        if (is_array($arrLP)) {
8332
            reset($arrLP);
8333
        }
8334
8335
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8336
8337
        //fix in order to use the tab
8338
        if ($item_type === 'dir') {
8339
            $form->addElement('hidden', 'type', 'dir');
8340
        }
8341
8342
        $extension = null;
8343
        if (!empty($item_path)) {
8344
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8345
        }
8346
8347
        //assets can't be modified
8348
        //$item_type == 'asset' ||
8349
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8350
            if ($item_type == 'sco') {
8351
                $form->addElement(
8352
                    'html',
8353
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8354
                );
8355
            }
8356
            $renderer = $form->defaultRenderer();
8357
            $renderer->setElementTemplate(
8358
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8359
                'content_lp'
8360
            );
8361
8362
            $relative_prefix = '';
8363
            $editor_config = [
8364
                'ToolbarSet' => 'LearningPathDocuments',
8365
                'Width' => '100%',
8366
                'Height' => '500',
8367
                'FullPage' => true,
8368
                'CreateDocumentDir' => $relative_prefix,
8369
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8370
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8371
            ];
8372
8373
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8374
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8375
            $defaults['content_lp'] = file_get_contents($content_path);
8376
        }
8377
8378
        if (!empty($id)) {
8379
            $form->addHidden('id', $id);
8380
        }
8381
8382
        $form->addElement('hidden', 'type', $item_type);
8383
        $form->addElement('hidden', 'post_time', time());
8384
        $form->setDefaults($defaults);
8385
8386
        return $form->returnForm();
8387
    }
8388
8389
    /**
8390
     * @return string
8391
     */
8392
    public function getCurrentBuildingModeURL()
8393
    {
8394
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8395
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8396
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8397
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8398
8399
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8400
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8401
8402
        return $currentUrl;
8403
    }
8404
8405
    /**
8406
     * Returns the form to update or create a document.
8407
     *
8408
     * @param string $action     (add/edit)
8409
     * @param int    $id         ID of the lp_item (if already exists)
8410
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8411
     *
8412
     * @throws Exception
8413
     * @throws HTML_QuickForm_Error
8414
     *
8415
     * @return string HTML form
8416
     */
8417
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8418
    {
8419
        $course_id = api_get_course_int_id();
8420
        $_course = api_get_course_info();
8421
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8422
8423
        $no_display_edit_textarea = false;
8424
        $item_description = '';
8425
        //If action==edit document
8426
        //We don't display the document form if it's not an editable document (html or txt file)
8427
        if ($action === 'edit') {
8428
            if (is_array($extra_info)) {
8429
                $path_parts = pathinfo($extra_info['dir']);
8430
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8431
                    $no_display_edit_textarea = true;
8432
                }
8433
            }
8434
        }
8435
        $no_display_add = false;
8436
8437
        // If action==add an existing document
8438
        // We don't display the document form if it's not an editable document (html or txt file).
8439
        if ($action === 'add') {
8440
            if (is_numeric($extra_info)) {
8441
                $extra_info = (int) $extra_info;
8442
                $sql_doc = "SELECT path FROM $tbl_doc
8443
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8444
                $result = Database::query($sql_doc);
8445
                $path_file = Database::result($result, 0, 0);
8446
                $path_parts = pathinfo($path_file);
8447
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8448
                    $no_display_add = true;
8449
                }
8450
            }
8451
        }
8452
8453
        $item_title = '';
8454
        $item_description = '';
8455
        if ($id != 0 && is_array($extra_info)) {
8456
            $item_title = stripslashes($extra_info['title']);
8457
            $item_description = stripslashes($extra_info['description']);
8458
            if (empty($item_title)) {
8459
                $path_parts = pathinfo($extra_info['path']);
8460
                $item_title = stripslashes($path_parts['filename']);
8461
            }
8462
        } elseif (is_numeric($extra_info)) {
8463
            $sql = "SELECT path, title FROM $tbl_doc
8464
                    WHERE
8465
                        c_id = ".$course_id." AND
8466
                        iid = ".intval($extra_info);
8467
            $result = Database::query($sql);
8468
            $row = Database::fetch_array($result);
8469
            $item_title = $row['title'];
8470
            $item_title = str_replace('_', ' ', $item_title);
8471
            if (empty($item_title)) {
8472
                $path_parts = pathinfo($row['path']);
8473
                $item_title = stripslashes($path_parts['filename']);
8474
            }
8475
        }
8476
8477
        $return = '<legend>';
8478
        $parent = 0;
8479
        if ($id != 0 && is_array($extra_info)) {
8480
            $parent = $extra_info['parent_item_id'];
8481
        }
8482
8483
        $arrLP = $this->getItemsForForm();
8484
        $this->tree_array($arrLP);
8485
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8486
        unset($this->arrMenu);
8487
8488
        if ($action == 'add') {
8489
            $return .= get_lang('CreateTheDocument');
8490
        } elseif ($action == 'move') {
8491
            $return .= get_lang('MoveTheCurrentDocument');
8492
        } else {
8493
            $return .= get_lang('EditTheCurrentDocument');
8494
        }
8495
        $return .= '</legend>';
8496
8497
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8498
            $return .= Display::return_message(
8499
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8500
                false
8501
            );
8502
        }
8503
        $form = new FormValidator(
8504
            'form',
8505
            'POST',
8506
            $this->getCurrentBuildingModeURL(),
8507
            '',
8508
            ['enctype' => 'multipart/form-data']
8509
        );
8510
        $defaults['title'] = Security::remove_XSS($item_title);
8511
        if (empty($item_title)) {
8512
            $defaults['title'] = Security::remove_XSS($item_title);
8513
        }
8514
        $defaults['description'] = $item_description;
8515
        $form->addElement('html', $return);
8516
8517
        if ($action != 'move') {
8518
            $data = $this->generate_lp_folder($_course);
8519
            if ($action != 'edit') {
8520
                $folders = DocumentManager::get_all_document_folders(
8521
                    $_course,
8522
                    0,
8523
                    true
8524
                );
8525
                DocumentManager::build_directory_selector(
8526
                    $folders,
8527
                    '',
8528
                    [],
8529
                    true,
8530
                    $form,
8531
                    'directory_parent_id'
8532
                );
8533
            }
8534
8535
            if (isset($data['id'])) {
8536
                $defaults['directory_parent_id'] = $data['id'];
8537
            }
8538
            $this->setItemTitle($form);
8539
        }
8540
8541
        $arrHide[0]['value'] = $this->name;
8542
        $arrHide[0]['padding'] = 20;
8543
8544
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8545
            if ($action != 'add') {
8546
                if ($arrLP[$i]['item_type'] == 'dir' &&
8547
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8548
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8549
                ) {
8550
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8551
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8552
                }
8553
            } else {
8554
                if ($arrLP[$i]['item_type'] == 'dir') {
8555
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8556
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8557
                }
8558
            }
8559
        }
8560
8561
        $parentSelect = $form->addSelect(
8562
            'parent',
8563
            get_lang('Parent'),
8564
            [],
8565
            [
8566
                'id' => 'idParent',
8567
                'onchange' => 'javascript: load_cbo(this.value);',
8568
            ]
8569
        );
8570
8571
        $my_count = 0;
8572
        foreach ($arrHide as $key => $value) {
8573
            if ($my_count != 0) {
8574
                // The LP name is also the first section and is not in the same charset like the other sections.
8575
                $value['value'] = Security::remove_XSS($value['value']);
8576
                $parentSelect->addOption(
8577
                    $value['value'],
8578
                    $key,
8579
                    'style="padding-left:'.$value['padding'].'px;"'
8580
                );
8581
            } else {
8582
                $value['value'] = Security::remove_XSS($value['value']);
8583
                $parentSelect->addOption(
8584
                    $value['value'],
8585
                    $key,
8586
                    'style="padding-left:'.$value['padding'].'px;"'
8587
                );
8588
            }
8589
            $my_count++;
8590
        }
8591
8592
        if (!empty($id)) {
8593
            $parentSelect->setSelected($parent);
8594
        } else {
8595
            $parent_item_id = Session::read('parent_item_id', 0);
8596
            $parentSelect->setSelected($parent_item_id);
8597
        }
8598
8599
        if (is_array($arrLP)) {
8600
            reset($arrLP);
8601
        }
8602
8603
        $arrHide = [];
8604
        // POSITION
8605
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8606
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8607
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8608
            ) {
8609
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8610
            }
8611
        }
8612
8613
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8614
8615
        $position = $form->addSelect(
8616
            'previous',
8617
            get_lang('Position'),
8618
            [],
8619
            ['id' => 'previous']
8620
        );
8621
8622
        $position->addOption(get_lang('FirstPosition'), 0);
8623
        foreach ($arrHide as $key => $value) {
8624
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8625
            $position->addOption(
8626
                $value['value'],
8627
                $key,
8628
                'style="padding-left:'.$padding.'px;"'
8629
            );
8630
        }
8631
8632
        $position->setSelected($selectedPosition);
8633
8634
        if (is_array($arrLP)) {
8635
            reset($arrLP);
8636
        }
8637
8638
        if ($action != 'move') {
8639
            $arrHide = [];
8640
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8641
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8642
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8643
                ) {
8644
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8645
                }
8646
            }
8647
8648
            if (!$no_display_add) {
8649
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8650
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8651
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8652
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8653
                ) {
8654
                    if (isset($_POST['content'])) {
8655
                        $content = stripslashes($_POST['content']);
8656
                    } elseif (is_array($extra_info)) {
8657
                        //If it's an html document or a text file
8658
                        if (!$no_display_edit_textarea) {
8659
                            $content = $this->display_document(
8660
                                $extra_info['path'],
8661
                                false,
8662
                                false
8663
                            );
8664
                        }
8665
                    } elseif (is_numeric($extra_info)) {
8666
                        $content = $this->display_document(
8667
                            $extra_info,
8668
                            false,
8669
                            false
8670
                        );
8671
                    } else {
8672
                        $content = '';
8673
                    }
8674
8675
                    if (!$no_display_edit_textarea) {
8676
                        // We need to calculate here some specific settings for the online editor.
8677
                        // The calculated settings work for documents in the Documents tool
8678
                        // (on the root or in subfolders).
8679
                        // For documents in native scorm packages it is unclear whether the
8680
                        // online editor should be activated or not.
8681
8682
                        // A new document, it is in the root of the repository.
8683
                        $relative_path = '';
8684
                        $relative_prefix = '';
8685
                        if (is_array($extra_info) && $extra_info != 'new') {
8686
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8687
                            $relative_path = explode('/', $extra_info['dir']);
8688
                            $cnt = count($relative_path) - 2;
8689
                            if ($cnt < 0) {
8690
                                $cnt = 0;
8691
                            }
8692
                            $relative_prefix = str_repeat('../', $cnt);
8693
                            $relative_path = array_slice($relative_path, 1, $cnt);
8694
                            $relative_path = implode('/', $relative_path);
8695
                            if (strlen($relative_path) > 0) {
8696
                                $relative_path = $relative_path.'/';
8697
                            }
8698
                        } else {
8699
                            $result = $this->generate_lp_folder($_course);
8700
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8701
                            $relative_prefix = '../../';
8702
                        }
8703
8704
                        $editor_config = [
8705
                            'ToolbarSet' => 'LearningPathDocuments',
8706
                            'Width' => '100%',
8707
                            'Height' => '500',
8708
                            'FullPage' => true,
8709
                            'CreateDocumentDir' => $relative_prefix,
8710
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8711
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8712
                        ];
8713
8714
                        if ($_GET['action'] == 'add_item') {
8715
                            $class = 'add';
8716
                            $text = get_lang('LPCreateDocument');
8717
                        } else {
8718
                            if ($_GET['action'] == 'edit_item') {
8719
                                $class = 'save';
8720
                                $text = get_lang('SaveDocument');
8721
                            }
8722
                        }
8723
8724
                        $form->addButtonSave($text, 'submit_button');
8725
                        $renderer = $form->defaultRenderer();
8726
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8727
                        $form->addElement('html', '<div class="editor-lp">');
8728
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8729
                        $form->addElement('html', '</div>');
8730
                        $defaults['content_lp'] = $content;
8731
                    }
8732
                } elseif (is_numeric($extra_info)) {
8733
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8734
8735
                    $return = $this->display_document($extra_info, true, true, true);
8736
                    $form->addElement('html', $return);
8737
                }
8738
            }
8739
        }
8740
        if (isset($extra_info['item_type']) &&
8741
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8742
        ) {
8743
            $parentSelect->freeze();
8744
            $position->freeze();
8745
        }
8746
8747
        if ($action == 'move') {
8748
            $form->addElement('hidden', 'title', $item_title);
8749
            $form->addElement('hidden', 'description', $item_description);
8750
        }
8751
        if (is_numeric($extra_info)) {
8752
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8753
            $form->addElement('hidden', 'path', $extra_info);
8754
        } elseif (is_array($extra_info)) {
8755
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8756
            $form->addElement('hidden', 'path', $extra_info['path']);
8757
        }
8758
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8759
        $form->addElement('hidden', 'post_time', time());
8760
        $form->setDefaults($defaults);
8761
8762
        return $form->returnForm();
8763
    }
8764
8765
    /**
8766
     * Returns the form to update or create a read-out text.
8767
     *
8768
     * @param string $action     "add" or "edit"
8769
     * @param int    $id         ID of the lp_item (if already exists)
8770
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8771
     *
8772
     * @throws Exception
8773
     * @throws HTML_QuickForm_Error
8774
     *
8775
     * @return string HTML form
8776
     */
8777
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8778
    {
8779
        $course_id = api_get_course_int_id();
8780
        $_course = api_get_course_info();
8781
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8782
8783
        $no_display_edit_textarea = false;
8784
        //If action==edit document
8785
        //We don't display the document form if it's not an editable document (html or txt file)
8786
        if ($action == 'edit') {
8787
            if (is_array($extra_info)) {
8788
                $path_parts = pathinfo($extra_info['dir']);
8789
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8790
                    $no_display_edit_textarea = true;
8791
                }
8792
            }
8793
        }
8794
        $no_display_add = false;
8795
8796
        $item_title = '';
8797
        $item_description = '';
8798
        if ($id != 0 && is_array($extra_info)) {
8799
            $item_title = stripslashes($extra_info['title']);
8800
            $item_description = stripslashes($extra_info['description']);
8801
            $item_terms = stripslashes($extra_info['terms']);
8802
            if (empty($item_title)) {
8803
                $path_parts = pathinfo($extra_info['path']);
8804
                $item_title = stripslashes($path_parts['filename']);
8805
            }
8806
        } elseif (is_numeric($extra_info)) {
8807
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8808
            $result = Database::query($sql);
8809
            $row = Database::fetch_array($result);
8810
            $item_title = $row['title'];
8811
            $item_title = str_replace('_', ' ', $item_title);
8812
            if (empty($item_title)) {
8813
                $path_parts = pathinfo($row['path']);
8814
                $item_title = stripslashes($path_parts['filename']);
8815
            }
8816
        }
8817
8818
        $parent = 0;
8819
        if ($id != 0 && is_array($extra_info)) {
8820
            $parent = $extra_info['parent_item_id'];
8821
        }
8822
8823
        $arrLP = $this->getItemsForForm();
8824
        $this->tree_array($arrLP);
8825
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8826
        unset($this->arrMenu);
8827
8828
        if ($action === 'add') {
8829
            $formHeader = get_lang('CreateTheDocument');
8830
        } else {
8831
            $formHeader = get_lang('EditTheCurrentDocument');
8832
        }
8833
8834
        if ('edit' === $action) {
8835
            $urlAudioIcon = Display::url(
8836
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
8837
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8838
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8839
            );
8840
        } else {
8841
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
8842
        }
8843
8844
        $form = new FormValidator(
8845
            'frm_add_reading',
8846
            'POST',
8847
            $this->getCurrentBuildingModeURL(),
8848
            '',
8849
            ['enctype' => 'multipart/form-data']
8850
        );
8851
        $form->addHeader($formHeader);
8852
        $form->addHtml(
8853
            Display::return_message(
8854
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
8855
                'normal',
8856
                false
8857
            )
8858
        );
8859
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8860
        $defaults['description'] = $item_description;
8861
8862
        $data = $this->generate_lp_folder($_course);
8863
8864
        if ($action != 'edit') {
8865
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8866
            DocumentManager::build_directory_selector(
8867
                $folders,
8868
                '',
8869
                [],
8870
                true,
8871
                $form,
8872
                'directory_parent_id'
8873
            );
8874
        }
8875
8876
        if (isset($data['id'])) {
8877
            $defaults['directory_parent_id'] = $data['id'];
8878
        }
8879
        $this->setItemTitle($form);
8880
8881
        $arrHide[0]['value'] = $this->name;
8882
        $arrHide[0]['padding'] = 20;
8883
8884
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8885
            if ($action != 'add') {
8886
                if ($arrLP[$i]['item_type'] == 'dir' &&
8887
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8888
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8889
                ) {
8890
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8891
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8892
                }
8893
            } else {
8894
                if ($arrLP[$i]['item_type'] == 'dir') {
8895
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8896
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8897
                }
8898
            }
8899
        }
8900
8901
        $parent_select = $form->addSelect(
8902
            'parent',
8903
            get_lang('Parent'),
8904
            [],
8905
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8906
        );
8907
8908
        $my_count = 0;
8909
        foreach ($arrHide as $key => $value) {
8910
            if ($my_count != 0) {
8911
                // The LP name is also the first section and is not in the same charset like the other sections.
8912
                $value['value'] = Security::remove_XSS($value['value']);
8913
                $parent_select->addOption(
8914
                    $value['value'],
8915
                    $key,
8916
                    'style="padding-left:'.$value['padding'].'px;"'
8917
                );
8918
            } else {
8919
                $value['value'] = Security::remove_XSS($value['value']);
8920
                $parent_select->addOption(
8921
                    $value['value'],
8922
                    $key,
8923
                    'style="padding-left:'.$value['padding'].'px;"'
8924
                );
8925
            }
8926
            $my_count++;
8927
        }
8928
8929
        if (!empty($id)) {
8930
            $parent_select->setSelected($parent);
8931
        } else {
8932
            $parent_item_id = Session::read('parent_item_id', 0);
8933
            $parent_select->setSelected($parent_item_id);
8934
        }
8935
8936
        if (is_array($arrLP)) {
8937
            reset($arrLP);
8938
        }
8939
8940
        $arrHide = [];
8941
        $s_selected_position = null;
8942
8943
        // POSITION
8944
        $lastPosition = null;
8945
8946
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8947
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8948
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8949
            ) {
8950
                if ((isset($extra_info['previous_item_id']) &&
8951
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8952
                ) {
8953
                    $s_selected_position = $arrLP[$i]['id'];
8954
                }
8955
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8956
            }
8957
            $lastPosition = $arrLP[$i]['id'];
8958
        }
8959
8960
        if (empty($s_selected_position)) {
8961
            $s_selected_position = $lastPosition;
8962
        }
8963
8964
        $position = $form->addSelect(
8965
            'previous',
8966
            get_lang('Position'),
8967
            []
8968
        );
8969
        $position->addOption(get_lang('FirstPosition'), 0);
8970
8971
        foreach ($arrHide as $key => $value) {
8972
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8973
            $position->addOption(
8974
                $value['value'],
8975
                $key,
8976
                'style="padding-left:'.$padding.'px;"'
8977
            );
8978
        }
8979
        $position->setSelected($s_selected_position);
8980
8981
        if (is_array($arrLP)) {
8982
            reset($arrLP);
8983
        }
8984
8985
        $arrHide = [];
8986
8987
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8988
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8989
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8990
            ) {
8991
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8992
            }
8993
        }
8994
8995
        if (!$no_display_add) {
8996
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8997
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8998
8999
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9000
                if (!$no_display_edit_textarea) {
9001
                    $content = '';
9002
9003
                    if (isset($_POST['content'])) {
9004
                        $content = stripslashes($_POST['content']);
9005
                    } elseif (is_array($extra_info)) {
9006
                        $content = $this->display_document($extra_info['path'], false, false);
9007
                    } elseif (is_numeric($extra_info)) {
9008
                        $content = $this->display_document($extra_info, false, false);
9009
                    }
9010
9011
                    // A new document, it is in the root of the repository.
9012
                    if (is_array($extra_info) && $extra_info != 'new') {
9013
                    } else {
9014
                        $this->generate_lp_folder($_course);
9015
                    }
9016
9017
                    if ($_GET['action'] == 'add_item') {
9018
                        $text = get_lang('LPCreateDocument');
9019
                    } else {
9020
                        $text = get_lang('SaveDocument');
9021
                    }
9022
9023
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9024
                    $form
9025
                        ->defaultRenderer()
9026
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9027
                    $form->addButtonSave($text, 'submit_button');
9028
                    $defaults['content_lp'] = $content;
9029
                }
9030
            } elseif (is_numeric($extra_info)) {
9031
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9032
9033
                $return = $this->display_document($extra_info, true, true, true);
9034
                $form->addElement('html', $return);
9035
            }
9036
        }
9037
9038
        if (is_numeric($extra_info)) {
9039
            $form->addElement('hidden', 'path', $extra_info);
9040
        } elseif (is_array($extra_info)) {
9041
            $form->addElement('hidden', 'path', $extra_info['path']);
9042
        }
9043
9044
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9045
        $form->addElement('hidden', 'post_time', time());
9046
        $form->setDefaults($defaults);
9047
9048
        return $form->returnForm();
9049
    }
9050
9051
    /**
9052
     * @param array  $courseInfo
9053
     * @param string $content
9054
     * @param string $title
9055
     * @param int    $parentId
9056
     *
9057
     * @throws \Doctrine\ORM\ORMException
9058
     * @throws \Doctrine\ORM\OptimisticLockException
9059
     * @throws \Doctrine\ORM\TransactionRequiredException
9060
     *
9061
     * @return int
9062
     */
9063
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9064
    {
9065
        $creatorId = api_get_user_id();
9066
        $sessionId = api_get_session_id();
9067
9068
        // Generates folder
9069
        $result = $this->generate_lp_folder($courseInfo);
9070
        $dir = $result['dir'];
9071
9072
        if (empty($parentId) || $parentId == '/') {
9073
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9074
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9075
9076
            if ($parentId === '/') {
9077
                $dir = '/';
9078
            }
9079
9080
            // Please, do not modify this dirname formatting.
9081
            if (strstr($dir, '..')) {
9082
                $dir = '/';
9083
            }
9084
9085
            if (!empty($dir[0]) && $dir[0] == '.') {
9086
                $dir = substr($dir, 1);
9087
            }
9088
            if (!empty($dir[0]) && $dir[0] != '/') {
9089
                $dir = '/'.$dir;
9090
            }
9091
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9092
                $dir .= '/';
9093
            }
9094
        } else {
9095
            $parentInfo = DocumentManager::get_document_data_by_id(
9096
                $parentId,
9097
                $courseInfo['code']
9098
            );
9099
            if (!empty($parentInfo)) {
9100
                $dir = $parentInfo['path'].'/';
9101
            }
9102
        }
9103
9104
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9105
9106
        if (!is_dir($filepath)) {
9107
            $dir = '/';
9108
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9109
        }
9110
9111
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9112
9113
        if (!empty($title)) {
9114
            $title = api_replace_dangerous_char(stripslashes($title));
9115
        } else {
9116
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9117
        }
9118
9119
        $title = disable_dangerous_file($title);
9120
        $filename = $title;
9121
        $content = !empty($content) ? $content : $_POST['content_lp'];
9122
        $tmpFileName = $filename;
9123
9124
        $i = 0;
9125
        while (file_exists($filepath.$tmpFileName.'.html')) {
9126
            $tmpFileName = $filename.'_'.++$i;
9127
        }
9128
9129
        $filename = $tmpFileName.'.html';
9130
        $content = stripslashes($content);
9131
9132
        if (file_exists($filepath.$filename)) {
9133
            return 0;
9134
        }
9135
9136
        $putContent = file_put_contents($filepath.$filename, $content);
9137
9138
        if ($putContent === false) {
9139
            return 0;
9140
        }
9141
9142
        $fileSize = filesize($filepath.$filename);
9143
        $saveFilePath = $dir.$filename;
9144
9145
        $documentId = add_document(
9146
            $courseInfo,
9147
            $saveFilePath,
9148
            'file',
9149
            $fileSize,
9150
            $tmpFileName,
9151
            '',
9152
            0, //readonly
9153
            true,
9154
            null,
9155
            $sessionId,
9156
            $creatorId
9157
        );
9158
9159
        if (!$documentId) {
9160
            return 0;
9161
        }
9162
9163
        api_item_property_update(
9164
            $courseInfo,
9165
            TOOL_DOCUMENT,
9166
            $documentId,
9167
            'DocumentAdded',
9168
            $creatorId,
9169
            null,
9170
            null,
9171
            null,
9172
            null,
9173
            $sessionId
9174
        );
9175
9176
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9177
        $newTitle = $originalTitle;
9178
9179
        if ($newComment || $newTitle) {
9180
            $em = Database::getManager();
9181
9182
            /** @var CDocument $doc */
9183
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9184
9185
            if ($newComment) {
9186
                $doc->setComment($newComment);
9187
            }
9188
9189
            if ($newTitle) {
9190
                $doc->setTitle($newTitle);
9191
            }
9192
9193
            $em->persist($doc);
9194
            $em->flush();
9195
        }
9196
9197
        return $documentId;
9198
    }
9199
9200
    /**
9201
     * Return HTML form to add/edit a link item.
9202
     *
9203
     * @param string $action     (add/edit)
9204
     * @param int    $id         Item ID if exists
9205
     * @param mixed  $extra_info
9206
     *
9207
     * @throws Exception
9208
     * @throws HTML_QuickForm_Error
9209
     *
9210
     * @return string HTML form
9211
     */
9212
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9213
    {
9214
        $course_id = api_get_course_int_id();
9215
        $tbl_link = Database::get_course_table(TABLE_LINK);
9216
9217
        $item_title = '';
9218
        $item_description = '';
9219
        $item_url = '';
9220
9221
        if ($id != 0 && is_array($extra_info)) {
9222
            $item_title = stripslashes($extra_info['title']);
9223
            $item_description = stripslashes($extra_info['description']);
9224
            $item_url = stripslashes($extra_info['url']);
9225
        } elseif (is_numeric($extra_info)) {
9226
            $extra_info = (int) $extra_info;
9227
            $sql = "SELECT title, description, url
9228
                    FROM $tbl_link
9229
                    WHERE c_id = $course_id AND iid = $extra_info";
9230
            $result = Database::query($sql);
9231
            $row = Database::fetch_array($result);
9232
            $item_title = $row['title'];
9233
            $item_description = $row['description'];
9234
            $item_url = $row['url'];
9235
        }
9236
9237
        $form = new FormValidator(
9238
            'edit_link',
9239
            'POST',
9240
            $this->getCurrentBuildingModeURL()
9241
        );
9242
        $defaults = [];
9243
        $parent = 0;
9244
        if ($id != 0 && is_array($extra_info)) {
9245
            $parent = $extra_info['parent_item_id'];
9246
        }
9247
9248
        $arrLP = $this->getItemsForForm();
9249
9250
        $this->tree_array($arrLP);
9251
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9252
        unset($this->arrMenu);
9253
9254
        if ($action == 'add') {
9255
            $legend = get_lang('CreateTheLink');
9256
        } elseif ($action == 'move') {
9257
            $legend = get_lang('MoveCurrentLink');
9258
        } else {
9259
            $legend = get_lang('EditCurrentLink');
9260
        }
9261
9262
        $form->addHeader($legend);
9263
9264
        if ($action != 'move') {
9265
            $this->setItemTitle($form);
9266
            $defaults['title'] = $item_title;
9267
        }
9268
9269
        $selectParent = $form->addSelect(
9270
            'parent',
9271
            get_lang('Parent'),
9272
            [],
9273
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9274
        );
9275
        $selectParent->addOption($this->name, 0);
9276
        $arrHide = [
9277
            $id,
9278
        ];
9279
9280
        $parent_item_id = Session::read('parent_item_id', 0);
9281
9282
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9283
            if ($action != 'add') {
9284
                if (
9285
                    ($arrLP[$i]['item_type'] == 'dir') &&
9286
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9287
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9288
                ) {
9289
                    $selectParent->addOption(
9290
                        $arrLP[$i]['title'],
9291
                        $arrLP[$i]['id'],
9292
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9293
                    );
9294
9295
                    if ($parent == $arrLP[$i]['id']) {
9296
                        $selectParent->setSelected($arrLP[$i]['id']);
9297
                    }
9298
                } else {
9299
                    $arrHide[] = $arrLP[$i]['id'];
9300
                }
9301
            } else {
9302
                if ($arrLP[$i]['item_type'] == 'dir') {
9303
                    $selectParent->addOption(
9304
                        $arrLP[$i]['title'],
9305
                        $arrLP[$i]['id'],
9306
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9307
                    );
9308
9309
                    if ($parent_item_id == $arrLP[$i]['id']) {
9310
                        $selectParent->setSelected($arrLP[$i]['id']);
9311
                    }
9312
                }
9313
            }
9314
        }
9315
9316
        if (is_array($arrLP)) {
9317
            reset($arrLP);
9318
        }
9319
9320
        $selectPrevious = $form->addSelect(
9321
            'previous',
9322
            get_lang('Position'),
9323
            [],
9324
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9325
        );
9326
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9327
9328
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9329
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9330
                $selectPrevious->addOption(
9331
                    $arrLP[$i]['title'],
9332
                    $arrLP[$i]['id']
9333
                );
9334
9335
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9336
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9337
                } elseif ($action == 'add') {
9338
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9339
                }
9340
            }
9341
        }
9342
9343
        if ($action != 'move') {
9344
            $urlAttributes = ['class' => 'learnpath_item_form'];
9345
9346
            if (is_numeric($extra_info)) {
9347
                $urlAttributes['disabled'] = 'disabled';
9348
            }
9349
9350
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9351
            $defaults['url'] = $item_url;
9352
            $arrHide = [];
9353
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9354
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9355
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9356
                }
9357
            }
9358
        }
9359
9360
        if ($action == 'add') {
9361
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9362
        } else {
9363
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9364
        }
9365
9366
        if ($action == 'move') {
9367
            $form->addHidden('title', $item_title);
9368
            $form->addHidden('description', $item_description);
9369
        }
9370
9371
        if (is_numeric($extra_info)) {
9372
            $form->addHidden('path', $extra_info);
9373
        } elseif (is_array($extra_info)) {
9374
            $form->addHidden('path', $extra_info['path']);
9375
        }
9376
        $form->addHidden('type', TOOL_LINK);
9377
        $form->addHidden('post_time', time());
9378
        $form->setDefaults($defaults);
9379
9380
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9381
    }
9382
9383
    /**
9384
     * Return HTML form to add/edit a student publication (work).
9385
     *
9386
     * @param string $action
9387
     * @param int    $id         Item ID if already exists
9388
     * @param string $extra_info
9389
     *
9390
     * @throws Exception
9391
     *
9392
     * @return string HTML form
9393
     */
9394
    public function display_student_publication_form(
9395
        $action = 'add',
9396
        $id = 0,
9397
        $extra_info = ''
9398
    ) {
9399
        $course_id = api_get_course_int_id();
9400
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9401
9402
        $item_title = get_lang('Student_publication');
9403
        if ($id != 0 && is_array($extra_info)) {
9404
            $item_title = stripslashes($extra_info['title']);
9405
            $item_description = stripslashes($extra_info['description']);
9406
        } elseif (is_numeric($extra_info)) {
9407
            $extra_info = (int) $extra_info;
9408
            $sql = "SELECT title, description
9409
                    FROM $tbl_publication
9410
                    WHERE c_id = $course_id AND id = ".$extra_info;
9411
9412
            $result = Database::query($sql);
9413
            $row = Database::fetch_array($result);
9414
            if ($row) {
9415
                $item_title = $row['title'];
9416
            }
9417
        }
9418
9419
        $parent = 0;
9420
        if ($id != 0 && is_array($extra_info)) {
9421
            $parent = $extra_info['parent_item_id'];
9422
        }
9423
9424
        $arrLP = $this->getItemsForForm();
9425
9426
        $this->tree_array($arrLP);
9427
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9428
        unset($this->arrMenu);
9429
9430
        $form = new FormValidator('frm_student_publication', 'post', '#');
9431
9432
        if ($action == 'add') {
9433
            $form->addHeader(get_lang('Student_publication'));
9434
        } elseif ($action == 'move') {
9435
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9436
        } else {
9437
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9438
        }
9439
9440
        if ($action != 'move') {
9441
            $this->setItemTitle($form);
9442
        }
9443
9444
        $parentSelect = $form->addSelect(
9445
            'parent',
9446
            get_lang('Parent'),
9447
            ['0' => $this->name],
9448
            [
9449
                'onchange' => 'javascript: load_cbo(this.value);',
9450
                'class' => 'learnpath_item_form',
9451
                'id' => 'idParent',
9452
            ]
9453
        );
9454
9455
        $arrHide = [$id];
9456
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9457
            if ($action != 'add') {
9458
                if (
9459
                    ($arrLP[$i]['item_type'] == 'dir') &&
9460
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9461
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9462
                ) {
9463
                    $parentSelect->addOption(
9464
                        $arrLP[$i]['title'],
9465
                        $arrLP[$i]['id'],
9466
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9467
                    );
9468
9469
                    if ($parent == $arrLP[$i]['id']) {
9470
                        $parentSelect->setSelected($arrLP[$i]['id']);
9471
                    }
9472
                } else {
9473
                    $arrHide[] = $arrLP[$i]['id'];
9474
                }
9475
            } else {
9476
                if ($arrLP[$i]['item_type'] == 'dir') {
9477
                    $parentSelect->addOption(
9478
                        $arrLP[$i]['title'],
9479
                        $arrLP[$i]['id'],
9480
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9481
                    );
9482
9483
                    if ($parent == $arrLP[$i]['id']) {
9484
                        $parentSelect->setSelected($arrLP[$i]['id']);
9485
                    }
9486
                }
9487
            }
9488
        }
9489
9490
        if (is_array($arrLP)) {
9491
            reset($arrLP);
9492
        }
9493
9494
        $previousSelect = $form->addSelect(
9495
            'previous',
9496
            get_lang('Position'),
9497
            ['0' => get_lang('FirstPosition')],
9498
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9499
        );
9500
9501
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9502
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9503
                $previousSelect->addOption(
9504
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9505
                    $arrLP[$i]['id']
9506
                );
9507
9508
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9509
                    $previousSelect->setSelected($arrLP[$i]['id']);
9510
                } elseif ($action == 'add') {
9511
                    $previousSelect->setSelected($arrLP[$i]['id']);
9512
                }
9513
            }
9514
        }
9515
9516
        if ($action == 'add') {
9517
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9518
        } else {
9519
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9520
        }
9521
9522
        if ($action == 'move') {
9523
            $form->addHidden('title', $item_title);
9524
            $form->addHidden('description', $item_description);
9525
        }
9526
9527
        if (is_numeric($extra_info)) {
9528
            $form->addHidden('path', $extra_info);
9529
        } elseif (is_array($extra_info)) {
9530
            $form->addHidden('path', $extra_info['path']);
9531
        }
9532
9533
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9534
        $form->addHidden('post_time', time());
9535
        $form->setDefaults(['title' => $item_title]);
9536
9537
        $return = '<div class="sectioncomment">';
9538
        $return .= $form->returnForm();
9539
        $return .= '</div>';
9540
9541
        return $return;
9542
    }
9543
9544
    /**
9545
     * Displays the menu for manipulating a step.
9546
     *
9547
     * @param id     $item_id
9548
     * @param string $item_type
9549
     *
9550
     * @return string
9551
     */
9552
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9553
    {
9554
        $_course = api_get_course_info();
9555
        $course_code = api_get_course_id();
9556
        $item_id = (int) $item_id;
9557
9558
        $return = '<div class="actions">';
9559
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9560
        $sql = "SELECT * FROM $tbl_lp_item
9561
                WHERE iid = ".$item_id;
9562
        $result = Database::query($sql);
9563
        $row = Database::fetch_assoc($result);
9564
9565
        $audio_player = null;
9566
        // We display an audio player if needed.
9567
        if (!empty($row['audio'])) {
9568
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9569
9570
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9571
                .'<audio src="'.$webAudioPath.'" controls>'
9572
                .'</div><br>';
9573
        }
9574
9575
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9576
9577
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9578
            $return .= Display::url(
9579
                Display::return_icon(
9580
                    'edit.png',
9581
                    get_lang('Edit'),
9582
                    [],
9583
                    ICON_SIZE_SMALL
9584
                ),
9585
                $url.'&action=edit_item&path_item='.$row['path']
9586
            );
9587
9588
            $return .= Display::url(
9589
                Display::return_icon(
9590
                    'move.png',
9591
                    get_lang('Move'),
9592
                    [],
9593
                    ICON_SIZE_SMALL
9594
                ),
9595
                $url.'&action=move_item'
9596
            );
9597
        }
9598
9599
        // Commented for now as prerequisites cannot be added to chapters.
9600
        if ($item_type != 'dir') {
9601
            $return .= Display::url(
9602
                Display::return_icon(
9603
                    'accept.png',
9604
                    get_lang('LearnpathPrerequisites'),
9605
                    [],
9606
                    ICON_SIZE_SMALL
9607
                ),
9608
                $url.'&action=edit_item_prereq'
9609
            );
9610
        }
9611
        $return .= Display::url(
9612
            Display::return_icon(
9613
                'delete.png',
9614
                get_lang('Delete'),
9615
                [],
9616
                ICON_SIZE_SMALL
9617
            ),
9618
            $url.'&action=delete_item'
9619
        );
9620
9621
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9622
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9623
            if (empty($documentData)) {
9624
                // Try with iid
9625
                $table = Database::get_course_table(TABLE_DOCUMENT);
9626
                $sql = "SELECT path FROM $table
9627
                        WHERE
9628
                              c_id = ".api_get_course_int_id()." AND
9629
                              iid = ".$row['path']." AND
9630
                              path NOT LIKE '%_DELETED_%'";
9631
                $result = Database::query($sql);
9632
                $documentData = Database::fetch_array($result);
9633
                if ($documentData) {
9634
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9635
                }
9636
            }
9637
            if (isset($documentData['absolute_path_from_document'])) {
9638
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9639
            }
9640
        }
9641
9642
        $return .= '</div>';
9643
9644
        if (!empty($audio_player)) {
9645
            $return .= $audio_player;
9646
        }
9647
9648
        return $return;
9649
    }
9650
9651
    /**
9652
     * Creates the javascript needed for filling up the checkboxes without page reload.
9653
     *
9654
     * @return string
9655
     */
9656
    public function get_js_dropdown_array()
9657
    {
9658
        $course_id = api_get_course_int_id();
9659
        $return = 'var child_name = new Array();'."\n";
9660
        $return .= 'var child_value = new Array();'."\n\n";
9661
        $return .= 'child_name[0] = new Array();'."\n";
9662
        $return .= 'child_value[0] = new Array();'."\n\n";
9663
9664
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9665
        $sql = "SELECT * FROM ".$tbl_lp_item."
9666
                WHERE
9667
                    c_id = $course_id AND
9668
                    lp_id = ".$this->lp_id." AND
9669
                    parent_item_id = 0
9670
                ORDER BY display_order ASC";
9671
        $res_zero = Database::query($sql);
9672
        $i = 0;
9673
9674
        $list = $this->getItemsForForm(true);
9675
9676
        foreach ($list as $row_zero) {
9677
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9678
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9679
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9680
                }
9681
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9682
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9683
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9684
            }
9685
        }
9686
9687
        $return .= "\n";
9688
        $sql = "SELECT * FROM $tbl_lp_item
9689
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9690
        $res = Database::query($sql);
9691
        while ($row = Database::fetch_array($res)) {
9692
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9693
                           WHERE
9694
                                c_id = ".$course_id." AND
9695
                                parent_item_id = ".$row['iid']."
9696
                           ORDER BY display_order ASC";
9697
            $res_parent = Database::query($sql_parent);
9698
            $i = 0;
9699
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9700
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9701
9702
            while ($row_parent = Database::fetch_array($res_parent)) {
9703
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9704
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9705
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9706
            }
9707
            $return .= "\n";
9708
        }
9709
9710
        $return .= "
9711
            function load_cbo(id) {
9712
                if (!id) {
9713
                    return false;
9714
                }
9715
9716
                var cbo = document.getElementById('previous');
9717
                for(var i = cbo.length - 1; i > 0; i--) {
9718
                    cbo.options[i] = null;
9719
                }
9720
9721
                var k=0;
9722
                for(var i = 1; i <= child_name[id].length; i++){
9723
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9724
                    option.style.paddingLeft = '40px';
9725
                    cbo.options[i] = option;
9726
                    k = i;
9727
                }
9728
9729
                cbo.options[k].selected = true;
9730
                $('#previous').selectpicker('refresh');
9731
            }";
9732
9733
        return $return;
9734
    }
9735
9736
    /**
9737
     * Display the form to allow moving an item.
9738
     *
9739
     * @param int $item_id Item ID
9740
     *
9741
     * @throws Exception
9742
     * @throws HTML_QuickForm_Error
9743
     *
9744
     * @return string HTML form
9745
     */
9746
    public function display_move_item($item_id)
9747
    {
9748
        $return = '';
9749
        if (is_numeric($item_id)) {
9750
            $item_id = (int) $item_id;
9751
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9752
9753
            $sql = "SELECT * FROM $tbl_lp_item
9754
                    WHERE iid = $item_id";
9755
            $res = Database::query($sql);
9756
            $row = Database::fetch_array($res);
9757
9758
            switch ($row['item_type']) {
9759
                case 'dir':
9760
                case 'asset':
9761
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9762
                    $return .= $this->display_item_form(
9763
                        $row['item_type'],
9764
                        get_lang('MoveCurrentChapter'),
9765
                        'move',
9766
                        $item_id,
9767
                        $row
9768
                    );
9769
                    break;
9770
                case TOOL_DOCUMENT:
9771
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9772
                    $return .= $this->display_document_form('move', $item_id, $row);
9773
                    break;
9774
                case TOOL_LINK:
9775
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9776
                    $return .= $this->display_link_form('move', $item_id, $row);
9777
                    break;
9778
                case TOOL_HOTPOTATOES:
9779
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9780
                    $return .= $this->display_link_form('move', $item_id, $row);
9781
                    break;
9782
                case TOOL_QUIZ:
9783
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9784
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9785
                    break;
9786
                case TOOL_STUDENTPUBLICATION:
9787
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9788
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9789
                    break;
9790
                case TOOL_FORUM:
9791
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9792
                    $return .= $this->display_forum_form('move', $item_id, $row);
9793
                    break;
9794
                case TOOL_THREAD:
9795
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9796
                    $return .= $this->display_forum_form('move', $item_id, $row);
9797
                    break;
9798
            }
9799
        }
9800
9801
        return $return;
9802
    }
9803
9804
    /**
9805
     * Return HTML form to allow prerequisites selection.
9806
     *
9807
     * @todo use FormValidator
9808
     *
9809
     * @param int Item ID
9810
     *
9811
     * @return string HTML form
9812
     */
9813
    public function display_item_prerequisites_form($item_id = 0)
9814
    {
9815
        $course_id = api_get_course_int_id();
9816
        $item_id = (int) $item_id;
9817
9818
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9819
9820
        /* Current prerequisite */
9821
        $sql = "SELECT * FROM $tbl_lp_item
9822
                WHERE iid = $item_id";
9823
        $result = Database::query($sql);
9824
        $row = Database::fetch_array($result);
9825
        $prerequisiteId = $row['prerequisite'];
9826
        $return = '<legend>';
9827
        $return .= get_lang('AddEditPrerequisites');
9828
        $return .= '</legend>';
9829
        $return .= '<form method="POST">';
9830
        $return .= '<div class="table-responsive">';
9831
        $return .= '<table class="table table-hover">';
9832
        $return .= '<thead>';
9833
        $return .= '<tr>';
9834
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9835
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9836
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9837
        $return .= '</tr>';
9838
        $return .= '</thead>';
9839
9840
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9841
        $return .= '<tbody>';
9842
        $return .= '<tr>';
9843
        $return .= '<td colspan="3">';
9844
        $return .= '<div class="radio learnpath"><label for="idNone">';
9845
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9846
        $return .= get_lang('None').'</label>';
9847
        $return .= '</div>';
9848
        $return .= '</tr>';
9849
9850
        $sql = "SELECT * FROM $tbl_lp_item
9851
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9852
        $result = Database::query($sql);
9853
9854
        $selectedMinScore = [];
9855
        $selectedMaxScore = [];
9856
        $masteryScore = [];
9857
        while ($row = Database::fetch_array($result)) {
9858
            if ($row['iid'] == $item_id) {
9859
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9860
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9861
            }
9862
            $masteryScore[$row['iid']] = $row['mastery_score'];
9863
        }
9864
9865
        $arrLP = $this->getItemsForForm();
9866
        $this->tree_array($arrLP);
9867
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9868
        unset($this->arrMenu);
9869
9870
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9871
            $item = $arrLP[$i];
9872
9873
            if ($item['id'] == $item_id) {
9874
                break;
9875
            }
9876
9877
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9878
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9879
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9880
9881
            $return .= '<tr>';
9882
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9883
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9884
            $return .= '<label for="id'.$item['id'].'">';
9885
            $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').($item['item_type'] == 'dir' ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
9886
9887
            $icon_name = str_replace(' ', '', $item['item_type']);
9888
9889
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9890
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9891
            } else {
9892
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9893
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9894
                } else {
9895
                    $return .= Display::return_icon('folder_document.png');
9896
                }
9897
            }
9898
9899
            $return .= $item['title'].'</label>';
9900
            $return .= '</div>';
9901
            $return .= '</td>';
9902
9903
            if ($item['item_type'] == TOOL_QUIZ) {
9904
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9905
                $lpItemObj = new LpItem($course_id, $item['id']);
9906
                $exercise = new Exercise($course_id);
9907
                $exercise->read($lpItemObj->path);
9908
                $lpItemObj->max_score = $exercise->get_max_score();
9909
                $lpItemObj->update();
9910
                $item['max_score'] = $lpItemObj->max_score;
9911
9912
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9913
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9914
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9915
                }
9916
9917
                $return .= '<td>';
9918
                $return .= '<input
9919
                    class="form-control"
9920
                    size="4" maxlength="3"
9921
                    name="min_'.$item['id'].'"
9922
                    type="number"
9923
                    min="0"
9924
                    step="any"
9925
                    max="'.$item['max_score'].'"
9926
                    value="'.$selectedMinScoreValue.'"
9927
                />';
9928
                $return .= '</td>';
9929
                $return .= '<td>';
9930
                $return .= '<input
9931
                    class="form-control"
9932
                    size="4"
9933
                    maxlength="3"
9934
                    name="max_'.$item['id'].'"
9935
                    type="number"
9936
                    min="0"
9937
                    step="any"
9938
                    max="'.$item['max_score'].'"
9939
                    value="'.$selectedMaxScoreValue.'"
9940
                />';
9941
                $return .= '</td>';
9942
            }
9943
9944
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9945
                $return .= '<td>';
9946
                $return .= '<input
9947
                    size="4"
9948
                    maxlength="3"
9949
                    name="min_'.$item['id'].'"
9950
                    type="number"
9951
                    min="0"
9952
                    step="any"
9953
                    max="'.$item['max_score'].'"
9954
                    value="'.$selectedMinScoreValue.'"
9955
                />';
9956
                $return .= '</td>';
9957
                $return .= '<td>';
9958
                $return .= '<input
9959
                    size="4"
9960
                    maxlength="3"
9961
                    name="max_'.$item['id'].'"
9962
                    type="number"
9963
                    min="0"
9964
                    step="any"
9965
                    max="'.$item['max_score'].'"
9966
                    value="'.$selectedMaxScoreValue.'"
9967
                />';
9968
                $return .= '</td>';
9969
            }
9970
            $return .= '</tr>';
9971
        }
9972
        $return .= '<tr>';
9973
        $return .= '</tr>';
9974
        $return .= '</tbody>';
9975
        $return .= '</table>';
9976
        $return .= '</div>';
9977
        $return .= '<div class="form-group">';
9978
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9979
            get_lang('ModifyPrerequisites').'</button>';
9980
        $return .= '</form>';
9981
9982
        return $return;
9983
    }
9984
9985
    /**
9986
     * Return HTML list to allow prerequisites selection for lp.
9987
     *
9988
     * @return string HTML form
9989
     */
9990
    public function display_lp_prerequisites_list()
9991
    {
9992
        $course_id = api_get_course_int_id();
9993
        $lp_id = $this->lp_id;
9994
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9995
9996
        // get current prerequisite
9997
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9998
        $result = Database::query($sql);
9999
        $row = Database::fetch_array($result);
10000
        $prerequisiteId = $row['prerequisite'];
10001
        $session_id = api_get_session_id();
10002
        $session_condition = api_get_session_condition($session_id, true, true);
10003
        $sql = "SELECT * FROM $tbl_lp
10004
                WHERE c_id = $course_id $session_condition
10005
                ORDER BY display_order ";
10006
        $rs = Database::query($sql);
10007
        $return = '';
10008
        $return .= '<select name="prerequisites" class="form-control">';
10009
        $return .= '<option value="0">'.get_lang('None').'</option>';
10010
        if (Database::num_rows($rs) > 0) {
10011
            while ($row = Database::fetch_array($rs)) {
10012
                if ($row['id'] == $lp_id) {
10013
                    continue;
10014
                }
10015
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10016
            }
10017
        }
10018
        $return .= '</select>';
10019
10020
        return $return;
10021
    }
10022
10023
    /**
10024
     * Creates a list with all the documents in it.
10025
     *
10026
     * @param bool $showInvisibleFiles
10027
     *
10028
     * @throws Exception
10029
     * @throws HTML_QuickForm_Error
10030
     *
10031
     * @return string
10032
     */
10033
    public function get_documents($showInvisibleFiles = false)
10034
    {
10035
        $course_info = api_get_course_info();
10036
        $sessionId = api_get_session_id();
10037
        $documentTree = DocumentManager::get_document_preview(
10038
            $course_info,
10039
            $this->lp_id,
10040
            null,
10041
            $sessionId,
10042
            true,
10043
            null,
10044
            null,
10045
            $showInvisibleFiles,
10046
            true
10047
        );
10048
10049
        $headers = [
10050
            get_lang('Files'),
10051
            get_lang('CreateTheDocument'),
10052
            get_lang('CreateReadOutText'),
10053
            get_lang('Upload'),
10054
        ];
10055
10056
        $form = new FormValidator(
10057
            'form_upload',
10058
            'POST',
10059
            $this->getCurrentBuildingModeURL(),
10060
            '',
10061
            ['enctype' => 'multipart/form-data']
10062
        );
10063
10064
        $folders = DocumentManager::get_all_document_folders(
10065
            api_get_course_info(),
10066
            0,
10067
            true
10068
        );
10069
10070
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10071
10072
        DocumentManager::build_directory_selector(
10073
            $folders,
10074
            $lpPathInfo['id'],
10075
            [],
10076
            true,
10077
            $form,
10078
            'directory_parent_id'
10079
        );
10080
10081
        $group = [
10082
            $form->createElement(
10083
                'radio',
10084
                'if_exists',
10085
                get_lang('UplWhatIfFileExists'),
10086
                get_lang('UplDoNothing'),
10087
                'nothing'
10088
            ),
10089
            $form->createElement(
10090
                'radio',
10091
                'if_exists',
10092
                null,
10093
                get_lang('UplOverwriteLong'),
10094
                'overwrite'
10095
            ),
10096
            $form->createElement(
10097
                'radio',
10098
                'if_exists',
10099
                null,
10100
                get_lang('UplRenameLong'),
10101
                'rename'
10102
            ),
10103
        ];
10104
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10105
10106
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10107
        $defaultFileExistsOption = 'rename';
10108
        if (!empty($fileExistsOption)) {
10109
            $defaultFileExistsOption = $fileExistsOption;
10110
        }
10111
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10112
10113
        // Check box options
10114
        $form->addElement(
10115
            'checkbox',
10116
            'unzip',
10117
            get_lang('Options'),
10118
            get_lang('Uncompress')
10119
        );
10120
10121
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10122
        $form->addMultipleUpload($url);
10123
        $new = $this->display_document_form('add', 0);
10124
        $frmReadOutText = $this->displayFrmReadOutText('add');
10125
        $tabs = Display::tabs(
10126
            $headers,
10127
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10128
            'subtab'
10129
        );
10130
10131
        return $tabs;
10132
    }
10133
10134
    /**
10135
     * Creates a list with all the exercises (quiz) in it.
10136
     *
10137
     * @return string
10138
     */
10139
    public function get_exercises()
10140
    {
10141
        $course_id = api_get_course_int_id();
10142
        $session_id = api_get_session_id();
10143
        $userInfo = api_get_user_info();
10144
10145
        // New for hotpotatoes.
10146
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10147
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10148
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10149
        $condition_session = api_get_session_condition($session_id, true, true);
10150
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10151
10152
        $activeCondition = ' active <> -1 ';
10153
        if ($setting) {
10154
            $activeCondition = ' active = 1 ';
10155
        }
10156
10157
        $categoryCondition = '';
10158
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10159
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10160
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10161
        }
10162
10163
        $keywordCondition = '';
10164
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10165
10166
        if (!empty($keyword)) {
10167
            $keyword = Database::escape_string($keyword);
10168
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10169
        }
10170
10171
        $sql_quiz = "SELECT * FROM $tbl_quiz
10172
                     WHERE
10173
                            c_id = $course_id AND
10174
                            $activeCondition
10175
                            $condition_session
10176
                            $categoryCondition
10177
                            $keywordCondition
10178
                     ORDER BY title ASC";
10179
10180
        $sql_hot = "SELECT * FROM $tbl_doc
10181
                    WHERE
10182
                        c_id = $course_id AND
10183
                        path LIKE '".$uploadPath."/%/%htm%'
10184
                        $condition_session
10185
                     ORDER BY id ASC";
10186
10187
        $res_quiz = Database::query($sql_quiz);
10188
        $res_hot = Database::query($sql_hot);
10189
10190
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10191
10192
        // Create a search-box
10193
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10194
        $form->addHidden('action', 'add_item');
10195
        $form->addHidden('type', 'step');
10196
        $form->addHidden('lp_id', $this->lp_id);
10197
        $form->addHidden('lp_build_selected', '2');
10198
10199
        $form->addCourseHiddenParams();
10200
        $form->addText(
10201
            'keyword',
10202
            get_lang('Search'),
10203
            false,
10204
            [
10205
                'aria-label' => get_lang('Search'),
10206
            ]
10207
        );
10208
10209
        if (api_get_configuration_value('allow_exercise_categories')) {
10210
            $manager = new ExerciseCategoryManager();
10211
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10212
            if (!empty($options)) {
10213
                $form->addSelect(
10214
                    'category_id',
10215
                    get_lang('Category'),
10216
                    $options,
10217
                    ['placeholder' => get_lang('SelectAnOption')]
10218
                );
10219
            }
10220
        }
10221
10222
        $form->addButtonSearch(get_lang('Search'));
10223
        $return = $form->returnForm();
10224
10225
        $return .= '<ul class="lp_resource">';
10226
10227
        $return .= '<li class="lp_resource_element">';
10228
        $return .= Display::return_icon('new_exercice.png');
10229
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10230
            get_lang('NewExercise').'</a>';
10231
        $return .= '</li>';
10232
10233
        $previewIcon = Display::return_icon(
10234
            'preview_view.png',
10235
            get_lang('Preview')
10236
        );
10237
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10238
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10239
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10240
10241
        // Display hotpotatoes
10242
        while ($row_hot = Database::fetch_array($res_hot)) {
10243
            $link = Display::url(
10244
                $previewIcon,
10245
                $exerciseUrl.'&file='.$row_hot['path'],
10246
                ['target' => '_blank']
10247
            );
10248
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10249
            $return .= '<a class="moved" href="#">';
10250
            $return .= Display::return_icon(
10251
                'move_everywhere.png',
10252
                get_lang('Move'),
10253
                [],
10254
                ICON_SIZE_TINY
10255
            );
10256
            $return .= '</a> ';
10257
            $return .= Display::return_icon('hotpotatoes_s.png');
10258
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10259
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10260
            $return .= '</li>';
10261
        }
10262
10263
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10264
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10265
            $title = strip_tags(
10266
                api_html_entity_decode($row_quiz['title'])
10267
            );
10268
10269
            $visibility = api_get_item_visibility(
10270
                ['real_id' => $course_id],
10271
                TOOL_QUIZ,
10272
                $row_quiz['iid'],
10273
                $session_id
10274
            );
10275
10276
            $link = Display::url(
10277
                $previewIcon,
10278
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10279
                ['target' => '_blank']
10280
            );
10281
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10282
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10283
            $return .= $quizIcon;
10284
            $sessionStar = api_get_session_image(
10285
                $row_quiz['session_id'],
10286
                $userInfo['status']
10287
            );
10288
            $return .= Display::url(
10289
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10290
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10291
                [
10292
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10293
                ]
10294
            );
10295
            $return .= '</li>';
10296
        }
10297
10298
        $return .= '</ul>';
10299
10300
        return $return;
10301
    }
10302
10303
    /**
10304
     * Creates a list with all the links in it.
10305
     *
10306
     * @return string
10307
     */
10308
    public function get_links()
10309
    {
10310
        $selfUrl = api_get_self();
10311
        $courseIdReq = api_get_cidreq();
10312
        $course = api_get_course_info();
10313
        $userInfo = api_get_user_info();
10314
10315
        $course_id = $course['real_id'];
10316
        $tbl_link = Database::get_course_table(TABLE_LINK);
10317
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10318
        $moveEverywhereIcon = Display::return_icon(
10319
            'move_everywhere.png',
10320
            get_lang('Move'),
10321
            [],
10322
            ICON_SIZE_TINY
10323
        );
10324
10325
        $session_id = api_get_session_id();
10326
        $condition_session = api_get_session_condition(
10327
            $session_id,
10328
            true,
10329
            true,
10330
            'link.session_id'
10331
        );
10332
10333
        $sql = "SELECT
10334
                    link.id as link_id,
10335
                    link.title as link_title,
10336
                    link.session_id as link_session_id,
10337
                    link.category_id as category_id,
10338
                    link_category.category_title as category_title
10339
                FROM $tbl_link as link
10340
                LEFT JOIN $linkCategoryTable as link_category
10341
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10342
                WHERE link.c_id = $course_id $condition_session
10343
                ORDER BY link_category.category_title ASC, link.title ASC";
10344
        $result = Database::query($sql);
10345
        $categorizedLinks = [];
10346
        $categories = [];
10347
10348
        while ($link = Database::fetch_array($result)) {
10349
            if (!$link['category_id']) {
10350
                $link['category_title'] = get_lang('Uncategorized');
10351
            }
10352
            $categories[$link['category_id']] = $link['category_title'];
10353
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10354
        }
10355
10356
        $linksHtmlCode =
10357
            '<script>
10358
            function toggle_tool(tool, id) {
10359
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10360
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10361
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10362
                } else {
10363
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10364
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10365
                }
10366
            }
10367
        </script>
10368
10369
        <ul class="lp_resource">
10370
            <li class="lp_resource_element">
10371
                '.Display::return_icon('linksnew.gif').'
10372
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10373
                get_lang('LinkAdd').'
10374
                </a>
10375
            </li>';
10376
10377
        foreach ($categorizedLinks as $categoryId => $links) {
10378
            $linkNodes = null;
10379
            foreach ($links as $key => $linkInfo) {
10380
                $title = $linkInfo['link_title'];
10381
                $linkSessionId = $linkInfo['link_session_id'];
10382
10383
                $link = Display::url(
10384
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10385
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10386
                    ['target' => '_blank']
10387
                );
10388
10389
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10390
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10391
                    $linkNodes .=
10392
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10393
                        <a class="moved" href="#">'.
10394
                            $moveEverywhereIcon.
10395
                        '</a>
10396
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10397
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10398
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10399
                        Security::remove_XSS($title).$sessionStar.$link.
10400
                        '</a>
10401
                    </li>';
10402
                }
10403
            }
10404
            $linksHtmlCode .=
10405
                '<li>
10406
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10407
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10408
                    align="absbottom" />
10409
                </a>
10410
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10411
            </li>
10412
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10413
        }
10414
        $linksHtmlCode .= '</ul>';
10415
10416
        return $linksHtmlCode;
10417
    }
10418
10419
    /**
10420
     * Creates a list with all the student publications in it.
10421
     *
10422
     * @return string
10423
     */
10424
    public function get_student_publications()
10425
    {
10426
        $return = '<ul class="lp_resource">';
10427
        $return .= '<li class="lp_resource_element">';
10428
        $return .= Display::return_icon('works_new.gif');
10429
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10430
            get_lang('AddAssignmentPage').'</a>';
10431
        $return .= '</li>';
10432
10433
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10434
        $works = getWorkListTeacher(0, 100, null, null, null);
10435
        if (!empty($works)) {
10436
            foreach ($works as $work) {
10437
                $link = Display::url(
10438
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10439
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10440
                    ['target' => '_blank']
10441
                );
10442
10443
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10444
                $return .= '<a class="moved" href="#">';
10445
                $return .= Display::return_icon(
10446
                    'move_everywhere.png',
10447
                    get_lang('Move'),
10448
                    [],
10449
                    ICON_SIZE_TINY
10450
                );
10451
                $return .= '</a> ';
10452
10453
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10454
                $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.'">'.
10455
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10456
                </a>';
10457
10458
                $return .= '</li>';
10459
            }
10460
        }
10461
10462
        $return .= '</ul>';
10463
10464
        return $return;
10465
    }
10466
10467
    /**
10468
     * Creates a list with all the forums in it.
10469
     *
10470
     * @return string
10471
     */
10472
    public function get_forums()
10473
    {
10474
        require_once '../forum/forumfunction.inc.php';
10475
10476
        $forumCategories = get_forum_categories();
10477
        $forumsInNoCategory = get_forums_in_category(0);
10478
        if (!empty($forumsInNoCategory)) {
10479
            $forumCategories = array_merge(
10480
                $forumCategories,
10481
                [
10482
                    [
10483
                        'cat_id' => 0,
10484
                        'session_id' => 0,
10485
                        'visibility' => 1,
10486
                        'cat_comment' => null,
10487
                    ],
10488
                ]
10489
            );
10490
        }
10491
10492
        $forumList = get_forums();
10493
        $a_forums = [];
10494
        foreach ($forumCategories as $forumCategory) {
10495
            // The forums in this category.
10496
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10497
            if (!empty($forumsInCategory)) {
10498
                foreach ($forumList as $forum) {
10499
                    if (isset($forum['forum_category']) &&
10500
                        $forum['forum_category'] == $forumCategory['cat_id']
10501
                    ) {
10502
                        $a_forums[] = $forum;
10503
                    }
10504
                }
10505
            }
10506
        }
10507
10508
        $return = '<ul class="lp_resource">';
10509
10510
        // First add link
10511
        $return .= '<li class="lp_resource_element">';
10512
        $return .= Display::return_icon('new_forum.png');
10513
        $return .= Display::url(
10514
            get_lang('CreateANewForum'),
10515
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10516
                'action' => 'add',
10517
                'content' => 'forum',
10518
                'lp_id' => $this->lp_id,
10519
            ]),
10520
            ['title' => get_lang('CreateANewForum')]
10521
        );
10522
        $return .= '</li>';
10523
10524
        $return .= '<script>
10525
            function toggle_forum(forum_id) {
10526
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10527
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10528
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10529
                } else {
10530
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10531
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10532
                }
10533
            }
10534
        </script>';
10535
10536
        foreach ($a_forums as $forum) {
10537
            if (!empty($forum['forum_id'])) {
10538
                $link = Display::url(
10539
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10540
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10541
                    ['target' => '_blank']
10542
                );
10543
10544
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10545
                $return .= '<a class="moved" href="#">';
10546
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10547
                $return .= ' </a>';
10548
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10549
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10550
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10551
                            </a>
10552
                            <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">'.
10553
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10554
10555
                $return .= '</li>';
10556
10557
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10558
                $a_threads = get_threads($forum['forum_id']);
10559
                if (is_array($a_threads)) {
10560
                    foreach ($a_threads as $thread) {
10561
                        $link = Display::url(
10562
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10563
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10564
                            ['target' => '_blank']
10565
                        );
10566
10567
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10568
                        $return .= '&nbsp;<a class="moved" href="#">';
10569
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10570
                        $return .= ' </a>';
10571
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10572
                        $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.'">'.
10573
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10574
                        $return .= '</li>';
10575
                    }
10576
                }
10577
                $return .= '</div>';
10578
            }
10579
        }
10580
        $return .= '</ul>';
10581
10582
        return $return;
10583
    }
10584
10585
    /**
10586
     * // TODO: The output encoding should be equal to the system encoding.
10587
     *
10588
     * Exports the learning path as a SCORM package. This is the main function that
10589
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10590
     * whole thing and returns the zip.
10591
     *
10592
     * This method needs to be called in PHP5, as it will fail with non-adequate
10593
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10594
     * you need to call it on a learnpath object.
10595
     *
10596
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10597
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10598
     * path has been modified, it should use the generic method here below.
10599
     *
10600
     * @return string Returns the zip package string, or null if error
10601
     */
10602
    public function scormExport()
10603
    {
10604
        api_set_more_memory_and_time_limits();
10605
10606
        $_course = api_get_course_info();
10607
        $course_id = $_course['real_id'];
10608
        // Create the zip handler (this will remain available throughout the method).
10609
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10610
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10611
        $temp_dir_short = uniqid('scorm_export', true);
10612
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10613
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10614
        $zip_folder = new PclZip($temp_zip_file);
10615
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10616
        $root_path = $main_path = api_get_path(SYS_PATH);
10617
        $files_cleanup = [];
10618
10619
        // Place to temporarily stash the zip file.
10620
        // create the temp dir if it doesn't exist
10621
        // or do a cleanup before creating the zip file.
10622
        if (!is_dir($temp_zip_dir)) {
10623
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10624
        } else {
10625
            // Cleanup: Check the temp dir for old files and delete them.
10626
            $handle = opendir($temp_zip_dir);
10627
            while (false !== ($file = readdir($handle))) {
10628
                if ($file != '.' && $file != '..') {
10629
                    unlink("$temp_zip_dir/$file");
10630
                }
10631
            }
10632
            closedir($handle);
10633
        }
10634
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10635
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10636
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10637
        ) {
10638
            // Remove the possible . at the end of the path.
10639
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10640
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10641
            mkdir(
10642
                $dest_path_to_scorm_folder,
10643
                api_get_permissions_for_new_directories(),
10644
                true
10645
            );
10646
            copyr(
10647
                $current_course_path.'/scorm/'.$this->path,
10648
                $dest_path_to_scorm_folder,
10649
                ['imsmanifest'],
10650
                $zip_files
10651
            );
10652
        }
10653
10654
        // Build a dummy imsmanifest structure.
10655
        // Do not add to the zip yet (we still need it).
10656
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10657
        // Aggregation Model official document, section "2.3 Content Packaging".
10658
        // We are going to build a UTF-8 encoded manifest.
10659
        // Later we will recode it to the desired (and supported) encoding.
10660
        $xmldoc = new DOMDocument('1.0');
10661
        $root = $xmldoc->createElement('manifest');
10662
        $root->setAttribute('identifier', 'SingleCourseManifest');
10663
        $root->setAttribute('version', '1.1');
10664
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10665
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10666
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10667
        $root->setAttribute(
10668
            'xsi:schemaLocation',
10669
            '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'
10670
        );
10671
        // Build mandatory sub-root container elements.
10672
        $metadata = $xmldoc->createElement('metadata');
10673
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10674
        $metadata->appendChild($md_schema);
10675
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10676
        $metadata->appendChild($md_schemaversion);
10677
        $root->appendChild($metadata);
10678
10679
        $organizations = $xmldoc->createElement('organizations');
10680
        $resources = $xmldoc->createElement('resources');
10681
10682
        // Build the only organization we will use in building our learnpaths.
10683
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10684
        $organization = $xmldoc->createElement('organization');
10685
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10686
        // To set the title of the SCORM entity (=organization), we take the name given
10687
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10688
        // learning path charset) as it is the encoding that defines how it is stored
10689
        // in the database. Then we convert it to HTML entities again as the "&" character
10690
        // alone is not authorized in XML (must be &amp;).
10691
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10692
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10693
        $organization->appendChild($org_title);
10694
        $folder_name = 'document';
10695
10696
        // Removes the learning_path/scorm_folder path when exporting see #4841
10697
        $path_to_remove = '';
10698
        $path_to_replace = '';
10699
        $result = $this->generate_lp_folder($_course);
10700
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10701
            $path_to_remove = 'document'.$result['dir'];
10702
            $path_to_replace = $folder_name.'/';
10703
        }
10704
10705
        // Fixes chamilo scorm exports
10706
        if ($this->ref === 'chamilo_scorm_export') {
10707
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10708
        }
10709
10710
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10711
        $link_updates = [];
10712
        $links_to_create = [];
10713
        foreach ($this->ordered_items as $index => $itemId) {
10714
            /** @var learnpathItem $item */
10715
            $item = $this->items[$itemId];
10716
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10717
                // Get included documents from this item.
10718
                if ($item->type === 'sco') {
10719
                    $inc_docs = $item->get_resources_from_source(
10720
                        null,
10721
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10722
                    );
10723
                } else {
10724
                    $inc_docs = $item->get_resources_from_source();
10725
                }
10726
10727
                // Give a child element <item> to the <organization> element.
10728
                $my_item_id = $item->get_id();
10729
                $my_item = $xmldoc->createElement('item');
10730
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10731
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10732
                $my_item->setAttribute('isvisible', 'true');
10733
                // Give a child element <title> to the <item> element.
10734
                $my_title = $xmldoc->createElement(
10735
                    'title',
10736
                    htmlspecialchars(
10737
                        api_utf8_encode($item->get_title()),
10738
                        ENT_QUOTES,
10739
                        'UTF-8'
10740
                    )
10741
                );
10742
                $my_item->appendChild($my_title);
10743
                // Give a child element <adlcp:prerequisites> to the <item> element.
10744
                $my_prereqs = $xmldoc->createElement(
10745
                    'adlcp:prerequisites',
10746
                    $this->get_scorm_prereq_string($my_item_id)
10747
                );
10748
                $my_prereqs->setAttribute('type', 'aicc_script');
10749
                $my_item->appendChild($my_prereqs);
10750
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10751
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10752
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10753
                //$xmldoc->createElement('adlcp:timelimitaction','');
10754
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10755
                //$xmldoc->createElement('adlcp:datafromlms','');
10756
                // Give a child element <adlcp:masteryscore> to the <item> element.
10757
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10758
                $my_item->appendChild($my_masteryscore);
10759
10760
                // Attach this item to the organization element or hits parent if there is one.
10761
                if (!empty($item->parent) && $item->parent != 0) {
10762
                    $children = $organization->childNodes;
10763
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10764
                    if (is_object($possible_parent)) {
10765
                        $possible_parent->appendChild($my_item);
10766
                    } else {
10767
                        if ($this->debug > 0) {
10768
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10769
                        }
10770
                    }
10771
                } else {
10772
                    if ($this->debug > 0) {
10773
                        error_log('No parent');
10774
                    }
10775
                    $organization->appendChild($my_item);
10776
                }
10777
10778
                // Get the path of the file(s) from the course directory root.
10779
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10780
                $my_xml_file_path = $my_file_path;
10781
                if (!empty($path_to_remove)) {
10782
                    // From docs
10783
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10784
10785
                    // From quiz
10786
                    if ($this->ref === 'chamilo_scorm_export') {
10787
                        $path_to_remove = 'scorm/'.$this->path.'/';
10788
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10789
                    }
10790
                }
10791
10792
                $my_sub_dir = dirname($my_file_path);
10793
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10794
                $my_xml_sub_dir = $my_sub_dir;
10795
                // Give a <resource> child to the <resources> element
10796
                $my_resource = $xmldoc->createElement('resource');
10797
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10798
                $my_resource->setAttribute('type', 'webcontent');
10799
                $my_resource->setAttribute('href', $my_xml_file_path);
10800
                // adlcp:scormtype can be either 'sco' or 'asset'.
10801
                if ($item->type === 'sco') {
10802
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10803
                } else {
10804
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10805
                }
10806
                // xml:base is the base directory to find the files declared in this resource.
10807
                $my_resource->setAttribute('xml:base', '');
10808
                // Give a <file> child to the <resource> element.
10809
                $my_file = $xmldoc->createElement('file');
10810
                $my_file->setAttribute('href', $my_xml_file_path);
10811
                $my_resource->appendChild($my_file);
10812
10813
                // Dependency to other files - not yet supported.
10814
                $i = 1;
10815
                if ($inc_docs) {
10816
                    foreach ($inc_docs as $doc_info) {
10817
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10818
                            continue;
10819
                        }
10820
                        $my_dep = $xmldoc->createElement('resource');
10821
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10822
                        $my_dep->setAttribute('identifier', $res_id);
10823
                        $my_dep->setAttribute('type', 'webcontent');
10824
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10825
                        $my_dep_file = $xmldoc->createElement('file');
10826
                        // Check type of URL.
10827
                        if ($doc_info[1] == 'remote') {
10828
                            // Remote file. Save url as is.
10829
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10830
                            $my_dep->setAttribute('xml:base', '');
10831
                        } elseif ($doc_info[1] === 'local') {
10832
                            switch ($doc_info[2]) {
10833
                                case 'url':
10834
                                    // Local URL - save path as url for now, don't zip file.
10835
                                    $abs_path = api_get_path(SYS_PATH).
10836
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10837
                                    $current_dir = dirname($abs_path);
10838
                                    $current_dir = str_replace('\\', '/', $current_dir);
10839
                                    $file_path = realpath($abs_path);
10840
                                    $file_path = str_replace('\\', '/', $file_path);
10841
                                    $my_dep_file->setAttribute('href', $file_path);
10842
                                    $my_dep->setAttribute('xml:base', '');
10843
                                    if (strstr($file_path, $main_path) !== false) {
10844
                                        // The calculated real path is really inside Chamilo's root path.
10845
                                        // Reduce file path to what's under the DocumentRoot.
10846
                                        $replace = $file_path;
10847
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10848
                                        $destinationFile = $file_path;
10849
10850
                                        if (strstr($file_path, 'upload/users') !== false) {
10851
                                            $pos = strpos($file_path, 'my_files/');
10852
                                            if ($pos !== false) {
10853
                                                $onlyDirectory = str_replace(
10854
                                                    'upload/users/',
10855
                                                    '',
10856
                                                    substr($file_path, $pos, strlen($file_path))
10857
                                                );
10858
                                            }
10859
                                            $replace = $onlyDirectory;
10860
                                            $destinationFile = $replace;
10861
                                        }
10862
                                        $zip_files_abs[] = $file_path;
10863
                                        $link_updates[$my_file_path][] = [
10864
                                            'orig' => $doc_info[0],
10865
                                            'dest' => $destinationFile,
10866
                                            'replace' => $replace,
10867
                                        ];
10868
                                        $my_dep_file->setAttribute('href', $file_path);
10869
                                        $my_dep->setAttribute('xml:base', '');
10870
                                    } elseif (empty($file_path)) {
10871
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10872
                                        $file_path = str_replace('//', '/', $file_path);
10873
                                        if (file_exists($file_path)) {
10874
                                            // We get the relative path.
10875
                                            $file_path = substr($file_path, strlen($current_dir));
10876
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10877
                                            $link_updates[$my_file_path][] = [
10878
                                                'orig' => $doc_info[0],
10879
                                                'dest' => $file_path,
10880
                                            ];
10881
                                            $my_dep_file->setAttribute('href', $file_path);
10882
                                            $my_dep->setAttribute('xml:base', '');
10883
                                        }
10884
                                    }
10885
                                    break;
10886
                                case 'abs':
10887
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10888
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10889
                                    $my_dep->setAttribute('xml:base', '');
10890
10891
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10892
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10893
                                    $abs_img_path_without_subdir = $doc_info[0];
10894
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10895
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10896
                                    if ($pos === 0) {
10897
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10898
                                    }
10899
10900
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10901
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10902
10903
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10904
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10905
                                    // Check if the current document is in that path.
10906
                                    if (strstr($file_path, $cur_path) !== false) {
10907
                                        $destinationFile = substr($file_path, strlen($cur_path));
10908
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10909
10910
                                        $fileToTest = $cur_path.$my_file_path;
10911
                                        if (!empty($path_to_remove)) {
10912
                                            $fileToTest = str_replace(
10913
                                                $path_to_remove.'/',
10914
                                                $path_to_replace,
10915
                                                $cur_path.$my_file_path
10916
                                            );
10917
                                        }
10918
10919
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10920
10921
                                        // Put the current document in the zip (this array is the array
10922
                                        // that will manage documents already in the course folder - relative).
10923
                                        $zip_files[] = $filePathNoCoursePart;
10924
                                        // Update the links to the current document in the
10925
                                        // containing document (make them relative).
10926
                                        $link_updates[$my_file_path][] = [
10927
                                            'orig' => $doc_info[0],
10928
                                            'dest' => $destinationFile,
10929
                                            'replace' => $relative_path,
10930
                                        ];
10931
10932
                                        $my_dep_file->setAttribute('href', $file_path);
10933
                                        $my_dep->setAttribute('xml:base', '');
10934
                                    } elseif (strstr($file_path, $main_path) !== false) {
10935
                                        // The calculated real path is really inside Chamilo's root path.
10936
                                        // Reduce file path to what's under the DocumentRoot.
10937
                                        $file_path = substr($file_path, strlen($root_path));
10938
                                        $zip_files_abs[] = $file_path;
10939
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10940
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10941
                                        $my_dep->setAttribute('xml:base', '');
10942
                                    } elseif (empty($file_path)) {
10943
                                        // Probably this is an image inside "/main" directory
10944
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10945
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10946
10947
                                        if (file_exists($file_path)) {
10948
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10949
                                                // We get the relative path.
10950
                                                $pos = strpos($file_path, 'main/default_course_document/');
10951
                                                if ($pos !== false) {
10952
                                                    $onlyDirectory = str_replace(
10953
                                                        'main/default_course_document/',
10954
                                                        '',
10955
                                                        substr($file_path, $pos, strlen($file_path))
10956
                                                    );
10957
                                                }
10958
10959
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10960
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10961
                                                $zip_files_abs[] = $fileAbs;
10962
                                                $link_updates[$my_file_path][] = [
10963
                                                    'orig' => $doc_info[0],
10964
                                                    'dest' => $destinationFile,
10965
                                                ];
10966
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10967
                                                $my_dep->setAttribute('xml:base', '');
10968
                                            }
10969
                                        }
10970
                                    }
10971
                                    break;
10972
                                case 'rel':
10973
                                    // Path relative to the current document.
10974
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10975
                                    if (substr($doc_info[0], 0, 2) === '..') {
10976
                                        // Relative path going up.
10977
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10978
                                        $current_dir = str_replace('\\', '/', $current_dir);
10979
                                        $file_path = realpath($current_dir.$doc_info[0]);
10980
                                        $file_path = str_replace('\\', '/', $file_path);
10981
                                        if (strstr($file_path, $main_path) !== false) {
10982
                                            // The calculated real path is really inside Chamilo's root path.
10983
                                            // Reduce file path to what's under the DocumentRoot.
10984
                                            $file_path = substr($file_path, strlen($root_path));
10985
                                            $zip_files_abs[] = $file_path;
10986
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10987
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10988
                                            $my_dep->setAttribute('xml:base', '');
10989
                                        }
10990
                                    } else {
10991
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10992
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10993
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10994
                                    }
10995
                                    break;
10996
                                default:
10997
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10998
                                    $my_dep->setAttribute('xml:base', '');
10999
                                    break;
11000
                            }
11001
                        }
11002
                        $my_dep->appendChild($my_dep_file);
11003
                        $resources->appendChild($my_dep);
11004
                        $dependency = $xmldoc->createElement('dependency');
11005
                        $dependency->setAttribute('identifierref', $res_id);
11006
                        $my_resource->appendChild($dependency);
11007
                        $i++;
11008
                    }
11009
                }
11010
                $resources->appendChild($my_resource);
11011
                $zip_files[] = $my_file_path;
11012
            } else {
11013
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11014
                switch ($item->type) {
11015
                    case TOOL_LINK:
11016
                        $my_item = $xmldoc->createElement('item');
11017
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11018
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11019
                        $my_item->setAttribute('isvisible', 'true');
11020
                        // Give a child element <title> to the <item> element.
11021
                        $my_title = $xmldoc->createElement(
11022
                            'title',
11023
                            htmlspecialchars(
11024
                                api_utf8_encode($item->get_title()),
11025
                                ENT_QUOTES,
11026
                                'UTF-8'
11027
                            )
11028
                        );
11029
                        $my_item->appendChild($my_title);
11030
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11031
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11032
                        $my_prereqs->setAttribute('type', 'aicc_script');
11033
                        $my_item->appendChild($my_prereqs);
11034
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11035
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11036
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11037
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11038
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11039
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11040
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11041
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11042
                        $my_item->appendChild($my_masteryscore);
11043
11044
                        // Attach this item to the organization element or its parent if there is one.
11045
                        if (!empty($item->parent) && $item->parent != 0) {
11046
                            $children = $organization->childNodes;
11047
                            for ($i = 0; $i < $children->length; $i++) {
11048
                                $item_temp = $children->item($i);
11049
                                if ($item_temp->nodeName == 'item') {
11050
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11051
                                        $item_temp->appendChild($my_item);
11052
                                    }
11053
                                }
11054
                            }
11055
                        } else {
11056
                            $organization->appendChild($my_item);
11057
                        }
11058
11059
                        $my_file_path = 'link_'.$item->get_id().'.html';
11060
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11061
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11062
                        $rs = Database::query($sql);
11063
                        if ($link = Database::fetch_array($rs)) {
11064
                            $url = $link['url'];
11065
                            $title = stripslashes($link['title']);
11066
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11067
                            $my_xml_file_path = $my_file_path;
11068
                            $my_sub_dir = dirname($my_file_path);
11069
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11070
                            $my_xml_sub_dir = $my_sub_dir;
11071
                            // Give a <resource> child to the <resources> element.
11072
                            $my_resource = $xmldoc->createElement('resource');
11073
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11074
                            $my_resource->setAttribute('type', 'webcontent');
11075
                            $my_resource->setAttribute('href', $my_xml_file_path);
11076
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11077
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11078
                            // xml:base is the base directory to find the files declared in this resource.
11079
                            $my_resource->setAttribute('xml:base', '');
11080
                            // give a <file> child to the <resource> element.
11081
                            $my_file = $xmldoc->createElement('file');
11082
                            $my_file->setAttribute('href', $my_xml_file_path);
11083
                            $my_resource->appendChild($my_file);
11084
                            $resources->appendChild($my_resource);
11085
                        }
11086
                        break;
11087
                    case TOOL_QUIZ:
11088
                        $exe_id = $item->path;
11089
                        // Should be using ref when everything will be cleaned up in this regard.
11090
                        $exe = new Exercise();
11091
                        $exe->read($exe_id);
11092
                        $my_item = $xmldoc->createElement('item');
11093
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11094
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11095
                        $my_item->setAttribute('isvisible', 'true');
11096
                        // Give a child element <title> to the <item> element.
11097
                        $my_title = $xmldoc->createElement(
11098
                            'title',
11099
                            htmlspecialchars(
11100
                                api_utf8_encode($item->get_title()),
11101
                                ENT_QUOTES,
11102
                                'UTF-8'
11103
                            )
11104
                        );
11105
                        $my_item->appendChild($my_title);
11106
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11107
                        $my_item->appendChild($my_max_score);
11108
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11109
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11110
                        $my_prereqs->setAttribute('type', 'aicc_script');
11111
                        $my_item->appendChild($my_prereqs);
11112
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11113
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11114
                        $my_item->appendChild($my_masteryscore);
11115
11116
                        // Attach this item to the organization element or hits parent if there is one.
11117
                        if (!empty($item->parent) && $item->parent != 0) {
11118
                            $children = $organization->childNodes;
11119
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11120
                            if ($possible_parent) {
11121
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11122
                                    $possible_parent->appendChild($my_item);
11123
                                }
11124
                            }
11125
                        } else {
11126
                            $organization->appendChild($my_item);
11127
                        }
11128
11129
                        // Get the path of the file(s) from the course directory root
11130
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11131
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11132
                        // Write the contents of the exported exercise into a (big) html file
11133
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11134
                        $scormExercise = new ScormExercise($exe, true);
11135
                        $contents = $scormExercise->export();
11136
11137
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11138
                        $res = file_put_contents($tmp_file_path, $contents);
11139
                        if ($res === false) {
11140
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11141
                        }
11142
                        $files_cleanup[] = $tmp_file_path;
11143
                        $my_xml_file_path = $my_file_path;
11144
                        $my_sub_dir = dirname($my_file_path);
11145
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11146
                        $my_xml_sub_dir = $my_sub_dir;
11147
                        // Give a <resource> child to the <resources> element.
11148
                        $my_resource = $xmldoc->createElement('resource');
11149
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11150
                        $my_resource->setAttribute('type', 'webcontent');
11151
                        $my_resource->setAttribute('href', $my_xml_file_path);
11152
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11153
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11154
                        // xml:base is the base directory to find the files declared in this resource.
11155
                        $my_resource->setAttribute('xml:base', '');
11156
                        // Give a <file> child to the <resource> element.
11157
                        $my_file = $xmldoc->createElement('file');
11158
                        $my_file->setAttribute('href', $my_xml_file_path);
11159
                        $my_resource->appendChild($my_file);
11160
11161
                        // Get included docs.
11162
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11163
11164
                        // Dependency to other files - not yet supported.
11165
                        $i = 1;
11166
                        foreach ($inc_docs as $doc_info) {
11167
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11168
                                continue;
11169
                            }
11170
                            $my_dep = $xmldoc->createElement('resource');
11171
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11172
                            $my_dep->setAttribute('identifier', $res_id);
11173
                            $my_dep->setAttribute('type', 'webcontent');
11174
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11175
                            $my_dep_file = $xmldoc->createElement('file');
11176
                            // Check type of URL.
11177
                            if ($doc_info[1] == 'remote') {
11178
                                // Remote file. Save url as is.
11179
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11180
                                $my_dep->setAttribute('xml:base', '');
11181
                            } elseif ($doc_info[1] == 'local') {
11182
                                switch ($doc_info[2]) {
11183
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11184
                                        // Save file but as local file (retrieve from URL).
11185
                                        $abs_path = api_get_path(SYS_PATH).
11186
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11187
                                        $current_dir = dirname($abs_path);
11188
                                        $current_dir = str_replace('\\', '/', $current_dir);
11189
                                        $file_path = realpath($abs_path);
11190
                                        $file_path = str_replace('\\', '/', $file_path);
11191
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11192
                                        $my_dep->setAttribute('xml:base', '');
11193
                                        if (strstr($file_path, $main_path) !== false) {
11194
                                            // The calculated real path is really inside the chamilo root path.
11195
                                            // Reduce file path to what's under the DocumentRoot.
11196
                                            $file_path = substr($file_path, strlen($root_path));
11197
                                            $zip_files_abs[] = $file_path;
11198
                                            $link_updates[$my_file_path][] = [
11199
                                                'orig' => $doc_info[0],
11200
                                                'dest' => 'document/'.$file_path,
11201
                                            ];
11202
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11203
                                            $my_dep->setAttribute('xml:base', '');
11204
                                        } elseif (empty($file_path)) {
11205
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11206
                                            $file_path = str_replace('//', '/', $file_path);
11207
                                            if (file_exists($file_path)) {
11208
                                                $file_path = substr($file_path, strlen($current_dir));
11209
                                                // We get the relative path.
11210
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11211
                                                $link_updates[$my_file_path][] = [
11212
                                                    'orig' => $doc_info[0],
11213
                                                    'dest' => 'document/'.$file_path,
11214
                                                ];
11215
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11216
                                                $my_dep->setAttribute('xml:base', '');
11217
                                            }
11218
                                        }
11219
                                        break;
11220
                                    case 'abs':
11221
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11222
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11223
                                        $current_dir = str_replace('\\', '/', $current_dir);
11224
                                        $file_path = realpath($doc_info[0]);
11225
                                        $file_path = str_replace('\\', '/', $file_path);
11226
                                        $my_dep_file->setAttribute('href', $file_path);
11227
                                        $my_dep->setAttribute('xml:base', '');
11228
11229
                                        if (strstr($file_path, $main_path) !== false) {
11230
                                            // The calculated real path is really inside the chamilo root path.
11231
                                            // Reduce file path to what's under the DocumentRoot.
11232
                                            $file_path = substr($file_path, strlen($root_path));
11233
                                            $zip_files_abs[] = $file_path;
11234
                                            $link_updates[$my_file_path][] = [
11235
                                                'orig' => $doc_info[0],
11236
                                                'dest' => $file_path,
11237
                                            ];
11238
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11239
                                            $my_dep->setAttribute('xml:base', '');
11240
                                        } elseif (empty($file_path)) {
11241
                                            $docSysPartPath = str_replace(
11242
                                                api_get_path(REL_COURSE_PATH),
11243
                                                '',
11244
                                                $doc_info[0]
11245
                                            );
11246
11247
                                            $docSysPartPathNoCourseCode = str_replace(
11248
                                                $_course['directory'].'/',
11249
                                                '',
11250
                                                $docSysPartPath
11251
                                            );
11252
11253
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11254
                                            if (file_exists($docSysPath)) {
11255
                                                $file_path = $docSysPartPathNoCourseCode;
11256
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11257
                                                $link_updates[$my_file_path][] = [
11258
                                                    'orig' => $doc_info[0],
11259
                                                    'dest' => $file_path,
11260
                                                ];
11261
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11262
                                                $my_dep->setAttribute('xml:base', '');
11263
                                            }
11264
                                        }
11265
                                        break;
11266
                                    case 'rel':
11267
                                        // Path relative to the current document. Save xml:base as current document's
11268
                                        // directory and save file in zip as subdir.file_path
11269
                                        if (substr($doc_info[0], 0, 2) === '..') {
11270
                                            // Relative path going up.
11271
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11272
                                            $current_dir = str_replace('\\', '/', $current_dir);
11273
                                            $file_path = realpath($current_dir.$doc_info[0]);
11274
                                            $file_path = str_replace('\\', '/', $file_path);
11275
                                            if (strstr($file_path, $main_path) !== false) {
11276
                                                // The calculated real path is really inside Chamilo's root path.
11277
                                                // Reduce file path to what's under the DocumentRoot.
11278
11279
                                                $file_path = substr($file_path, strlen($root_path));
11280
                                                $file_path_dest = $file_path;
11281
11282
                                                // File path is courses/CHAMILO/document/....
11283
                                                $info_file_path = explode('/', $file_path);
11284
                                                if ($info_file_path[0] == 'courses') {
11285
                                                    // Add character "/" in file path.
11286
                                                    $file_path_dest = 'document/'.$file_path;
11287
                                                }
11288
                                                $zip_files_abs[] = $file_path;
11289
11290
                                                $link_updates[$my_file_path][] = [
11291
                                                    'orig' => $doc_info[0],
11292
                                                    'dest' => $file_path_dest,
11293
                                                ];
11294
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11295
                                                $my_dep->setAttribute('xml:base', '');
11296
                                            }
11297
                                        } else {
11298
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11299
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11300
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11301
                                        }
11302
                                        break;
11303
                                    default:
11304
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11305
                                        $my_dep->setAttribute('xml:base', '');
11306
                                        break;
11307
                                }
11308
                            }
11309
                            $my_dep->appendChild($my_dep_file);
11310
                            $resources->appendChild($my_dep);
11311
                            $dependency = $xmldoc->createElement('dependency');
11312
                            $dependency->setAttribute('identifierref', $res_id);
11313
                            $my_resource->appendChild($dependency);
11314
                            $i++;
11315
                        }
11316
                        $resources->appendChild($my_resource);
11317
                        $zip_files[] = $my_file_path;
11318
                        break;
11319
                    default:
11320
                        // Get the path of the file(s) from the course directory root
11321
                        $my_file_path = 'non_exportable.html';
11322
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11323
                        $my_xml_file_path = $my_file_path;
11324
                        $my_sub_dir = dirname($my_file_path);
11325
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11326
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11327
                        $my_xml_sub_dir = $my_sub_dir;
11328
                        // Give a <resource> child to the <resources> element.
11329
                        $my_resource = $xmldoc->createElement('resource');
11330
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11331
                        $my_resource->setAttribute('type', 'webcontent');
11332
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11333
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11334
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11335
                        // xml:base is the base directory to find the files declared in this resource.
11336
                        $my_resource->setAttribute('xml:base', '');
11337
                        // Give a <file> child to the <resource> element.
11338
                        $my_file = $xmldoc->createElement('file');
11339
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11340
                        $my_resource->appendChild($my_file);
11341
                        $resources->appendChild($my_resource);
11342
                        break;
11343
                }
11344
            }
11345
        }
11346
        $organizations->appendChild($organization);
11347
        $root->appendChild($organizations);
11348
        $root->appendChild($resources);
11349
        $xmldoc->appendChild($root);
11350
11351
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11352
11353
        // then add the file to the zip, then destroy the file (this is done automatically).
11354
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11355
        foreach ($zip_files as $file_path) {
11356
            if (empty($file_path)) {
11357
                continue;
11358
            }
11359
11360
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11361
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11362
11363
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11364
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11365
            }
11366
11367
            $this->create_path($dest_file);
11368
            @copy($filePath, $dest_file);
11369
11370
            // Check if the file needs a link update.
11371
            if (in_array($file_path, array_keys($link_updates))) {
11372
                $string = file_get_contents($dest_file);
11373
                unlink($dest_file);
11374
                foreach ($link_updates[$file_path] as $old_new) {
11375
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11376
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11377
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11378
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11379
                    if (substr($old_new['dest'], -3) === 'flv' &&
11380
                        substr($old_new['dest'], 0, 5) === 'main/'
11381
                    ) {
11382
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11383
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11384
                        substr($old_new['dest'], 0, 6) === 'video/'
11385
                    ) {
11386
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11387
                    }
11388
11389
                    // Fix to avoid problems with default_course_document
11390
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11391
                        $newDestination = $old_new['dest'];
11392
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11393
                            $newDestination = $old_new['replace'];
11394
                        }
11395
                    } else {
11396
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11397
                    }
11398
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11399
11400
                    // Add files inside the HTMLs
11401
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11402
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11403
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11404
                        copy(
11405
                            $sys_course_path.$new_path,
11406
                            $destinationFile
11407
                        );
11408
                    }
11409
                }
11410
                file_put_contents($dest_file, $string);
11411
            }
11412
11413
            if (file_exists($filePath) && $copyAll) {
11414
                $extension = $this->get_extension($filePath);
11415
                if (in_array($extension, ['html', 'html'])) {
11416
                    $containerOrigin = dirname($filePath);
11417
                    $containerDestination = dirname($dest_file);
11418
11419
                    $finder = new Finder();
11420
                    $finder->files()->in($containerOrigin)
11421
                        ->notName('*_DELETED_*')
11422
                        ->exclude('share_folder')
11423
                        ->exclude('chat_files')
11424
                        ->exclude('certificates')
11425
                    ;
11426
11427
                    if (is_dir($containerOrigin) &&
11428
                        is_dir($containerDestination)
11429
                    ) {
11430
                        $fs = new Filesystem();
11431
                        $fs->mirror(
11432
                            $containerOrigin,
11433
                            $containerDestination,
11434
                            $finder
11435
                        );
11436
                    }
11437
                }
11438
            }
11439
        }
11440
11441
        foreach ($zip_files_abs as $file_path) {
11442
            if (empty($file_path)) {
11443
                continue;
11444
            }
11445
11446
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11447
                continue;
11448
            }
11449
11450
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11451
            if (strstr($file_path, 'upload/users') !== false) {
11452
                $pos = strpos($file_path, 'my_files/');
11453
                if ($pos !== false) {
11454
                    $onlyDirectory = str_replace(
11455
                        'upload/users/',
11456
                        '',
11457
                        substr($file_path, $pos, strlen($file_path))
11458
                    );
11459
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11460
                }
11461
            }
11462
11463
            if (strstr($file_path, 'default_course_document/') !== false) {
11464
                $replace = str_replace('/main', '', $file_path);
11465
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11466
            }
11467
11468
            if (empty($dest_file)) {
11469
                continue;
11470
            }
11471
11472
            $this->create_path($dest_file);
11473
            copy($main_path.$file_path, $dest_file);
11474
            // Check if the file needs a link update.
11475
            if (in_array($file_path, array_keys($link_updates))) {
11476
                $string = file_get_contents($dest_file);
11477
                unlink($dest_file);
11478
                foreach ($link_updates[$file_path] as $old_new) {
11479
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11480
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11481
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11482
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11483
                    if (substr($old_new['dest'], -3) == 'flv' &&
11484
                        substr($old_new['dest'], 0, 5) == 'main/'
11485
                    ) {
11486
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11487
                    }
11488
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11489
                }
11490
                file_put_contents($dest_file, $string);
11491
            }
11492
        }
11493
11494
        if (is_array($links_to_create)) {
11495
            foreach ($links_to_create as $file => $link) {
11496
                $content = '<!DOCTYPE html><head>
11497
                            <meta charset="'.api_get_language_isocode().'" />
11498
                            <title>'.$link['title'].'</title>
11499
                            </head>
11500
                            <body dir="'.api_get_text_direction().'">
11501
                            <div style="text-align:center">
11502
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11503
                            </body>
11504
                            </html>';
11505
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11506
            }
11507
        }
11508
11509
        // Add non exportable message explanation.
11510
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11511
        $file_content = '<!DOCTYPE html><head>
11512
                        <meta charset="'.api_get_language_isocode().'" />
11513
                        <title>'.$lang_not_exportable.'</title>
11514
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11515
                        </head>
11516
                        <body dir="'.api_get_text_direction().'">';
11517
        $file_content .=
11518
            <<<EOD
11519
                    <style>
11520
            .error-message {
11521
                font-family: arial, verdana, helvetica, sans-serif;
11522
                border-width: 1px;
11523
                border-style: solid;
11524
                left: 50%;
11525
                margin: 10px auto;
11526
                min-height: 30px;
11527
                padding: 5px;
11528
                right: 50%;
11529
                width: 500px;
11530
                background-color: #FFD1D1;
11531
                border-color: #FF0000;
11532
                color: #000;
11533
            }
11534
        </style>
11535
    <body>
11536
        <div class="error-message">
11537
            $lang_not_exportable
11538
        </div>
11539
    </body>
11540
</html>
11541
EOD;
11542
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11543
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11544
        }
11545
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11546
11547
        // Add the extra files that go along with a SCORM package.
11548
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11549
11550
        $fs = new Filesystem();
11551
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11552
11553
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11554
        $manifest = @$xmldoc->saveXML();
11555
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11556
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11557
        $zip_folder->add(
11558
            $archivePath.'/'.$temp_dir_short,
11559
            PCLZIP_OPT_REMOVE_PATH,
11560
            $archivePath.'/'.$temp_dir_short.'/'
11561
        );
11562
11563
        // Clean possible temporary files.
11564
        foreach ($files_cleanup as $file) {
11565
            $res = unlink($file);
11566
            if ($res === false) {
11567
                error_log(
11568
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11569
                    0
11570
                );
11571
            }
11572
        }
11573
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11574
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11575
    }
11576
11577
    /**
11578
     * @param int $lp_id
11579
     *
11580
     * @return bool
11581
     */
11582
    public function scorm_export_to_pdf($lp_id)
11583
    {
11584
        $lp_id = (int) $lp_id;
11585
        $files_to_export = [];
11586
11587
        $sessionId = api_get_session_id();
11588
        $course_data = api_get_course_info($this->cc);
11589
11590
        if (!empty($course_data)) {
11591
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11592
            $list = self::get_flat_ordered_items_list($lp_id);
11593
            if (!empty($list)) {
11594
                foreach ($list as $item_id) {
11595
                    $item = $this->items[$item_id];
11596
                    switch ($item->type) {
11597
                        case 'document':
11598
                            // Getting documents from a LP with chamilo documents
11599
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11600
                            // Try loading document from the base course.
11601
                            if (empty($file_data) && !empty($sessionId)) {
11602
                                $file_data = DocumentManager::get_document_data_by_id(
11603
                                    $item->path,
11604
                                    $this->cc,
11605
                                    false,
11606
                                    0
11607
                                );
11608
                            }
11609
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11610
                            if (file_exists($file_path)) {
11611
                                $files_to_export[] = [
11612
                                    'title' => $item->get_title(),
11613
                                    'path' => $file_path,
11614
                                ];
11615
                            }
11616
                            break;
11617
                        case 'asset': //commes from a scorm package generated by chamilo
11618
                        case 'sco':
11619
                            $file_path = $scorm_path.'/'.$item->path;
11620
                            if (file_exists($file_path)) {
11621
                                $files_to_export[] = [
11622
                                    'title' => $item->get_title(),
11623
                                    'path' => $file_path,
11624
                                ];
11625
                            }
11626
                            break;
11627
                        case 'dir':
11628
                            $files_to_export[] = [
11629
                                'title' => $item->get_title(),
11630
                                'path' => null,
11631
                            ];
11632
                            break;
11633
                    }
11634
                }
11635
            }
11636
11637
            $pdf = new PDF();
11638
            $result = $pdf->html_to_pdf(
11639
                $files_to_export,
11640
                $this->name,
11641
                $this->cc,
11642
                true,
11643
                true,
11644
                true,
11645
                $this->get_name()
11646
            );
11647
11648
            return $result;
11649
        }
11650
11651
        return false;
11652
    }
11653
11654
    /**
11655
     * Temp function to be moved in main_api or the best place around for this.
11656
     * Creates a file path if it doesn't exist.
11657
     *
11658
     * @param string $path
11659
     */
11660
    public function create_path($path)
11661
    {
11662
        $path_bits = explode('/', dirname($path));
11663
11664
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11665
        $path_built = IS_WINDOWS_OS ? '' : '/';
11666
        foreach ($path_bits as $bit) {
11667
            if (!empty($bit)) {
11668
                $new_path = $path_built.$bit;
11669
                if (is_dir($new_path)) {
11670
                    $path_built = $new_path.'/';
11671
                } else {
11672
                    mkdir($new_path, api_get_permissions_for_new_directories());
11673
                    $path_built = $new_path.'/';
11674
                }
11675
            }
11676
        }
11677
    }
11678
11679
    /**
11680
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11681
     *
11682
     * @return bool The results of the unlink function, or false if there was no image to start with
11683
     */
11684
    public function delete_lp_image()
11685
    {
11686
        $img = $this->get_preview_image();
11687
        if ($img != '') {
11688
            $del_file = $this->get_preview_image_path(null, 'sys');
11689
            if (isset($del_file) && file_exists($del_file)) {
11690
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11691
                if (file_exists($del_file_2)) {
11692
                    unlink($del_file_2);
11693
                }
11694
                $this->set_preview_image('');
11695
11696
                return @unlink($del_file);
11697
            }
11698
        }
11699
11700
        return false;
11701
    }
11702
11703
    /**
11704
     * Uploads an author image to the upload/learning_path/images directory.
11705
     *
11706
     * @param array    The image array, coming from the $_FILES superglobal
11707
     *
11708
     * @return bool True on success, false on error
11709
     */
11710
    public function upload_image($image_array)
11711
    {
11712
        if (!empty($image_array['name'])) {
11713
            $upload_ok = process_uploaded_file($image_array);
11714
            $has_attachment = true;
11715
        }
11716
11717
        if ($upload_ok && $has_attachment) {
11718
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11719
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11720
            $updir = $sys_course_path.$courseDir;
11721
            // Try to add an extension to the file if it hasn't one.
11722
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11723
11724
            if (filter_extension($new_file_name)) {
11725
                $file_extension = explode('.', $image_array['name']);
11726
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11727
                $filename = uniqid('');
11728
                $new_file_name = $filename.'.'.$file_extension;
11729
                $new_path = $updir.'/'.$new_file_name;
11730
11731
                // Resize the image.
11732
                $temp = new Image($image_array['tmp_name']);
11733
                $temp->resize(104);
11734
                $result = $temp->send_image($new_path);
11735
11736
                // Storing the image filename.
11737
                if ($result) {
11738
                    $this->set_preview_image($new_file_name);
11739
11740
                    //Resize to 64px to use on course homepage
11741
                    $temp->resize(64);
11742
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11743
11744
                    return true;
11745
                }
11746
            }
11747
        }
11748
11749
        return false;
11750
    }
11751
11752
    /**
11753
     * @param int    $lp_id
11754
     * @param string $status
11755
     */
11756
    public function set_autolaunch($lp_id, $status)
11757
    {
11758
        $course_id = api_get_course_int_id();
11759
        $lp_id = (int) $lp_id;
11760
        $status = (int) $status;
11761
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11762
11763
        // Setting everything to autolaunch = 0
11764
        $attributes['autolaunch'] = 0;
11765
        $where = [
11766
            'session_id = ? AND c_id = ? ' => [
11767
                api_get_session_id(),
11768
                $course_id,
11769
            ],
11770
        ];
11771
        Database::update($lp_table, $attributes, $where);
11772
        if ($status == 1) {
11773
            //Setting my lp_id to autolaunch = 1
11774
            $attributes['autolaunch'] = 1;
11775
            $where = [
11776
                'iid = ? AND session_id = ? AND c_id = ?' => [
11777
                    $lp_id,
11778
                    api_get_session_id(),
11779
                    $course_id,
11780
                ],
11781
            ];
11782
            Database::update($lp_table, $attributes, $where);
11783
        }
11784
    }
11785
11786
    /**
11787
     * Gets previous_item_id for the next element of the lp_item table.
11788
     *
11789
     * @author Isaac flores paz
11790
     *
11791
     * @return int Previous item ID
11792
     */
11793
    public function select_previous_item_id()
11794
    {
11795
        $course_id = api_get_course_int_id();
11796
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11797
11798
        // Get the max order of the items
11799
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11800
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11801
        $rs_max_order = Database::query($sql);
11802
        $row_max_order = Database::fetch_object($rs_max_order);
11803
        $max_order = $row_max_order->display_order;
11804
        // Get the previous item ID
11805
        $sql = "SELECT iid as previous FROM $table_lp_item
11806
                WHERE
11807
                    c_id = $course_id AND
11808
                    lp_id = ".$this->lp_id." AND
11809
                    display_order = '$max_order' ";
11810
        $rs_max = Database::query($sql);
11811
        $row_max = Database::fetch_object($rs_max);
11812
11813
        // Return the previous item ID
11814
        return $row_max->previous;
11815
    }
11816
11817
    /**
11818
     * Copies an LP.
11819
     */
11820
    public function copy()
11821
    {
11822
        // Course builder
11823
        $cb = new CourseBuilder();
11824
11825
        //Setting tools that will be copied
11826
        $cb->set_tools_to_build(['learnpaths']);
11827
11828
        //Setting elements that will be copied
11829
        $cb->set_tools_specific_id_list(
11830
            ['learnpaths' => [$this->lp_id]]
11831
        );
11832
11833
        $course = $cb->build();
11834
11835
        //Course restorer
11836
        $course_restorer = new CourseRestorer($course);
11837
        $course_restorer->set_add_text_in_items(true);
11838
        $course_restorer->set_tool_copy_settings(
11839
            ['learnpaths' => ['reset_dates' => true]]
11840
        );
11841
        $course_restorer->restore(
11842
            api_get_course_id(),
11843
            api_get_session_id(),
11844
            false,
11845
            false
11846
        );
11847
    }
11848
11849
    /**
11850
     * Verify document size.
11851
     *
11852
     * @param string $s
11853
     *
11854
     * @return bool
11855
     */
11856
    public static function verify_document_size($s)
11857
    {
11858
        $post_max = ini_get('post_max_size');
11859
        if (substr($post_max, -1, 1) == 'M') {
11860
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11861
        } elseif (substr($post_max, -1, 1) == 'G') {
11862
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11863
        }
11864
        $upl_max = ini_get('upload_max_filesize');
11865
        if (substr($upl_max, -1, 1) == 'M') {
11866
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11867
        } elseif (substr($upl_max, -1, 1) == 'G') {
11868
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11869
        }
11870
        $documents_total_space = DocumentManager::documents_total_space();
11871
        $course_max_space = DocumentManager::get_course_quota();
11872
        $total_size = filesize($s) + $documents_total_space;
11873
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11874
            return true;
11875
        }
11876
11877
        return false;
11878
    }
11879
11880
    /**
11881
     * Clear LP prerequisites.
11882
     */
11883
    public function clear_prerequisites()
11884
    {
11885
        $course_id = $this->get_course_int_id();
11886
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11887
        $lp_id = $this->get_id();
11888
        // Cleaning prerequisites
11889
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11890
                WHERE c_id = $course_id AND lp_id = $lp_id";
11891
        Database::query($sql);
11892
11893
        // Cleaning mastery score for exercises
11894
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11895
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11896
        Database::query($sql);
11897
    }
11898
11899
    public function set_previous_step_as_prerequisite_for_all_items()
11900
    {
11901
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11902
        $course_id = $this->get_course_int_id();
11903
        $lp_id = $this->get_id();
11904
11905
        if (!empty($this->items)) {
11906
            $previous_item_id = null;
11907
            $previous_item_max = 0;
11908
            $previous_item_type = null;
11909
            $last_item_not_dir = null;
11910
            $last_item_not_dir_type = null;
11911
            $last_item_not_dir_max = null;
11912
11913
            foreach ($this->ordered_items as $itemId) {
11914
                $item = $this->getItem($itemId);
11915
                // if there was a previous item... (otherwise jump to set it)
11916
                if (!empty($previous_item_id)) {
11917
                    $current_item_id = $item->get_id(); //save current id
11918
                    if ($item->get_type() != 'dir') {
11919
                        // Current item is not a folder, so it qualifies to get a prerequisites
11920
                        if ($last_item_not_dir_type == 'quiz') {
11921
                            // if previous is quiz, mark its max score as default score to be achieved
11922
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11923
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11924
                            Database::query($sql);
11925
                        }
11926
                        // now simply update the prerequisite to set it to the last non-chapter item
11927
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11928
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11929
                        Database::query($sql);
11930
                        // record item as 'non-chapter' reference
11931
                        $last_item_not_dir = $item->get_id();
11932
                        $last_item_not_dir_type = $item->get_type();
11933
                        $last_item_not_dir_max = $item->get_max();
11934
                    }
11935
                } else {
11936
                    if ($item->get_type() != 'dir') {
11937
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11938
                        $last_item_not_dir = $item->get_id();
11939
                        $last_item_not_dir_type = $item->get_type();
11940
                        $last_item_not_dir_max = $item->get_max();
11941
                    }
11942
                }
11943
                // Saving the item as "previous item" for the next loop
11944
                $previous_item_id = $item->get_id();
11945
                $previous_item_max = $item->get_max();
11946
                $previous_item_type = $item->get_type();
11947
            }
11948
        }
11949
    }
11950
11951
    /**
11952
     * @param array $params
11953
     *
11954
     * @throws \Doctrine\ORM\OptimisticLockException
11955
     *
11956
     * @return int
11957
     */
11958
    public static function createCategory($params)
11959
    {
11960
        $em = Database::getManager();
11961
        $item = new CLpCategory();
11962
        $item->setName($params['name']);
11963
        $item->setCId($params['c_id']);
11964
        $em->persist($item);
11965
        $em->flush();
11966
11967
        api_item_property_update(
11968
            api_get_course_info(),
11969
            TOOL_LEARNPATH_CATEGORY,
11970
            $item->getId(),
11971
            'visible',
11972
            api_get_user_id()
11973
        );
11974
11975
        return $item->getId();
11976
    }
11977
11978
    /**
11979
     * @param array $params
11980
     *
11981
     * @throws \Doctrine\ORM\ORMException
11982
     * @throws \Doctrine\ORM\OptimisticLockException
11983
     * @throws \Doctrine\ORM\TransactionRequiredException
11984
     */
11985
    public static function updateCategory($params)
11986
    {
11987
        $em = Database::getManager();
11988
        /** @var CLpCategory $item */
11989
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11990
        if ($item) {
11991
            $item->setName($params['name']);
11992
            $em->merge($item);
11993
            $em->flush();
11994
        }
11995
    }
11996
11997
    /**
11998
     * @param int $id
11999
     *
12000
     * @throws \Doctrine\ORM\ORMException
12001
     * @throws \Doctrine\ORM\OptimisticLockException
12002
     * @throws \Doctrine\ORM\TransactionRequiredException
12003
     */
12004
    public static function moveUpCategory($id)
12005
    {
12006
        $id = (int) $id;
12007
        $em = Database::getManager();
12008
        /** @var CLpCategory $item */
12009
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12010
        if ($item) {
12011
            $position = $item->getPosition() - 1;
12012
            $item->setPosition($position);
12013
            $em->persist($item);
12014
            $em->flush();
12015
        }
12016
    }
12017
12018
    /**
12019
     * @param int $id
12020
     *
12021
     * @throws \Doctrine\ORM\ORMException
12022
     * @throws \Doctrine\ORM\OptimisticLockException
12023
     * @throws \Doctrine\ORM\TransactionRequiredException
12024
     */
12025
    public static function moveDownCategory($id)
12026
    {
12027
        $id = (int) $id;
12028
        $em = Database::getManager();
12029
        /** @var CLpCategory $item */
12030
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12031
        if ($item) {
12032
            $position = $item->getPosition() + 1;
12033
            $item->setPosition($position);
12034
            $em->persist($item);
12035
            $em->flush();
12036
        }
12037
    }
12038
12039
    public static function getLpList($courseId)
12040
    {
12041
        $table = Database::get_course_table(TABLE_LP_MAIN);
12042
        $courseId = (int) $courseId;
12043
12044
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
12045
        $result = Database::query($sql);
12046
12047
        return Database::store_result($result, 'ASSOC');
12048
    }
12049
12050
    /**
12051
     * @param int $courseId
12052
     *
12053
     * @throws \Doctrine\ORM\Query\QueryException
12054
     *
12055
     * @return int|mixed
12056
     */
12057
    public static function getCountCategories($courseId)
12058
    {
12059
        if (empty($courseId)) {
12060
            return 0;
12061
        }
12062
        $em = Database::getManager();
12063
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12064
        $query->setParameter('id', $courseId);
12065
12066
        return $query->getSingleScalarResult();
12067
    }
12068
12069
    /**
12070
     * @param int $courseId
12071
     *
12072
     * @return mixed
12073
     */
12074
    public static function getCategories($courseId)
12075
    {
12076
        $em = Database::getManager();
12077
12078
        // Using doctrine extensions
12079
        /** @var SortableRepository $repo */
12080
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12081
        $items = $repo
12082
            ->getBySortableGroupsQuery(['cId' => $courseId])
12083
            ->getResult();
12084
12085
        return $items;
12086
    }
12087
12088
    /**
12089
     * @param int $id
12090
     *
12091
     * @throws \Doctrine\ORM\ORMException
12092
     * @throws \Doctrine\ORM\OptimisticLockException
12093
     * @throws \Doctrine\ORM\TransactionRequiredException
12094
     *
12095
     * @return CLpCategory
12096
     */
12097
    public static function getCategory($id)
12098
    {
12099
        $id = (int) $id;
12100
        $em = Database::getManager();
12101
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12102
12103
        return $item;
12104
    }
12105
12106
    /**
12107
     * @param int $courseId
12108
     *
12109
     * @return array
12110
     */
12111
    public static function getCategoryByCourse($courseId)
12112
    {
12113
        $em = Database::getManager();
12114
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12115
            ['cId' => $courseId]
12116
        );
12117
12118
        return $items;
12119
    }
12120
12121
    /**
12122
     * @param int $id
12123
     *
12124
     * @throws \Doctrine\ORM\ORMException
12125
     * @throws \Doctrine\ORM\OptimisticLockException
12126
     * @throws \Doctrine\ORM\TransactionRequiredException
12127
     *
12128
     * @return mixed
12129
     */
12130
    public static function deleteCategory($id)
12131
    {
12132
        $em = Database::getManager();
12133
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12134
        if ($item) {
12135
            $courseId = $item->getCId();
12136
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12137
            $query->setParameter('id', $courseId);
12138
            $query->setParameter('catId', $item->getId());
12139
            $lps = $query->getResult();
12140
12141
            // Setting category = 0.
12142
            if ($lps) {
12143
                foreach ($lps as $lpItem) {
12144
                    $lpItem->setCategoryId(0);
12145
                }
12146
            }
12147
12148
            // Removing category.
12149
            $em->remove($item);
12150
            $em->flush();
12151
12152
            $courseInfo = api_get_course_info_by_id($courseId);
12153
            $sessionId = api_get_session_id();
12154
12155
            // Delete link tool
12156
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12157
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12158
            // Delete tools
12159
            $sql = "DELETE FROM $tbl_tool
12160
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12161
            Database::query($sql);
12162
12163
            return true;
12164
        }
12165
12166
        return false;
12167
    }
12168
12169
    /**
12170
     * @param int  $courseId
12171
     * @param bool $addSelectOption
12172
     *
12173
     * @return mixed
12174
     */
12175
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12176
    {
12177
        $items = self::getCategoryByCourse($courseId);
12178
        $cats = [];
12179
        if ($addSelectOption) {
12180
            $cats = [get_lang('SelectACategory')];
12181
        }
12182
12183
        if (!empty($items)) {
12184
            foreach ($items as $cat) {
12185
                $cats[$cat->getId()] = $cat->getName();
12186
            }
12187
        }
12188
12189
        return $cats;
12190
    }
12191
12192
    /**
12193
     * @param string $courseCode
12194
     * @param int    $lpId
12195
     * @param int    $user_id
12196
     *
12197
     * @return learnpath
12198
     */
12199
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12200
    {
12201
        $debug = 0;
12202
        $learnPath = null;
12203
        $lpObject = Session::read('lpobject');
12204
        if ($lpObject !== null) {
12205
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12206
            if ($debug) {
12207
                error_log('getLpFromSession: unserialize');
12208
                error_log('------getLpFromSession------');
12209
                error_log('------unserialize------');
12210
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12211
                error_log("api_get_sessionid: ".api_get_session_id());
12212
            }
12213
        }
12214
12215
        if (!is_object($learnPath)) {
12216
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12217
            if ($debug) {
12218
                error_log('------getLpFromSession------');
12219
                error_log('getLpFromSession: create new learnpath');
12220
                error_log("create new LP with $courseCode - $lpId - $user_id");
12221
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12222
                error_log("api_get_sessionid: ".api_get_session_id());
12223
            }
12224
        }
12225
12226
        return $learnPath;
12227
    }
12228
12229
    /**
12230
     * @param int $itemId
12231
     *
12232
     * @return learnpathItem|false
12233
     */
12234
    public function getItem($itemId)
12235
    {
12236
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12237
            return $this->items[$itemId];
12238
        }
12239
12240
        return false;
12241
    }
12242
12243
    /**
12244
     * @return int
12245
     */
12246
    public function getCurrentAttempt()
12247
    {
12248
        $attempt = $this->getItem($this->get_current_item_id());
12249
        if ($attempt) {
12250
            $attemptId = $attempt->get_attempt_id();
12251
12252
            return $attemptId;
12253
        }
12254
12255
        return 0;
12256
    }
12257
12258
    /**
12259
     * @return int
12260
     */
12261
    public function getCategoryId()
12262
    {
12263
        return (int) $this->categoryId;
12264
    }
12265
12266
    /**
12267
     * @param int $categoryId
12268
     *
12269
     * @return bool
12270
     */
12271
    public function setCategoryId($categoryId)
12272
    {
12273
        $this->categoryId = (int) $categoryId;
12274
        $table = Database::get_course_table(TABLE_LP_MAIN);
12275
        $lp_id = $this->get_id();
12276
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12277
                WHERE iid = $lp_id";
12278
        Database::query($sql);
12279
12280
        return true;
12281
    }
12282
12283
    /**
12284
     * Get whether this is a learning path with the possibility to subscribe
12285
     * users or not.
12286
     *
12287
     * @return int
12288
     */
12289
    public function getSubscribeUsers()
12290
    {
12291
        return $this->subscribeUsers;
12292
    }
12293
12294
    /**
12295
     * Set whether this is a learning path with the possibility to subscribe
12296
     * users or not.
12297
     *
12298
     * @param int $value (0 = false, 1 = true)
12299
     *
12300
     * @return bool
12301
     */
12302
    public function setSubscribeUsers($value)
12303
    {
12304
        $this->subscribeUsers = (int) $value;
12305
        $table = Database::get_course_table(TABLE_LP_MAIN);
12306
        $lp_id = $this->get_id();
12307
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12308
                WHERE iid = $lp_id";
12309
        Database::query($sql);
12310
12311
        return true;
12312
    }
12313
12314
    /**
12315
     * Calculate the count of stars for a user in this LP
12316
     * This calculation is based on the following rules:
12317
     * - the student gets one star when he gets to 50% of the learning path
12318
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12319
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12320
     * - the student gets the final star when the score for the *last* test is >= 80%.
12321
     *
12322
     * @param int $sessionId Optional. The session ID
12323
     *
12324
     * @return int The count of stars
12325
     */
12326
    public function getCalculateStars($sessionId = 0)
12327
    {
12328
        $stars = 0;
12329
        $progress = self::getProgress(
12330
            $this->lp_id,
12331
            $this->user_id,
12332
            $this->course_int_id,
12333
            $sessionId
12334
        );
12335
12336
        if ($progress >= 50) {
12337
            $stars++;
12338
        }
12339
12340
        // Calculate stars chapters evaluation
12341
        $exercisesItems = $this->getExercisesItems();
12342
12343
        if (!empty($exercisesItems)) {
12344
            $totalResult = 0;
12345
12346
            foreach ($exercisesItems as $exerciseItem) {
12347
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12348
                    $this->user_id,
12349
                    $exerciseItem->path,
12350
                    $this->course_int_id,
12351
                    $sessionId,
12352
                    $this->lp_id,
12353
                    $exerciseItem->db_id
12354
                );
12355
12356
                $exerciseResultInfo = end($exerciseResultInfo);
12357
12358
                if (!$exerciseResultInfo) {
12359
                    continue;
12360
                }
12361
12362
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12363
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12364
                } else {
12365
                    $exerciseResult = 0;
12366
                }
12367
                $totalResult += $exerciseResult;
12368
            }
12369
12370
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12371
12372
            if ($totalExerciseAverage >= 50) {
12373
                $stars++;
12374
            }
12375
12376
            if ($totalExerciseAverage >= 80) {
12377
                $stars++;
12378
            }
12379
        }
12380
12381
        // Calculate star for final evaluation
12382
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12383
12384
        if (!empty($finalEvaluationItem)) {
12385
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12386
                $this->user_id,
12387
                $finalEvaluationItem->path,
12388
                $this->course_int_id,
12389
                $sessionId,
12390
                $this->lp_id,
12391
                $finalEvaluationItem->db_id
12392
            );
12393
12394
            $evaluationResultInfo = end($evaluationResultInfo);
12395
12396
            if ($evaluationResultInfo) {
12397
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12398
12399
                if ($evaluationResult >= 80) {
12400
                    $stars++;
12401
                }
12402
            }
12403
        }
12404
12405
        return $stars;
12406
    }
12407
12408
    /**
12409
     * Get the items of exercise type.
12410
     *
12411
     * @return array The items. Otherwise return false
12412
     */
12413
    public function getExercisesItems()
12414
    {
12415
        $exercises = [];
12416
        foreach ($this->items as $item) {
12417
            if ($item->type != 'quiz') {
12418
                continue;
12419
            }
12420
            $exercises[] = $item;
12421
        }
12422
12423
        array_pop($exercises);
12424
12425
        return $exercises;
12426
    }
12427
12428
    /**
12429
     * Get the item of exercise type (evaluation type).
12430
     *
12431
     * @return array The final evaluation. Otherwise return false
12432
     */
12433
    public function getFinalEvaluationItem()
12434
    {
12435
        $exercises = [];
12436
        foreach ($this->items as $item) {
12437
            if ($item->type != 'quiz') {
12438
                continue;
12439
            }
12440
12441
            $exercises[] = $item;
12442
        }
12443
12444
        return array_pop($exercises);
12445
    }
12446
12447
    /**
12448
     * Calculate the total points achieved for the current user in this learning path.
12449
     *
12450
     * @param int $sessionId Optional. The session Id
12451
     *
12452
     * @return int
12453
     */
12454
    public function getCalculateScore($sessionId = 0)
12455
    {
12456
        // Calculate stars chapters evaluation
12457
        $exercisesItems = $this->getExercisesItems();
12458
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12459
        $totalExercisesResult = 0;
12460
        $totalEvaluationResult = 0;
12461
12462
        if ($exercisesItems !== false) {
12463
            foreach ($exercisesItems as $exerciseItem) {
12464
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12465
                    $this->user_id,
12466
                    $exerciseItem->path,
12467
                    $this->course_int_id,
12468
                    $sessionId,
12469
                    $this->lp_id,
12470
                    $exerciseItem->db_id
12471
                );
12472
12473
                $exerciseResultInfo = end($exerciseResultInfo);
12474
12475
                if (!$exerciseResultInfo) {
12476
                    continue;
12477
                }
12478
12479
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12480
            }
12481
        }
12482
12483
        if (!empty($finalEvaluationItem)) {
12484
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12485
                $this->user_id,
12486
                $finalEvaluationItem->path,
12487
                $this->course_int_id,
12488
                $sessionId,
12489
                $this->lp_id,
12490
                $finalEvaluationItem->db_id
12491
            );
12492
12493
            $evaluationResultInfo = end($evaluationResultInfo);
12494
12495
            if ($evaluationResultInfo) {
12496
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12497
            }
12498
        }
12499
12500
        return $totalExercisesResult + $totalEvaluationResult;
12501
    }
12502
12503
    /**
12504
     * Check if URL is not allowed to be show in a iframe.
12505
     *
12506
     * @param string $src
12507
     *
12508
     * @return string
12509
     */
12510
    public function fixBlockedLinks($src)
12511
    {
12512
        $urlInfo = parse_url($src);
12513
12514
        $platformProtocol = 'https';
12515
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12516
            $platformProtocol = 'http';
12517
        }
12518
12519
        $protocolFixApplied = false;
12520
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12521
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12522
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12523
12524
        if ($platformProtocol != $scheme) {
12525
            Session::write('x_frame_source', $src);
12526
            $src = 'blank.php?error=x_frames_options';
12527
            $protocolFixApplied = true;
12528
        }
12529
12530
        if ($protocolFixApplied == false) {
12531
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12532
                // Check X-Frame-Options
12533
                $ch = curl_init();
12534
                $options = [
12535
                    CURLOPT_URL => $src,
12536
                    CURLOPT_RETURNTRANSFER => true,
12537
                    CURLOPT_HEADER => true,
12538
                    CURLOPT_FOLLOWLOCATION => true,
12539
                    CURLOPT_ENCODING => "",
12540
                    CURLOPT_AUTOREFERER => true,
12541
                    CURLOPT_CONNECTTIMEOUT => 120,
12542
                    CURLOPT_TIMEOUT => 120,
12543
                    CURLOPT_MAXREDIRS => 10,
12544
                ];
12545
12546
                $proxySettings = api_get_configuration_value('proxy_settings');
12547
                if (!empty($proxySettings) &&
12548
                    isset($proxySettings['curl_setopt_array'])
12549
                ) {
12550
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12551
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12552
                }
12553
12554
                curl_setopt_array($ch, $options);
12555
                $response = curl_exec($ch);
12556
                $httpCode = curl_getinfo($ch);
12557
                $headers = substr($response, 0, $httpCode['header_size']);
12558
12559
                $error = false;
12560
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12561
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12562
                ) {
12563
                    $error = true;
12564
                }
12565
12566
                if ($error) {
12567
                    Session::write('x_frame_source', $src);
12568
                    $src = 'blank.php?error=x_frames_options';
12569
                }
12570
            }
12571
        }
12572
12573
        return $src;
12574
    }
12575
12576
    /**
12577
     * Check if this LP has a created forum in the basis course.
12578
     *
12579
     * @return bool
12580
     */
12581
    public function lpHasForum()
12582
    {
12583
        $forumTable = Database::get_course_table(TABLE_FORUM);
12584
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12585
12586
        $fakeFrom = "
12587
            $forumTable f
12588
            INNER JOIN $itemProperty ip
12589
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12590
        ";
12591
12592
        $resultData = Database::select(
12593
            'COUNT(f.iid) AS qty',
12594
            $fakeFrom,
12595
            [
12596
                'where' => [
12597
                    'ip.visibility != ? AND ' => 2,
12598
                    'ip.tool = ? AND ' => TOOL_FORUM,
12599
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12600
                    'f.lp_id = ?' => intval($this->lp_id),
12601
                ],
12602
            ],
12603
            'first'
12604
        );
12605
12606
        return $resultData['qty'] > 0;
12607
    }
12608
12609
    /**
12610
     * Get the forum for this learning path.
12611
     *
12612
     * @param int $sessionId
12613
     *
12614
     * @return bool
12615
     */
12616
    public function getForum($sessionId = 0)
12617
    {
12618
        $forumTable = Database::get_course_table(TABLE_FORUM);
12619
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12620
12621
        $fakeFrom = "$forumTable f
12622
            INNER JOIN $itemProperty ip ";
12623
12624
        if ($this->lp_session_id == 0) {
12625
            $fakeFrom .= "
12626
                ON (
12627
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12628
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12629
                    )
12630
                )
12631
            ";
12632
        } else {
12633
            $fakeFrom .= "
12634
                ON (
12635
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12636
                )
12637
            ";
12638
        }
12639
12640
        $resultData = Database::select(
12641
            'f.*',
12642
            $fakeFrom,
12643
            [
12644
                'where' => [
12645
                    'ip.visibility != ? AND ' => 2,
12646
                    'ip.tool = ? AND ' => TOOL_FORUM,
12647
                    'f.session_id = ? AND ' => $sessionId,
12648
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12649
                    'f.lp_id = ?' => intval($this->lp_id),
12650
                ],
12651
            ],
12652
            'first'
12653
        );
12654
12655
        if (empty($resultData)) {
12656
            return false;
12657
        }
12658
12659
        return $resultData;
12660
    }
12661
12662
    /**
12663
     * Create a forum for this learning path.
12664
     *
12665
     * @param int $forumCategoryId
12666
     *
12667
     * @return int The forum ID if was created. Otherwise return false
12668
     */
12669
    public function createForum($forumCategoryId)
12670
    {
12671
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12672
12673
        $forumId = store_forum(
12674
            [
12675
                'lp_id' => $this->lp_id,
12676
                'forum_title' => $this->name,
12677
                'forum_comment' => null,
12678
                'forum_category' => (int) $forumCategoryId,
12679
                'students_can_edit_group' => ['students_can_edit' => 0],
12680
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12681
                'default_view_type_group' => ['default_view_type' => 'flat'],
12682
                'group_forum' => 0,
12683
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12684
            ],
12685
            [],
12686
            true
12687
        );
12688
12689
        return $forumId;
12690
    }
12691
12692
    /**
12693
     * Get the LP Final Item form.
12694
     *
12695
     * @throws Exception
12696
     * @throws HTML_QuickForm_Error
12697
     *
12698
     * @return string
12699
     */
12700
    public function getFinalItemForm()
12701
    {
12702
        $finalItem = $this->getFinalItem();
12703
        $title = '';
12704
12705
        if ($finalItem) {
12706
            $title = $finalItem->get_title();
12707
            $buttonText = get_lang('Save');
12708
            $content = $this->getSavedFinalItem();
12709
        } else {
12710
            $buttonText = get_lang('LPCreateDocument');
12711
            $content = $this->getFinalItemTemplate();
12712
        }
12713
12714
        $courseInfo = api_get_course_info();
12715
        $result = $this->generate_lp_folder($courseInfo);
12716
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12717
        $relative_prefix = '../../';
12718
12719
        $editorConfig = [
12720
            'ToolbarSet' => 'LearningPathDocuments',
12721
            'Width' => '100%',
12722
            'Height' => '500',
12723
            'FullPage' => true,
12724
            'CreateDocumentDir' => $relative_prefix,
12725
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12726
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12727
        ];
12728
12729
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12730
            'type' => 'document',
12731
            'lp_id' => $this->lp_id,
12732
        ]);
12733
12734
        $form = new FormValidator('final_item', 'POST', $url);
12735
        $form->addText('title', get_lang('Title'));
12736
        $form->addButtonSave($buttonText);
12737
        $form->addHtml(
12738
            Display::return_message(
12739
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12740
                'normal',
12741
                false
12742
            )
12743
        );
12744
12745
        $renderer = $form->defaultRenderer();
12746
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12747
12748
        $form->addHtmlEditor(
12749
            'content_lp_certificate',
12750
            null,
12751
            true,
12752
            false,
12753
            $editorConfig,
12754
            true
12755
        );
12756
        $form->addHidden('action', 'add_final_item');
12757
        $form->addHidden('path', Session::read('pathItem'));
12758
        $form->addHidden('previous', $this->get_last());
12759
        $form->setDefaults(
12760
            ['title' => $title, 'content_lp_certificate' => $content]
12761
        );
12762
12763
        if ($form->validate()) {
12764
            $values = $form->exportValues();
12765
            $lastItemId = $this->getLastInFirstLevel();
12766
12767
            if (!$finalItem) {
12768
                $documentId = $this->create_document(
12769
                    $this->course_info,
12770
                    $values['content_lp_certificate'],
12771
                    $values['title']
12772
                );
12773
                $this->add_item(
12774
                    0,
12775
                    $lastItemId,
12776
                    'final_item',
12777
                    $documentId,
12778
                    $values['title'],
12779
                    ''
12780
                );
12781
12782
                Display::addFlash(
12783
                    Display::return_message(get_lang('Added'))
12784
                );
12785
            } else {
12786
                $this->edit_document($this->course_info);
12787
            }
12788
        }
12789
12790
        return $form->returnForm();
12791
    }
12792
12793
    /**
12794
     * Check if the current lp item is first, both, last or none from lp list.
12795
     *
12796
     * @param int $currentItemId
12797
     *
12798
     * @return string
12799
     */
12800
    public function isFirstOrLastItem($currentItemId)
12801
    {
12802
        $lpItemId = [];
12803
        $typeListNotToVerify = self::getChapterTypes();
12804
12805
        // Using get_toc() function instead $this->items because returns the correct order of the items
12806
        foreach ($this->get_toc() as $item) {
12807
            if (!in_array($item['type'], $typeListNotToVerify)) {
12808
                $lpItemId[] = $item['id'];
12809
            }
12810
        }
12811
12812
        $lastLpItemIndex = count($lpItemId) - 1;
12813
        $position = array_search($currentItemId, $lpItemId);
12814
12815
        switch ($position) {
12816
            case 0:
12817
                if (!$lastLpItemIndex) {
12818
                    $answer = 'both';
12819
                    break;
12820
                }
12821
12822
                $answer = 'first';
12823
                break;
12824
            case $lastLpItemIndex:
12825
                $answer = 'last';
12826
                break;
12827
            default:
12828
                $answer = 'none';
12829
        }
12830
12831
        return $answer;
12832
    }
12833
12834
    /**
12835
     * Get whether this is a learning path with the accumulated SCORM time or not.
12836
     *
12837
     * @return int
12838
     */
12839
    public function getAccumulateScormTime()
12840
    {
12841
        return $this->accumulateScormTime;
12842
    }
12843
12844
    /**
12845
     * Set whether this is a learning path with the accumulated SCORM time or not.
12846
     *
12847
     * @param int $value (0 = false, 1 = true)
12848
     *
12849
     * @return bool Always returns true
12850
     */
12851
    public function setAccumulateScormTime($value)
12852
    {
12853
        $this->accumulateScormTime = (int) $value;
12854
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12855
        $lp_id = $this->get_id();
12856
        $sql = "UPDATE $lp_table
12857
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12858
                WHERE iid = $lp_id";
12859
        Database::query($sql);
12860
12861
        return true;
12862
    }
12863
12864
    /**
12865
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12866
     * the new learning path tool.
12867
     *
12868
     * The function is a big switch on tool type.
12869
     * In each case, we query the corresponding table for information and build the link
12870
     * with that information.
12871
     *
12872
     * @author Yannick Warnier <[email protected]> - rebranding based on
12873
     * previous work (display_addedresource_link_in_learnpath())
12874
     *
12875
     * @param int $course_id      Course code
12876
     * @param int $learningPathId The learning path ID (in lp table)
12877
     * @param int $id_in_path     the unique index in the items table
12878
     * @param int $lpViewId
12879
     *
12880
     * @return string
12881
     */
12882
    public static function rl_get_resource_link_for_learnpath(
12883
        $course_id,
12884
        $learningPathId,
12885
        $id_in_path,
12886
        $lpViewId
12887
    ) {
12888
        $session_id = api_get_session_id();
12889
        $course_info = api_get_course_info_by_id($course_id);
12890
12891
        $learningPathId = (int) $learningPathId;
12892
        $id_in_path = (int) $id_in_path;
12893
        $lpViewId = (int) $lpViewId;
12894
12895
        $em = Database::getManager();
12896
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12897
12898
        /** @var CLpItem $rowItem */
12899
        $rowItem = $lpItemRepo->findOneBy([
12900
            'cId' => $course_id,
12901
            'lpId' => $learningPathId,
12902
            'iid' => $id_in_path,
12903
        ]);
12904
12905
        if (!$rowItem) {
12906
            // Try one more time with "id"
12907
            /** @var CLpItem $rowItem */
12908
            $rowItem = $lpItemRepo->findOneBy([
12909
                'cId' => $course_id,
12910
                'lpId' => $learningPathId,
12911
                'id' => $id_in_path,
12912
            ]);
12913
12914
            if (!$rowItem) {
12915
                return -1;
12916
            }
12917
        }
12918
12919
        $course_code = $course_info['code'];
12920
        $type = $rowItem->getItemType();
12921
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12922
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12923
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12924
        $link = '';
12925
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12926
12927
        switch ($type) {
12928
            case 'dir':
12929
                return $main_dir_path.'lp/blank.php';
12930
            case TOOL_CALENDAR_EVENT:
12931
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12932
            case TOOL_ANNOUNCEMENT:
12933
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12934
            case TOOL_LINK:
12935
                $linkInfo = Link::getLinkInfo($id);
12936
                if (isset($linkInfo['url'])) {
12937
                    return $linkInfo['url'];
12938
                }
12939
12940
                return '';
12941
            case TOOL_QUIZ:
12942
                if (empty($id)) {
12943
                    return '';
12944
                }
12945
12946
                // Get the lp_item_view with the highest view_count.
12947
                $learnpathItemViewResult = $em
12948
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12949
                    ->findBy(
12950
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12951
                        ['viewCount' => 'DESC'],
12952
                        1
12953
                    );
12954
                /** @var CLpItemView $learnpathItemViewData */
12955
                $learnpathItemViewData = current($learnpathItemViewResult);
12956
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12957
12958
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12959
                    .http_build_query([
12960
                        'lp_init' => 1,
12961
                        'learnpath_item_view_id' => $learnpathItemViewId,
12962
                        'learnpath_id' => $learningPathId,
12963
                        'learnpath_item_id' => $id_in_path,
12964
                        'exerciseId' => $id,
12965
                    ]);
12966
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12967
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12968
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12969
                $myrow = Database::fetch_array($result);
12970
                $path = $myrow['path'];
12971
12972
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12973
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12974
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12975
            case TOOL_FORUM:
12976
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12977
            case TOOL_THREAD:
12978
                // forum post
12979
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12980
                if (empty($id)) {
12981
                    return '';
12982
                }
12983
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12984
                $result = Database::query($sql);
12985
                $myrow = Database::fetch_array($result);
12986
12987
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12988
                    .$extraParams;
12989
            case TOOL_POST:
12990
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12991
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12992
                $myrow = Database::fetch_array($result);
12993
12994
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12995
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12996
            case TOOL_READOUT_TEXT:
12997
                return api_get_path(WEB_CODE_PATH).
12998
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12999
            case TOOL_DOCUMENT:
13000
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13001
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13002
13003
                if (empty($document)) {
13004
                    // Try with normal id
13005
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13006
13007
                    if (empty($document)) {
13008
                        return '';
13009
                    }
13010
                }
13011
13012
                $documentPathInfo = pathinfo($document->getPath());
13013
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13014
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13015
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13016
13017
                $openmethod = 2;
13018
                $officedoc = false;
13019
                Session::write('openmethod', $openmethod);
13020
                Session::write('officedoc', $officedoc);
13021
13022
                if ($showDirectUrl) {
13023
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13024
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13025
                        if (Link::isPdfLink($file)) {
13026
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13027
13028
                            return $pdfUrl;
13029
                        }
13030
                    }
13031
13032
                    return $file;
13033
                }
13034
13035
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13036
            case TOOL_LP_FINAL_ITEM:
13037
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13038
                    .$extraParams;
13039
            case 'assignments':
13040
                return $main_dir_path.'work/work.php?'.$extraParams;
13041
            case TOOL_DROPBOX:
13042
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13043
            case 'introduction_text': //DEPRECATED
13044
                return '';
13045
            case TOOL_COURSE_DESCRIPTION:
13046
                return $main_dir_path.'course_description?'.$extraParams;
13047
            case TOOL_GROUP:
13048
                return $main_dir_path.'group/group.php?'.$extraParams;
13049
            case TOOL_USER:
13050
                return $main_dir_path.'user/user.php?'.$extraParams;
13051
            case TOOL_STUDENTPUBLICATION:
13052
                if (!empty($rowItem->getPath())) {
13053
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13054
                }
13055
13056
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13057
        }
13058
13059
        return $link;
13060
    }
13061
13062
    /**
13063
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13064
     *
13065
     * @author Yannick Warnier <[email protected]>
13066
     *
13067
     * @param string $course_code    Course code
13068
     * @param int    $learningPathId
13069
     * @param int    $id_in_path     The resource ID
13070
     *
13071
     * @return string
13072
     */
13073
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13074
    {
13075
        $_course = api_get_course_info($course_code);
13076
        if (empty($_course)) {
13077
            return '';
13078
        }
13079
        $course_id = $_course['real_id'];
13080
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13081
        $learningPathId = (int) $learningPathId;
13082
        $id_in_path = (int) $id_in_path;
13083
13084
        $sql = "SELECT item_type, title, ref
13085
                FROM $tbl_lp_item
13086
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13087
        $res_item = Database::query($sql);
13088
13089
        if (Database::num_rows($res_item) < 1) {
13090
            return '';
13091
        }
13092
        $row_item = Database::fetch_array($res_item);
13093
        $type = strtolower($row_item['item_type']);
13094
        $id = $row_item['ref'];
13095
        $output = '';
13096
13097
        switch ($type) {
13098
            case TOOL_CALENDAR_EVENT:
13099
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13100
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13101
                $myrow = Database::fetch_array($result);
13102
                $output = $myrow['title'];
13103
                break;
13104
            case TOOL_ANNOUNCEMENT:
13105
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13106
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13107
                $myrow = Database::fetch_array($result);
13108
                $output = $myrow['title'];
13109
                break;
13110
            case TOOL_LINK:
13111
                // Doesn't take $target into account.
13112
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13113
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13114
                $myrow = Database::fetch_array($result);
13115
                $output = $myrow['title'];
13116
                break;
13117
            case TOOL_QUIZ:
13118
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13119
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13120
                $myrow = Database::fetch_array($result);
13121
                $output = $myrow['title'];
13122
                break;
13123
            case TOOL_FORUM:
13124
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13125
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13126
                $myrow = Database::fetch_array($result);
13127
                $output = $myrow['forum_name'];
13128
                break;
13129
            case TOOL_THREAD:
13130
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13131
                // Grabbing the title of the post.
13132
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13133
                $result_title = Database::query($sql_title);
13134
                $myrow_title = Database::fetch_array($result_title);
13135
                $output = $myrow_title['post_title'];
13136
                break;
13137
            case TOOL_POST:
13138
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13139
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13140
                $result = Database::query($sql);
13141
                $post = Database::fetch_array($result);
13142
                $output = $post['post_title'];
13143
                break;
13144
            case 'dir':
13145
            case TOOL_DOCUMENT:
13146
                $title = $row_item['title'];
13147
                $output = '-';
13148
                if (!empty($title)) {
13149
                    $output = $title;
13150
                }
13151
                break;
13152
            case 'hotpotatoes':
13153
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13154
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13155
                $myrow = Database::fetch_array($result);
13156
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13157
                $last = count($pathname) - 1; // Making a correct name for the link.
13158
                $filename = $pathname[$last]; // Making a correct name for the link.
13159
                $myrow['path'] = rawurlencode($myrow['path']);
13160
                $output = $filename;
13161
                break;
13162
        }
13163
13164
        return stripslashes($output);
13165
    }
13166
13167
    /**
13168
     * Get the parent names for the current item.
13169
     *
13170
     * @param int $newItemId Optional. The item ID
13171
     *
13172
     * @return array
13173
     */
13174
    public function getCurrentItemParentNames($newItemId = 0)
13175
    {
13176
        $newItemId = $newItemId ?: $this->get_current_item_id();
13177
        $return = [];
13178
        $item = $this->getItem($newItemId);
13179
        $parent = $this->getItem($item->get_parent());
13180
13181
        while ($parent) {
13182
            $return[] = $parent->get_title();
13183
            $parent = $this->getItem($parent->get_parent());
13184
        }
13185
13186
        return array_reverse($return);
13187
    }
13188
13189
    /**
13190
     * Reads and process "lp_subscription_settings" setting.
13191
     *
13192
     * @return array
13193
     */
13194
    public static function getSubscriptionSettings()
13195
    {
13196
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13197
        if (empty($subscriptionSettings)) {
13198
            // By default allow both settings
13199
            $subscriptionSettings = [
13200
                'allow_add_users_to_lp' => true,
13201
                'allow_add_users_to_lp_category' => true,
13202
            ];
13203
        } else {
13204
            $subscriptionSettings = $subscriptionSettings['options'];
13205
        }
13206
13207
        return $subscriptionSettings;
13208
    }
13209
13210
    /**
13211
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13212
     */
13213
    public function exportToCourseBuildFormat()
13214
    {
13215
        if (!api_is_allowed_to_edit()) {
13216
            return false;
13217
        }
13218
13219
        $courseBuilder = new CourseBuilder();
13220
        $itemList = [];
13221
        /** @var learnpathItem $item */
13222
        foreach ($this->items as $item) {
13223
            $itemList[$item->get_type()][] = $item->get_path();
13224
        }
13225
13226
        if (empty($itemList)) {
13227
            return false;
13228
        }
13229
13230
        if (isset($itemList['document'])) {
13231
            // Get parents
13232
            foreach ($itemList['document'] as $documentId) {
13233
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13234
                if (!empty($documentInfo['parents'])) {
13235
                    foreach ($documentInfo['parents'] as $parentInfo) {
13236
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13237
                            continue;
13238
                        }
13239
                        $itemList['document'][] = $parentInfo['iid'];
13240
                    }
13241
                }
13242
            }
13243
13244
            $courseInfo = api_get_course_info();
13245
            foreach ($itemList['document'] as $documentId) {
13246
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13247
                $items = DocumentManager::get_resources_from_source_html(
13248
                    $documentInfo['absolute_path'],
13249
                    true,
13250
                    TOOL_DOCUMENT
13251
                );
13252
13253
                if (!empty($items)) {
13254
                    foreach ($items as $item) {
13255
                        // Get information about source url
13256
                        $url = $item[0]; // url
13257
                        $scope = $item[1]; // scope (local, remote)
13258
                        $type = $item[2]; // type (rel, abs, url)
13259
13260
                        $origParseUrl = parse_url($url);
13261
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13262
13263
                        if ($scope == 'local') {
13264
                            if ($type == 'abs' || $type == 'rel') {
13265
                                $documentFile = strstr($realOrigPath, 'document');
13266
                                if (strpos($realOrigPath, $documentFile) !== false) {
13267
                                    $documentFile = str_replace('document', '', $documentFile);
13268
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13269
                                    // Document found! Add it to the list
13270
                                    if ($itemDocumentId) {
13271
                                        $itemList['document'][] = $itemDocumentId;
13272
                                    }
13273
                                }
13274
                            }
13275
                        }
13276
                    }
13277
                }
13278
            }
13279
13280
            $courseBuilder->build_documents(
13281
                api_get_session_id(),
13282
                $this->get_course_int_id(),
13283
                true,
13284
                $itemList['document']
13285
            );
13286
        }
13287
13288
        if (isset($itemList['quiz'])) {
13289
            $courseBuilder->build_quizzes(
13290
                api_get_session_id(),
13291
                $this->get_course_int_id(),
13292
                true,
13293
                $itemList['quiz']
13294
            );
13295
        }
13296
13297
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13298
13299
        /*if (!empty($itemList['thread'])) {
13300
            $postList = [];
13301
            foreach ($itemList['thread'] as $postId) {
13302
                $post = get_post_information($postId);
13303
                if ($post) {
13304
                    if (!isset($itemList['forum'])) {
13305
                        $itemList['forum'] = [];
13306
                    }
13307
                    $itemList['forum'][] = $post['forum_id'];
13308
                    $postList[] = $postId;
13309
                }
13310
            }
13311
13312
            if (!empty($postList)) {
13313
                $courseBuilder->build_forum_posts(
13314
                    $this->get_course_int_id(),
13315
                    null,
13316
                    null,
13317
                    $postList
13318
                );
13319
            }
13320
        }*/
13321
13322
        if (!empty($itemList['thread'])) {
13323
            $threadList = [];
13324
            $em = Database::getManager();
13325
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13326
            foreach ($itemList['thread'] as $threadId) {
13327
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13328
                $thread = $repo->find($threadId);
13329
                if ($thread) {
13330
                    $itemList['forum'][] = $thread->getForumId();
13331
                    $threadList[] = $thread->getIid();
13332
                }
13333
            }
13334
13335
            if (!empty($threadList)) {
13336
                $courseBuilder->build_forum_topics(
13337
                    api_get_session_id(),
13338
                    $this->get_course_int_id(),
13339
                    null,
13340
                    $threadList
13341
                );
13342
            }
13343
        }
13344
13345
        $forumCategoryList = [];
13346
        if (isset($itemList['forum'])) {
13347
            foreach ($itemList['forum'] as $forumId) {
13348
                $forumInfo = get_forums($forumId);
13349
                $forumCategoryList[] = $forumInfo['forum_category'];
13350
            }
13351
        }
13352
13353
        if (!empty($forumCategoryList)) {
13354
            $courseBuilder->build_forum_category(
13355
                api_get_session_id(),
13356
                $this->get_course_int_id(),
13357
                true,
13358
                $forumCategoryList
13359
            );
13360
        }
13361
13362
        if (!empty($itemList['forum'])) {
13363
            $courseBuilder->build_forums(
13364
                api_get_session_id(),
13365
                $this->get_course_int_id(),
13366
                true,
13367
                $itemList['forum']
13368
            );
13369
        }
13370
13371
        if (isset($itemList['link'])) {
13372
            $courseBuilder->build_links(
13373
                api_get_session_id(),
13374
                $this->get_course_int_id(),
13375
                true,
13376
                $itemList['link']
13377
            );
13378
        }
13379
13380
        if (!empty($itemList['student_publication'])) {
13381
            $courseBuilder->build_works(
13382
                api_get_session_id(),
13383
                $this->get_course_int_id(),
13384
                true,
13385
                $itemList['student_publication']
13386
            );
13387
        }
13388
13389
        $courseBuilder->build_learnpaths(
13390
            api_get_session_id(),
13391
            $this->get_course_int_id(),
13392
            true,
13393
            [$this->get_id()],
13394
            false
13395
        );
13396
13397
        $courseBuilder->restoreDocumentsFromList();
13398
13399
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13400
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13401
        $result = DocumentManager::file_send_for_download(
13402
            $zipPath,
13403
            true,
13404
            $this->get_name().'.zip'
13405
        );
13406
13407
        if ($result) {
13408
            api_not_allowed();
13409
        }
13410
13411
        return true;
13412
    }
13413
13414
    /**
13415
     * Get whether this is a learning path with the accumulated work time or not.
13416
     *
13417
     * @return int
13418
     */
13419
    public function getAccumulateWorkTime()
13420
    {
13421
        return (int) $this->accumulateWorkTime;
13422
    }
13423
13424
    /**
13425
     * Get whether this is a learning path with the accumulated work time or not.
13426
     *
13427
     * @return int
13428
     */
13429
    public function getAccumulateWorkTimeTotalCourse()
13430
    {
13431
        $table = Database::get_course_table(TABLE_LP_MAIN);
13432
        $sql = "SELECT SUM(accumulate_work_time) AS total
13433
                FROM $table
13434
                WHERE c_id = ".$this->course_int_id;
13435
        $result = Database::query($sql);
13436
        $row = Database::fetch_array($result);
13437
13438
        return (int) $row['total'];
13439
    }
13440
13441
    /**
13442
     * Set whether this is a learning path with the accumulated work time or not.
13443
     *
13444
     * @param int $value (0 = false, 1 = true)
13445
     *
13446
     * @return bool
13447
     */
13448
    public function setAccumulateWorkTime($value)
13449
    {
13450
        if (!api_get_configuration_value('lp_minimum_time')) {
13451
            return false;
13452
        }
13453
13454
        $this->accumulateWorkTime = (int) $value;
13455
        $table = Database::get_course_table(TABLE_LP_MAIN);
13456
        $lp_id = $this->get_id();
13457
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13458
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13459
        Database::query($sql);
13460
13461
        return true;
13462
    }
13463
13464
    /**
13465
     * @param int $lpId
13466
     * @param int $courseId
13467
     *
13468
     * @return mixed
13469
     */
13470
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13471
    {
13472
        $lpId = (int) $lpId;
13473
        $courseId = (int) $courseId;
13474
13475
        $table = Database::get_course_table(TABLE_LP_MAIN);
13476
        $sql = "SELECT accumulate_work_time
13477
                FROM $table
13478
                WHERE c_id = $courseId AND id = $lpId";
13479
        $result = Database::query($sql);
13480
        $row = Database::fetch_array($result);
13481
13482
        return $row['accumulate_work_time'];
13483
    }
13484
13485
    /**
13486
     * @param int $courseId
13487
     *
13488
     * @return int
13489
     */
13490
    public static function getAccumulateWorkTimeTotal($courseId)
13491
    {
13492
        $table = Database::get_course_table(TABLE_LP_MAIN);
13493
        $courseId = (int) $courseId;
13494
        $sql = "SELECT SUM(accumulate_work_time) AS total
13495
                FROM $table
13496
                WHERE c_id = $courseId";
13497
        $result = Database::query($sql);
13498
        $row = Database::fetch_array($result);
13499
13500
        return (int) $row['total'];
13501
    }
13502
13503
    /**
13504
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13505
     * and put the images in.
13506
     *
13507
     * @return array
13508
     */
13509
    public static function getIconSelect()
13510
    {
13511
        $theme = api_get_visual_theme();
13512
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13513
        $icons = ['' => get_lang('SelectAnOption')];
13514
13515
        if (is_dir($path)) {
13516
            $finder = new Finder();
13517
            $finder->files()->in($path);
13518
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13519
            /** @var SplFileInfo $file */
13520
            foreach ($finder as $file) {
13521
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13522
                    $icons[$file->getFilename()] = $file->getFilename();
13523
                }
13524
            }
13525
        }
13526
13527
        return $icons;
13528
    }
13529
13530
    /**
13531
     * @param int $lpId
13532
     *
13533
     * @return string
13534
     */
13535
    public static function getSelectedIcon($lpId)
13536
    {
13537
        $extraFieldValue = new ExtraFieldValue('lp');
13538
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13539
        $icon = '';
13540
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13541
            $icon = $lpIcon['value'];
13542
        }
13543
13544
        return $icon;
13545
    }
13546
13547
    /**
13548
     * @param int $lpId
13549
     *
13550
     * @return string
13551
     */
13552
    public static function getSelectedIconHtml($lpId)
13553
    {
13554
        $icon = self::getSelectedIcon($lpId);
13555
13556
        if (empty($icon)) {
13557
            return '';
13558
        }
13559
13560
        $theme = api_get_visual_theme();
13561
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13562
13563
        return Display::img($path);
13564
    }
13565
13566
    /**
13567
     * @param string $value
13568
     *
13569
     * @return string
13570
     */
13571
    public function cleanItemTitle($value)
13572
    {
13573
        $value = Security::remove_XSS(strip_tags($value));
13574
13575
        return $value;
13576
    }
13577
13578
    public function setItemTitle(FormValidator $form)
13579
    {
13580
        if (api_get_configuration_value('save_titles_as_html')) {
13581
            $form->addHtmlEditor(
13582
                'title',
13583
                get_lang('Title'),
13584
                true,
13585
                false,
13586
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13587
            );
13588
        } else {
13589
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13590
            $form->applyFilter('title', 'trim');
13591
            $form->applyFilter('title', 'html_filter');
13592
        }
13593
    }
13594
13595
    /**
13596
     * @return array
13597
     */
13598
    public function getItemsForForm($addParentCondition = false)
13599
    {
13600
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13601
        $course_id = api_get_course_int_id();
13602
13603
        $sql = "SELECT * FROM $tbl_lp_item
13604
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13605
13606
        if ($addParentCondition) {
13607
            $sql .= ' AND parent_item_id = 0 ';
13608
        }
13609
        $sql .= ' ORDER BY display_order ASC';
13610
13611
        $result = Database::query($sql);
13612
        $arrLP = [];
13613
        while ($row = Database::fetch_array($result)) {
13614
            $arrLP[] = [
13615
                'iid' => $row['iid'],
13616
                'id' => $row['iid'],
13617
                'item_type' => $row['item_type'],
13618
                'title' => $this->cleanItemTitle($row['title']),
13619
                'title_raw' => $row['title'],
13620
                'path' => $row['path'],
13621
                'description' => Security::remove_XSS($row['description']),
13622
                'parent_item_id' => $row['parent_item_id'],
13623
                'previous_item_id' => $row['previous_item_id'],
13624
                'next_item_id' => $row['next_item_id'],
13625
                'display_order' => $row['display_order'],
13626
                'max_score' => $row['max_score'],
13627
                'min_score' => $row['min_score'],
13628
                'mastery_score' => $row['mastery_score'],
13629
                'prerequisite' => $row['prerequisite'],
13630
                'max_time_allowed' => $row['max_time_allowed'],
13631
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13632
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13633
            ];
13634
        }
13635
13636
        return $arrLP;
13637
    }
13638
13639
    /**
13640
     * Get the depth level of LP item.
13641
     *
13642
     * @param array $items
13643
     * @param int   $currentItemId
13644
     *
13645
     * @return int
13646
     */
13647
    private static function get_level_for_item($items, $currentItemId)
13648
    {
13649
        $parentItemId = 0;
13650
        if (isset($items[$currentItemId])) {
13651
            $parentItemId = $items[$currentItemId]->parent;
13652
        }
13653
13654
        if ($parentItemId == 0) {
13655
            return 0;
13656
        } else {
13657
            return self::get_level_for_item($items, $parentItemId) + 1;
13658
        }
13659
    }
13660
13661
    /**
13662
     * Generate the link for a learnpath category as course tool.
13663
     *
13664
     * @param int $categoryId
13665
     *
13666
     * @return string
13667
     */
13668
    private static function getCategoryLinkForTool($categoryId)
13669
    {
13670
        $categoryId = (int) $categoryId;
13671
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13672
            .http_build_query(
13673
                [
13674
                    'action' => 'view_category',
13675
                    'id' => $categoryId,
13676
                ]
13677
            );
13678
13679
        return $link;
13680
    }
13681
13682
    /**
13683
     * Return the scorm item type object with spaces replaced with _
13684
     * The return result is use to build a css classname like scorm_type_$return.
13685
     *
13686
     * @param $in_type
13687
     *
13688
     * @return mixed
13689
     */
13690
    private static function format_scorm_type_item($in_type)
13691
    {
13692
        return str_replace(' ', '_', $in_type);
13693
    }
13694
13695
    /**
13696
     * Check and obtain the lp final item if exist.
13697
     *
13698
     * @return learnpathItem
13699
     */
13700
    private function getFinalItem()
13701
    {
13702
        if (empty($this->items)) {
13703
            return null;
13704
        }
13705
13706
        foreach ($this->items as $item) {
13707
            if ($item->type !== 'final_item') {
13708
                continue;
13709
            }
13710
13711
            return $item;
13712
        }
13713
    }
13714
13715
    /**
13716
     * Get the LP Final Item Template.
13717
     *
13718
     * @return string
13719
     */
13720
    private function getFinalItemTemplate()
13721
    {
13722
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13723
    }
13724
13725
    /**
13726
     * Get the LP Final Item Url.
13727
     *
13728
     * @return string
13729
     */
13730
    private function getSavedFinalItem()
13731
    {
13732
        $finalItem = $this->getFinalItem();
13733
        $doc = DocumentManager::get_document_data_by_id(
13734
            $finalItem->path,
13735
            $this->cc
13736
        );
13737
        if ($doc && file_exists($doc['absolute_path'])) {
13738
            return file_get_contents($doc['absolute_path']);
13739
        }
13740
13741
        return '';
13742
    }
13743
}
13744