Completed
Push — preprodparkur ( 8b68ba...f1fa91 )
by
unknown
09:06 queued 08:26
created

learnpath::getForum()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 44
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 21
nc 4
nop 1
dl 0
loc 44
rs 9.584
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
        $name = Database::escape_string($name);
769
        $categoryId = (int) $categoryId;
770
771
        // Session id.
772
        $session_id = api_get_session_id();
773
        $userId = empty($userId) ? api_get_user_id() : $userId;
774
        $check_name = "SELECT * FROM $tbl_lp
775
                       WHERE c_id = $course_id AND name = '$name'";
776
777
        $res_name = Database::query($check_name);
778
779
        if (empty($publicated_on)) {
780
            $publicated_on = null;
781
        } else {
782
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
783
        }
784
785
        if (empty($expired_on)) {
786
            $expired_on = null;
787
        } else {
788
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
789
        }
790
791
        while (Database::num_rows($res_name)) {
792
            // There is already one such name, update the current one a bit.
793
            $i++;
794
            $name = $name.' - '.$i;
795
            $check_name = "SELECT * FROM $tbl_lp
796
                           WHERE c_id = $course_id AND name = '$name'";
797
            $res_name = Database::query($check_name);
798
        }
799
        // New name does not exist yet; keep it.
800
        // Escape description.
801
        // Kevin: added htmlentities().
802
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
803
        $type = 1;
804
        switch ($learnpath) {
805
            case 'guess':
806
                break;
807
            case 'dokeos':
808
            case 'chamilo':
809
                $type = 1;
810
                break;
811
            case 'aicc':
812
                break;
813
        }
814
815
        switch ($origin) {
816
            case 'zip':
817
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
818
                break;
819
            case 'manual':
820
            default:
821
                $get_max = "SELECT MAX(display_order)
822
                            FROM $tbl_lp WHERE c_id = $course_id";
823
                $res_max = Database::query($get_max);
824
                if (Database::num_rows($res_max) < 1) {
825
                    $dsp = 1;
826
                } else {
827
                    $row = Database::fetch_array($res_max);
828
                    $dsp = $row[0] + 1;
829
                }
830
831
                $params = [
832
                    'c_id' => $course_id,
833
                    'lp_type' => $type,
834
                    'name' => $name,
835
                    'description' => $description,
836
                    'path' => '',
837
                    'default_view_mod' => 'embedded',
838
                    'default_encoding' => 'UTF-8',
839
                    'display_order' => $dsp,
840
                    'content_maker' => 'Chamilo',
841
                    'content_local' => 'local',
842
                    'js_lib' => '',
843
                    'session_id' => $session_id,
844
                    'created_on' => api_get_utc_datetime(),
845
                    'modified_on' => api_get_utc_datetime(),
846
                    'publicated_on' => $publicated_on,
847
                    'expired_on' => $expired_on,
848
                    'category_id' => $categoryId,
849
                    'force_commit' => 0,
850
                    'content_license' => '',
851
                    'debug' => 0,
852
                    'theme' => '',
853
                    'preview_image' => '',
854
                    'author' => '',
855
                    'prerequisite' => 0,
856
                    'hide_toc_frame' => 0,
857
                    'seriousgame_mode' => 0,
858
                    'autolaunch' => 0,
859
                    'max_attempts' => 0,
860
                    'subscribe_users' => 0,
861
                    'accumulate_scorm_time' => 1,
862
                ];
863
                $id = Database::insert($tbl_lp, $params);
864
865
                if ($id > 0) {
866
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
867
                    Database::query($sql);
868
869
                    // Insert into item_property.
870
                    api_item_property_update(
871
                        $courseInfo,
872
                        TOOL_LEARNPATH,
873
                        $id,
874
                        'LearnpathAdded',
875
                        $userId
876
                    );
877
                    api_set_default_visibility(
878
                        $id,
879
                        TOOL_LEARNPATH,
880
                        0,
881
                        $courseInfo,
882
                        $session_id,
883
                        $userId
884
                    );
885
886
                    return $id;
887
                }
888
                break;
889
        }
890
    }
891
892
    /**
893
     * Auto completes the parents of an item in case it's been completed or passed.
894
     *
895
     * @param int $item Optional ID of the item from which to look for parents
896
     */
897
    public function autocomplete_parents($item)
898
    {
899
        $debug = $this->debug;
900
901
        if (empty($item)) {
902
            $item = $this->current;
903
        }
904
905
        $currentItem = $this->getItem($item);
906
        if ($currentItem) {
907
            $parent_id = $currentItem->get_parent();
908
            $parent = $this->getItem($parent_id);
909
            if ($parent) {
910
                // if $item points to an object and there is a parent.
911
                if ($debug) {
912
                    error_log(
913
                        'Autocompleting parent of item '.$item.' '.
914
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
915
                        0
916
                    );
917
                }
918
919
                // New experiment including failed and browsed in completed status.
920
                //$current_status = $currentItem->get_status();
921
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
922
                // Fixes chapter auto complete
923
                if (true) {
924
                    // If the current item is completed or passes or succeeded.
925
                    $updateParentStatus = true;
926
                    if ($debug) {
927
                        error_log('Status of current item is alright');
928
                    }
929
930
                    foreach ($parent->get_children() as $childItemId) {
931
                        $childItem = $this->getItem($childItemId);
932
933
                        // If children was not set try to get the info
934
                        if (empty($childItem->db_item_view_id)) {
935
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
936
                        }
937
938
                        // Check all his brothers (parent's children) for completion status.
939
                        if ($childItemId != $item) {
940
                            if ($debug) {
941
                                error_log(
942
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
943
                                    0
944
                                );
945
                            }
946
                            // Trying completing parents of failed and browsed items as well.
947
                            if ($childItem->status_is(
948
                                [
949
                                    'completed',
950
                                    'passed',
951
                                    'succeeded',
952
                                    'browsed',
953
                                    'failed',
954
                                ]
955
                            )
956
                            ) {
957
                                // Keep completion status to true.
958
                                continue;
959
                            } else {
960
                                if ($debug > 2) {
961
                                    error_log(
962
                                        '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,
963
                                        0
964
                                    );
965
                                }
966
                                $updateParentStatus = false;
967
                                break;
968
                            }
969
                        }
970
                    }
971
972
                    if ($updateParentStatus) {
973
                        // If all the children were completed:
974
                        $parent->set_status('completed');
975
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
976
                        // Force the status to "completed"
977
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
978
                        $this->update_queue[$parent->get_id()] = 'completed';
979
                        if ($debug) {
980
                            error_log(
981
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
982
                                print_r($this->update_queue, 1),
983
                                0
984
                            );
985
                        }
986
                        // Recursive call.
987
                        $this->autocomplete_parents($parent->get_id());
988
                    }
989
                }
990
            } else {
991
                if ($debug) {
992
                    error_log("Parent #$parent_id does not exists");
993
                }
994
            }
995
        } else {
996
            if ($debug) {
997
                error_log("#$item is an item that doesn't have parents");
998
            }
999
        }
1000
    }
1001
1002
    /**
1003
     * Closes the current resource.
1004
     *
1005
     * Stops the timer
1006
     * Saves into the database if required
1007
     * Clears the current resource data from this object
1008
     *
1009
     * @return bool True on success, false on failure
1010
     */
1011
    public function close()
1012
    {
1013
        if (empty($this->lp_id)) {
1014
            $this->error = 'Trying to close this learnpath but no ID is set';
1015
1016
            return false;
1017
        }
1018
        $this->current_time_stop = time();
1019
        $this->ordered_items = [];
1020
        $this->index = 0;
1021
        unset($this->lp_id);
1022
        //unset other stuff
1023
        return true;
1024
    }
1025
1026
    /**
1027
     * Static admin function allowing removal of a learnpath.
1028
     *
1029
     * @param array  $courseInfo
1030
     * @param int    $id         Learnpath ID
1031
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1032
     *
1033
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1034
     */
1035
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1036
    {
1037
        $course_id = api_get_course_int_id();
1038
        if (!empty($courseInfo)) {
1039
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1040
        }
1041
1042
        // TODO: Implement a way of getting this to work when the current object is not set.
1043
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1044
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1045
        if (!empty($id) && ($id != $this->lp_id)) {
1046
            return false;
1047
        }
1048
1049
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1050
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1051
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1052
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1053
1054
        // Delete lp item id.
1055
        foreach ($this->items as $lpItemId => $dummy) {
1056
            $sql = "DELETE FROM $lp_item_view
1057
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1058
            Database::query($sql);
1059
        }
1060
1061
        // Proposed by Christophe (nickname: clefevre)
1062
        $sql = "DELETE FROM $lp_item
1063
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1064
        Database::query($sql);
1065
1066
        $sql = "DELETE FROM $lp_view
1067
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1068
        Database::query($sql);
1069
1070
        self::toggle_publish($this->lp_id, 'i');
1071
1072
        if ($this->type == 2 || $this->type == 3) {
1073
            // This is a scorm learning path, delete the files as well.
1074
            $sql = "SELECT path FROM $lp
1075
                    WHERE iid = ".$this->lp_id;
1076
            $res = Database::query($sql);
1077
            if (Database::num_rows($res) > 0) {
1078
                $row = Database::fetch_array($res);
1079
                $path = $row['path'];
1080
                $sql = "SELECT id FROM $lp
1081
                        WHERE
1082
                            c_id = $course_id AND
1083
                            path = '$path' AND
1084
                            iid != ".$this->lp_id;
1085
                $res = Database::query($sql);
1086
                if (Database::num_rows($res) > 0) {
1087
                    // Another learning path uses this directory, so don't delete it.
1088
                    if ($this->debug > 2) {
1089
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1090
                    }
1091
                } else {
1092
                    // No other LP uses that directory, delete it.
1093
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1094
                    // The absolute system path for this course.
1095
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1096
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1097
                        if ($this->debug > 2) {
1098
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1099
                        }
1100
                        // Proposed by Christophe (clefevre).
1101
                        if (strcmp(substr($path, -2), "/.") == 0) {
1102
                            $path = substr($path, 0, -1); // Remove "." at the end.
1103
                        }
1104
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1105
                        rmdirr($course_scorm_dir.$path);
1106
                    }
1107
                }
1108
            }
1109
        }
1110
1111
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1112
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1113
        // Delete tools
1114
        $sql = "DELETE FROM $tbl_tool
1115
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1116
        Database::query($sql);
1117
1118
        $sql = "DELETE FROM $lp
1119
                WHERE iid = ".$this->lp_id;
1120
        Database::query($sql);
1121
        // Updates the display order of all lps.
1122
        $this->update_display_order();
1123
1124
        api_item_property_update(
1125
            api_get_course_info(),
1126
            TOOL_LEARNPATH,
1127
            $this->lp_id,
1128
            'delete',
1129
            api_get_user_id()
1130
        );
1131
1132
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1133
            api_get_course_id(),
1134
            4,
1135
            $id,
1136
            api_get_session_id()
1137
        );
1138
1139
        if ($link_info !== false) {
1140
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1141
        }
1142
1143
        if (api_get_setting('search_enabled') == 'true') {
1144
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1145
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1146
        }
1147
    }
1148
1149
    /**
1150
     * Removes all the children of one item - dangerous!
1151
     *
1152
     * @param int $id Element ID of which children have to be removed
1153
     *
1154
     * @return int Total number of children removed
1155
     */
1156
    public function delete_children_items($id)
1157
    {
1158
        $course_id = $this->course_info['real_id'];
1159
1160
        $num = 0;
1161
        $id = (int) $id;
1162
        if (empty($id) || empty($course_id)) {
1163
            return false;
1164
        }
1165
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1166
        $sql = "SELECT * FROM $lp_item
1167
                WHERE c_id = $course_id AND parent_item_id = $id";
1168
        $res = Database::query($sql);
1169
        while ($row = Database::fetch_array($res)) {
1170
            $num += $this->delete_children_items($row['iid']);
1171
            $sql = "DELETE FROM $lp_item
1172
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1173
            Database::query($sql);
1174
            $num++;
1175
        }
1176
1177
        return $num;
1178
    }
1179
1180
    /**
1181
     * Removes an item from the current learnpath.
1182
     *
1183
     * @param int $id Elem ID (0 if first)
1184
     *
1185
     * @return int Number of elements moved
1186
     *
1187
     * @todo implement resource removal
1188
     */
1189
    public function delete_item($id)
1190
    {
1191
        $course_id = api_get_course_int_id();
1192
        $id = (int) $id;
1193
        // TODO: Implement the resource removal.
1194
        if (empty($id) || empty($course_id)) {
1195
            return false;
1196
        }
1197
        // First select item to get previous, next, and display order.
1198
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1199
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1200
        $res_sel = Database::query($sql_sel);
1201
        if (Database::num_rows($res_sel) < 1) {
1202
            return false;
1203
        }
1204
        $row = Database::fetch_array($res_sel);
1205
        $previous = $row['previous_item_id'];
1206
        $next = $row['next_item_id'];
1207
        $display = $row['display_order'];
1208
        $parent = $row['parent_item_id'];
1209
        $lp = $row['lp_id'];
1210
        // Delete children items.
1211
        $this->delete_children_items($id);
1212
        // Now delete the item.
1213
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1214
        Database::query($sql_del);
1215
        // Now update surrounding items.
1216
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1217
                    WHERE iid = $previous";
1218
        Database::query($sql_upd);
1219
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1220
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1221
        Database::query($sql_upd);
1222
        // Now update all following items with new display order.
1223
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1224
                    WHERE
1225
                        c_id = $course_id AND
1226
                        lp_id = $lp AND
1227
                        parent_item_id = $parent AND
1228
                        display_order > $display";
1229
        Database::query($sql_all);
1230
1231
        //Removing prerequisites since the item will not longer exist
1232
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1233
                    WHERE c_id = $course_id AND prerequisite = $id";
1234
        Database::query($sql_all);
1235
1236
        $sql = "UPDATE $lp_item
1237
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
1238
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1239
        Database::query($sql);
1240
1241
        // Remove from search engine if enabled.
1242
        if (api_get_setting('search_enabled') === 'true') {
1243
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1244
            $sql = 'SELECT * FROM %s
1245
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1246
                    LIMIT 1';
1247
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1248
            $res = Database::query($sql);
1249
            if (Database::num_rows($res) > 0) {
1250
                $row2 = Database::fetch_array($res);
1251
                $di = new ChamiloIndexer();
1252
                $di->remove_document($row2['search_did']);
1253
            }
1254
            $sql = 'DELETE FROM %s
1255
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1256
                    LIMIT 1';
1257
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1258
            Database::query($sql);
1259
        }
1260
    }
1261
1262
    /**
1263
     * Updates an item's content in place.
1264
     *
1265
     * @param int    $id               Element ID
1266
     * @param int    $parent           Parent item ID
1267
     * @param int    $previous         Previous item ID
1268
     * @param string $title            Item title
1269
     * @param string $description      Item description
1270
     * @param string $prerequisites    Prerequisites (optional)
1271
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1272
     * @param int    $max_time_allowed
1273
     * @param string $url
1274
     *
1275
     * @return bool True on success, false on error
1276
     */
1277
    public function edit_item(
1278
        $id,
1279
        $parent,
1280
        $previous,
1281
        $title,
1282
        $description,
1283
        $prerequisites = '0',
1284
        $audio = [],
1285
        $max_time_allowed = 0,
1286
        $url = ''
1287
    ) {
1288
        $course_id = api_get_course_int_id();
1289
        $_course = api_get_course_info();
1290
        $id = (int) $id;
1291
1292
        if (empty($max_time_allowed)) {
1293
            $max_time_allowed = 0;
1294
        }
1295
1296
        if (empty($id) || empty($_course)) {
1297
            return false;
1298
        }
1299
1300
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1301
        $sql = "SELECT * FROM $tbl_lp_item
1302
                WHERE iid = $id";
1303
        $res_select = Database::query($sql);
1304
        $row_select = Database::fetch_array($res_select);
1305
        $audio_update_sql = '';
1306
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1307
            // Create the audio folder if it does not exist yet.
1308
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1309
            if (!is_dir($filepath.'audio')) {
1310
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1311
                $audio_id = add_document(
1312
                    $_course,
1313
                    '/audio',
1314
                    'folder',
1315
                    0,
1316
                    'audio'
1317
                );
1318
                api_item_property_update(
1319
                    $_course,
1320
                    TOOL_DOCUMENT,
1321
                    $audio_id,
1322
                    'FolderCreated',
1323
                    api_get_user_id(),
1324
                    null,
1325
                    null,
1326
                    null,
1327
                    null,
1328
                    api_get_session_id()
1329
                );
1330
                api_item_property_update(
1331
                    $_course,
1332
                    TOOL_DOCUMENT,
1333
                    $audio_id,
1334
                    'invisible',
1335
                    api_get_user_id(),
1336
                    null,
1337
                    null,
1338
                    null,
1339
                    null,
1340
                    api_get_session_id()
1341
                );
1342
            }
1343
1344
            // Upload file in documents.
1345
            $pi = pathinfo($audio['name']);
1346
            if ($pi['extension'] === 'mp3') {
1347
                $c_det = api_get_course_info($this->cc);
1348
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1349
                $path = handle_uploaded_document(
1350
                    $c_det,
1351
                    $audio,
1352
                    $bp,
1353
                    '/audio',
1354
                    api_get_user_id(),
1355
                    0,
1356
                    null,
1357
                    0,
1358
                    'rename',
1359
                    false,
1360
                    0
1361
                );
1362
                $path = substr($path, 7);
1363
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1364
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1365
            }
1366
        }
1367
1368
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1369
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1370
1371
        // TODO: htmlspecialchars to be checked for encoding related problems.
1372
        if ($same_parent && $same_previous) {
1373
            // Only update title and description.
1374
            $sql = "UPDATE $tbl_lp_item
1375
                    SET title = '".Database::escape_string($title)."',
1376
                        prerequisite = '".$prerequisites."',
1377
                        description = '".Database::escape_string($description)."'
1378
                        ".$audio_update_sql.",
1379
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1380
                    WHERE iid = $id";
1381
            Database::query($sql);
1382
        } else {
1383
            $old_parent = $row_select['parent_item_id'];
1384
            $old_previous = $row_select['previous_item_id'];
1385
            $old_next = $row_select['next_item_id'];
1386
            $old_order = $row_select['display_order'];
1387
            $old_prerequisite = $row_select['prerequisite'];
1388
            $old_max_time_allowed = $row_select['max_time_allowed'];
1389
1390
            /* BEGIN -- virtually remove the current item id */
1391
            /* for the next and previous item it is like the current item doesn't exist anymore */
1392
            if ($old_previous != 0) {
1393
                // Next
1394
                $sql = "UPDATE $tbl_lp_item
1395
                        SET next_item_id = $old_next
1396
                        WHERE iid = $old_previous";
1397
                Database::query($sql);
1398
            }
1399
1400
            if (!empty($old_next)) {
1401
                // Previous
1402
                $sql = "UPDATE $tbl_lp_item
1403
                        SET previous_item_id = $old_previous
1404
                        WHERE iid = $old_next";
1405
                Database::query($sql);
1406
            }
1407
1408
            // display_order - 1 for every item with a display_order
1409
            // bigger then the display_order of the current item.
1410
            $sql = "UPDATE $tbl_lp_item
1411
                    SET display_order = display_order - 1
1412
                    WHERE
1413
                        c_id = $course_id AND
1414
                        display_order > $old_order AND
1415
                        lp_id = ".$this->lp_id." AND
1416
                        parent_item_id = $old_parent";
1417
            Database::query($sql);
1418
            /* END -- virtually remove the current item id */
1419
1420
            /* BEGIN -- update the current item id to his new location */
1421
            if ($previous == 0) {
1422
                // Select the data of the item that should come after the current item.
1423
                $sql = "SELECT id, display_order
1424
                        FROM $tbl_lp_item
1425
                        WHERE
1426
                            c_id = $course_id AND
1427
                            lp_id = ".$this->lp_id." AND
1428
                            parent_item_id = $parent AND
1429
                            previous_item_id = $previous";
1430
                $res_select_old = Database::query($sql);
1431
                $row_select_old = Database::fetch_array($res_select_old);
1432
1433
                // If the new parent didn't have children before.
1434
                if (Database::num_rows($res_select_old) == 0) {
1435
                    $new_next = 0;
1436
                    $new_order = 1;
1437
                } else {
1438
                    $new_next = $row_select_old['id'];
1439
                    $new_order = $row_select_old['display_order'];
1440
                }
1441
            } else {
1442
                // Select the data of the item that should come before the current item.
1443
                $sql = "SELECT next_item_id, display_order
1444
                        FROM $tbl_lp_item
1445
                        WHERE iid = $previous";
1446
                $res_select_old = Database::query($sql);
1447
                $row_select_old = Database::fetch_array($res_select_old);
1448
                $new_next = $row_select_old['next_item_id'];
1449
                $new_order = $row_select_old['display_order'] + 1;
1450
            }
1451
1452
            // TODO: htmlspecialchars to be checked for encoding related problems.
1453
            // Update the current item with the new data.
1454
            $sql = "UPDATE $tbl_lp_item
1455
                    SET
1456
                        title = '".Database::escape_string($title)."',
1457
                        description = '".Database::escape_string($description)."',
1458
                        parent_item_id = $parent,
1459
                        previous_item_id = $previous,
1460
                        next_item_id = $new_next,
1461
                        display_order = $new_order
1462
                        $audio_update_sql
1463
                    WHERE iid = $id";
1464
            Database::query($sql);
1465
1466
            if ($previous != 0) {
1467
                // Update the previous item's next_item_id.
1468
                $sql = "UPDATE $tbl_lp_item
1469
                        SET next_item_id = $id
1470
                        WHERE iid = $previous";
1471
                Database::query($sql);
1472
            }
1473
1474
            if (!empty($new_next)) {
1475
                // Update the next item's previous_item_id.
1476
                $sql = "UPDATE $tbl_lp_item
1477
                        SET previous_item_id = $id
1478
                        WHERE iid = $new_next";
1479
                Database::query($sql);
1480
            }
1481
1482
            if ($old_prerequisite != $prerequisites) {
1483
                $sql = "UPDATE $tbl_lp_item
1484
                        SET prerequisite = '$prerequisites'
1485
                        WHERE iid = $id";
1486
                Database::query($sql);
1487
            }
1488
1489
            if ($old_max_time_allowed != $max_time_allowed) {
1490
                // update max time allowed
1491
                $sql = "UPDATE $tbl_lp_item
1492
                        SET max_time_allowed = $max_time_allowed
1493
                        WHERE iid = $id";
1494
                Database::query($sql);
1495
            }
1496
1497
            // Update all the items with the same or a bigger display_order than the current item.
1498
            $sql = "UPDATE $tbl_lp_item
1499
                    SET display_order = display_order + 1
1500
                    WHERE
1501
                       c_id = $course_id AND
1502
                       lp_id = ".$this->get_id()." AND
1503
                       iid <> $id AND
1504
                       parent_item_id = $parent AND
1505
                       display_order >= $new_order";
1506
            Database::query($sql);
1507
        }
1508
1509
        if ($row_select['item_type'] == 'link') {
1510
            $link = new Link();
1511
            $linkId = $row_select['path'];
1512
            $link->updateLink($linkId, $url);
1513
        }
1514
    }
1515
1516
    /**
1517
     * Updates an item's prereq in place.
1518
     *
1519
     * @param int    $id              Element ID
1520
     * @param string $prerequisite_id Prerequisite Element ID
1521
     * @param int    $minScore        Prerequisite min score
1522
     * @param int    $maxScore        Prerequisite max score
1523
     *
1524
     * @return bool True on success, false on error
1525
     */
1526
    public function edit_item_prereq(
1527
        $id,
1528
        $prerequisite_id,
1529
        $minScore = 0,
1530
        $maxScore = 100
1531
    ) {
1532
        $id = (int) $id;
1533
        $prerequisite_id = (int) $prerequisite_id;
1534
1535
        if (empty($id)) {
1536
            return false;
1537
        }
1538
1539
        if (empty($minScore) || $minScore < 0) {
1540
            $minScore = 0;
1541
        }
1542
1543
        if (empty($maxScore) || $maxScore < 0) {
1544
            $maxScore = 100;
1545
        }
1546
1547
        $minScore = floatval($minScore);
1548
        $maxScore = floatval($maxScore);
1549
1550
        if (empty($prerequisite_id)) {
1551
            $prerequisite_id = 'NULL';
1552
            $minScore = 0;
1553
            $maxScore = 100;
1554
        }
1555
1556
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1557
        $sql = " UPDATE $tbl_lp_item
1558
                 SET
1559
                    prerequisite = $prerequisite_id ,
1560
                    prerequisite_min_score = $minScore ,
1561
                    prerequisite_max_score = $maxScore
1562
                 WHERE iid = $id";
1563
1564
        Database::query($sql);
1565
1566
        return true;
1567
    }
1568
1569
    /**
1570
     * Gets all the chapters belonging to the same parent as the item/chapter given
1571
     * Can also be called as abstract method.
1572
     *
1573
     * @deprecated not used
1574
     *
1575
     * @param int $id Item ID
1576
     *
1577
     * @return array A list of all the "brother items" (or an empty array on failure)
1578
     */
1579
    public function getSiblingDirectories($id)
1580
    {
1581
        $course_id = api_get_course_int_id();
1582
1583
        if (empty($id) || $id != strval(intval($id))) {
1584
            return [];
1585
        }
1586
1587
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1588
        $sql_parent = "SELECT * FROM $lp_item
1589
                       WHERE iid = $id AND item_type='dir'";
1590
        $res_parent = Database::query($sql_parent);
1591
        if (Database::num_rows($res_parent) > 0) {
1592
            $row_parent = Database::fetch_array($res_parent);
1593
            $parent = $row_parent['parent_item_id'];
1594
            $sql = "SELECT * FROM $lp_item
1595
                    WHERE
1596
                        parent_item_id = $parent AND
1597
                        iid = $id AND
1598
                        item_type='dir'
1599
                    ORDER BY display_order";
1600
            $res_bros = Database::query($sql);
1601
1602
            $list = [];
1603
            while ($row_bro = Database::fetch_array($res_bros)) {
1604
                $list[] = $row_bro;
1605
            }
1606
1607
            return $list;
1608
        }
1609
1610
        return [];
1611
    }
1612
1613
    /**
1614
     * Gets all the items belonging to the same parent as the item given
1615
     * Can also be called as abstract method.
1616
     *
1617
     * @deprecated not used
1618
     *
1619
     * @param int $id Item ID
1620
     *
1621
     * @return array A list of all the "brother items" (or an empty array on failure)
1622
     */
1623
    public function get_brother_items($id)
1624
    {
1625
        $course_id = api_get_course_int_id();
1626
1627
        if (empty($id) || $id != strval(intval($id))) {
1628
            return [];
1629
        }
1630
1631
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1632
        $sql_parent = "SELECT * FROM $lp_item
1633
                       WHERE iid = $id";
1634
        $res_parent = Database::query($sql_parent);
1635
        if (Database::num_rows($res_parent) > 0) {
1636
            $row_parent = Database::fetch_array($res_parent);
1637
            $parent = $row_parent['parent_item_id'];
1638
            $sql = "SELECT * FROM $lp_item
1639
                    WHERE c_id = $course_id AND parent_item_id = $parent
1640
                    ORDER BY display_order";
1641
            $res_bros = Database::query($sql);
1642
            $list = [];
1643
            while ($row_bro = Database::fetch_array($res_bros)) {
1644
                $list[] = $row_bro;
1645
            }
1646
1647
            return $list;
1648
        }
1649
1650
        return [];
1651
    }
1652
1653
    /**
1654
     * Get the specific prefix index terms of this learning path.
1655
     *
1656
     * @param string $prefix
1657
     *
1658
     * @return array Array of terms
1659
     */
1660
    public function get_common_index_terms_by_prefix($prefix)
1661
    {
1662
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1663
        $terms = get_specific_field_values_list_by_prefix(
1664
            $prefix,
1665
            $this->cc,
1666
            TOOL_LEARNPATH,
1667
            $this->lp_id
1668
        );
1669
        $prefix_terms = [];
1670
        if (!empty($terms)) {
1671
            foreach ($terms as $term) {
1672
                $prefix_terms[] = $term['value'];
1673
            }
1674
        }
1675
1676
        return $prefix_terms;
1677
    }
1678
1679
    /**
1680
     * Gets the number of items currently completed.
1681
     *
1682
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1683
     *
1684
     * @return int The number of items currently completed
1685
     */
1686
    public function get_complete_items_count($failedStatusException = false)
1687
    {
1688
        $i = 0;
1689
        $completedStatusList = [
1690
            'completed',
1691
            'passed',
1692
            'succeeded',
1693
            'browsed',
1694
        ];
1695
1696
        if (!$failedStatusException) {
1697
            $completedStatusList[] = 'failed';
1698
        }
1699
1700
        foreach ($this->items as $id => $dummy) {
1701
            // Trying failed and browsed considered "progressed" as well.
1702
            if ($this->items[$id]->status_is($completedStatusList) &&
1703
                $this->items[$id]->get_type() != 'dir'
1704
            ) {
1705
                $i++;
1706
            }
1707
        }
1708
1709
        return $i;
1710
    }
1711
1712
    /**
1713
     * Gets the current item ID.
1714
     *
1715
     * @return int The current learnpath item id
1716
     */
1717
    public function get_current_item_id()
1718
    {
1719
        $current = 0;
1720
        if (!empty($this->current)) {
1721
            $current = (int) $this->current;
1722
        }
1723
1724
        return $current;
1725
    }
1726
1727
    /**
1728
     * Force to get the first learnpath item id.
1729
     *
1730
     * @return int The current learnpath item id
1731
     */
1732
    public function get_first_item_id()
1733
    {
1734
        $current = 0;
1735
        if (is_array($this->ordered_items)) {
1736
            $current = $this->ordered_items[0];
1737
        }
1738
1739
        return $current;
1740
    }
1741
1742
    /**
1743
     * Gets the total number of items available for viewing in this SCORM.
1744
     *
1745
     * @return int The total number of items
1746
     */
1747
    public function get_total_items_count()
1748
    {
1749
        return count($this->items);
1750
    }
1751
1752
    /**
1753
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1754
     *
1755
     * @return int The total no-chapters number of items
1756
     */
1757
    public function getTotalItemsCountWithoutDirs()
1758
    {
1759
        $total = 0;
1760
        $typeListNotToCount = self::getChapterTypes();
1761
        foreach ($this->items as $temp2) {
1762
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1763
                $total++;
1764
            }
1765
        }
1766
1767
        return $total;
1768
    }
1769
1770
    /**
1771
     *  Sets the first element URL.
1772
     */
1773
    public function first()
1774
    {
1775
        if ($this->debug > 0) {
1776
            error_log('In learnpath::first()', 0);
1777
            error_log('$this->last_item_seen '.$this->last_item_seen);
1778
        }
1779
1780
        // Test if the last_item_seen exists and is not a dir.
1781
        if (count($this->ordered_items) == 0) {
1782
            $this->index = 0;
1783
        }
1784
1785
        if (!empty($this->last_item_seen) &&
1786
            !empty($this->items[$this->last_item_seen]) &&
1787
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1788
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1789
            //&& !$this->items[$this->last_item_seen]->is_done()
1790
        ) {
1791
            if ($this->debug > 2) {
1792
                error_log(
1793
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1794
                    $this->items[$this->last_item_seen]->get_type()
1795
                );
1796
            }
1797
            $index = -1;
1798
            foreach ($this->ordered_items as $myindex => $item_id) {
1799
                if ($item_id == $this->last_item_seen) {
1800
                    $index = $myindex;
1801
                    break;
1802
                }
1803
            }
1804
            if ($index == -1) {
1805
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1806
                if ($this->debug > 2) {
1807
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1808
                }
1809
1810
                return false;
1811
            } else {
1812
                $this->last = $this->last_item_seen;
1813
                $this->current = $this->last_item_seen;
1814
                $this->index = $index;
1815
            }
1816
        } else {
1817
            if ($this->debug > 2) {
1818
                error_log('In learnpath::first() - No last item seen', 0);
1819
            }
1820
            $index = 0;
1821
            // Loop through all ordered items and stop at the first item that is
1822
            // not a directory *and* that has not been completed yet.
1823
            while (!empty($this->ordered_items[$index]) &&
1824
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1825
                (
1826
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1827
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1828
                ) && $index < $this->max_ordered_items) {
1829
                $index++;
1830
            }
1831
1832
            $this->last = $this->current;
1833
            // current is
1834
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1835
            $this->index = $index;
1836
            if ($this->debug > 2) {
1837
                error_log('$index '.$index);
1838
                error_log('In learnpath::first() - No last item seen');
1839
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1840
            }
1841
        }
1842
        if ($this->debug > 2) {
1843
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1844
        }
1845
    }
1846
1847
    /**
1848
     * Gets the information about an item in a format usable as JavaScript to update
1849
     * the JS API by just printing this content into the <head> section of the message frame.
1850
     *
1851
     * @param int $item_id
1852
     *
1853
     * @return string
1854
     */
1855
    public function get_js_info($item_id = 0)
1856
    {
1857
        if ($this->debug > 0) {
1858
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1859
        }
1860
1861
        $info = '';
1862
        $item_id = (int) $item_id;
1863
1864
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1865
            //if item is defined, return values from DB
1866
            $oItem = $this->items[$item_id];
1867
            $info .= '<script language="javascript">';
1868
            $info .= "top.set_score(".$oItem->get_score().");\n";
1869
            $info .= "top.set_max(".$oItem->get_max().");\n";
1870
            $info .= "top.set_min(".$oItem->get_min().");\n";
1871
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1872
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1873
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1874
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1875
            $info .= "top.set_flag_synchronized();";
1876
            $info .= '</script>';
1877
            if ($this->debug > 2) {
1878
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1879
            }
1880
1881
            return $info;
1882
        } else {
1883
            // If item_id is empty, just update to default SCORM data.
1884
            $info .= '<script language="javascript">';
1885
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1886
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1887
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1888
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1889
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1890
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1891
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1892
            $info .= "top.set_flag_synchronized();";
1893
            $info .= '</script>';
1894
            if ($this->debug > 2) {
1895
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1896
            }
1897
1898
            return $info;
1899
        }
1900
    }
1901
1902
    /**
1903
     * Gets the js library from the database.
1904
     *
1905
     * @return string The name of the javascript library to be used
1906
     */
1907
    public function get_js_lib()
1908
    {
1909
        $lib = '';
1910
        if (!empty($this->js_lib)) {
1911
            $lib = $this->js_lib;
1912
        }
1913
1914
        return $lib;
1915
    }
1916
1917
    /**
1918
     * Gets the learnpath database ID.
1919
     *
1920
     * @return int Learnpath ID in the lp table
1921
     */
1922
    public function get_id()
1923
    {
1924
        if (!empty($this->lp_id)) {
1925
            return (int) $this->lp_id;
1926
        }
1927
1928
        return 0;
1929
    }
1930
1931
    /**
1932
     * Gets the last element URL.
1933
     *
1934
     * @return string URL to load into the viewer
1935
     */
1936
    public function get_last()
1937
    {
1938
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1939
        if (count($this->ordered_items) > 0) {
1940
            $this->index = count($this->ordered_items) - 1;
1941
1942
            return $this->ordered_items[$this->index];
1943
        }
1944
1945
        return false;
1946
    }
1947
1948
    /**
1949
     * Get the last element in the first level.
1950
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1951
     *
1952
     * @return mixed
1953
     */
1954
    public function getLastInFirstLevel()
1955
    {
1956
        try {
1957
            $lastId = Database::getManager()
1958
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1959
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1960
                ->setMaxResults(1)
1961
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1962
                ->getSingleScalarResult();
1963
1964
            return $lastId;
1965
        } catch (Exception $exception) {
1966
            return 0;
1967
        }
1968
    }
1969
1970
    /**
1971
     * Get the learning path name by id
1972
     *
1973
     * @param int $lpId
1974
     *
1975
     * @return mixed
1976
     */
1977
    public static function getLpNameById($lpId)
1978
    {
1979
        $em = Database::getManager();
1980
1981
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1982
            WHERE clp.iid = :iid')
1983
            ->setParameter('iid', $lpId)
1984
            ->getSingleScalarResult();
1985
    }
1986
1987
    /**
1988
     * Gets the navigation bar for the learnpath display screen.
1989
     *
1990
     * @param string $barId
1991
     *
1992
     * @return string The HTML string to use as a navigation bar
1993
     */
1994
    public function get_navigation_bar($barId = '')
1995
    {
1996
        if (empty($barId)) {
1997
            $barId = 'control-top';
1998
        }
1999
        $lpId = $this->lp_id;
2000
        $mycurrentitemid = $this->get_current_item_id();
2001
2002
        $reportingText = get_lang('Reporting');
2003
        $previousText = get_lang('ScormPrevious');
2004
        $nextText = get_lang('ScormNext');
2005
        $fullScreenText = get_lang('ScormExitFullScreen');
2006
2007
        $settings = api_get_configuration_value('lp_view_settings');
2008
        $display = isset($settings['display']) ? $settings['display'] : false;
2009
        $reportingIcon = '
2010
            <a class="icon-toolbar"
2011
                id="stats_link"
2012
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
2013
                onclick="window.parent.API.save_asset(); return true;"
2014
                target="content_name" title="'.$reportingText.'">
2015
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2016
            </a>';
2017
2018
        if (!empty($display)) {
2019
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2020
            if ($showReporting === false) {
2021
                $reportingIcon = '';
2022
            }
2023
        }
2024
2025
        $hideArrows = false;
2026
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
2027
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
2028
        }
2029
2030
        $previousIcon = '';
2031
        $nextIcon = '';
2032
        if ($hideArrows === false) {
2033
            $previousIcon = '
2034
                <a class="icon-toolbar" id="scorm-previous" href="#"
2035
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2036
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2037
                </a>';
2038
2039
            $nextIcon = '
2040
                <a class="icon-toolbar" id="scorm-next" href="#"
2041
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2042
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2043
                </a>';
2044
        }
2045
2046
        if ($this->mode === 'fullscreen') {
2047
            $navbar = '
2048
                  <span id="'.$barId.'" class="buttons">
2049
                    '.$reportingIcon.'
2050
                    '.$previousIcon.'
2051
                    '.$nextIcon.'
2052
                    <a class="icon-toolbar" id="view-embedded"
2053
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2054
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2055
                    </a>
2056
                  </span>';
2057
        } else {
2058
            $navbar = '
2059
                 <span id="'.$barId.'" class="buttons text-right">
2060
                    '.$reportingIcon.'
2061
                    '.$previousIcon.'
2062
                    '.$nextIcon.'
2063
                </span>';
2064
        }
2065
2066
        return $navbar;
2067
    }
2068
2069
    /**
2070
     * Gets the next resource in queue (url).
2071
     *
2072
     * @return string URL to load into the viewer
2073
     */
2074
    public function get_next_index()
2075
    {
2076
        if ($this->debug > 0) {
2077
            error_log('In learnpath::get_next_index()', 0);
2078
        }
2079
        // TODO
2080
        $index = $this->index;
2081
        $index++;
2082
        if ($this->debug > 2) {
2083
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2084
        }
2085
        while (
2086
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2087
            $index < $this->max_ordered_items
2088
        ) {
2089
            $index++;
2090
            if ($index == $this->max_ordered_items) {
2091
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2092
                    return $this->index;
2093
                } else {
2094
                    return $index;
2095
                }
2096
            }
2097
        }
2098
        if (empty($this->ordered_items[$index])) {
2099
            return $this->index;
2100
        }
2101
        if ($this->debug > 2) {
2102
            error_log('index is now '.$index, 0);
2103
        }
2104
2105
        return $index;
2106
    }
2107
2108
    /**
2109
     * Gets item_id for the next element.
2110
     *
2111
     * @return int Next item (DB) ID
2112
     */
2113
    public function get_next_item_id()
2114
    {
2115
        if ($this->debug > 0) {
2116
            error_log('In learnpath::get_next_item_id()', 0);
2117
        }
2118
        $new_index = $this->get_next_index();
2119
        if (!empty($new_index)) {
2120
            if (isset($this->ordered_items[$new_index])) {
2121
                if ($this->debug > 2) {
2122
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2123
                }
2124
2125
                return $this->ordered_items[$new_index];
2126
            }
2127
        }
2128
        if ($this->debug > 2) {
2129
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2130
        }
2131
2132
        return 0;
2133
    }
2134
2135
    /**
2136
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2137
     *
2138
     * Generally, the package provided is in the form of a zip file, so the function
2139
     * has been written to test a zip file. If not a zip, the function will return the
2140
     * default return value: ''
2141
     *
2142
     * @param string $file_path the path to the file
2143
     * @param string $file_name the original name of the file
2144
     *
2145
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2146
     */
2147
    public static function get_package_type($file_path, $file_name)
2148
    {
2149
        // Get name of the zip file without the extension.
2150
        $file_info = pathinfo($file_name);
2151
        $extension = $file_info['extension']; // Extension only.
2152
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2153
                'dll',
2154
                'exe',
2155
            ])) {
2156
            return 'oogie';
2157
        }
2158
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2159
                'dll',
2160
                'exe',
2161
            ])) {
2162
            return 'woogie';
2163
        }
2164
2165
        $zipFile = new PclZip($file_path);
2166
        // Check the zip content (real size and file extension).
2167
        $zipContentArray = $zipFile->listContent();
2168
        $package_type = '';
2169
        $manifest = '';
2170
        $aicc_match_crs = 0;
2171
        $aicc_match_au = 0;
2172
        $aicc_match_des = 0;
2173
        $aicc_match_cst = 0;
2174
2175
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2176
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2177
            foreach ($zipContentArray as $thisContent) {
2178
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2179
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2180
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2181
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2182
                    $package_type = 'scorm';
2183
                    break; // Exit the foreach loop.
2184
                } elseif (
2185
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2186
                    in_array(
2187
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2188
                        ['crs', 'au', 'des', 'cst']
2189
                    )
2190
                ) {
2191
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2192
                    switch ($ext) {
2193
                        case 'crs':
2194
                            $aicc_match_crs = 1;
2195
                            break;
2196
                        case 'au':
2197
                            $aicc_match_au = 1;
2198
                            break;
2199
                        case 'des':
2200
                            $aicc_match_des = 1;
2201
                            break;
2202
                        case 'cst':
2203
                            $aicc_match_cst = 1;
2204
                            break;
2205
                        default:
2206
                            break;
2207
                    }
2208
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2209
                } else {
2210
                    $package_type = '';
2211
                }
2212
            }
2213
        }
2214
2215
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2216
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2217
            $package_type = 'aicc';
2218
        }
2219
2220
        // Try with chamilo course builder
2221
        if (empty($package_type)) {
2222
            $package_type = 'chamilo';
2223
        }
2224
2225
        return $package_type;
2226
    }
2227
2228
    /**
2229
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2230
     *
2231
     * @return string URL to load into the viewer
2232
     */
2233
    public function get_previous_index()
2234
    {
2235
        $index = $this->index;
2236
        if (isset($this->ordered_items[$index - 1])) {
2237
            $index--;
2238
            while (isset($this->ordered_items[$index]) &&
2239
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2240
            ) {
2241
                $index--;
2242
                if ($index < 0) {
2243
                    return $this->index;
2244
                }
2245
            }
2246
        }
2247
2248
        return $index;
2249
    }
2250
2251
    /**
2252
     * Gets item_id for the next element.
2253
     *
2254
     * @return int Previous item (DB) ID
2255
     */
2256
    public function get_previous_item_id()
2257
    {
2258
        $index = $this->get_previous_index();
2259
2260
        return $this->ordered_items[$index];
2261
    }
2262
2263
    /**
2264
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2265
     *
2266
     * @param int    $lpItemId
2267
     * @param string $autostart
2268
     *
2269
     * @return string The mediaplayer HTML
2270
     */
2271
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2272
    {
2273
        $course_id = api_get_course_int_id();
2274
        $_course = api_get_course_info();
2275
        if (empty($_course)) {
2276
            return '';
2277
        }
2278
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2279
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2280
        $lpItemId = (int) $lpItemId;
2281
2282
        /** @var learnpathItem $item */
2283
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2284
        $itemViewId = 0;
2285
        if ($item) {
2286
            $itemViewId = (int) $item->db_item_view_id;
2287
        }
2288
2289
        // Getting all the information about the item.
2290
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2291
                FROM $tbl_lp_item as lpi
2292
                INNER JOIN $tbl_lp_item_view as lp_view
2293
                ON (lpi.iid = lp_view.lp_item_id)
2294
                WHERE
2295
                    lp_view.iid = $itemViewId AND
2296
                    lpi.iid = $lpItemId AND
2297
                    lp_view.c_id = $course_id";
2298
        $result = Database::query($sql);
2299
        $row = Database::fetch_assoc($result);
2300
        $output = '';
2301
2302
        if (!empty($row['audio'])) {
2303
            $list = $_SESSION['oLP']->get_toc();
2304
2305
            switch ($row['item_type']) {
2306
                case 'quiz':
2307
                    $type_quiz = false;
2308
                    foreach ($list as $toc) {
2309
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2310
                            $type_quiz = true;
2311
                        }
2312
                    }
2313
2314
                    if ($type_quiz) {
2315
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2316
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2317
                        } else {
2318
                            $autostart_audio = $autostart;
2319
                        }
2320
                    }
2321
                    break;
2322
                case TOOL_READOUT_TEXT:;
2323
                    $autostart_audio = 'false';
2324
                    break;
2325
                default:
2326
                    $autostart_audio = 'true';
2327
            }
2328
2329
            $courseInfo = api_get_course_info();
2330
            $audio = $row['audio'];
2331
2332
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2333
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2334
2335
            if (!file_exists($file)) {
2336
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2337
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2338
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2339
            }
2340
2341
            $player = Display::getMediaPlayer(
2342
                $file,
2343
                [
2344
                    'id' => 'lp_audio_media_player',
2345
                    'url' => $url,
2346
                    'autoplay' => $autostart_audio,
2347
                    'width' => '100%',
2348
                ]
2349
            );
2350
2351
            // The mp3 player.
2352
            $output = '<div id="container">';
2353
            $output .= $player;
2354
            $output .= '</div>';
2355
        }
2356
2357
        return $output;
2358
    }
2359
2360
    /**
2361
     * @param int   $studentId
2362
     * @param int   $prerequisite
2363
     * @param array $courseInfo
2364
     * @param int   $sessionId
2365
     *
2366
     * @return bool
2367
     */
2368
    public static function isBlockedByPrerequisite(
2369
        $studentId,
2370
        $prerequisite,
2371
        $courseInfo,
2372
        $sessionId
2373
    ) {
2374
        if (empty($courseInfo)) {
2375
            return false;
2376
        }
2377
2378
        $courseId = $courseInfo['real_id'];
2379
2380
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2381
        if ($allow) {
2382
            if (api_is_allowed_to_edit() ||
2383
                api_is_platform_admin(true) ||
2384
                api_is_drh() ||
2385
                api_is_coach($sessionId, $courseId, false)
2386
            ) {
2387
                return false;
2388
            }
2389
        }
2390
2391
        $isBlocked = false;
2392
        if (!empty($prerequisite)) {
2393
            $progress = self::getProgress(
2394
                $prerequisite,
2395
                $studentId,
2396
                $courseId,
2397
                $sessionId
2398
            );
2399
            if ($progress < 100) {
2400
                $isBlocked = true;
2401
            }
2402
2403
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2404
                // Block if it does not exceed minimum time
2405
                // Minimum time (in minutes) to pass the learning path
2406
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2407
2408
                if ($accumulateWorkTime > 0) {
2409
                    // Total time in course (sum of times in learning paths from course)
2410
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2411
2412
                    // Connect with the plugin_licences_course_session table
2413
                    // which indicates what percentage of the time applies
2414
                    // Minimum connection percentage
2415
                    $perc = 100;
2416
                    // Time from the course
2417
                    $tc = $accumulateWorkTimeTotal;
2418
2419
                    // Percentage of the learning paths
2420
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2421
                    // Minimum time for each learning path
2422
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2423
2424
                    // Spent time (in seconds) so far in the learning path
2425
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2426
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2427
2428
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2429
                        $isBlocked = true;
2430
                    }
2431
                }
2432
            }
2433
        }
2434
2435
        return $isBlocked;
2436
    }
2437
2438
    /**
2439
     * Checks if the learning path is visible for student after the progress
2440
     * of its prerequisite is completed, considering the time availability and
2441
     * the LP visibility.
2442
     *
2443
     * @param int   $lp_id
2444
     * @param int   $student_id
2445
     * @param array $courseInfo
2446
     * @param int   $sessionId
2447
     *
2448
     * @return bool
2449
     */
2450
    public static function is_lp_visible_for_student(
2451
        $lp_id,
2452
        $student_id,
2453
        $courseInfo = [],
2454
        $sessionId = 0
2455
    ) {
2456
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2457
        $lp_id = (int) $lp_id;
2458
        $sessionId = (int) $sessionId;
2459
2460
        if (empty($courseInfo)) {
2461
            return false;
2462
        }
2463
2464
        if (empty($sessionId)) {
2465
            $sessionId = api_get_session_id();
2466
        }
2467
2468
        $courseId = $courseInfo['real_id'];
2469
2470
        $itemInfo = api_get_item_property_info(
2471
            $courseId,
2472
            TOOL_LEARNPATH,
2473
            $lp_id,
2474
            $sessionId
2475
        );
2476
2477
        // If the item was deleted.
2478
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2479
            return false;
2480
        }
2481
2482
        // @todo remove this query and load the row info as a parameter
2483
        $table = Database::get_course_table(TABLE_LP_MAIN);
2484
        // Get current prerequisite
2485
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2486
                FROM $table
2487
                WHERE iid = $lp_id";
2488
        $rs = Database::query($sql);
2489
        $now = time();
2490
        if (Database::num_rows($rs) > 0) {
2491
            $row = Database::fetch_array($rs, 'ASSOC');
2492
            $prerequisite = $row['prerequisite'];
2493
            $is_visible = true;
2494
2495
            $isBlocked = self::isBlockedByPrerequisite(
2496
                $student_id,
2497
                $prerequisite,
2498
                $courseInfo,
2499
                $sessionId
2500
            );
2501
2502
            if ($isBlocked) {
2503
                $is_visible = false;
2504
            }
2505
2506
            // Also check the time availability of the LP
2507
            if ($is_visible) {
2508
                // Adding visibility restrictions
2509
                if (!empty($row['publicated_on'])) {
2510
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2511
                        $is_visible = false;
2512
                    }
2513
                }
2514
                // Blocking empty start times see BT#2800
2515
                global $_custom;
2516
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2517
                    $_custom['lps_hidden_when_no_start_date']
2518
                ) {
2519
                    if (empty($row['publicated_on'])) {
2520
                        $is_visible = false;
2521
                    }
2522
                }
2523
2524
                if (!empty($row['expired_on'])) {
2525
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2526
                        $is_visible = false;
2527
                    }
2528
                }
2529
            }
2530
2531
            if ($is_visible) {
2532
                $subscriptionSettings = self::getSubscriptionSettings();
2533
2534
                // Check if the subscription users/group to a LP is ON
2535
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2536
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2537
                ) {
2538
                    // Try group
2539
                    $is_visible = false;
2540
                    // Checking only the user visibility
2541
                    $userVisibility = api_get_item_visibility(
2542
                        $courseInfo,
2543
                        'learnpath',
2544
                        $row['id'],
2545
                        $sessionId,
2546
                        $student_id,
2547
                        'LearnpathSubscription'
2548
                    );
2549
2550
                    if ($userVisibility == 1) {
2551
                        $is_visible = true;
2552
                    } else {
2553
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2554
                        if (!empty($userGroups)) {
2555
                            foreach ($userGroups as $groupInfo) {
2556
                                $groupId = $groupInfo['iid'];
2557
                                $userVisibility = api_get_item_visibility(
2558
                                    $courseInfo,
2559
                                    'learnpath',
2560
                                    $row['id'],
2561
                                    $sessionId,
2562
                                    null,
2563
                                    'LearnpathSubscription',
2564
                                    $groupId
2565
                                );
2566
2567
                                if ($userVisibility == 1) {
2568
                                    $is_visible = true;
2569
                                    break;
2570
                                }
2571
                            }
2572
                        }
2573
                    }
2574
                }
2575
            }
2576
2577
            return $is_visible;
2578
        }
2579
2580
        return false;
2581
    }
2582
2583
    /**
2584
     * @param int $lpId
2585
     * @param int $userId
2586
     * @param int $courseId
2587
     * @param int $sessionId
2588
     *
2589
     * @return int
2590
     */
2591
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2592
    {
2593
        $lpId = (int) $lpId;
2594
        $userId = (int) $userId;
2595
        $courseId = (int) $courseId;
2596
        $sessionId = (int) $sessionId;
2597
2598
        $sessionCondition = api_get_session_condition($sessionId);
2599
        $table = Database::get_course_table(TABLE_LP_VIEW);
2600
        $sql = "SELECT progress FROM $table
2601
                WHERE
2602
                    c_id = $courseId AND
2603
                    lp_id = $lpId AND
2604
                    user_id = $userId $sessionCondition ";
2605
        $res = Database::query($sql);
2606
2607
        $progress = 0;
2608
        if (Database::num_rows($res) > 0) {
2609
            $row = Database::fetch_array($res);
2610
            $progress = (int) $row['progress'];
2611
        }
2612
2613
        return $progress;
2614
    }
2615
2616
    /**
2617
     * @param array $lpList
2618
     * @param int   $userId
2619
     * @param int   $courseId
2620
     * @param int   $sessionId
2621
     *
2622
     * @return array
2623
     */
2624
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2625
    {
2626
        $lpList = array_map('intval', $lpList);
2627
        if (empty($lpList)) {
2628
            return [];
2629
        }
2630
2631
        $lpList = implode("','", $lpList);
2632
2633
        $userId = (int) $userId;
2634
        $courseId = (int) $courseId;
2635
        $sessionId = (int) $sessionId;
2636
2637
        $sessionCondition = api_get_session_condition($sessionId);
2638
        $table = Database::get_course_table(TABLE_LP_VIEW);
2639
        $sql = "SELECT lp_id, progress FROM $table
2640
                WHERE
2641
                    c_id = $courseId AND
2642
                    lp_id IN ('".$lpList."') AND
2643
                    user_id = $userId $sessionCondition ";
2644
        $res = Database::query($sql);
2645
2646
        if (Database::num_rows($res) > 0) {
2647
            $list = [];
2648
            while ($row = Database::fetch_array($res)) {
2649
                $list[$row['lp_id']] = $row['progress'];
2650
            }
2651
2652
            return $list;
2653
        }
2654
2655
        return [];
2656
    }
2657
2658
    /**
2659
     * Displays a progress bar
2660
     * completed so far.
2661
     *
2662
     * @param int    $percentage Progress value to display
2663
     * @param string $text_add   Text to display near the progress value
2664
     *
2665
     * @return string HTML string containing the progress bar
2666
     */
2667
    public static function get_progress_bar($percentage = -1, $text_add = '')
2668
    {
2669
        $text = $percentage.$text_add;
2670
        $output = '<div class="progress">
2671
            <div id="progress_bar_value"
2672
                class="progress-bar progress-bar-success" role="progressbar"
2673
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2674
            '.$text.'
2675
            </div>
2676
        </div>';
2677
2678
        return $output;
2679
    }
2680
2681
    /**
2682
     * @param string $mode can be '%' or 'abs'
2683
     *                     otherwise this value will be used $this->progress_bar_mode
2684
     *
2685
     * @return string
2686
     */
2687
    public function getProgressBar($mode = null)
2688
    {
2689
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2690
2691
        return self::get_progress_bar($percentage, $text_add);
2692
    }
2693
2694
    /**
2695
     * Gets the progress bar info to display inside the progress bar.
2696
     * Also used by scorm_api.php.
2697
     *
2698
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2699
     *                     we display a number of completed elements per total elements
2700
     * @param int    $add  Additional steps to fake as completed
2701
     *
2702
     * @return array Percentage or number and symbol (% or /xx)
2703
     */
2704
    public function get_progress_bar_text($mode = '', $add = 0)
2705
    {
2706
        if ($this->debug > 0) {
2707
            error_log('In learnpath::get_progress_bar_text()', 0);
2708
        }
2709
        if (empty($mode)) {
2710
            $mode = $this->progress_bar_mode;
2711
        }
2712
        $total_items = $this->getTotalItemsCountWithoutDirs();
2713
        if ($this->debug > 2) {
2714
            error_log('Total items available in this learnpath: '.$total_items, 0);
2715
        }
2716
        $completeItems = $this->get_complete_items_count();
2717
        if ($this->debug > 2) {
2718
            error_log('Items completed so far: '.$completeItems, 0);
2719
        }
2720
        if ($add != 0) {
2721
            $completeItems += $add;
2722
            if ($this->debug > 2) {
2723
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2724
            }
2725
        }
2726
        $text = '';
2727
        if ($completeItems > $total_items) {
2728
            $completeItems = $total_items;
2729
        }
2730
        $percentage = 0;
2731
        if ($mode == '%') {
2732
            if ($total_items > 0) {
2733
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2734
            } else {
2735
                $percentage = 0;
2736
            }
2737
            $percentage = number_format($percentage, 0);
2738
            $text = '%';
2739
        } elseif ($mode == 'abs') {
2740
            $percentage = $completeItems;
2741
            $text = '/'.$total_items;
2742
        }
2743
2744
        return [
2745
            $percentage,
2746
            $text,
2747
        ];
2748
    }
2749
2750
    /**
2751
     * Gets the progress bar mode.
2752
     *
2753
     * @return string The progress bar mode attribute
2754
     */
2755
    public function get_progress_bar_mode()
2756
    {
2757
        if (!empty($this->progress_bar_mode)) {
2758
            return $this->progress_bar_mode;
2759
        }
2760
2761
        return '%';
2762
    }
2763
2764
    /**
2765
     * Gets the learnpath theme (remote or local).
2766
     *
2767
     * @return string Learnpath theme
2768
     */
2769
    public function get_theme()
2770
    {
2771
        if (!empty($this->theme)) {
2772
            return $this->theme;
2773
        }
2774
2775
        return '';
2776
    }
2777
2778
    /**
2779
     * Gets the learnpath session id.
2780
     *
2781
     * @return int
2782
     */
2783
    public function get_lp_session_id()
2784
    {
2785
        if (!empty($this->lp_session_id)) {
2786
            return (int) $this->lp_session_id;
2787
        }
2788
2789
        return 0;
2790
    }
2791
2792
    /**
2793
     * Gets the learnpath image.
2794
     *
2795
     * @return string Web URL of the LP image
2796
     */
2797
    public function get_preview_image()
2798
    {
2799
        if (!empty($this->preview_image)) {
2800
            return $this->preview_image;
2801
        }
2802
2803
        return '';
2804
    }
2805
2806
    /**
2807
     * @param string $size
2808
     * @param string $path_type
2809
     *
2810
     * @return bool|string
2811
     */
2812
    public function get_preview_image_path($size = null, $path_type = 'web')
2813
    {
2814
        $preview_image = $this->get_preview_image();
2815
        if (isset($preview_image) && !empty($preview_image)) {
2816
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2817
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2818
2819
            if (isset($size)) {
2820
                $info = pathinfo($preview_image);
2821
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2822
2823
                if (file_exists($image_sys_path.$image_custom_size)) {
2824
                    if ($path_type == 'web') {
2825
                        return $image_path.$image_custom_size;
2826
                    } else {
2827
                        return $image_sys_path.$image_custom_size;
2828
                    }
2829
                }
2830
            } else {
2831
                if ($path_type == 'web') {
2832
                    return $image_path.$preview_image;
2833
                } else {
2834
                    return $image_sys_path.$preview_image;
2835
                }
2836
            }
2837
        }
2838
2839
        return false;
2840
    }
2841
2842
    /**
2843
     * Gets the learnpath author.
2844
     *
2845
     * @return string LP's author
2846
     */
2847
    public function get_author()
2848
    {
2849
        if (!empty($this->author)) {
2850
            return $this->author;
2851
        }
2852
2853
        return '';
2854
    }
2855
2856
    /**
2857
     * Gets hide table of contents.
2858
     *
2859
     * @return int
2860
     */
2861
    public function getHideTableOfContents()
2862
    {
2863
        return (int) $this->hide_toc_frame;
2864
    }
2865
2866
    /**
2867
     * Generate a new prerequisites string for a given item. If this item was a sco and
2868
     * its prerequisites were strings (instead of IDs), then transform those strings into
2869
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2870
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2871
     * same rule as the scormExport() method.
2872
     *
2873
     * @param int $item_id Item ID
2874
     *
2875
     * @return string Prerequisites string ready for the export as SCORM
2876
     */
2877
    public function get_scorm_prereq_string($item_id)
2878
    {
2879
        if ($this->debug > 0) {
2880
            error_log('In learnpath::get_scorm_prereq_string()');
2881
        }
2882
        if (!is_object($this->items[$item_id])) {
2883
            return false;
2884
        }
2885
        /** @var learnpathItem $oItem */
2886
        $oItem = $this->items[$item_id];
2887
        $prereq = $oItem->get_prereq_string();
2888
2889
        if (empty($prereq)) {
2890
            return '';
2891
        }
2892
        if (preg_match('/^\d+$/', $prereq) &&
2893
            isset($this->items[$prereq]) &&
2894
            is_object($this->items[$prereq])
2895
        ) {
2896
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2897
            // then simply return it (with the ITEM_ prefix).
2898
            //return 'ITEM_' . $prereq;
2899
            return $this->items[$prereq]->ref;
2900
        } else {
2901
            if (isset($this->refs_list[$prereq])) {
2902
                // It's a simple string item from which the ID can be found in the refs list,
2903
                // so we can transform it directly to an ID for export.
2904
                return $this->items[$this->refs_list[$prereq]]->ref;
2905
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2906
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2907
            } else {
2908
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2909
                // and replace them, one by one, by the internal IDs (chamilo db)
2910
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2911
                // by a space as well.
2912
                $find = [
2913
                    '&',
2914
                    '|',
2915
                    '~',
2916
                    '=',
2917
                    '<>',
2918
                    '{',
2919
                    '}',
2920
                    '*',
2921
                    '(',
2922
                    ')',
2923
                ];
2924
                $replace = [
2925
                    ' ',
2926
                    ' ',
2927
                    ' ',
2928
                    ' ',
2929
                    ' ',
2930
                    ' ',
2931
                    ' ',
2932
                    ' ',
2933
                    ' ',
2934
                    ' ',
2935
                ];
2936
                $prereq_mod = str_replace($find, $replace, $prereq);
2937
                $ids = explode(' ', $prereq_mod);
2938
                foreach ($ids as $id) {
2939
                    $id = trim($id);
2940
                    if (isset($this->refs_list[$id])) {
2941
                        $prereq = preg_replace(
2942
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2943
                            'ITEM_'.$this->refs_list[$id],
2944
                            $prereq
2945
                        );
2946
                    }
2947
                }
2948
2949
                return $prereq;
2950
            }
2951
        }
2952
    }
2953
2954
    /**
2955
     * Returns the XML DOM document's node.
2956
     *
2957
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2958
     * @param string   $id       The identifier to look for
2959
     *
2960
     * @return mixed The reference to the element found with that identifier. False if not found
2961
     */
2962
    public function get_scorm_xml_node(&$children, $id)
2963
    {
2964
        for ($i = 0; $i < $children->length; $i++) {
2965
            $item_temp = $children->item($i);
2966
            if ($item_temp->nodeName == 'item') {
2967
                if ($item_temp->getAttribute('identifier') == $id) {
2968
                    return $item_temp;
2969
                }
2970
            }
2971
            $subchildren = $item_temp->childNodes;
2972
            if ($subchildren && $subchildren->length > 0) {
2973
                $val = $this->get_scorm_xml_node($subchildren, $id);
2974
                if (is_object($val)) {
2975
                    return $val;
2976
                }
2977
            }
2978
        }
2979
2980
        return false;
2981
    }
2982
2983
    /**
2984
     * Gets the status list for all LP's items.
2985
     *
2986
     * @return array Array of [index] => [item ID => current status]
2987
     */
2988
    public function get_items_status_list()
2989
    {
2990
        $list = [];
2991
        foreach ($this->ordered_items as $item_id) {
2992
            $list[] = [
2993
                $item_id => $this->items[$item_id]->get_status(),
2994
            ];
2995
        }
2996
2997
        return $list;
2998
    }
2999
3000
    /**
3001
     * Return the number of interactions for the given learnpath Item View ID.
3002
     * This method can be used as static.
3003
     *
3004
     * @param int $lp_iv_id  Item View ID
3005
     * @param int $course_id course id
3006
     *
3007
     * @return int
3008
     */
3009
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
3010
    {
3011
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3012
        $lp_iv_id = (int) $lp_iv_id;
3013
        $course_id = (int) $course_id;
3014
3015
        $sql = "SELECT count(*) FROM $table
3016
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3017
        $res = Database::query($sql);
3018
        $num = 0;
3019
        if (Database::num_rows($res)) {
3020
            $row = Database::fetch_array($res);
3021
            $num = $row[0];
3022
        }
3023
3024
        return $num;
3025
    }
3026
3027
    /**
3028
     * Return the interactions as an array for the given lp_iv_id.
3029
     * This method can be used as static.
3030
     *
3031
     * @param int $lp_iv_id Learnpath Item View ID
3032
     *
3033
     * @return array
3034
     *
3035
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
3036
     */
3037
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
3038
    {
3039
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3040
        $list = [];
3041
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3042
        $lp_iv_id = (int) $lp_iv_id;
3043
3044
        if (empty($lp_iv_id) || empty($course_id)) {
3045
            return [];
3046
        }
3047
3048
        $sql = "SELECT * FROM $table
3049
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
3050
                ORDER BY order_id ASC";
3051
        $res = Database::query($sql);
3052
        $num = Database::num_rows($res);
3053
        if ($num > 0) {
3054
            $list[] = [
3055
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3056
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
3057
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
3058
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
3059
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
3060
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
3061
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
3062
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
3063
                'student_response_formatted' => '',
3064
            ];
3065
            while ($row = Database::fetch_array($res)) {
3066
                $studentResponseFormatted = urldecode($row['student_response']);
3067
                $content_student_response = explode('__|', $studentResponseFormatted);
3068
                if (count($content_student_response) > 0) {
3069
                    if (count($content_student_response) >= 3) {
3070
                        // Pop the element off the end of array.
3071
                        array_pop($content_student_response);
3072
                    }
3073
                    $studentResponseFormatted = implode(',', $content_student_response);
3074
                }
3075
3076
                $list[] = [
3077
                    'order_id' => $row['order_id'] + 1,
3078
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
3079
                    'type' => $row['interaction_type'],
3080
                    'time' => $row['completion_time'],
3081
                    'correct_responses' => '', // Hide correct responses from students.
3082
                    'student_response' => $row['student_response'],
3083
                    'result' => $row['result'],
3084
                    'latency' => $row['latency'],
3085
                    'student_response_formatted' => $studentResponseFormatted,
3086
                ];
3087
            }
3088
        }
3089
3090
        return $list;
3091
    }
3092
3093
    /**
3094
     * Return the number of objectives for the given learnpath Item View ID.
3095
     * This method can be used as static.
3096
     *
3097
     * @param int $lp_iv_id  Item View ID
3098
     * @param int $course_id Course ID
3099
     *
3100
     * @return int Number of objectives
3101
     */
3102
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3103
    {
3104
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3105
        $course_id = (int) $course_id;
3106
        $lp_iv_id = (int) $lp_iv_id;
3107
        $sql = "SELECT count(*) FROM $table
3108
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3109
        //@todo seems that this always returns 0
3110
        $res = Database::query($sql);
3111
        $num = 0;
3112
        if (Database::num_rows($res)) {
3113
            $row = Database::fetch_array($res);
3114
            $num = $row[0];
3115
        }
3116
3117
        return $num;
3118
    }
3119
3120
    /**
3121
     * Return the objectives as an array for the given lp_iv_id.
3122
     * This method can be used as static.
3123
     *
3124
     * @param int $lpItemViewId Learnpath Item View ID
3125
     * @param int $course_id
3126
     *
3127
     * @return array
3128
     *
3129
     * @todo    Translate labels
3130
     */
3131
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3132
    {
3133
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3134
        $lpItemViewId = (int) $lpItemViewId;
3135
3136
        if (empty($course_id) || empty($lpItemViewId)) {
3137
            return [];
3138
        }
3139
3140
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3141
        $sql = "SELECT * FROM $table
3142
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3143
                ORDER BY order_id ASC";
3144
        $res = Database::query($sql);
3145
        $num = Database::num_rows($res);
3146
        $list = [];
3147
        if ($num > 0) {
3148
            $list[] = [
3149
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3150
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3151
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3152
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3153
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3154
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3155
            ];
3156
            while ($row = Database::fetch_array($res)) {
3157
                $list[] = [
3158
                    'order_id' => $row['order_id'] + 1,
3159
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3160
                    'score_raw' => $row['score_raw'],
3161
                    'score_max' => $row['score_max'],
3162
                    'score_min' => $row['score_min'],
3163
                    'status' => $row['status'],
3164
                ];
3165
            }
3166
        }
3167
3168
        return $list;
3169
    }
3170
3171
    /**
3172
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3173
     * used by get_html_toc() to be ready to display.
3174
     *
3175
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3176
     */
3177
    public function get_toc()
3178
    {
3179
        $toc = [];
3180
        foreach ($this->ordered_items as $item_id) {
3181
            if ($this->debug > 2) {
3182
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3183
            }
3184
            // TODO: Change this link generation and use new function instead.
3185
            $toc[] = [
3186
                'id' => $item_id,
3187
                'title' => $this->items[$item_id]->get_title(),
3188
                'status' => $this->items[$item_id]->get_status(),
3189
                'level' => $this->items[$item_id]->get_level(),
3190
                'type' => $this->items[$item_id]->get_type(),
3191
                'description' => $this->items[$item_id]->get_description(),
3192
                'path' => $this->items[$item_id]->get_path(),
3193
                'parent' => $this->items[$item_id]->get_parent(),
3194
            ];
3195
        }
3196
        if ($this->debug > 2) {
3197
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3198
        }
3199
3200
        return $toc;
3201
    }
3202
3203
    /**
3204
     * Generate and return the table of contents for this learnpath. The JS
3205
     * table returned is used inside of scorm_api.php.
3206
     *
3207
     * @param string $varname
3208
     *
3209
     * @return string A JS array variable construction
3210
     */
3211
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3212
    {
3213
        $toc = $varname.' = new Array();';
3214
        foreach ($this->ordered_items as $item_id) {
3215
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3216
        }
3217
3218
        return $toc;
3219
    }
3220
3221
    /**
3222
     * Gets the learning path type.
3223
     *
3224
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3225
     *
3226
     * @return mixed Type ID or name, depending on the parameter
3227
     */
3228
    public function get_type($get_name = false)
3229
    {
3230
        $res = false;
3231
        if (!empty($this->type) && (!$get_name)) {
3232
            $res = $this->type;
3233
        }
3234
3235
        return $res;
3236
    }
3237
3238
    /**
3239
     * Gets the learning path type as static method.
3240
     *
3241
     * @param int $lp_id
3242
     *
3243
     * @return mixed Type ID or name, depending on the parameter
3244
     */
3245
    public static function get_type_static($lp_id = 0)
3246
    {
3247
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3248
        $lp_id = (int) $lp_id;
3249
        $sql = "SELECT lp_type FROM $tbl_lp
3250
                WHERE iid = $lp_id";
3251
        $res = Database::query($sql);
3252
        if ($res === false) {
3253
            return null;
3254
        }
3255
        if (Database::num_rows($res) <= 0) {
3256
            return null;
3257
        }
3258
        $row = Database::fetch_array($res);
3259
3260
        return $row['lp_type'];
3261
    }
3262
3263
    /**
3264
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3265
     * This method can be used as abstract and is recursive.
3266
     *
3267
     * @param int $lp        Learnpath ID
3268
     * @param int $parent    Parent ID of the items to look for
3269
     * @param int $course_id
3270
     *
3271
     * @return array Ordered list of item IDs (empty array on error)
3272
     */
3273
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3274
    {
3275
        if (empty($course_id)) {
3276
            $course_id = api_get_course_int_id();
3277
        } else {
3278
            $course_id = (int) $course_id;
3279
        }
3280
        $list = [];
3281
3282
        if (empty($lp)) {
3283
            return $list;
3284
        }
3285
3286
        $lp = (int) $lp;
3287
        $parent = (int) $parent;
3288
3289
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3290
        $sql = "SELECT iid FROM $tbl_lp_item
3291
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3292
                ORDER BY display_order";
3293
3294
        $res = Database::query($sql);
3295
        while ($row = Database::fetch_array($res)) {
3296
            $sublist = self::get_flat_ordered_items_list(
3297
                $lp,
3298
                $row['iid'],
3299
                $course_id
3300
            );
3301
            $list[] = $row['iid'];
3302
            foreach ($sublist as $item) {
3303
                $list[] = $item;
3304
            }
3305
        }
3306
3307
        return $list;
3308
    }
3309
3310
    /**
3311
     * @return array
3312
     */
3313
    public static function getChapterTypes()
3314
    {
3315
        return [
3316
            'dir',
3317
        ];
3318
    }
3319
3320
    /**
3321
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3322
     *
3323
     * @param $tree
3324
     *
3325
     * @return array HTML TOC ready to display
3326
     */
3327
    public function getParentToc($tree)
3328
    {
3329
        if (empty($tree)) {
3330
            $tree = $this->get_toc();
3331
        }
3332
        $dirTypes = self::getChapterTypes();
3333
        $myCurrentId = $this->get_current_item_id();
3334
        $listParent = [];
3335
        $listChildren = [];
3336
        $listNotParent = [];
3337
        $list = [];
3338
        foreach ($tree as $subtree) {
3339
            if (in_array($subtree['type'], $dirTypes)) {
3340
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3341
                $subtree['children'] = $listChildren;
3342
                if (!empty($subtree['children'])) {
3343
                    foreach ($subtree['children'] as $subItem) {
3344
                        if ($subItem['id'] == $this->current) {
3345
                            $subtree['parent_current'] = 'in';
3346
                            $subtree['current'] = 'on';
3347
                        }
3348
                    }
3349
                }
3350
                $listParent[] = $subtree;
3351
            }
3352
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3353
                $classStatus = [
3354
                    'not attempted' => 'scorm_not_attempted',
3355
                    'incomplete' => 'scorm_not_attempted',
3356
                    'failed' => 'scorm_failed',
3357
                    'completed' => 'scorm_completed',
3358
                    'passed' => 'scorm_completed',
3359
                    'succeeded' => 'scorm_completed',
3360
                    'browsed' => 'scorm_completed',
3361
                ];
3362
3363
                if (isset($classStatus[$subtree['status']])) {
3364
                    $cssStatus = $classStatus[$subtree['status']];
3365
                }
3366
3367
                $title = Security::remove_XSS($subtree['title']);
3368
                unset($subtree['title']);
3369
3370
                if (empty($title)) {
3371
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3372
                }
3373
                $classStyle = null;
3374
                if ($subtree['id'] == $this->current) {
3375
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3376
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3377
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3378
                }
3379
                $subtree['title'] = $title;
3380
                $subtree['class'] = $classStyle.' '.$cssStatus;
3381
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3382
                $subtree['current_id'] = $myCurrentId;
3383
                $listNotParent[] = $subtree;
3384
            }
3385
        }
3386
3387
        $list['are_parents'] = $listParent;
3388
        $list['not_parents'] = $listNotParent;
3389
3390
        return $list;
3391
    }
3392
3393
    /**
3394
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3395
     *
3396
     * @param array $tree
3397
     * @param int   $id
3398
     * @param bool  $parent
3399
     *
3400
     * @return array HTML TOC ready to display
3401
     */
3402
    public function getChildrenToc($tree, $id, $parent = true)
3403
    {
3404
        if (empty($tree)) {
3405
            $tree = $this->get_toc();
3406
        }
3407
3408
        $dirTypes = self::getChapterTypes();
3409
        $mycurrentitemid = $this->get_current_item_id();
3410
        $list = [];
3411
        $classStatus = [
3412
            'not attempted' => 'scorm_not_attempted',
3413
            'incomplete' => 'scorm_not_attempted',
3414
            'failed' => 'scorm_failed',
3415
            'completed' => 'scorm_completed',
3416
            'passed' => 'scorm_completed',
3417
            'succeeded' => 'scorm_completed',
3418
            'browsed' => 'scorm_completed',
3419
        ];
3420
3421
        foreach ($tree as $subtree) {
3422
            $subtree['tree'] = null;
3423
3424
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3425
                if ($subtree['id'] == $this->current) {
3426
                    $subtree['current'] = 'active';
3427
                } else {
3428
                    $subtree['current'] = null;
3429
                }
3430
                if (isset($classStatus[$subtree['status']])) {
3431
                    $cssStatus = $classStatus[$subtree['status']];
3432
                }
3433
3434
                $title = Security::remove_XSS($subtree['title']);
3435
                unset($subtree['title']);
3436
                if (empty($title)) {
3437
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3438
                }
3439
3440
                $classStyle = null;
3441
                if ($subtree['id'] == $this->current) {
3442
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3443
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3444
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3445
                }
3446
3447
                if (in_array($subtree['type'], $dirTypes)) {
3448
                    $subtree['title'] = stripslashes($title);
3449
                } else {
3450
                    $subtree['title'] = $title;
3451
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3452
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3453
                    $subtree['current_id'] = $mycurrentitemid;
3454
                }
3455
                $list[] = $subtree;
3456
            }
3457
        }
3458
3459
        return $list;
3460
    }
3461
3462
    /**
3463
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3464
     *
3465
     * @param array $toc_list
3466
     *
3467
     * @return array HTML TOC ready to display
3468
     */
3469
    public function getListArrayToc($toc_list = [])
3470
    {
3471
        if (empty($toc_list)) {
3472
            $toc_list = $this->get_toc();
3473
        }
3474
        // Temporary variables.
3475
        $mycurrentitemid = $this->get_current_item_id();
3476
        $list = [];
3477
        $arrayList = [];
3478
        $classStatus = [
3479
            'not attempted' => 'scorm_not_attempted',
3480
            'incomplete' => 'scorm_not_attempted',
3481
            'failed' => 'scorm_failed',
3482
            'completed' => 'scorm_completed',
3483
            'passed' => 'scorm_completed',
3484
            'succeeded' => 'scorm_completed',
3485
            'browsed' => 'scorm_completed',
3486
        ];
3487
3488
        foreach ($toc_list as $item) {
3489
            $list['id'] = $item['id'];
3490
            $list['status'] = $item['status'];
3491
            $cssStatus = null;
3492
3493
            if (isset($classStatus[$item['status']])) {
3494
                $cssStatus = $classStatus[$item['status']];
3495
            }
3496
3497
            $classStyle = ' ';
3498
            $dirTypes = self::getChapterTypes();
3499
3500
            if (in_array($item['type'], $dirTypes)) {
3501
                $classStyle = 'scorm_item_section ';
3502
            }
3503
            if ($item['id'] == $this->current) {
3504
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3505
            } elseif (!in_array($item['type'], $dirTypes)) {
3506
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3507
            }
3508
            $title = $item['title'];
3509
            if (empty($title)) {
3510
                $title = self::rl_get_resource_name(
3511
                    api_get_course_id(),
3512
                    $this->get_id(),
3513
                    $item['id']
3514
                );
3515
            }
3516
            $title = Security::remove_XSS($item['title']);
3517
3518
            if (empty($item['description'])) {
3519
                $list['description'] = $title;
3520
            } else {
3521
                $list['description'] = $item['description'];
3522
            }
3523
3524
            $list['class'] = $classStyle.' '.$cssStatus;
3525
            $list['level'] = $item['level'];
3526
            $list['type'] = $item['type'];
3527
3528
            if (in_array($item['type'], $dirTypes)) {
3529
                $list['css_level'] = 'level_'.$item['level'];
3530
            } else {
3531
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3532
            }
3533
3534
            if (in_array($item['type'], $dirTypes)) {
3535
                $list['title'] = stripslashes($title);
3536
            } else {
3537
                $list['title'] = stripslashes($title);
3538
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3539
                $list['current_id'] = $mycurrentitemid;
3540
            }
3541
            $arrayList[] = $list;
3542
        }
3543
3544
        return $arrayList;
3545
    }
3546
3547
    /**
3548
     * Returns an HTML-formatted string ready to display with teacher buttons
3549
     * in LP view menu.
3550
     *
3551
     * @return string HTML TOC ready to display
3552
     */
3553
    public function get_teacher_toc_buttons()
3554
    {
3555
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3556
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3557
        $html = '';
3558
        if ($isAllow && $hideIcons == false) {
3559
            if ($this->get_lp_session_id() == api_get_session_id()) {
3560
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3561
                $html .= '<div class="btn-group">';
3562
                $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'>".
3563
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3564
                $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'>".
3565
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3566
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3567
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3568
                $html .= '</div>';
3569
                $html .= '</div>';
3570
            }
3571
        }
3572
3573
        return $html;
3574
    }
3575
3576
    /**
3577
     * Gets the learnpath maker name - generally the editor's name.
3578
     *
3579
     * @return string Learnpath maker name
3580
     */
3581
    public function get_maker()
3582
    {
3583
        if (!empty($this->maker)) {
3584
            return $this->maker;
3585
        }
3586
3587
        return '';
3588
    }
3589
3590
    /**
3591
     * Gets the learnpath name/title.
3592
     *
3593
     * @return string Learnpath name/title
3594
     */
3595
    public function get_name()
3596
    {
3597
        if (!empty($this->name)) {
3598
            return $this->name;
3599
        }
3600
3601
        return 'N/A';
3602
    }
3603
3604
    /**
3605
     * Gets a link to the resource from the present location, depending on item ID.
3606
     *
3607
     * @param string $type         Type of link expected
3608
     * @param int    $item_id      Learnpath item ID
3609
     * @param bool   $provided_toc
3610
     *
3611
     * @return string $provided_toc Link to the lp_item resource
3612
     */
3613
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3614
    {
3615
        $course_id = $this->get_course_int_id();
3616
        if ($this->debug > 0) {
3617
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3618
        }
3619
        if (empty($item_id)) {
3620
            if ($this->debug > 2) {
3621
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3622
                error_log('using current: '.$this->get_current_item_id(), 0);
3623
            }
3624
            $item_id = $this->get_current_item_id();
3625
3626
            if (empty($item_id)) {
3627
                if ($this->debug > 2) {
3628
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3629
                }
3630
                //still empty, this means there was no item_id given and we are not in an object context or
3631
                //the object property is empty, return empty link
3632
                $this->first();
3633
3634
                return '';
3635
            }
3636
        }
3637
3638
        $file = '';
3639
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3640
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3641
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3642
        $item_id = (int) $item_id;
3643
3644
        $sql = "SELECT
3645
                    l.lp_type as ltype,
3646
                    l.path as lpath,
3647
                    li.item_type as litype,
3648
                    li.path as lipath,
3649
                    li.parameters as liparams
3650
        		FROM $lp_table l
3651
                INNER JOIN $lp_item_table li
3652
                ON (li.lp_id = l.iid)
3653
        		WHERE
3654
        		    li.iid = $item_id
3655
        		";
3656
        if ($this->debug > 2) {
3657
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3658
        }
3659
        $res = Database::query($sql);
3660
        if (Database::num_rows($res) > 0) {
3661
            $row = Database::fetch_array($res);
3662
            $lp_type = $row['ltype'];
3663
            $lp_path = $row['lpath'];
3664
            $lp_item_type = $row['litype'];
3665
            $lp_item_path = $row['lipath'];
3666
            $lp_item_params = $row['liparams'];
3667
3668
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3669
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3670
            }
3671
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3672
            if ($type === 'http') {
3673
                //web path
3674
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3675
            } else {
3676
                $course_path = $sys_course_path; //system path
3677
            }
3678
3679
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3680
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3681
            if (in_array(
3682
                $lp_item_type,
3683
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3684
            )
3685
            ) {
3686
                $lp_type = 1;
3687
            }
3688
3689
            if ($this->debug > 2) {
3690
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3691
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3692
            }
3693
3694
            // Now go through the specific cases to get the end of the path
3695
            // @todo Use constants instead of int values.
3696
            switch ($lp_type) {
3697
                case 1:
3698
                    $file = self::rl_get_resource_link_for_learnpath(
3699
                        $course_id,
3700
                        $this->get_id(),
3701
                        $item_id,
3702
                        $this->get_view_id()
3703
                    );
3704
                    if ($this->debug > 0) {
3705
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3706
                    }
3707
                    switch ($lp_item_type) {
3708
                        case 'document':
3709
                            // Shows a button to download the file instead of just downloading the file directly.
3710
                            $documentPathInfo = pathinfo($file);
3711
                            if (isset($documentPathInfo['extension'])) {
3712
                                $parsed = parse_url($documentPathInfo['extension']);
3713
                                if (isset($parsed['path'])) {
3714
                                    $extension = $parsed['path'];
3715
                                    $extensionsToDownload = [
3716
                                        'zip',
3717
                                        'ppt',
3718
                                        'pptx',
3719
                                        'ods',
3720
                                        'xlsx',
3721
                                        'xls',
3722
                                        'csv',
3723
                                        'doc',
3724
                                        'docx',
3725
                                        'dot',
3726
                                    ];
3727
3728
                                    if (in_array($extension, $extensionsToDownload)) {
3729
                                        $file = api_get_path(WEB_CODE_PATH).
3730
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3731
                                    }
3732
                                }
3733
                            }
3734
                            break;
3735
                        case 'dir':
3736
                            $file = 'lp_content.php?type=dir';
3737
                            break;
3738
                        case 'link':
3739
                            if (Link::is_youtube_link($file)) {
3740
                                $src = Link::get_youtube_video_id($file);
3741
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3742
                            } elseif (Link::isVimeoLink($file)) {
3743
                                $src = Link::getVimeoLinkId($file);
3744
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3745
                            } else {
3746
                                // If the current site is HTTPS and the link is
3747
                                // HTTP, browsers will refuse opening the link
3748
                                $urlId = api_get_current_access_url_id();
3749
                                $url = api_get_access_url($urlId, false);
3750
                                $protocol = substr($url['url'], 0, 5);
3751
                                if ($protocol === 'https') {
3752
                                    $linkProtocol = substr($file, 0, 5);
3753
                                    if ($linkProtocol === 'http:') {
3754
                                        //this is the special intervention case
3755
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3756
                                    }
3757
                                }
3758
                            }
3759
                            break;
3760
                        case 'quiz':
3761
                            // Check how much attempts of a exercise exits in lp
3762
                            $lp_item_id = $this->get_current_item_id();
3763
                            $lp_view_id = $this->get_view_id();
3764
3765
                            $prevent_reinit = null;
3766
                            if (isset($this->items[$this->current])) {
3767
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3768
                            }
3769
3770
                            if (empty($provided_toc)) {
3771
                                if ($this->debug > 0) {
3772
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3773
                                }
3774
                                $list = $this->get_toc();
3775
                            } else {
3776
                                if ($this->debug > 0) {
3777
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3778
                                }
3779
                                $list = $provided_toc;
3780
                            }
3781
3782
                            $type_quiz = false;
3783
                            foreach ($list as $toc) {
3784
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3785
                                    $type_quiz = true;
3786
                                }
3787
                            }
3788
3789
                            if ($type_quiz) {
3790
                                $lp_item_id = (int) $lp_item_id;
3791
                                $lp_view_id = (int) $lp_view_id;
3792
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3793
                                        WHERE
3794
                                            c_id = $course_id AND
3795
                                            lp_item_id='".$lp_item_id."' AND
3796
                                            lp_view_id ='".$lp_view_id."' AND
3797
                                            status='completed'";
3798
                                $result = Database::query($sql);
3799
                                $row_count = Database:: fetch_row($result);
3800
                                $count_item_view = (int) $row_count[0];
3801
                                $not_multiple_attempt = 0;
3802
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3803
                                    $not_multiple_attempt = 1;
3804
                                }
3805
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3806
                            }
3807
                            break;
3808
                    }
3809
3810
                    $tmp_array = explode('/', $file);
3811
                    $document_name = $tmp_array[count($tmp_array) - 1];
3812
                    if (strpos($document_name, '_DELETED_')) {
3813
                        $file = 'blank.php?error=document_deleted';
3814
                    }
3815
                    break;
3816
                case 2:
3817
                    if ($this->debug > 2) {
3818
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3819
                    }
3820
3821
                    if ($lp_item_type != 'dir') {
3822
                        // Quite complex here:
3823
                        // We want to make sure 'http://' (and similar) links can
3824
                        // be loaded as is (withouth the Chamilo path in front) but
3825
                        // some contents use this form: resource.htm?resource=http://blablabla
3826
                        // which means we have to find a protocol at the path's start, otherwise
3827
                        // it should not be considered as an external URL.
3828
                        // if ($this->prerequisites_match($item_id)) {
3829
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3830
                            if ($this->debug > 2) {
3831
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3832
                            }
3833
                            // Distant url, return as is.
3834
                            $file = $lp_item_path;
3835
                        } else {
3836
                            if ($this->debug > 2) {
3837
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3838
                            }
3839
                            // Prevent getting untranslatable urls.
3840
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3841
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3842
                            // Prepare the path.
3843
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3844
                            // TODO: Fix this for urls with protocol header.
3845
                            $file = str_replace('//', '/', $file);
3846
                            $file = str_replace(':/', '://', $file);
3847
                            if (substr($lp_path, -1) == '/') {
3848
                                $lp_path = substr($lp_path, 0, -1);
3849
                            }
3850
3851
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3852
                                // if file not found.
3853
                                $decoded = html_entity_decode($lp_item_path);
3854
                                list($decoded) = explode('?', $decoded);
3855
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3856
                                    $file = self::rl_get_resource_link_for_learnpath(
3857
                                        $course_id,
3858
                                        $this->get_id(),
3859
                                        $item_id,
3860
                                        $this->get_view_id()
3861
                                    );
3862
                                    if (empty($file)) {
3863
                                        $file = 'blank.php?error=document_not_found';
3864
                                    } else {
3865
                                        $tmp_array = explode('/', $file);
3866
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3867
                                        if (strpos($document_name, '_DELETED_')) {
3868
                                            $file = 'blank.php?error=document_deleted';
3869
                                        } else {
3870
                                            $file = 'blank.php?error=document_not_found';
3871
                                        }
3872
                                    }
3873
                                } else {
3874
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3875
                                }
3876
                            }
3877
                        }
3878
3879
                        // We want to use parameters if they were defined in the imsmanifest
3880
                        if (strpos($file, 'blank.php') === false) {
3881
                            $lp_item_params = ltrim($lp_item_params, '?');
3882
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3883
                        }
3884
                    } else {
3885
                        $file = 'lp_content.php?type=dir';
3886
                    }
3887
                    break;
3888
                case 3:
3889
                    if ($this->debug > 2) {
3890
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3891
                    }
3892
                    // Formatting AICC HACP append URL.
3893
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3894
                    if (!empty($lp_item_params)) {
3895
                        $aicc_append .= $lp_item_params.'&';
3896
                    }
3897
                    if ($lp_item_type != 'dir') {
3898
                        // Quite complex here:
3899
                        // We want to make sure 'http://' (and similar) links can
3900
                        // be loaded as is (withouth the Chamilo path in front) but
3901
                        // some contents use this form: resource.htm?resource=http://blablabla
3902
                        // which means we have to find a protocol at the path's start, otherwise
3903
                        // it should not be considered as an external URL.
3904
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3905
                            if ($this->debug > 2) {
3906
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3907
                            }
3908
                            // Distant url, return as is.
3909
                            $file = $lp_item_path;
3910
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3911
                            /*
3912
                            if (stristr($file,'<servername>') !== false) {
3913
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3914
                            }
3915
                            */
3916
                            if (stripos($file, '<servername>') !== false) {
3917
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3918
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3919
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3920
                            }
3921
3922
                            $file .= $aicc_append;
3923
                        } else {
3924
                            if ($this->debug > 2) {
3925
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3926
                            }
3927
                            // Prevent getting untranslatable urls.
3928
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3929
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3930
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3931
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3932
                            // TODO: Fix this for urls with protocol header.
3933
                            $file = str_replace('//', '/', $file);
3934
                            $file = str_replace(':/', '://', $file);
3935
                            $file .= $aicc_append;
3936
                        }
3937
                    } else {
3938
                        $file = 'lp_content.php?type=dir';
3939
                    }
3940
                    break;
3941
                case 4:
3942
                    break;
3943
                default:
3944
                    break;
3945
            }
3946
            // Replace &amp; by & because &amp; will break URL with params
3947
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3948
        }
3949
        if ($this->debug > 2) {
3950
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3951
        }
3952
3953
        return $file;
3954
    }
3955
3956
    /**
3957
     * Gets the latest usable view or generate a new one.
3958
     *
3959
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3960
     *
3961
     * @return int DB lp_view id
3962
     */
3963
    public function get_view($attempt_num = 0)
3964
    {
3965
        $search = '';
3966
        // Use $attempt_num to enable multi-views management (disabled so far).
3967
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3968
            $search = 'AND view_count = '.$attempt_num;
3969
        }
3970
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3971
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3972
3973
        $course_id = api_get_course_int_id();
3974
        $sessionId = api_get_session_id();
3975
3976
        $sql = "SELECT iid, view_count FROM $lp_view_table
3977
        		WHERE
3978
        		    c_id = $course_id AND
3979
        		    lp_id = ".$this->get_id()." AND
3980
        		    user_id = ".$this->get_user_id()." AND
3981
        		    session_id = $sessionId
3982
        		    $search
3983
                ORDER BY view_count DESC";
3984
        $res = Database::query($sql);
3985
        if (Database::num_rows($res) > 0) {
3986
            $row = Database::fetch_array($res);
3987
            $this->lp_view_id = $row['iid'];
3988
        } elseif (!api_is_invitee()) {
3989
            // There is no database record, create one.
3990
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3991
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3992
            Database::query($sql);
3993
            $id = Database::insert_id();
3994
            $this->lp_view_id = $id;
3995
3996
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3997
            Database::query($sql);
3998
        }
3999
4000
        return $this->lp_view_id;
4001
    }
4002
4003
    /**
4004
     * Gets the current view id.
4005
     *
4006
     * @return int View ID (from lp_view)
4007
     */
4008
    public function get_view_id()
4009
    {
4010
        if (!empty($this->lp_view_id)) {
4011
            return (int) $this->lp_view_id;
4012
        }
4013
4014
        return 0;
4015
    }
4016
4017
    /**
4018
     * Gets the update queue.
4019
     *
4020
     * @return array Array containing IDs of items to be updated by JavaScript
4021
     */
4022
    public function get_update_queue()
4023
    {
4024
        return $this->update_queue;
4025
    }
4026
4027
    /**
4028
     * Gets the user ID.
4029
     *
4030
     * @return int User ID
4031
     */
4032
    public function get_user_id()
4033
    {
4034
        if (!empty($this->user_id)) {
4035
            return (int) $this->user_id;
4036
        }
4037
4038
        return false;
4039
    }
4040
4041
    /**
4042
     * Checks if any of the items has an audio element attached.
4043
     *
4044
     * @return bool True or false
4045
     */
4046
    public function has_audio()
4047
    {
4048
        $has = false;
4049
        foreach ($this->items as $i => $item) {
4050
            if (!empty($this->items[$i]->audio)) {
4051
                $has = true;
4052
                break;
4053
            }
4054
        }
4055
4056
        return $has;
4057
    }
4058
4059
    /**
4060
     * Moves an item up and down at its level.
4061
     *
4062
     * @param int    $id        Item to move up and down
4063
     * @param string $direction Direction 'up' or 'down'
4064
     *
4065
     * @return bool|int
4066
     */
4067
    public function move_item($id, $direction)
4068
    {
4069
        $course_id = api_get_course_int_id();
4070
        if ($this->debug > 0) {
4071
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
4072
        }
4073
        if (empty($id) || empty($direction)) {
4074
            return false;
4075
        }
4076
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4077
        $sql_sel = "SELECT *
4078
                    FROM $tbl_lp_item
4079
                    WHERE
4080
                        iid = $id
4081
                    ";
4082
        $res_sel = Database::query($sql_sel);
4083
        // Check if elem exists.
4084
        if (Database::num_rows($res_sel) < 1) {
4085
            return false;
4086
        }
4087
        // Gather data.
4088
        $row = Database::fetch_array($res_sel);
4089
        $previous = $row['previous_item_id'];
4090
        $next = $row['next_item_id'];
4091
        $display = $row['display_order'];
4092
        $parent = $row['parent_item_id'];
4093
        $lp = $row['lp_id'];
4094
        // Update the item (switch with previous/next one).
4095
        switch ($direction) {
4096
            case 'up':
4097
                if ($this->debug > 2) {
4098
                    error_log('Movement up detected', 0);
4099
                }
4100
                if ($display > 1) {
4101
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4102
                                 WHERE iid = $previous";
4103
4104
                    if ($this->debug > 2) {
4105
                        error_log('Selecting previous: '.$sql_sel2, 0);
4106
                    }
4107
                    $res_sel2 = Database::query($sql_sel2);
4108
                    if (Database::num_rows($res_sel2) < 1) {
4109
                        $previous_previous = 0;
4110
                    }
4111
                    // Gather data.
4112
                    $row2 = Database::fetch_array($res_sel2);
4113
                    $previous_previous = $row2['previous_item_id'];
4114
                    // Update previous_previous item (switch "next" with current).
4115
                    if ($previous_previous != 0) {
4116
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4117
                                        next_item_id = $id
4118
                                    WHERE iid = $previous_previous";
4119
                        if ($this->debug > 2) {
4120
                            error_log($sql_upd2, 0);
4121
                        }
4122
                        Database::query($sql_upd2);
4123
                    }
4124
                    // Update previous item (switch with current).
4125
                    if ($previous != 0) {
4126
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4127
                                    next_item_id = $next,
4128
                                    previous_item_id = $id,
4129
                                    display_order = display_order +1
4130
                                    WHERE iid = $previous";
4131
                        if ($this->debug > 2) {
4132
                            error_log($sql_upd2, 0);
4133
                        }
4134
                        Database::query($sql_upd2);
4135
                    }
4136
4137
                    // Update current item (switch with previous).
4138
                    if ($id != 0) {
4139
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4140
                                        next_item_id = $previous,
4141
                                        previous_item_id = $previous_previous,
4142
                                        display_order = display_order-1
4143
                                    WHERE c_id = ".$course_id." AND id = $id";
4144
                        if ($this->debug > 2) {
4145
                            error_log($sql_upd2, 0);
4146
                        }
4147
                        Database::query($sql_upd2);
4148
                    }
4149
                    // Update next item (new previous item).
4150
                    if (!empty($next)) {
4151
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4152
                                     WHERE iid = $next";
4153
                        if ($this->debug > 2) {
4154
                            error_log($sql_upd2, 0);
4155
                        }
4156
                        Database::query($sql_upd2);
4157
                    }
4158
                    $display = $display - 1;
4159
                }
4160
                break;
4161
            case 'down':
4162
                if ($this->debug > 2) {
4163
                    error_log('Movement down detected', 0);
4164
                }
4165
                if ($next != 0) {
4166
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4167
                                 WHERE iid = $next";
4168
                    if ($this->debug > 2) {
4169
                        error_log('Selecting next: '.$sql_sel2, 0);
4170
                    }
4171
                    $res_sel2 = Database::query($sql_sel2);
4172
                    if (Database::num_rows($res_sel2) < 1) {
4173
                        $next_next = 0;
4174
                    }
4175
                    // Gather data.
4176
                    $row2 = Database::fetch_array($res_sel2);
4177
                    $next_next = $row2['next_item_id'];
4178
                    // Update previous item (switch with current).
4179
                    if ($previous != 0) {
4180
                        $sql_upd2 = "UPDATE $tbl_lp_item
4181
                                     SET next_item_id = $next
4182
                                     WHERE iid = $previous";
4183
                        Database::query($sql_upd2);
4184
                    }
4185
                    // Update current item (switch with previous).
4186
                    if ($id != 0) {
4187
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4188
                                     previous_item_id = $next,
4189
                                     next_item_id = $next_next,
4190
                                     display_order = display_order + 1
4191
                                     WHERE iid = $id";
4192
                        Database::query($sql_upd2);
4193
                    }
4194
4195
                    // Update next item (new previous item).
4196
                    if ($next != 0) {
4197
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4198
                                     previous_item_id = $previous,
4199
                                     next_item_id = $id,
4200
                                     display_order = display_order-1
4201
                                     WHERE iid = $next";
4202
                        Database::query($sql_upd2);
4203
                    }
4204
4205
                    // Update next_next item (switch "previous" with current).
4206
                    if ($next_next != 0) {
4207
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4208
                                     previous_item_id = $id
4209
                                     WHERE iid = $next_next";
4210
                        Database::query($sql_upd2);
4211
                    }
4212
                    $display = $display + 1;
4213
                }
4214
                break;
4215
            default:
4216
                return false;
4217
        }
4218
4219
        return $display;
4220
    }
4221
4222
    /**
4223
     * Move a LP up (display_order).
4224
     *
4225
     * @param int $lp_id      Learnpath ID
4226
     * @param int $categoryId Category ID
4227
     *
4228
     * @return bool
4229
     */
4230
    public static function move_up($lp_id, $categoryId = 0)
4231
    {
4232
        $courseId = api_get_course_int_id();
4233
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4234
4235
        $categoryCondition = '';
4236
        if (!empty($categoryId)) {
4237
            $categoryId = (int) $categoryId;
4238
            $categoryCondition = " AND category_id = $categoryId";
4239
        }
4240
        $sql = "SELECT * FROM $lp_table
4241
                WHERE c_id = $courseId
4242
                $categoryCondition
4243
                ORDER BY display_order";
4244
        $res = Database::query($sql);
4245
        if ($res === false) {
4246
            return false;
4247
        }
4248
4249
        $lps = [];
4250
        $lp_order = [];
4251
        $num = Database::num_rows($res);
4252
        // First check the order is correct, globally (might be wrong because
4253
        // of versions < 1.8.4)
4254
        if ($num > 0) {
4255
            $i = 1;
4256
            while ($row = Database::fetch_array($res)) {
4257
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4258
                    $sql = "UPDATE $lp_table SET display_order = $i
4259
                            WHERE iid = ".$row['iid'];
4260
                    Database::query($sql);
4261
                }
4262
                $row['display_order'] = $i;
4263
                $lps[$row['iid']] = $row;
4264
                $lp_order[$i] = $row['iid'];
4265
                $i++;
4266
            }
4267
        }
4268
        if ($num > 1) { // If there's only one element, no need to sort.
4269
            $order = $lps[$lp_id]['display_order'];
4270
            if ($order > 1) { // If it's the first element, no need to move up.
4271
                $sql = "UPDATE $lp_table SET display_order = $order
4272
                        WHERE iid = ".$lp_order[$order - 1];
4273
                Database::query($sql);
4274
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4275
                        WHERE iid = $lp_id";
4276
                Database::query($sql);
4277
            }
4278
        }
4279
4280
        return true;
4281
    }
4282
4283
    /**
4284
     * Move a learnpath down (display_order).
4285
     *
4286
     * @param int $lp_id      Learnpath ID
4287
     * @param int $categoryId Category ID
4288
     *
4289
     * @return bool
4290
     */
4291
    public static function move_down($lp_id, $categoryId = 0)
4292
    {
4293
        $courseId = api_get_course_int_id();
4294
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4295
4296
        $categoryCondition = '';
4297
        if (!empty($categoryId)) {
4298
            $categoryId = (int) $categoryId;
4299
            $categoryCondition = " AND category_id = $categoryId";
4300
        }
4301
4302
        $sql = "SELECT * FROM $lp_table
4303
                WHERE c_id = $courseId
4304
                $categoryCondition
4305
                ORDER BY display_order";
4306
        $res = Database::query($sql);
4307
        if ($res === false) {
4308
            return false;
4309
        }
4310
        $lps = [];
4311
        $lp_order = [];
4312
        $num = Database::num_rows($res);
4313
        $max = 0;
4314
        // First check the order is correct, globally (might be wrong because
4315
        // of versions < 1.8.4).
4316
        if ($num > 0) {
4317
            $i = 1;
4318
            while ($row = Database::fetch_array($res)) {
4319
                $max = $i;
4320
                if ($row['display_order'] != $i) {
4321
                    // If we find a gap in the order, we need to fix it.
4322
                    $sql = "UPDATE $lp_table SET display_order = $i
4323
                              WHERE iid = ".$row['iid'];
4324
                    Database::query($sql);
4325
                }
4326
                $row['display_order'] = $i;
4327
                $lps[$row['iid']] = $row;
4328
                $lp_order[$i] = $row['iid'];
4329
                $i++;
4330
            }
4331
        }
4332
        if ($num > 1) { // If there's only one element, no need to sort.
4333
            $order = $lps[$lp_id]['display_order'];
4334
            if ($order < $max) { // If it's the first element, no need to move up.
4335
                $sql = "UPDATE $lp_table SET display_order = $order
4336
                        WHERE iid = ".$lp_order[$order + 1];
4337
                Database::query($sql);
4338
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4339
                        WHERE iid = $lp_id";
4340
                Database::query($sql);
4341
            }
4342
        }
4343
4344
        return true;
4345
    }
4346
4347
    /**
4348
     * Updates learnpath attributes to point to the next element
4349
     * The last part is similar to set_current_item but processing the other way around.
4350
     */
4351
    public function next()
4352
    {
4353
        if ($this->debug > 0) {
4354
            error_log('In learnpath::next()', 0);
4355
        }
4356
        $this->last = $this->get_current_item_id();
4357
        $this->items[$this->last]->save(
4358
            false,
4359
            $this->prerequisites_match($this->last)
4360
        );
4361
        $this->autocomplete_parents($this->last);
4362
        $new_index = $this->get_next_index();
4363
        if ($this->debug > 2) {
4364
            error_log('New index: '.$new_index, 0);
4365
        }
4366
        $this->index = $new_index;
4367
        if ($this->debug > 2) {
4368
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4369
        }
4370
        $this->current = $this->ordered_items[$new_index];
4371
        if ($this->debug > 2) {
4372
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4373
        }
4374
    }
4375
4376
    /**
4377
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4378
     * class, this might be redefined to allow several behaviours depending on the document type.
4379
     *
4380
     * @param int $id Resource ID
4381
     */
4382
    public function open($id)
4383
    {
4384
        if ($this->debug > 0) {
4385
            error_log('In learnpath::open()', 0);
4386
        }
4387
        // TODO:
4388
        // set the current resource attribute to this resource
4389
        // switch on element type (redefine in child class?)
4390
        // set status for this item to "opened"
4391
        // start timer
4392
        // initialise score
4393
        $this->index = 0; //or = the last item seen (see $this->last)
4394
    }
4395
4396
    /**
4397
     * Check that all prerequisites are fulfilled. Returns true and an
4398
     * empty string on success, returns false
4399
     * and the prerequisite string on error.
4400
     * This function is based on the rules for aicc_script language as
4401
     * described in the SCORM 1.2 CAM documentation page 108.
4402
     *
4403
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4404
     *
4405
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4406
     *              string otherwise
4407
     */
4408
    public function prerequisites_match($itemId = null)
4409
    {
4410
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4411
        if ($allow) {
4412
            if (api_is_allowed_to_edit() ||
4413
                api_is_platform_admin(true) ||
4414
                api_is_drh() ||
4415
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4416
            ) {
4417
                return true;
4418
            }
4419
        }
4420
4421
        $debug = $this->debug;
4422
        if ($debug > 0) {
4423
            error_log('In learnpath::prerequisites_match()');
4424
        }
4425
4426
        if (empty($itemId)) {
4427
            $itemId = $this->current;
4428
        }
4429
4430
        $currentItem = $this->getItem($itemId);
4431
4432
        if ($currentItem) {
4433
            if ($this->type == 2) {
4434
                // Getting prereq from scorm
4435
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4436
            } else {
4437
                $prereq_string = $currentItem->get_prereq_string();
4438
            }
4439
4440
            if (empty($prereq_string)) {
4441
                if ($debug > 0) {
4442
                    error_log('Found prereq_string is empty return true');
4443
                }
4444
4445
                return true;
4446
            }
4447
4448
            // Clean spaces.
4449
            $prereq_string = str_replace(' ', '', $prereq_string);
4450
            if ($debug > 0) {
4451
                error_log('Found prereq_string: '.$prereq_string, 0);
4452
            }
4453
4454
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4455
            $result = $currentItem->parse_prereq(
4456
                $prereq_string,
4457
                $this->items,
4458
                $this->refs_list,
4459
                $this->get_user_id()
4460
            );
4461
4462
            if ($result === false) {
4463
                $this->set_error_msg($currentItem->prereq_alert);
4464
            }
4465
        } else {
4466
            $result = true;
4467
            if ($debug > 1) {
4468
                error_log('$this->items['.$itemId.'] was not an object', 0);
4469
            }
4470
        }
4471
4472
        if ($debug > 1) {
4473
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4474
        }
4475
4476
        return $result;
4477
    }
4478
4479
    /**
4480
     * Updates learnpath attributes to point to the previous element
4481
     * The last part is similar to set_current_item but processing the other way around.
4482
     */
4483
    public function previous()
4484
    {
4485
        $this->last = $this->get_current_item_id();
4486
        $this->items[$this->last]->save(
4487
            false,
4488
            $this->prerequisites_match($this->last)
4489
        );
4490
        $this->autocomplete_parents($this->last);
4491
        $new_index = $this->get_previous_index();
4492
        $this->index = $new_index;
4493
        $this->current = $this->ordered_items[$new_index];
4494
    }
4495
4496
    /**
4497
     * Publishes a learnpath. This basically means show or hide the learnpath
4498
     * to normal users.
4499
     * Can be used as abstract.
4500
     *
4501
     * @param int $lp_id          Learnpath ID
4502
     * @param int $set_visibility New visibility
4503
     *
4504
     * @return bool
4505
     */
4506
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4507
    {
4508
        $action = 'visible';
4509
        if ($set_visibility != 1) {
4510
            $action = 'invisible';
4511
            self::toggle_publish($lp_id, 'i');
4512
        }
4513
4514
        return api_item_property_update(
4515
            api_get_course_info(),
4516
            TOOL_LEARNPATH,
4517
            $lp_id,
4518
            $action,
4519
            api_get_user_id()
4520
        );
4521
    }
4522
4523
    /**
4524
     * Publishes a learnpath category.
4525
     * This basically means show or hide the learnpath category to normal users.
4526
     *
4527
     * @param int $id
4528
     * @param int $visibility
4529
     *
4530
     * @throws \Doctrine\ORM\NonUniqueResultException
4531
     * @throws \Doctrine\ORM\ORMException
4532
     * @throws \Doctrine\ORM\OptimisticLockException
4533
     * @throws \Doctrine\ORM\TransactionRequiredException
4534
     *
4535
     * @return bool
4536
     */
4537
    public static function toggleCategoryVisibility($id, $visibility = 1)
4538
    {
4539
        $action = 'visible';
4540
4541
        if ($visibility != 1) {
4542
            $action = 'invisible';
4543
            $list = new LearnpathList(
4544
                api_get_user_id(),
4545
                null,
4546
                null,
4547
                null,
4548
                false,
4549
                $id
4550
            );
4551
4552
            $lpList = $list->get_flat_list();
4553
            foreach ($lpList as $lp) {
4554
                self::toggle_visibility($lp['iid'], 0);
4555
            }
4556
4557
            self::toggleCategoryPublish($id, 0);
4558
        }
4559
4560
        return api_item_property_update(
4561
            api_get_course_info(),
4562
            TOOL_LEARNPATH_CATEGORY,
4563
            $id,
4564
            $action,
4565
            api_get_user_id()
4566
        );
4567
    }
4568
4569
    /**
4570
     * Publishes a learnpath. This basically means show or hide the learnpath
4571
     * on the course homepage
4572
     * Can be used as abstract.
4573
     *
4574
     * @param int    $lp_id          Learnpath id
4575
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4576
     *
4577
     * @return bool
4578
     */
4579
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4580
    {
4581
        $course_id = api_get_course_int_id();
4582
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4583
        $lp_id = (int) $lp_id;
4584
        $sql = "SELECT * FROM $tbl_lp
4585
                WHERE iid = $lp_id";
4586
        $result = Database::query($sql);
4587
        if (Database::num_rows($result)) {
4588
            $row = Database::fetch_array($result);
4589
            $name = Database::escape_string($row['name']);
4590
            if ($set_visibility == 'i') {
4591
                $v = 0;
4592
            }
4593
            if ($set_visibility == 'v') {
4594
                $v = 1;
4595
            }
4596
4597
            $session_id = api_get_session_id();
4598
            $session_condition = api_get_session_condition($session_id);
4599
4600
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4601
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4602
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4603
4604
            $sql = "SELECT * FROM $tbl_tool
4605
                    WHERE
4606
                        c_id = $course_id AND
4607
                        (link = '$link' OR link = '$oldLink') AND
4608
                        image = 'scormbuilder.gif' AND
4609
                        (
4610
                            link LIKE '$link%' OR
4611
                            link LIKE '$oldLink%'
4612
                        )
4613
                        $session_condition
4614
                    ";
4615
4616
            $result = Database::query($sql);
4617
            $num = Database::num_rows($result);
4618
            if ($set_visibility == 'i' && $num > 0) {
4619
                $sql = "DELETE FROM $tbl_tool
4620
                        WHERE
4621
                            c_id = $course_id AND
4622
                            (link = '$link' OR link = '$oldLink') AND
4623
                            image='scormbuilder.gif'
4624
                            $session_condition";
4625
                Database::query($sql);
4626
            } elseif ($set_visibility == 'v' && $num == 0) {
4627
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4628
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4629
                Database::query($sql);
4630
                $insertId = Database::insert_id();
4631
                if ($insertId) {
4632
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4633
                    Database::query($sql);
4634
                }
4635
            } elseif ($set_visibility == 'v' && $num > 0) {
4636
                $sql = "UPDATE $tbl_tool SET
4637
                            c_id = $course_id,
4638
                            name = '$name',
4639
                            link = '$link',
4640
                            image = 'scormbuilder.gif',
4641
                            visibility = '$v',
4642
                            admin = '0',
4643
                            address = 'pastillegris.gif',
4644
                            added_tool = 0,
4645
                            session_id = $session_id
4646
                        WHERE
4647
                            c_id = ".$course_id." AND
4648
                            (link = '$link' OR link = '$oldLink') AND
4649
                            image='scormbuilder.gif'
4650
                            $session_condition
4651
                        ";
4652
                Database::query($sql);
4653
            } else {
4654
                // Parameter and database incompatible, do nothing, exit.
4655
                return false;
4656
            }
4657
        } else {
4658
            return false;
4659
        }
4660
    }
4661
4662
    /**
4663
     * Publishes a learnpath.
4664
     * Show or hide the learnpath category on the course homepage.
4665
     *
4666
     * @param int $id
4667
     * @param int $setVisibility
4668
     *
4669
     * @throws \Doctrine\ORM\NonUniqueResultException
4670
     * @throws \Doctrine\ORM\ORMException
4671
     * @throws \Doctrine\ORM\OptimisticLockException
4672
     * @throws \Doctrine\ORM\TransactionRequiredException
4673
     *
4674
     * @return bool
4675
     */
4676
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4677
    {
4678
        $courseId = api_get_course_int_id();
4679
        $sessionId = api_get_session_id();
4680
        $sessionCondition = api_get_session_condition(
4681
            $sessionId,
4682
            true,
4683
            false,
4684
            't.sessionId'
4685
        );
4686
4687
        $em = Database::getManager();
4688
4689
        /** @var CLpCategory $category */
4690
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4691
4692
        if (!$category) {
4693
            return false;
4694
        }
4695
4696
        if (empty($courseId)) {
4697
            return false;
4698
        }
4699
4700
        $link = self::getCategoryLinkForTool($id);
4701
4702
        /** @var CTool $tool */
4703
        $tool = $em->createQuery("
4704
                SELECT t FROM ChamiloCourseBundle:CTool t
4705
                WHERE
4706
                    t.cId = :course AND
4707
                    t.link = :link1 AND
4708
                    t.image = 'lp_category.gif' AND
4709
                    t.link LIKE :link2
4710
                    $sessionCondition
4711
            ")
4712
            ->setParameters([
4713
                'course' => (int) $courseId,
4714
                'link1' => $link,
4715
                'link2' => "$link%",
4716
            ])
4717
            ->getOneOrNullResult();
4718
4719
        if ($setVisibility == 0 && $tool) {
4720
            $em->remove($tool);
4721
            $em->flush();
4722
4723
            return true;
4724
        }
4725
4726
        if ($setVisibility == 1 && !$tool) {
4727
            $tool = new CTool();
4728
            $tool
4729
                ->setCategory('authoring')
4730
                ->setCId($courseId)
4731
                ->setName(strip_tags($category->getName()))
4732
                ->setLink($link)
4733
                ->setImage('lp_category.gif')
4734
                ->setVisibility(1)
4735
                ->setAdmin(0)
4736
                ->setAddress('pastillegris.gif')
4737
                ->setAddedTool(0)
4738
                ->setSessionId($sessionId)
4739
                ->setTarget('_self');
4740
4741
            $em->persist($tool);
4742
            $em->flush();
4743
4744
            $tool->setId($tool->getIid());
4745
4746
            $em->persist($tool);
4747
            $em->flush();
4748
4749
            return true;
4750
        }
4751
4752
        if ($setVisibility == 1 && $tool) {
4753
            $tool
4754
                ->setName(strip_tags($category->getName()))
4755
                ->setVisibility(1);
4756
4757
            $em->persist($tool);
4758
            $em->flush();
4759
4760
            return true;
4761
        }
4762
4763
        return false;
4764
    }
4765
4766
    /**
4767
     * Check if the learnpath category is visible for a user.
4768
     *
4769
     * @param CLpCategory $category
4770
     * @param User        $user
4771
     * @param int
4772
     * @param int
4773
     *
4774
     * @return bool
4775
     */
4776
    public static function categoryIsVisibleForStudent(
4777
        CLpCategory $category,
4778
        User $user,
4779
        $courseId = 0,
4780
        $sessionId = 0
4781
    ) {
4782
        $subscriptionSettings = self::getSubscriptionSettings();
4783
4784
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4785
            return true;
4786
        }
4787
4788
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4789
4790
        if ($isAllowedToEdit) {
4791
            return true;
4792
        }
4793
4794
        if (empty($category)) {
4795
            return false;
4796
        }
4797
4798
        $users = $category->getUsers();
4799
4800
        if (empty($users) || !$users->count()) {
4801
            return true;
4802
        }
4803
4804
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4805
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4806
4807
        if ($category->hasUserAdded($user)) {
4808
            return true;
4809
        }
4810
4811
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4812
        if (!empty($groups)) {
4813
            $em = Database::getManager();
4814
4815
            /** @var ItemPropertyRepository $itemRepo */
4816
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4817
4818
            /** @var CourseRepository $courseRepo */
4819
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4820
            $session = null;
4821
            if (!empty($sessionId)) {
4822
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4823
            }
4824
4825
            $course = $courseRepo->find($courseId);
4826
4827
            // Subscribed groups to a LP
4828
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4829
                TOOL_LEARNPATH_CATEGORY,
4830
                $category->getId(),
4831
                $course,
4832
                $session
4833
            );
4834
4835
            if (!empty($subscribedGroupsInLp)) {
4836
                $groups = array_column($groups, 'iid');
4837
                /** @var CItemProperty $item */
4838
                foreach ($subscribedGroupsInLp as $item) {
4839
                    if ($item->getGroup() &&
4840
                        in_array($item->getGroup()->getId(), $groups)
4841
                    ) {
4842
                        return true;
4843
                    }
4844
                }
4845
            }
4846
        }
4847
4848
        return false;
4849
    }
4850
4851
    /**
4852
     * Check if a learnpath category is published as course tool.
4853
     *
4854
     * @param CLpCategory $category
4855
     * @param int         $courseId
4856
     *
4857
     * @return bool
4858
     */
4859
    public static function categoryIsPublished(
4860
        CLpCategory $category,
4861
        $courseId
4862
    ) {
4863
        $link = self::getCategoryLinkForTool($category->getId());
4864
        $em = Database::getManager();
4865
4866
        $tools = $em
4867
            ->createQuery("
4868
                SELECT t FROM ChamiloCourseBundle:CTool t
4869
                WHERE t.cId = :course AND
4870
                    t.name = :name AND
4871
                    t.image = 'lp_category.gif' AND
4872
                    t.link LIKE :link
4873
            ")
4874
            ->setParameters([
4875
                'course' => $courseId,
4876
                'name' => strip_tags($category->getName()),
4877
                'link' => "$link%",
4878
            ])
4879
            ->getResult();
4880
4881
        /** @var CTool $tool */
4882
        $tool = current($tools);
4883
4884
        return $tool ? $tool->getVisibility() : false;
4885
    }
4886
4887
    /**
4888
     * Restart the whole learnpath. Return the URL of the first element.
4889
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4890
     * To use a similar method  statically, use the create_new_attempt() method.
4891
     *
4892
     * @return bool
4893
     */
4894
    public function restart()
4895
    {
4896
        if ($this->debug > 0) {
4897
            error_log('In learnpath::restart()', 0);
4898
        }
4899
        // TODO
4900
        // Call autosave method to save the current progress.
4901
        //$this->index = 0;
4902
        if (api_is_invitee()) {
4903
            return false;
4904
        }
4905
        $session_id = api_get_session_id();
4906
        $course_id = api_get_course_int_id();
4907
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4908
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4909
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4910
        if ($this->debug > 2) {
4911
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4912
        }
4913
        Database::query($sql);
4914
        $view_id = Database::insert_id();
4915
4916
        if ($view_id) {
4917
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4918
            Database::query($sql);
4919
            $this->lp_view_id = $view_id;
4920
            $this->attempt = $this->attempt + 1;
4921
        } else {
4922
            $this->error = 'Could not insert into item_view table...';
4923
4924
            return false;
4925
        }
4926
        $this->autocomplete_parents($this->current);
4927
        foreach ($this->items as $index => $dummy) {
4928
            $this->items[$index]->restart();
4929
            $this->items[$index]->set_lp_view($this->lp_view_id);
4930
        }
4931
        $this->first();
4932
4933
        return true;
4934
    }
4935
4936
    /**
4937
     * Saves the current item.
4938
     *
4939
     * @return bool
4940
     */
4941
    public function save_current()
4942
    {
4943
        $debug = $this->debug;
4944
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4945
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4946
        if ($debug) {
4947
            error_log('save_current() saving item '.$this->current, 0);
4948
            error_log(''.print_r($this->items, true), 0);
4949
        }
4950
        if (isset($this->items[$this->current]) &&
4951
            is_object($this->items[$this->current])
4952
        ) {
4953
            if ($debug) {
4954
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4955
            }
4956
4957
            $res = $this->items[$this->current]->save(
4958
                false,
4959
                $this->prerequisites_match($this->current)
4960
            );
4961
            $this->autocomplete_parents($this->current);
4962
            $status = $this->items[$this->current]->get_status();
4963
            $this->update_queue[$this->current] = $status;
4964
4965
            if ($debug) {
4966
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4967
            }
4968
4969
            return $res;
4970
        }
4971
4972
        return false;
4973
    }
4974
4975
    /**
4976
     * Saves the given item.
4977
     *
4978
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4979
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4980
     *
4981
     * @return bool
4982
     */
4983
    public function save_item($item_id = null, $from_outside = true)
4984
    {
4985
        $debug = $this->debug;
4986
        if ($debug) {
4987
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4988
        }
4989
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4990
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4991
        if (empty($item_id)) {
4992
            $item_id = (int) $_REQUEST['id'];
4993
        }
4994
4995
        if (empty($item_id)) {
4996
            $item_id = $this->get_current_item_id();
4997
        }
4998
        if (isset($this->items[$item_id]) &&
4999
            is_object($this->items[$item_id])
5000
        ) {
5001
            if ($debug) {
5002
                error_log('Object exists');
5003
            }
5004
5005
            // Saving the item.
5006
            $res = $this->items[$item_id]->save(
5007
                $from_outside,
5008
                $this->prerequisites_match($item_id)
5009
            );
5010
5011
            if ($debug) {
5012
                error_log('update_queue before:');
5013
                error_log(print_r($this->update_queue, 1));
5014
            }
5015
            $this->autocomplete_parents($item_id);
5016
5017
            $status = $this->items[$item_id]->get_status();
5018
            $this->update_queue[$item_id] = $status;
5019
5020
            if ($debug) {
5021
                error_log('get_status(): '.$status);
5022
                error_log('update_queue after:');
5023
                error_log(print_r($this->update_queue, 1));
5024
            }
5025
5026
            return $res;
5027
        }
5028
5029
        return false;
5030
    }
5031
5032
    /**
5033
     * Saves the last item seen's ID only in case.
5034
     */
5035
    public function save_last()
5036
    {
5037
        $course_id = api_get_course_int_id();
5038
        $debug = $this->debug;
5039
        if ($debug) {
5040
            error_log('In learnpath::save_last()', 0);
5041
        }
5042
        $session_condition = api_get_session_condition(
5043
            api_get_session_id(),
5044
            true,
5045
            false
5046
        );
5047
        $table = Database::get_course_table(TABLE_LP_VIEW);
5048
5049
        if (isset($this->current) && !api_is_invitee()) {
5050
            if ($debug) {
5051
                error_log('Saving current item ('.$this->current.') for later review', 0);
5052
            }
5053
            $sql = "UPDATE $table SET
5054
                        last_item = ".$this->get_current_item_id()."
5055
                    WHERE
5056
                        c_id = $course_id AND
5057
                        lp_id = ".$this->get_id()." AND
5058
                        user_id = ".$this->get_user_id()." ".$session_condition;
5059
5060
            if ($debug) {
5061
                error_log('Saving last item seen : '.$sql, 0);
5062
            }
5063
            Database::query($sql);
5064
        }
5065
5066
        if (!api_is_invitee()) {
5067
            // Save progress.
5068
            list($progress) = $this->get_progress_bar_text('%');
5069
            if ($progress >= 0 && $progress <= 100) {
5070
                $progress = (int) $progress;
5071
                $sql = "UPDATE $table SET
5072
                            progress = $progress
5073
                        WHERE
5074
                            c_id = $course_id AND
5075
                            lp_id = ".$this->get_id()." AND
5076
                            user_id = ".$this->get_user_id()." ".$session_condition;
5077
                // Ignore errors as some tables might not have the progress field just yet.
5078
                Database::query($sql);
5079
                $this->progress_db = $progress;
5080
            }
5081
        }
5082
    }
5083
5084
    /**
5085
     * Sets the current item ID (checks if valid and authorized first).
5086
     *
5087
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5088
     */
5089
    public function set_current_item($item_id = null)
5090
    {
5091
        $debug = $this->debug;
5092
        if ($debug) {
5093
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5094
        }
5095
        if (empty($item_id)) {
5096
            if ($debug) {
5097
                error_log('No new current item given, ignore...', 0);
5098
            }
5099
            // Do nothing.
5100
        } else {
5101
            if ($debug) {
5102
                error_log('New current item given is '.$item_id.'...', 0);
5103
            }
5104
            if (is_numeric($item_id)) {
5105
                $item_id = (int) $item_id;
5106
                // TODO: Check in database here.
5107
                $this->last = $this->current;
5108
                $this->current = $item_id;
5109
                // TODO: Update $this->index as well.
5110
                foreach ($this->ordered_items as $index => $item) {
5111
                    if ($item == $this->current) {
5112
                        $this->index = $index;
5113
                        break;
5114
                    }
5115
                }
5116
                if ($debug) {
5117
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5118
                }
5119
            } else {
5120
                if ($debug) {
5121
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5122
                }
5123
            }
5124
        }
5125
    }
5126
5127
    /**
5128
     * Sets the encoding.
5129
     *
5130
     * @param string $enc New encoding
5131
     *
5132
     * @return bool
5133
     *
5134
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5135
     */
5136
    public function set_encoding($enc = 'UTF-8')
5137
    {
5138
        $enc = api_refine_encoding_id($enc);
5139
        if (empty($enc)) {
5140
            $enc = api_get_system_encoding();
5141
        }
5142
        if (api_is_encoding_supported($enc)) {
5143
            $lp = $this->get_id();
5144
            if ($lp != 0) {
5145
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5146
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5147
                        WHERE iid = ".$lp;
5148
                $res = Database::query($sql);
5149
5150
                return $res;
5151
            }
5152
        }
5153
5154
        return false;
5155
    }
5156
5157
    /**
5158
     * Sets the JS lib setting in the database directly.
5159
     * This is the JavaScript library file this lp needs to load on startup.
5160
     *
5161
     * @param string $lib Proximity setting
5162
     *
5163
     * @return bool True on update success. False otherwise.
5164
     */
5165
    public function set_jslib($lib = '')
5166
    {
5167
        $lp = $this->get_id();
5168
5169
        if ($lp != 0) {
5170
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5171
            $lib = Database::escape_string($lib);
5172
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5173
                    WHERE iid = $lp";
5174
            $res = Database::query($sql);
5175
5176
            return $res;
5177
        }
5178
5179
        return false;
5180
    }
5181
5182
    /**
5183
     * Sets the name of the LP maker (publisher) (and save).
5184
     *
5185
     * @param string $name Optional string giving the new content_maker of this learnpath
5186
     *
5187
     * @return bool True
5188
     */
5189
    public function set_maker($name = '')
5190
    {
5191
        if (empty($name)) {
5192
            return false;
5193
        }
5194
        $this->maker = $name;
5195
        $table = Database::get_course_table(TABLE_LP_MAIN);
5196
        $lp_id = $this->get_id();
5197
        $sql = "UPDATE $table SET
5198
                content_maker = '".Database::escape_string($this->maker)."'
5199
                WHERE iid = $lp_id";
5200
        Database::query($sql);
5201
5202
        return true;
5203
    }
5204
5205
    /**
5206
     * Sets the name of the current learnpath (and save).
5207
     *
5208
     * @param string $name Optional string giving the new name of this learnpath
5209
     *
5210
     * @return bool True/False
5211
     */
5212
    public function set_name($name = null)
5213
    {
5214
        if (empty($name)) {
5215
            return false;
5216
        }
5217
        $this->name = Database::escape_string($name);
5218
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5219
        $lp_id = $this->get_id();
5220
        $course_id = $this->course_info['real_id'];
5221
        $sql = "UPDATE $lp_table SET
5222
                name = '".Database::escape_string($this->name)."'
5223
                WHERE iid = $lp_id";
5224
        $result = Database::query($sql);
5225
        // If the lp is visible on the homepage, change his name there.
5226
        if (Database::affected_rows($result)) {
5227
            $session_id = api_get_session_id();
5228
            $session_condition = api_get_session_condition($session_id);
5229
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5230
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5231
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5232
            	    WHERE
5233
            	        c_id = $course_id AND
5234
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5235
            Database::query($sql);
5236
5237
            return true;
5238
        }
5239
5240
        return false;
5241
    }
5242
5243
    /**
5244
     * Set index specified prefix terms for all items in this path.
5245
     *
5246
     * @param string $terms_string Comma-separated list of terms
5247
     * @param string $prefix       Xapian term prefix
5248
     *
5249
     * @return bool False on error, true otherwise
5250
     */
5251
    public function set_terms_by_prefix($terms_string, $prefix)
5252
    {
5253
        $course_id = api_get_course_int_id();
5254
        if (api_get_setting('search_enabled') !== 'true') {
5255
            return false;
5256
        }
5257
5258
        if (!extension_loaded('xapian')) {
5259
            return false;
5260
        }
5261
5262
        $terms_string = trim($terms_string);
5263
        $terms = explode(',', $terms_string);
5264
        array_walk($terms, 'trim_value');
5265
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5266
5267
        // Don't do anything if no change, verify only at DB, not the search engine.
5268
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5269
            return false;
5270
        }
5271
5272
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5273
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5274
5275
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5276
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5277
        $lp_id = (int) $_POST['lp_id'];
5278
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5279
        $result = Database::query($sql);
5280
        $di = new ChamiloIndexer();
5281
5282
        while ($lp_item = Database::fetch_array($result)) {
5283
            // Get search_did.
5284
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5285
            $sql = 'SELECT * FROM %s
5286
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5287
                    LIMIT 1';
5288
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5289
5290
            //echo $sql; echo '<br>';
5291
            $res = Database::query($sql);
5292
            if (Database::num_rows($res) > 0) {
5293
                $se_ref = Database::fetch_array($res);
5294
                // Compare terms.
5295
                $doc = $di->get_document($se_ref['search_did']);
5296
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5297
                $xterms = [];
5298
                foreach ($xapian_terms as $xapian_term) {
5299
                    $xterms[] = substr($xapian_term['name'], 1);
5300
                }
5301
5302
                $dterms = $terms;
5303
                $missing_terms = array_diff($dterms, $xterms);
5304
                $deprecated_terms = array_diff($xterms, $dterms);
5305
5306
                // Save it to search engine.
5307
                foreach ($missing_terms as $term) {
5308
                    $doc->add_term($prefix.$term, 1);
5309
                }
5310
                foreach ($deprecated_terms as $term) {
5311
                    $doc->remove_term($prefix.$term);
5312
                }
5313
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5314
                $di->getDb()->flush();
5315
            }
5316
        }
5317
5318
        return true;
5319
    }
5320
5321
    /**
5322
     * Sets the theme of the LP (local/remote) (and save).
5323
     *
5324
     * @param string $name Optional string giving the new theme of this learnpath
5325
     *
5326
     * @return bool Returns true if theme name is not empty
5327
     */
5328
    public function set_theme($name = '')
5329
    {
5330
        $this->theme = $name;
5331
        $table = Database::get_course_table(TABLE_LP_MAIN);
5332
        $lp_id = $this->get_id();
5333
        $sql = "UPDATE $table
5334
                SET theme = '".Database::escape_string($this->theme)."'
5335
                WHERE iid = $lp_id";
5336
        Database::query($sql);
5337
5338
        return true;
5339
    }
5340
5341
    /**
5342
     * Sets the image of an LP (and save).
5343
     *
5344
     * @param string $name Optional string giving the new image of this learnpath
5345
     *
5346
     * @return bool Returns true if theme name is not empty
5347
     */
5348
    public function set_preview_image($name = '')
5349
    {
5350
        $this->preview_image = $name;
5351
        $table = Database::get_course_table(TABLE_LP_MAIN);
5352
        $lp_id = $this->get_id();
5353
        $sql = "UPDATE $table SET
5354
                preview_image = '".Database::escape_string($this->preview_image)."'
5355
                WHERE iid = $lp_id";
5356
        Database::query($sql);
5357
5358
        return true;
5359
    }
5360
5361
    /**
5362
     * Sets the author of a LP (and save).
5363
     *
5364
     * @param string $name Optional string giving the new author of this learnpath
5365
     *
5366
     * @return bool Returns true if author's name is not empty
5367
     */
5368
    public function set_author($name = '')
5369
    {
5370
        $this->author = $name;
5371
        $table = Database::get_course_table(TABLE_LP_MAIN);
5372
        $lp_id = $this->get_id();
5373
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5374
                WHERE iid = $lp_id";
5375
        Database::query($sql);
5376
5377
        return true;
5378
    }
5379
5380
    /**
5381
     * Sets the hide_toc_frame parameter of a LP (and save).
5382
     *
5383
     * @param int $hide 1 if frame is hidden 0 then else
5384
     *
5385
     * @return bool Returns true if author's name is not empty
5386
     */
5387
    public function set_hide_toc_frame($hide)
5388
    {
5389
        if (intval($hide) == $hide) {
5390
            $this->hide_toc_frame = $hide;
5391
            $table = Database::get_course_table(TABLE_LP_MAIN);
5392
            $lp_id = $this->get_id();
5393
            $sql = "UPDATE $table SET
5394
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5395
                    WHERE iid = $lp_id";
5396
            if ($this->debug > 2) {
5397
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5398
            }
5399
            Database::query($sql);
5400
5401
            return true;
5402
        }
5403
5404
        return false;
5405
    }
5406
5407
    /**
5408
     * Sets the prerequisite of a LP (and save).
5409
     *
5410
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5411
     *
5412
     * @return bool returns true if prerequisite is not empty
5413
     */
5414
    public function set_prerequisite($prerequisite)
5415
    {
5416
        $this->prerequisite = (int) $prerequisite;
5417
        $table = Database::get_course_table(TABLE_LP_MAIN);
5418
        $lp_id = $this->get_id();
5419
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5420
                WHERE iid = $lp_id";
5421
        Database::query($sql);
5422
5423
        return true;
5424
    }
5425
5426
    /**
5427
     * Sets the location/proximity of the LP (local/remote) (and save).
5428
     *
5429
     * @param string $name Optional string giving the new location of this learnpath
5430
     *
5431
     * @return bool True on success / False on error
5432
     */
5433
    public function set_proximity($name = '')
5434
    {
5435
        if (empty($name)) {
5436
            return false;
5437
        }
5438
5439
        $this->proximity = $name;
5440
        $table = Database::get_course_table(TABLE_LP_MAIN);
5441
        $lp_id = $this->get_id();
5442
        $sql = "UPDATE $table SET
5443
                    content_local = '".Database::escape_string($name)."'
5444
                WHERE iid = $lp_id";
5445
        Database::query($sql);
5446
5447
        return true;
5448
    }
5449
5450
    /**
5451
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5452
     *
5453
     * @param int $id DB ID of the item
5454
     */
5455
    public function set_previous_item($id)
5456
    {
5457
        if ($this->debug > 0) {
5458
            error_log('In learnpath::set_previous_item()', 0);
5459
        }
5460
        $this->last = $id;
5461
    }
5462
5463
    /**
5464
     * Sets use_max_score.
5465
     *
5466
     * @param int $use_max_score Optional string giving the new location of this learnpath
5467
     *
5468
     * @return bool True on success / False on error
5469
     */
5470
    public function set_use_max_score($use_max_score = 1)
5471
    {
5472
        $use_max_score = (int) $use_max_score;
5473
        $this->use_max_score = $use_max_score;
5474
        $table = Database::get_course_table(TABLE_LP_MAIN);
5475
        $lp_id = $this->get_id();
5476
        $sql = "UPDATE $table SET
5477
                    use_max_score = '".$this->use_max_score."'
5478
                WHERE iid = $lp_id";
5479
        Database::query($sql);
5480
5481
        return true;
5482
    }
5483
5484
    /**
5485
     * Sets and saves the expired_on date.
5486
     *
5487
     * @param string $expired_on Optional string giving the new author of this learnpath
5488
     *
5489
     * @throws \Doctrine\ORM\OptimisticLockException
5490
     *
5491
     * @return bool Returns true if author's name is not empty
5492
     */
5493
    public function set_expired_on($expired_on)
5494
    {
5495
        $em = Database::getManager();
5496
        /** @var CLp $lp */
5497
        $lp = $em
5498
            ->getRepository('ChamiloCourseBundle:CLp')
5499
            ->findOneBy(
5500
                [
5501
                    'iid' => $this->get_id(),
5502
                ]
5503
            );
5504
5505
        if (!$lp) {
5506
            return false;
5507
        }
5508
5509
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5510
5511
        $lp->setExpiredOn($this->expired_on);
5512
        $em->persist($lp);
5513
        $em->flush();
5514
5515
        return true;
5516
    }
5517
5518
    /**
5519
     * Sets and saves the publicated_on date.
5520
     *
5521
     * @param string $publicated_on Optional string giving the new author of this learnpath
5522
     *
5523
     * @throws \Doctrine\ORM\OptimisticLockException
5524
     *
5525
     * @return bool Returns true if author's name is not empty
5526
     */
5527
    public function set_publicated_on($publicated_on)
5528
    {
5529
        $em = Database::getManager();
5530
        /** @var CLp $lp */
5531
        $lp = $em
5532
            ->getRepository('ChamiloCourseBundle:CLp')
5533
            ->findOneBy(
5534
                [
5535
                    'iid' => $this->get_id(),
5536
                ]
5537
            );
5538
5539
        if (!$lp) {
5540
            return false;
5541
        }
5542
5543
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5544
        $lp->setPublicatedOn($this->publicated_on);
5545
        $em->persist($lp);
5546
        $em->flush();
5547
5548
        return true;
5549
    }
5550
5551
    /**
5552
     * Sets and saves the expired_on date.
5553
     *
5554
     * @return bool Returns true if author's name is not empty
5555
     */
5556
    public function set_modified_on()
5557
    {
5558
        $this->modified_on = api_get_utc_datetime();
5559
        $table = Database::get_course_table(TABLE_LP_MAIN);
5560
        $lp_id = $this->get_id();
5561
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5562
                WHERE iid = $lp_id";
5563
        Database::query($sql);
5564
5565
        return true;
5566
    }
5567
5568
    /**
5569
     * Sets the object's error message.
5570
     *
5571
     * @param string $error Error message. If empty, reinits the error string
5572
     */
5573
    public function set_error_msg($error = '')
5574
    {
5575
        if ($this->debug > 0) {
5576
            error_log('In learnpath::set_error_msg()', 0);
5577
        }
5578
        if (empty($error)) {
5579
            $this->error = '';
5580
        } else {
5581
            $this->error .= $error;
5582
        }
5583
    }
5584
5585
    /**
5586
     * Launches the current item if not 'sco'
5587
     * (starts timer and make sure there is a record ready in the DB).
5588
     *
5589
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5590
     *
5591
     * @return bool
5592
     */
5593
    public function start_current_item($allow_new_attempt = false)
5594
    {
5595
        $debug = $this->debug;
5596
        if ($debug) {
5597
            error_log('In learnpath::start_current_item()');
5598
            error_log('current: '.$this->current);
5599
        }
5600
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5601
            $type = $this->get_type();
5602
            $item_type = $this->items[$this->current]->get_type();
5603
            if (($type == 2 && $item_type != 'sco') ||
5604
                ($type == 3 && $item_type != 'au') ||
5605
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5606
            ) {
5607
                if ($debug) {
5608
                    error_log('item type: '.$item_type);
5609
                    error_log('lp type: '.$type);
5610
                }
5611
                $this->items[$this->current]->open($allow_new_attempt);
5612
                $this->autocomplete_parents($this->current);
5613
                $prereq_check = $this->prerequisites_match($this->current);
5614
                if ($debug) {
5615
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5616
                }
5617
                $this->items[$this->current]->save(false, $prereq_check);
5618
            }
5619
            // If sco, then it is supposed to have been updated by some other call.
5620
            if ($item_type == 'sco') {
5621
                $this->items[$this->current]->restart();
5622
            }
5623
        }
5624
        if ($debug) {
5625
            error_log('lp_view_session_id');
5626
            error_log($this->lp_view_session_id);
5627
            error_log('api session id');
5628
            error_log(api_get_session_id());
5629
            error_log('End of learnpath::start_current_item()');
5630
        }
5631
5632
        return true;
5633
    }
5634
5635
    /**
5636
     * Stops the processing and counters for the old item (as held in $this->last).
5637
     *
5638
     * @return bool True/False
5639
     */
5640
    public function stop_previous_item()
5641
    {
5642
        $debug = $this->debug;
5643
        if ($debug) {
5644
            error_log('In learnpath::stop_previous_item()', 0);
5645
        }
5646
5647
        if ($this->last != 0 && $this->last != $this->current &&
5648
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5649
        ) {
5650
            if ($debug) {
5651
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5652
            }
5653
            switch ($this->get_type()) {
5654
                case '3':
5655
                    if ($this->items[$this->last]->get_type() != 'au') {
5656
                        if ($debug) {
5657
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5658
                        }
5659
                        $this->items[$this->last]->close();
5660
                    } else {
5661
                        if ($debug) {
5662
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5663
                        }
5664
                    }
5665
                    break;
5666
                case '2':
5667
                    if ($this->items[$this->last]->get_type() != 'sco') {
5668
                        if ($debug) {
5669
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5670
                        }
5671
                        $this->items[$this->last]->close();
5672
                    } else {
5673
                        if ($debug) {
5674
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5675
                        }
5676
                    }
5677
                    break;
5678
                case '1':
5679
                default:
5680
                    if ($debug) {
5681
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5682
                    }
5683
                    $this->items[$this->last]->close();
5684
                    break;
5685
            }
5686
        } else {
5687
            if ($debug) {
5688
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5689
            }
5690
5691
            return false;
5692
        }
5693
5694
        return true;
5695
    }
5696
5697
    /**
5698
     * Updates the default view mode from fullscreen to embedded and inversely.
5699
     *
5700
     * @return string The current default view mode ('fullscreen' or 'embedded')
5701
     */
5702
    public function update_default_view_mode()
5703
    {
5704
        $table = Database::get_course_table(TABLE_LP_MAIN);
5705
        $sql = "SELECT * FROM $table
5706
                WHERE iid = ".$this->get_id();
5707
        $res = Database::query($sql);
5708
        if (Database::num_rows($res) > 0) {
5709
            $row = Database::fetch_array($res);
5710
            $default_view_mode = $row['default_view_mod'];
5711
            $view_mode = $default_view_mode;
5712
            switch ($default_view_mode) {
5713
                case 'fullscreen': // default with popup
5714
                    $view_mode = 'embedded';
5715
                    break;
5716
                case 'embedded': // default view with left menu
5717
                    $view_mode = 'embedframe';
5718
                    break;
5719
                case 'embedframe': //folded menu
5720
                    $view_mode = 'impress';
5721
                    break;
5722
                case 'impress':
5723
                    $view_mode = 'fullscreen';
5724
                    break;
5725
            }
5726
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5727
                    WHERE iid = ".$this->get_id();
5728
            Database::query($sql);
5729
            $this->mode = $view_mode;
5730
5731
            return $view_mode;
5732
        }
5733
5734
        return -1;
5735
    }
5736
5737
    /**
5738
     * Updates the default behaviour about auto-commiting SCORM updates.
5739
     *
5740
     * @return bool True if auto-commit has been set to 'on', false otherwise
5741
     */
5742
    public function update_default_scorm_commit()
5743
    {
5744
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5745
        $sql = "SELECT * FROM $lp_table
5746
                WHERE iid = ".$this->get_id();
5747
        $res = Database::query($sql);
5748
        if (Database::num_rows($res) > 0) {
5749
            $row = Database::fetch_array($res);
5750
            $force = $row['force_commit'];
5751
            if ($force == 1) {
5752
                $force = 0;
5753
                $force_return = false;
5754
            } elseif ($force == 0) {
5755
                $force = 1;
5756
                $force_return = true;
5757
            }
5758
            $sql = "UPDATE $lp_table SET force_commit = $force
5759
                    WHERE iid = ".$this->get_id();
5760
            Database::query($sql);
5761
            $this->force_commit = $force_return;
5762
5763
            return $force_return;
5764
        }
5765
5766
        return -1;
5767
    }
5768
5769
    /**
5770
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5771
     *
5772
     * @return bool True on success, false on failure
5773
     */
5774
    public function update_display_order()
5775
    {
5776
        $course_id = api_get_course_int_id();
5777
        $table = Database::get_course_table(TABLE_LP_MAIN);
5778
        $sql = "SELECT * FROM $table
5779
                WHERE c_id = $course_id
5780
                ORDER BY display_order";
5781
        $res = Database::query($sql);
5782
        if ($res === false) {
5783
            return false;
5784
        }
5785
5786
        $num = Database::num_rows($res);
5787
        // First check the order is correct, globally (might be wrong because
5788
        // of versions < 1.8.4).
5789
        if ($num > 0) {
5790
            $i = 1;
5791
            while ($row = Database::fetch_array($res)) {
5792
                if ($row['display_order'] != $i) {
5793
                    // If we find a gap in the order, we need to fix it.
5794
                    $sql = "UPDATE $table SET display_order = $i
5795
                            WHERE iid = ".$row['iid'];
5796
                    Database::query($sql);
5797
                }
5798
                $i++;
5799
            }
5800
        }
5801
5802
        return true;
5803
    }
5804
5805
    /**
5806
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5807
     *
5808
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5809
     */
5810
    public function update_reinit()
5811
    {
5812
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5813
        $sql = "SELECT * FROM $lp_table
5814
                WHERE iid = ".$this->get_id();
5815
        $res = Database::query($sql);
5816
        if (Database::num_rows($res) > 0) {
5817
            $row = Database::fetch_array($res);
5818
            $force = $row['prevent_reinit'];
5819
            if ($force == 1) {
5820
                $force = 0;
5821
            } elseif ($force == 0) {
5822
                $force = 1;
5823
            }
5824
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5825
                    WHERE iid = ".$this->get_id();
5826
            Database::query($sql);
5827
            $this->prevent_reinit = $force;
5828
5829
            return $force;
5830
        }
5831
5832
        return -1;
5833
    }
5834
5835
    /**
5836
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5837
     *
5838
     * @return string 'single', 'multi' or 'seriousgame'
5839
     *
5840
     * @author ndiechburg <[email protected]>
5841
     */
5842
    public function get_attempt_mode()
5843
    {
5844
        //Set default value for seriousgame_mode
5845
        if (!isset($this->seriousgame_mode)) {
5846
            $this->seriousgame_mode = 0;
5847
        }
5848
        // Set default value for prevent_reinit
5849
        if (!isset($this->prevent_reinit)) {
5850
            $this->prevent_reinit = 1;
5851
        }
5852
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5853
            return 'seriousgame';
5854
        }
5855
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5856
            return 'single';
5857
        }
5858
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5859
            return 'multiple';
5860
        }
5861
5862
        return 'single';
5863
    }
5864
5865
    /**
5866
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5867
     *
5868
     * @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...
5869
     *
5870
     * @return bool
5871
     *
5872
     * @author ndiechburg <[email protected]>
5873
     */
5874
    public function set_attempt_mode($mode)
5875
    {
5876
        switch ($mode) {
5877
            case 'seriousgame':
5878
                $sg_mode = 1;
5879
                $prevent_reinit = 1;
5880
                break;
5881
            case 'single':
5882
                $sg_mode = 0;
5883
                $prevent_reinit = 1;
5884
                break;
5885
            case 'multiple':
5886
                $sg_mode = 0;
5887
                $prevent_reinit = 0;
5888
                break;
5889
            default:
5890
                $sg_mode = 0;
5891
                $prevent_reinit = 0;
5892
                break;
5893
        }
5894
        $this->prevent_reinit = $prevent_reinit;
5895
        $this->seriousgame_mode = $sg_mode;
5896
        $table = Database::get_course_table(TABLE_LP_MAIN);
5897
        $sql = "UPDATE $table SET
5898
                prevent_reinit = $prevent_reinit ,
5899
                seriousgame_mode = $sg_mode
5900
                WHERE iid = ".$this->get_id();
5901
        $res = Database::query($sql);
5902
        if ($res) {
5903
            return true;
5904
        } else {
5905
            return false;
5906
        }
5907
    }
5908
5909
    /**
5910
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5911
     *
5912
     * @author ndiechburg <[email protected]>
5913
     */
5914
    public function switch_attempt_mode()
5915
    {
5916
        if ($this->debug > 0) {
5917
            error_log('In learnpath::switch_attempt_mode()', 0);
5918
        }
5919
        $mode = $this->get_attempt_mode();
5920
        switch ($mode) {
5921
            case 'single':
5922
                $next_mode = 'multiple';
5923
                break;
5924
            case 'multiple':
5925
                $next_mode = 'seriousgame';
5926
                break;
5927
            case 'seriousgame':
5928
                $next_mode = 'single';
5929
                break;
5930
            default:
5931
                $next_mode = 'single';
5932
                break;
5933
        }
5934
        $this->set_attempt_mode($next_mode);
5935
    }
5936
5937
    /**
5938
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5939
     * but possibility to do again a completed item.
5940
     *
5941
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5942
     *
5943
     * @author ndiechburg <[email protected]>
5944
     */
5945
    public function set_seriousgame_mode()
5946
    {
5947
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5948
        $sql = "SELECT * FROM $lp_table
5949
                WHERE iid = ".$this->get_id();
5950
        $res = Database::query($sql);
5951
        if (Database::num_rows($res) > 0) {
5952
            $row = Database::fetch_array($res);
5953
            $force = $row['seriousgame_mode'];
5954
            if ($force == 1) {
5955
                $force = 0;
5956
            } elseif ($force == 0) {
5957
                $force = 1;
5958
            }
5959
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5960
			        WHERE iid = ".$this->get_id();
5961
            Database::query($sql);
5962
            $this->seriousgame_mode = $force;
5963
5964
            return $force;
5965
        }
5966
5967
        return -1;
5968
    }
5969
5970
    /**
5971
     * Updates the "scorm_debug" value that shows or hide the debug window.
5972
     *
5973
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5974
     */
5975
    public function update_scorm_debug()
5976
    {
5977
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5978
        $sql = "SELECT * FROM $lp_table
5979
                WHERE iid = ".$this->get_id();
5980
        $res = Database::query($sql);
5981
        if (Database::num_rows($res) > 0) {
5982
            $row = Database::fetch_array($res);
5983
            $force = $row['debug'];
5984
            if ($force == 1) {
5985
                $force = 0;
5986
            } elseif ($force == 0) {
5987
                $force = 1;
5988
            }
5989
            $sql = "UPDATE $lp_table SET debug = $force
5990
                    WHERE iid = ".$this->get_id();
5991
            Database::query($sql);
5992
            $this->scorm_debug = $force;
5993
5994
            return $force;
5995
        }
5996
5997
        return -1;
5998
    }
5999
6000
    /**
6001
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6002
     *
6003
     * @author Kevin Van Den Haute
6004
     *
6005
     * @param  array
6006
     */
6007
    public function tree_array($array)
6008
    {
6009
        $array = $this->sort_tree_array($array);
6010
        $this->create_tree_array($array);
6011
    }
6012
6013
    /**
6014
     * Creates an array with the elements of the learning path tree in it.
6015
     *
6016
     * @author Kevin Van Den Haute
6017
     *
6018
     * @param array $array
6019
     * @param int   $parent
6020
     * @param int   $depth
6021
     * @param array $tmp
6022
     */
6023
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6024
    {
6025
        if (is_array($array)) {
6026
            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...
6027
                if ($array[$i]['parent_item_id'] == $parent) {
6028
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6029
                        $tmp[] = $array[$i]['parent_item_id'];
6030
                        $depth++;
6031
                    }
6032
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6033
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6034
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6035
6036
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6037
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6038
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6039
                    $this->arrMenu[] = [
6040
                        'id' => $array[$i]['id'],
6041
                        'ref' => $ref,
6042
                        'item_type' => $array[$i]['item_type'],
6043
                        'title' => $array[$i]['title'],
6044
                        'path' => $path,
6045
                        'description' => $array[$i]['description'],
6046
                        'parent_item_id' => $array[$i]['parent_item_id'],
6047
                        'previous_item_id' => $array[$i]['previous_item_id'],
6048
                        'next_item_id' => $array[$i]['next_item_id'],
6049
                        'min_score' => $array[$i]['min_score'],
6050
                        'max_score' => $array[$i]['max_score'],
6051
                        'mastery_score' => $array[$i]['mastery_score'],
6052
                        'display_order' => $array[$i]['display_order'],
6053
                        'prerequisite' => $preq,
6054
                        'depth' => $depth,
6055
                        'audio' => $audio,
6056
                        'prerequisite_min_score' => $prerequisiteMinScore,
6057
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6058
                    ];
6059
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6060
                }
6061
            }
6062
        }
6063
    }
6064
6065
    /**
6066
     * Sorts a multi dimensional array by parent id and display order.
6067
     *
6068
     * @author Kevin Van Den Haute
6069
     *
6070
     * @param array $array (array with al the learning path items in it)
6071
     *
6072
     * @return array
6073
     */
6074
    public function sort_tree_array($array)
6075
    {
6076
        foreach ($array as $key => $row) {
6077
            $parent[$key] = $row['parent_item_id'];
6078
            $position[$key] = $row['display_order'];
6079
        }
6080
6081
        if (count($array) > 0) {
6082
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6083
        }
6084
6085
        return $array;
6086
    }
6087
6088
    /**
6089
     * Function that creates a html list of learning path items so that we can add audio files to them.
6090
     *
6091
     * @author Kevin Van Den Haute
6092
     *
6093
     * @return string
6094
     */
6095
    public function overview()
6096
    {
6097
        $return = '';
6098
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6099
6100
        // we need to start a form when we want to update all the mp3 files
6101
        if ($update_audio == 'true') {
6102
            $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">';
6103
        }
6104
        $return .= '<div id="message"></div>';
6105
        if (count($this->items) == 0) {
6106
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6107
        } else {
6108
            $return_audio = '<table class="data_table">';
6109
            $return_audio .= '<tr>';
6110
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6111
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6112
            $return_audio .= '</tr>';
6113
6114
            if ($update_audio != 'true') {
6115
                $return .= '<div class="col-md-12">';
6116
                $return .= self::return_new_tree($update_audio);
6117
                $return .= '</div>';
6118
                $return .= Display::div(
6119
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6120
                    ['style' => 'float:left; margin-top:15px;width:100%']
6121
                );
6122
            } else {
6123
                $return_audio .= self::return_new_tree($update_audio);
6124
                $return .= $return_audio.'</table>';
6125
            }
6126
6127
            // We need to close the form when we are updating the mp3 files.
6128
            if ($update_audio == 'true') {
6129
                $return .= '<div class="footer-audio">';
6130
                $return .= Display::button(
6131
                    'save_audio',
6132
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6133
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6134
                );
6135
                $return .= '</div>';
6136
            }
6137
        }
6138
6139
        // We need to close the form when we are updating the mp3 files.
6140
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6141
            $return .= '</form>';
6142
        }
6143
6144
        return $return;
6145
    }
6146
6147
    /**
6148
     * @param string $update_audio
6149
     *
6150
     * @return array
6151
     */
6152
    public function processBuildMenuElements($update_audio = 'false')
6153
    {
6154
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6155
        $course_id = api_get_course_int_id();
6156
        $table = Database::get_course_table(TABLE_LP_ITEM);
6157
6158
        $sql = "SELECT * FROM $table
6159
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6160
6161
        $result = Database::query($sql);
6162
        $arrLP = [];
6163
        while ($row = Database::fetch_array($result)) {
6164
            $arrLP[] = [
6165
                'id' => $row['iid'],
6166
                'item_type' => $row['item_type'],
6167
                'title' => Security::remove_XSS($row['title']),
6168
                'path' => $row['path'],
6169
                'description' => Security::remove_XSS($row['description']),
6170
                'parent_item_id' => $row['parent_item_id'],
6171
                'previous_item_id' => $row['previous_item_id'],
6172
                'next_item_id' => $row['next_item_id'],
6173
                'max_score' => $row['max_score'],
6174
                'min_score' => $row['min_score'],
6175
                'mastery_score' => $row['mastery_score'],
6176
                'prerequisite' => $row['prerequisite'],
6177
                'display_order' => $row['display_order'],
6178
                'audio' => $row['audio'],
6179
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6180
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6181
            ];
6182
        }
6183
6184
        $this->tree_array($arrLP);
6185
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6186
        unset($this->arrMenu);
6187
        $default_data = null;
6188
        $default_content = null;
6189
        $elements = [];
6190
        $return_audio = null;
6191
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6192
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6193
6194
        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...
6195
            $title = $arrLP[$i]['title'];
6196
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6197
6198
            // Link for the documents
6199
            if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6200
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6201
                $title_cut = Display::url(
6202
                    $title_cut,
6203
                    $url,
6204
                    [
6205
                        'class' => 'ajax moved',
6206
                        'data-title' => $title,
6207
                        'title' => $title,
6208
                    ]
6209
                );
6210
            }
6211
6212
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6213
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6214
                Session::write('pathItem', $arrLP[$i]['path']);
6215
            }
6216
6217
            $oddClass = 'row_even';
6218
            if (($i % 2) == 0) {
6219
                $oddClass = 'row_odd';
6220
            }
6221
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6222
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6223
6224
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6225
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6226
            } else {
6227
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6228
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6229
                } else {
6230
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6231
                        $icon = Display::return_icon('certificate.png');
6232
                    } else {
6233
                        $icon = Display::return_icon('folder_document.gif');
6234
                    }
6235
                }
6236
            }
6237
6238
            // The audio column.
6239
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6240
            $audio = '';
6241
            if (!$update_audio || $update_audio != 'true') {
6242
                if (empty($arrLP[$i]['audio'])) {
6243
                    $audio .= '';
6244
                }
6245
            } else {
6246
                $types = self::getChapterTypes();
6247
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6248
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6249
                    if (!empty($arrLP[$i]['audio'])) {
6250
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6251
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6252
                    }
6253
                }
6254
            }
6255
6256
            $return_audio .= Display::span($icon.' '.$title).
6257
                Display::tag(
6258
                    'td',
6259
                    $audio,
6260
                    ['style' => '']
6261
                );
6262
            $return_audio .= '</td>';
6263
            $move_icon = '';
6264
            $move_item_icon = '';
6265
            $edit_icon = '';
6266
            $delete_icon = '';
6267
            $audio_icon = '';
6268
            $prerequisities_icon = '';
6269
            $forumIcon = '';
6270
            $previewIcon = '';
6271
            $pluginCalendarIcon = '';
6272
            $orderIcons = '';
6273
6274
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6275
            $plugin = null;
6276
            if ($pluginCalendar) {
6277
                $plugin = LearningCalendarPlugin::create();
6278
            }
6279
6280
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6281
6282
            if ($is_allowed_to_edit) {
6283
                if (!$update_audio || $update_audio != 'true') {
6284
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6285
                        $move_icon .= '<a class="moved" href="#">';
6286
                        $move_icon .= Display::return_icon(
6287
                            'move_everywhere.png',
6288
                            get_lang('Move'),
6289
                            [],
6290
                            ICON_SIZE_TINY
6291
                        );
6292
                        $move_icon .= '</a>';
6293
                    }
6294
                }
6295
6296
                // No edit for this item types
6297
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6298
                    if ($arrLP[$i]['item_type'] != 'dir') {
6299
                        $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">';
6300
                        $edit_icon .= Display::return_icon(
6301
                            'edit.png',
6302
                            get_lang('LearnpathEditModule'),
6303
                            [],
6304
                            ICON_SIZE_TINY
6305
                        );
6306
                        $edit_icon .= '</a>';
6307
6308
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6309
                            $forumThread = null;
6310
                            if (isset($this->items[$arrLP[$i]['id']])) {
6311
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6312
                                    $this->course_int_id,
6313
                                    $this->lp_session_id
6314
                                );
6315
                            }
6316
                            if ($forumThread) {
6317
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6318
                                        'action' => 'dissociate_forum',
6319
                                        'id' => $arrLP[$i]['id'],
6320
                                        'lp_id' => $this->lp_id,
6321
                                    ]);
6322
                                $forumIcon = Display::url(
6323
                                    Display::return_icon(
6324
                                        'forum.png',
6325
                                        get_lang('DissociateForumToLPItem'),
6326
                                        [],
6327
                                        ICON_SIZE_TINY
6328
                                    ),
6329
                                    $forumIconUrl,
6330
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6331
                                );
6332
                            } else {
6333
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6334
                                        'action' => 'create_forum',
6335
                                        'id' => $arrLP[$i]['id'],
6336
                                        'lp_id' => $this->lp_id,
6337
                                    ]);
6338
                                $forumIcon = Display::url(
6339
                                    Display::return_icon(
6340
                                        'forum.png',
6341
                                        get_lang('AssociateForumToLPItem'),
6342
                                        [],
6343
                                        ICON_SIZE_TINY
6344
                                    ),
6345
                                    $forumIconUrl,
6346
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6347
                                );
6348
                            }
6349
                        }
6350
                    } else {
6351
                        $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">';
6352
                        $edit_icon .= Display::return_icon(
6353
                            'edit.png',
6354
                            get_lang('LearnpathEditModule'),
6355
                            [],
6356
                            ICON_SIZE_TINY
6357
                        );
6358
                        $edit_icon .= '</a>';
6359
                    }
6360
                } else {
6361
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6362
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6363
                        $edit_icon .= Display::return_icon(
6364
                            'edit.png',
6365
                            get_lang('Edit'),
6366
                            [],
6367
                            ICON_SIZE_TINY
6368
                        );
6369
                        $edit_icon .= '</a>';
6370
                    }
6371
                }
6372
6373
                if ($pluginCalendar) {
6374
                    $pluginLink = $pluginUrl.
6375
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6376
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6377
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6378
                    if ($itemInfo && $itemInfo['value'] == 1) {
6379
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6380
                    }
6381
                    $pluginCalendarIcon = Display::url(
6382
                        $iconCalendar,
6383
                        $pluginLink,
6384
                        ['class' => 'btn btn-default']
6385
                    );
6386
                }
6387
6388
                $delete_icon .= ' <a
6389
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6390
                    onclick="return confirmation(\''.addslashes($title).'\');"
6391
                    class="btn btn-default">';
6392
                $delete_icon .= Display::return_icon(
6393
                    'delete.png',
6394
                    get_lang('LearnpathDeleteModule'),
6395
                    [],
6396
                    ICON_SIZE_TINY
6397
                );
6398
                $delete_icon .= '</a>';
6399
6400
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6401
                $previewImage = Display::return_icon(
6402
                    'preview_view.png',
6403
                    get_lang('Preview'),
6404
                    [],
6405
                    ICON_SIZE_TINY
6406
                );
6407
6408
                switch ($arrLP[$i]['item_type']) {
6409
                    case TOOL_DOCUMENT:
6410
                    case TOOL_LP_FINAL_ITEM:
6411
                    case TOOL_READOUT_TEXT:
6412
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6413
                        $previewIcon = Display::url(
6414
                            $previewImage,
6415
                            $urlPreviewLink,
6416
                            [
6417
                                'target' => '_blank',
6418
                                'class' => 'btn btn-default',
6419
                                'data-title' => $arrLP[$i]['title'],
6420
                                'title' => $arrLP[$i]['title'],
6421
                            ]
6422
                        );
6423
                        break;
6424
                    case TOOL_THREAD:
6425
                    case TOOL_FORUM:
6426
                    case TOOL_QUIZ:
6427
                    case TOOL_STUDENTPUBLICATION:
6428
                    case TOOL_LP_FINAL_ITEM:
6429
                    case TOOL_LINK:
6430
                        //$target = '';
6431
                        //$class = 'btn btn-default ajax';
6432
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6433
                        $class = 'btn btn-default';
6434
                        $target = '_blank';
6435
                        //}
6436
6437
                        $link = self::rl_get_resource_link_for_learnpath(
6438
                            $this->course_int_id,
6439
                            $this->lp_id,
6440
                            $arrLP[$i]['id'],
6441
                            0
6442
                        );
6443
                        $previewIcon = Display::url(
6444
                            $previewImage,
6445
                            $link,
6446
                            [
6447
                                'class' => $class,
6448
                                'data-title' => $arrLP[$i]['title'],
6449
                                'title' => $arrLP[$i]['title'],
6450
                                'target' => $target,
6451
                            ]
6452
                        );
6453
                        break;
6454
                    default:
6455
                        $previewIcon = Display::url(
6456
                            $previewImage,
6457
                            $url.'&action=view_item',
6458
                            ['class' => 'btn btn-default', 'target' => '_blank']
6459
                        );
6460
                        break;
6461
                }
6462
6463
                if ($arrLP[$i]['item_type'] != 'dir') {
6464
                    $prerequisities_icon = Display::url(
6465
                        Display::return_icon(
6466
                            'accept.png',
6467
                            get_lang('LearnpathPrerequisites'),
6468
                            [],
6469
                            ICON_SIZE_TINY
6470
                        ),
6471
                        $url.'&action=edit_item_prereq',
6472
                        ['class' => 'btn btn-default']
6473
                    );
6474
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6475
                        $move_item_icon = Display::url(
6476
                            Display::return_icon(
6477
                                'move.png',
6478
                                get_lang('Move'),
6479
                                [],
6480
                                ICON_SIZE_TINY
6481
                            ),
6482
                            $url.'&action=move_item',
6483
                            ['class' => 'btn btn-default']
6484
                        );
6485
                    }
6486
                    $audio_icon = Display::url(
6487
                        Display::return_icon(
6488
                            'audio.png',
6489
                            get_lang('UplUpload'),
6490
                            [],
6491
                            ICON_SIZE_TINY
6492
                        ),
6493
                        $url.'&action=add_audio',
6494
                        ['class' => 'btn btn-default']
6495
                    );
6496
                }
6497
            }
6498
            if ($update_audio != 'true') {
6499
                $row = $move_icon.' '.$icon.
6500
                    Display::span($title_cut).
6501
                    Display::tag(
6502
                        'div',
6503
                        "<div class=\"btn-group btn-group-xs\">
6504
                                    $previewIcon
6505
                                    $audio
6506
                                    $edit_icon
6507
                                    $pluginCalendarIcon
6508
                                    $forumIcon
6509
                                    $prerequisities_icon
6510
                                    $move_item_icon
6511
                                    $audio_icon
6512
                                    $delete_icon
6513
                                </div>",
6514
                        ['class' => 'btn-toolbar button_actions']
6515
                    );
6516
            } else {
6517
                $row =
6518
                    Display::span($title.$icon).
6519
                    Display::span($audio, ['class' => 'button_actions']);
6520
            }
6521
6522
            $parent_id = $arrLP[$i]['parent_item_id'];
6523
            $default_data[$arrLP[$i]['id']] = $row;
6524
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6525
6526
            if (empty($parent_id)) {
6527
                $elements[$arrLP[$i]['id']]['data'] = $row;
6528
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6529
            } else {
6530
                $parent_arrays = [];
6531
                if ($arrLP[$i]['depth'] > 1) {
6532
                    //Getting list of parents
6533
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6534
                        foreach ($arrLP as $item) {
6535
                            if ($item['id'] == $parent_id) {
6536
                                if ($item['parent_item_id'] == 0) {
6537
                                    $parent_id = $item['id'];
6538
                                    break;
6539
                                } else {
6540
                                    $parent_id = $item['parent_item_id'];
6541
                                    if (empty($parent_arrays)) {
6542
                                        $parent_arrays[] = intval($item['id']);
6543
                                    }
6544
                                    $parent_arrays[] = $parent_id;
6545
                                    break;
6546
                                }
6547
                            }
6548
                        }
6549
                    }
6550
                }
6551
6552
                if (!empty($parent_arrays)) {
6553
                    $parent_arrays = array_reverse($parent_arrays);
6554
                    $val = '$elements';
6555
                    $x = 0;
6556
                    foreach ($parent_arrays as $item) {
6557
                        if ($x != count($parent_arrays) - 1) {
6558
                            $val .= '["'.$item.'"]["children"]';
6559
                        } else {
6560
                            $val .= '["'.$item.'"]["children"]';
6561
                        }
6562
                        $x++;
6563
                    }
6564
                    $val .= "";
6565
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6566
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6567
                } else {
6568
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6569
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6570
                }
6571
            }
6572
        }
6573
6574
        return [
6575
            'elements' => $elements,
6576
            'default_data' => $default_data,
6577
            'default_content' => $default_content,
6578
            'return_audio' => $return_audio,
6579
        ];
6580
    }
6581
6582
    /**
6583
     * @param string $updateAudio true/false strings
6584
     *
6585
     * @return string
6586
     */
6587
    public function returnLpItemList($updateAudio)
6588
    {
6589
        $result = $this->processBuildMenuElements($updateAudio);
6590
6591
        $html = self::print_recursive(
6592
            $result['elements'],
6593
            $result['default_data'],
6594
            $result['default_content']
6595
        );
6596
6597
        if (!empty($html)) {
6598
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6599
        }
6600
6601
        return $html;
6602
    }
6603
6604
    /**
6605
     * @param string $update_audio
6606
     * @param bool   $drop_element_here
6607
     *
6608
     * @return string
6609
     */
6610
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6611
    {
6612
        $return = '';
6613
        $result = $this->processBuildMenuElements($update_audio);
6614
6615
        $list = '<ul id="lp_item_list">';
6616
        $tree = $this->print_recursive(
6617
            $result['elements'],
6618
            $result['default_data'],
6619
            $result['default_content']
6620
        );
6621
6622
        if (!empty($tree)) {
6623
            $list .= $tree;
6624
        } else {
6625
            if ($drop_element_here) {
6626
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6627
            }
6628
        }
6629
        $list .= '</ul>';
6630
6631
        $return .= Display::panelCollapse(
6632
            $this->name,
6633
            $list,
6634
            'scorm-list',
6635
            null,
6636
            'scorm-list-accordion',
6637
            'scorm-list-collapse'
6638
        );
6639
6640
        if ($update_audio === 'true') {
6641
            $return = $result['return_audio'];
6642
        }
6643
6644
        return $return;
6645
    }
6646
6647
    /**
6648
     * @param array $elements
6649
     * @param array $default_data
6650
     * @param array $default_content
6651
     *
6652
     * @return string
6653
     */
6654
    public function print_recursive($elements, $default_data, $default_content)
6655
    {
6656
        $return = '';
6657
        foreach ($elements as $key => $item) {
6658
            if (isset($item['load_data']) || empty($item['data'])) {
6659
                $item['data'] = $default_data[$item['load_data']];
6660
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6661
            }
6662
            $sub_list = '';
6663
            if (isset($item['type']) && $item['type'] == 'dir') {
6664
                // empty value
6665
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6666
            }
6667
            if (empty($item['children'])) {
6668
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6669
                $active = null;
6670
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6671
                    $active = 'active';
6672
                }
6673
                $return .= Display::tag(
6674
                    'li',
6675
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6676
                    ['id' => $key, 'class' => 'record li_container']
6677
                );
6678
            } else {
6679
                // Sections
6680
                $data = '';
6681
                if (isset($item['children'])) {
6682
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6683
                }
6684
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6685
                $return .= Display::tag(
6686
                    'li',
6687
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6688
                    ['id' => $key, 'class' => 'record li_container']
6689
                );
6690
            }
6691
        }
6692
6693
        return $return;
6694
    }
6695
6696
    /**
6697
     * This function builds the action menu.
6698
     *
6699
     * @param bool $returnContent          Optional
6700
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6701
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6702
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6703
     *
6704
     * @return string
6705
     */
6706
    public function build_action_menu(
6707
        $returnContent = false,
6708
        $showRequirementButtons = true,
6709
        $isConfigPage = false,
6710
        $allowExpand = true
6711
    ) {
6712
        $actionsLeft = '';
6713
        $actionsRight = '';
6714
        $actionsLeft .= Display::url(
6715
            Display::return_icon(
6716
                'back.png',
6717
                get_lang('ReturnToLearningPaths'),
6718
                '',
6719
                ICON_SIZE_MEDIUM
6720
            ),
6721
            'lp_controller.php?'.api_get_cidreq()
6722
        );
6723
        $actionsLeft .= Display::url(
6724
            Display::return_icon(
6725
                'preview_view.png',
6726
                get_lang('Preview'),
6727
                '',
6728
                ICON_SIZE_MEDIUM
6729
            ),
6730
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6731
                'action' => 'view',
6732
                'lp_id' => $this->lp_id,
6733
                'isStudentView' => 'true',
6734
            ])
6735
        );
6736
6737
        $actionsLeft .= Display::url(
6738
            Display::return_icon(
6739
                'upload_audio.png',
6740
                get_lang('UpdateAllAudioFragments'),
6741
                '',
6742
                ICON_SIZE_MEDIUM
6743
            ),
6744
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6745
                'action' => 'admin_view',
6746
                'lp_id' => $this->lp_id,
6747
                'updateaudio' => 'true',
6748
            ])
6749
        );
6750
6751
        if (!$isConfigPage) {
6752
            $actionsLeft .= Display::url(
6753
                Display::return_icon(
6754
                    'settings.png',
6755
                    get_lang('CourseSettings'),
6756
                    '',
6757
                    ICON_SIZE_MEDIUM
6758
                ),
6759
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6760
                    'action' => 'edit',
6761
                    'lp_id' => $this->lp_id,
6762
                ])
6763
            );
6764
        } else {
6765
            $actionsLeft .= Display::url(
6766
                Display::return_icon(
6767
                    'edit.png',
6768
                    get_lang('Edit'),
6769
                    '',
6770
                    ICON_SIZE_MEDIUM
6771
                ),
6772
                'lp_controller.php?'.http_build_query([
6773
                    'action' => 'build',
6774
                    'lp_id' => $this->lp_id,
6775
                ]).'&'.api_get_cidreq()
6776
            );
6777
        }
6778
6779
        if ($allowExpand) {
6780
            $actionsLeft .= Display::url(
6781
                Display::return_icon(
6782
                    'expand.png',
6783
                    get_lang('Expand'),
6784
                    ['id' => 'expand'],
6785
                    ICON_SIZE_MEDIUM
6786
                ).
6787
                Display::return_icon(
6788
                    'contract.png',
6789
                    get_lang('Collapse'),
6790
                    ['id' => 'contract', 'class' => 'hide'],
6791
                    ICON_SIZE_MEDIUM
6792
                ),
6793
                '#',
6794
                ['role' => 'button', 'id' => 'hide_bar_template']
6795
            );
6796
        }
6797
6798
        if ($showRequirementButtons) {
6799
            $buttons = [
6800
                [
6801
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6802
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6803
                        'action' => 'set_previous_step_as_prerequisite',
6804
                        'lp_id' => $this->lp_id,
6805
                    ]),
6806
                ],
6807
                [
6808
                    'title' => get_lang('ClearAllPrerequisites'),
6809
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6810
                        'action' => 'clear_prerequisites',
6811
                        'lp_id' => $this->lp_id,
6812
                    ]),
6813
                ],
6814
            ];
6815
            $actionsRight = Display::groupButtonWithDropDown(
6816
                get_lang('PrerequisitesOptions'),
6817
                $buttons,
6818
                true
6819
            );
6820
        }
6821
6822
        $toolbar = Display::toolbarAction(
6823
            'actions-lp-controller',
6824
            [$actionsLeft, $actionsRight]
6825
        );
6826
6827
        if ($returnContent) {
6828
            return $toolbar;
6829
        }
6830
6831
        echo $toolbar;
6832
    }
6833
6834
    /**
6835
     * Creates the default learning path folder.
6836
     *
6837
     * @param array $course
6838
     * @param int   $creatorId
6839
     *
6840
     * @return bool
6841
     */
6842
    public static function generate_learning_path_folder($course, $creatorId = 0)
6843
    {
6844
        // Creating learning_path folder
6845
        $dir = '/learning_path';
6846
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6847
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6848
6849
        $folder = false;
6850
        if (!is_dir($filepath.'/'.$dir)) {
6851
            $folderData = create_unexisting_directory(
6852
                $course,
6853
                $creatorId,
6854
                0,
6855
                null,
6856
                0,
6857
                $filepath,
6858
                $dir,
6859
                get_lang('LearningPaths'),
6860
                0
6861
            );
6862
            if (!empty($folderData)) {
6863
                $folder = true;
6864
            }
6865
        } else {
6866
            $folder = true;
6867
        }
6868
6869
        return $folder;
6870
    }
6871
6872
    /**
6873
     * @param array  $course
6874
     * @param string $lp_name
6875
     * @param int    $creatorId
6876
     *
6877
     * @return array
6878
     */
6879
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6880
    {
6881
        $filepath = '';
6882
        $dir = '/learning_path/';
6883
6884
        if (empty($lp_name)) {
6885
            $lp_name = $this->name;
6886
        }
6887
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6888
        $folder = self::generate_learning_path_folder($course, $creatorId);
6889
6890
        // Limits title size
6891
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6892
        $dir = $dir.$title;
6893
6894
        // Creating LP folder
6895
        $documentId = null;
6896
        if ($folder) {
6897
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6898
            if (!is_dir($filepath.'/'.$dir)) {
6899
                $folderData = create_unexisting_directory(
6900
                    $course,
6901
                    $creatorId,
6902
                    0,
6903
                    0,
6904
                    0,
6905
                    $filepath,
6906
                    $dir,
6907
                    $lp_name
6908
                );
6909
                if (!empty($folderData)) {
6910
                    $folder = true;
6911
                }
6912
6913
                $documentId = $folderData['id'];
6914
            } else {
6915
                $folder = true;
6916
            }
6917
            $dir = $dir.'/';
6918
            if ($folder) {
6919
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6920
            }
6921
        }
6922
6923
        if (empty($documentId)) {
6924
            $dir = api_remove_trailing_slash($dir);
6925
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6926
        }
6927
6928
        $array = [
6929
            'dir' => $dir,
6930
            'filepath' => $filepath,
6931
            'folder' => $folder,
6932
            'id' => $documentId,
6933
        ];
6934
6935
        return $array;
6936
    }
6937
6938
    /**
6939
     * Create a new document //still needs some finetuning.
6940
     *
6941
     * @param array  $courseInfo
6942
     * @param string $content
6943
     * @param string $title
6944
     * @param string $extension
6945
     * @param int    $parentId
6946
     * @param int    $creatorId  creator id
6947
     *
6948
     * @return int
6949
     */
6950
    public function create_document(
6951
        $courseInfo,
6952
        $content = '',
6953
        $title = '',
6954
        $extension = 'html',
6955
        $parentId = 0,
6956
        $creatorId = 0
6957
    ) {
6958
        if (!empty($courseInfo)) {
6959
            $course_id = $courseInfo['real_id'];
6960
        } else {
6961
            $course_id = api_get_course_int_id();
6962
        }
6963
6964
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6965
        $sessionId = api_get_session_id();
6966
6967
        // Generates folder
6968
        $result = $this->generate_lp_folder($courseInfo);
6969
        $dir = $result['dir'];
6970
6971
        if (empty($parentId) || $parentId == '/') {
6972
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6973
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6974
6975
            if ($parentId === '/') {
6976
                $dir = '/';
6977
            }
6978
6979
            // Please, do not modify this dirname formatting.
6980
            if (strstr($dir, '..')) {
6981
                $dir = '/';
6982
            }
6983
6984
            if (!empty($dir[0]) && $dir[0] == '.') {
6985
                $dir = substr($dir, 1);
6986
            }
6987
            if (!empty($dir[0]) && $dir[0] != '/') {
6988
                $dir = '/'.$dir;
6989
            }
6990
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6991
                $dir .= '/';
6992
            }
6993
        } else {
6994
            $parentInfo = DocumentManager::get_document_data_by_id(
6995
                $parentId,
6996
                $courseInfo['code']
6997
            );
6998
            if (!empty($parentInfo)) {
6999
                $dir = $parentInfo['path'].'/';
7000
            }
7001
        }
7002
7003
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7004
        if (!is_dir($filepath)) {
7005
            $dir = '/';
7006
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7007
        }
7008
7009
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7010
        // is already escaped twice when it gets here.
7011
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7012
        if (!empty($title)) {
7013
            $title = api_replace_dangerous_char(stripslashes($title));
7014
        } else {
7015
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7016
        }
7017
7018
        $title = disable_dangerous_file($title);
7019
        $filename = $title;
7020
        $content = !empty($content) ? $content : $_POST['content_lp'];
7021
        $tmp_filename = $filename;
7022
7023
        $i = 0;
7024
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7025
            $tmp_filename = $filename.'_'.++$i;
7026
        }
7027
7028
        $filename = $tmp_filename.'.'.$extension;
7029
        if ($extension == 'html') {
7030
            $content = stripslashes($content);
7031
            $content = str_replace(
7032
                api_get_path(WEB_COURSE_PATH),
7033
                api_get_path(REL_PATH).'courses/',
7034
                $content
7035
            );
7036
7037
            // Change the path of mp3 to absolute.
7038
            // The first regexp deals with :// urls.
7039
            $content = preg_replace(
7040
                "|(flashvars=\"file=)([^:/]+)/|",
7041
                "$1".api_get_path(
7042
                    REL_COURSE_PATH
7043
                ).$courseInfo['path'].'/document/',
7044
                $content
7045
            );
7046
            // The second regexp deals with audio/ urls.
7047
            $content = preg_replace(
7048
                "|(flashvars=\"file=)([^/]+)/|",
7049
                "$1".api_get_path(
7050
                    REL_COURSE_PATH
7051
                ).$courseInfo['path'].'/document/$2/',
7052
                $content
7053
            );
7054
            // For flv player: To prevent edition problem with firefox,
7055
            // we have to use a strange tip (don't blame me please).
7056
            $content = str_replace(
7057
                '</body>',
7058
                '<style type="text/css">body{}</style></body>',
7059
                $content
7060
            );
7061
        }
7062
7063
        if (!file_exists($filepath.$filename)) {
7064
            if ($fp = @fopen($filepath.$filename, 'w')) {
7065
                fputs($fp, $content);
7066
                fclose($fp);
7067
7068
                $file_size = filesize($filepath.$filename);
7069
                $save_file_path = $dir.$filename;
7070
7071
                $document_id = add_document(
7072
                    $courseInfo,
7073
                    $save_file_path,
7074
                    'file',
7075
                    $file_size,
7076
                    $tmp_filename,
7077
                    '',
7078
                    0, //readonly
7079
                    true,
7080
                    null,
7081
                    $sessionId,
7082
                    $creatorId
7083
                );
7084
7085
                if ($document_id) {
7086
                    api_item_property_update(
7087
                        $courseInfo,
7088
                        TOOL_DOCUMENT,
7089
                        $document_id,
7090
                        'DocumentAdded',
7091
                        $creatorId,
7092
                        null,
7093
                        null,
7094
                        null,
7095
                        null,
7096
                        $sessionId
7097
                    );
7098
7099
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7100
                    $new_title = $originalTitle;
7101
7102
                    if ($new_comment || $new_title) {
7103
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7104
                        $ct = '';
7105
                        if ($new_comment) {
7106
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7107
                        }
7108
                        if ($new_title) {
7109
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7110
                        }
7111
7112
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7113
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7114
                        Database::query($sql);
7115
                    }
7116
                }
7117
7118
                return $document_id;
7119
            }
7120
        }
7121
    }
7122
7123
    /**
7124
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7125
     *
7126
     * @param array $_course array
7127
     */
7128
    public function edit_document($_course)
7129
    {
7130
        $course_id = api_get_course_int_id();
7131
        $urlAppend = api_get_configuration_value('url_append');
7132
        // Please, do not modify this dirname formatting.
7133
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7134
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7135
7136
        if (strstr($dir, '..')) {
7137
            $dir = '/';
7138
        }
7139
7140
        if (isset($dir[0]) && $dir[0] == '.') {
7141
            $dir = substr($dir, 1);
7142
        }
7143
7144
        if (isset($dir[0]) && $dir[0] != '/') {
7145
            $dir = '/'.$dir;
7146
        }
7147
7148
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7149
            $dir .= '/';
7150
        }
7151
7152
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7153
        if (!is_dir($filepath)) {
7154
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7155
        }
7156
7157
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7158
7159
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7160
            $document_id = (int) $_POST['path'];
7161
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7162
            if (empty($documentInfo)) {
7163
                // Try with iid
7164
                $table = Database::get_course_table(TABLE_DOCUMENT);
7165
                $sql = "SELECT id, path FROM $table
7166
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7167
                $res_doc = Database::query($sql);
7168
                $row = Database::fetch_array($res_doc);
7169
                if ($row) {
7170
                    $document_id = $row['id'];
7171
                    $documentPath = $row['path'];
7172
                }
7173
            } else {
7174
                $documentPath = $documentInfo['path'];
7175
            }
7176
7177
            $content = stripslashes($_POST['content_lp']);
7178
            $file = $filepath.$documentPath;
7179
7180
            if (!file_exists($file)) {
7181
                return false;
7182
            }
7183
7184
            if ($fp = @fopen($file, 'w')) {
7185
                $content = str_replace(
7186
                    api_get_path(WEB_COURSE_PATH),
7187
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7188
                    $content
7189
                );
7190
                // Change the path of mp3 to absolute.
7191
                // The first regexp deals with :// urls.
7192
                $content = preg_replace(
7193
                    "|(flashvars=\"file=)([^:/]+)/|",
7194
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7195
                    $content
7196
                );
7197
                // The second regexp deals with audio/ urls.
7198
                $content = preg_replace(
7199
                    "|(flashvars=\"file=)([^:/]+)/|",
7200
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7201
                    $content
7202
                );
7203
                fputs($fp, $content);
7204
                fclose($fp);
7205
7206
                $sql = "UPDATE $table_doc SET
7207
                            title='".Database::escape_string($_POST['title'])."'
7208
                        WHERE c_id = $course_id AND id = ".$document_id;
7209
                Database::query($sql);
7210
            }
7211
        }
7212
    }
7213
7214
    /**
7215
     * Displays the selected item, with a panel for manipulating the item.
7216
     *
7217
     * @param int    $item_id
7218
     * @param string $msg
7219
     * @param bool   $show_actions
7220
     *
7221
     * @return string
7222
     */
7223
    public function display_item($item_id, $msg = null, $show_actions = true)
7224
    {
7225
        $course_id = api_get_course_int_id();
7226
        $return = '';
7227
        if (is_numeric($item_id)) {
7228
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7229
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7230
                    WHERE lp.iid = ".intval($item_id);
7231
            $result = Database::query($sql);
7232
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7233
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7234
7235
                // Prevents wrong parent selection for document, see Bug#1251.
7236
                if ($row['item_type'] != 'dir') {
7237
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7238
                }
7239
7240
                if ($show_actions) {
7241
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7242
                }
7243
                $return .= '<div style="padding:10px;">';
7244
7245
                if ($msg != '') {
7246
                    $return .= $msg;
7247
                }
7248
7249
                $return .= '<h3>'.$row['title'].'</h3>';
7250
7251
                switch ($row['item_type']) {
7252
                    case TOOL_THREAD:
7253
                        $link = $this->rl_get_resource_link_for_learnpath(
7254
                            $course_id,
7255
                            $row['lp_id'],
7256
                            $item_id,
7257
                            0
7258
                        );
7259
                        $return .= Display::url(
7260
                            get_lang('GoToThread'),
7261
                            $link,
7262
                            ['class' => 'btn btn-primary']
7263
                        );
7264
                        break;
7265
                    case TOOL_FORUM:
7266
                        $return .= Display::url(
7267
                            get_lang('GoToForum'),
7268
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7269
                            ['class' => 'btn btn-primary']
7270
                        );
7271
                        break;
7272
                    case TOOL_QUIZ:
7273
                        if (!empty($row['path'])) {
7274
                            $exercise = new Exercise();
7275
                            $exercise->read($row['path']);
7276
                            $return .= $exercise->description.'<br />';
7277
                            $return .= Display::url(
7278
                                get_lang('GoToExercise'),
7279
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7280
                                ['class' => 'btn btn-primary']
7281
                            );
7282
                        }
7283
                        break;
7284
                    case TOOL_LP_FINAL_ITEM:
7285
                        $return .= $this->getSavedFinalItem();
7286
                        break;
7287
                    case TOOL_DOCUMENT:
7288
                    case TOOL_READOUT_TEXT:
7289
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7290
                        $sql_doc = "SELECT path FROM $tbl_doc
7291
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7292
                        $result = Database::query($sql_doc);
7293
                        $path_file = Database::result($result, 0, 0);
7294
                        $path_parts = pathinfo($path_file);
7295
                        // TODO: Correct the following naive comparisons.
7296
                        if (in_array($path_parts['extension'], [
7297
                            'html',
7298
                            'txt',
7299
                            'png',
7300
                            'jpg',
7301
                            'JPG',
7302
                            'jpeg',
7303
                            'JPEG',
7304
                            'gif',
7305
                            'swf',
7306
                            'pdf',
7307
                            'htm',
7308
                        ])) {
7309
                            $return .= $this->display_document($row['path'], true, true);
7310
                        }
7311
                        break;
7312
                    case TOOL_HOTPOTATOES:
7313
                        $return .= $this->display_document($row['path'], false, true);
7314
                        break;
7315
                }
7316
                $return .= '</div>';
7317
            }
7318
        }
7319
7320
        return $return;
7321
    }
7322
7323
    /**
7324
     * Shows the needed forms for editing a specific item.
7325
     *
7326
     * @param int $item_id
7327
     *
7328
     * @throws Exception
7329
     * @throws HTML_QuickForm_Error
7330
     *
7331
     * @return string
7332
     */
7333
    public function display_edit_item($item_id)
7334
    {
7335
        $course_id = api_get_course_int_id();
7336
        $return = '';
7337
        $item_id = (int) $item_id;
7338
7339
        if (empty($item_id)) {
7340
            return '';
7341
        }
7342
7343
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7344
        $sql = "SELECT * FROM $tbl_lp_item
7345
                WHERE iid = ".$item_id;
7346
        $res = Database::query($sql);
7347
        $row = Database::fetch_array($res);
7348
        switch ($row['item_type']) {
7349
            case 'dir':
7350
            case 'asset':
7351
            case 'sco':
7352
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7353
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7354
                    $return .= $this->display_item_form(
7355
                        $row['item_type'],
7356
                        get_lang('EditCurrentChapter').' :',
7357
                        'edit',
7358
                        $item_id,
7359
                        $row
7360
                    );
7361
                } else {
7362
                    $return .= $this->display_item_form(
7363
                        $row['item_type'],
7364
                        get_lang('EditCurrentChapter').' :',
7365
                        'edit_item',
7366
                        $item_id,
7367
                        $row
7368
                    );
7369
                }
7370
                break;
7371
            case TOOL_DOCUMENT:
7372
            case TOOL_READOUT_TEXT:
7373
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7374
                $sql = "SELECT lp.*, doc.path as dir
7375
                        FROM $tbl_lp_item as lp
7376
                        LEFT JOIN $tbl_doc as doc
7377
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7378
                        WHERE
7379
                            doc.c_id = $course_id AND
7380
                            lp.iid = ".$item_id;
7381
                $res_step = Database::query($sql);
7382
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7383
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7384
7385
                if ($row['item_type'] === TOOL_DOCUMENT) {
7386
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7387
                }
7388
7389
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7390
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7391
                }
7392
                break;
7393
            case TOOL_LINK:
7394
                $linkId = (int) $row['path'];
7395
                if (!empty($linkId)) {
7396
                    $table = Database::get_course_table(TABLE_LINK);
7397
                    $sql = 'SELECT url FROM '.$table.'
7398
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7399
                    $res_link = Database::query($sql);
7400
                    $row_link = Database::fetch_array($res_link);
7401
                    if (empty($row_link)) {
7402
                        // Try with id
7403
                        $sql = 'SELECT url FROM '.$table.'
7404
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7405
                        $res_link = Database::query($sql);
7406
                        $row_link = Database::fetch_array($res_link);
7407
                    }
7408
7409
                    if (is_array($row_link)) {
7410
                        $row['url'] = $row_link['url'];
7411
                    }
7412
                }
7413
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7414
                $return .= $this->display_link_form('edit', $item_id, $row);
7415
                break;
7416
            case TOOL_LP_FINAL_ITEM:
7417
                Session::write('finalItem', true);
7418
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7419
                $sql = "SELECT lp.*, doc.path as dir
7420
                        FROM $tbl_lp_item as lp
7421
                        LEFT JOIN $tbl_doc as doc
7422
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7423
                        WHERE
7424
                            doc.c_id = $course_id AND
7425
                            lp.iid = ".$item_id;
7426
                $res_step = Database::query($sql);
7427
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7428
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7429
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7430
                break;
7431
            case TOOL_QUIZ:
7432
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7433
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7434
                break;
7435
            case TOOL_HOTPOTATOES:
7436
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7437
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7438
                break;
7439
            case TOOL_STUDENTPUBLICATION:
7440
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7441
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7442
                break;
7443
            case TOOL_FORUM:
7444
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7445
                $return .= $this->display_forum_form('edit', $item_id, $row);
7446
                break;
7447
            case TOOL_THREAD:
7448
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7449
                $return .= $this->display_thread_form('edit', $item_id, $row);
7450
                break;
7451
        }
7452
7453
        return $return;
7454
    }
7455
7456
    /**
7457
     * Function that displays a list with al the resources that
7458
     * could be added to the learning path.
7459
     *
7460
     * @throws Exception
7461
     * @throws HTML_QuickForm_Error
7462
     *
7463
     * @return bool
7464
     */
7465
    public function display_resources()
7466
    {
7467
        $course_code = api_get_course_id();
7468
7469
        // Get all the docs.
7470
        $documents = $this->get_documents(true);
7471
7472
        // Get all the exercises.
7473
        $exercises = $this->get_exercises();
7474
7475
        // Get all the links.
7476
        $links = $this->get_links();
7477
7478
        // Get all the student publications.
7479
        $works = $this->get_student_publications();
7480
7481
        // Get all the forums.
7482
        $forums = $this->get_forums(null, $course_code);
7483
7484
        // Get the final item form (see BT#11048) .
7485
        $finish = $this->getFinalItemForm();
7486
7487
        $headers = [
7488
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7489
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7490
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7491
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7492
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7493
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7494
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7495
        ];
7496
7497
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7498
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7499
7500
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7501
7502
        echo Display::tabs(
7503
            $headers,
7504
            [
7505
                $documents,
7506
                $exercises,
7507
                $links,
7508
                $works,
7509
                $forums,
7510
                $dir,
7511
                $finish,
7512
            ],
7513
            'resource_tab',
7514
            [],
7515
            [],
7516
            $selected
7517
        );
7518
7519
        return true;
7520
    }
7521
7522
    /**
7523
     * Returns the extension of a document.
7524
     *
7525
     * @param string $filename
7526
     *
7527
     * @return string Extension (part after the last dot)
7528
     */
7529
    public function get_extension($filename)
7530
    {
7531
        $explode = explode('.', $filename);
7532
7533
        return $explode[count($explode) - 1];
7534
    }
7535
7536
    /**
7537
     * Displays a document by id.
7538
     *
7539
     * @param int  $id
7540
     * @param bool $show_title
7541
     * @param bool $iframe
7542
     * @param bool $edit_link
7543
     *
7544
     * @return string
7545
     */
7546
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7547
    {
7548
        $_course = api_get_course_info();
7549
        $course_id = api_get_course_int_id();
7550
        $id = (int) $id;
7551
        $return = '';
7552
        $table = Database::get_course_table(TABLE_DOCUMENT);
7553
        $sql_doc = "SELECT * FROM $table
7554
                    WHERE c_id = $course_id AND iid = $id";
7555
        $res_doc = Database::query($sql_doc);
7556
        $row_doc = Database::fetch_array($res_doc);
7557
7558
        // TODO: Add a path filter.
7559
        if ($iframe) {
7560
            $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>';
7561
        } else {
7562
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7563
        }
7564
7565
        return $return;
7566
    }
7567
7568
    /**
7569
     * Return HTML form to add/edit a quiz.
7570
     *
7571
     * @param string $action     Action (add/edit)
7572
     * @param int    $id         Item ID if already exists
7573
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7574
     *
7575
     * @throws Exception
7576
     *
7577
     * @return string HTML form
7578
     */
7579
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7580
    {
7581
        $course_id = api_get_course_int_id();
7582
        $id = (int) $id;
7583
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7584
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7585
7586
        if ($id != 0 && is_array($extra_info)) {
7587
            $item_title = $extra_info['title'];
7588
            $item_description = $extra_info['description'];
7589
        } elseif (is_numeric($extra_info)) {
7590
            $sql = "SELECT title, description
7591
                    FROM $tbl_quiz
7592
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7593
7594
            $result = Database::query($sql);
7595
            $row = Database::fetch_array($result);
7596
            $item_title = $row['title'];
7597
            $item_description = $row['description'];
7598
        } else {
7599
            $item_title = '';
7600
            $item_description = '';
7601
        }
7602
        $item_title = Security::remove_XSS($item_title);
7603
        $item_description = Security::remove_XSS($item_description);
7604
7605
        $parent = 0;
7606
        if ($id != 0 && is_array($extra_info)) {
7607
            $parent = $extra_info['parent_item_id'];
7608
        }
7609
7610
        $sql = "SELECT * FROM $tbl_lp_item
7611
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7612
7613
        $result = Database::query($sql);
7614
        $arrLP = [];
7615
        while ($row = Database::fetch_array($result)) {
7616
            $arrLP[] = [
7617
                'id' => $row['iid'],
7618
                'item_type' => $row['item_type'],
7619
                'title' => $row['title'],
7620
                'path' => $row['path'],
7621
                'description' => $row['description'],
7622
                'parent_item_id' => $row['parent_item_id'],
7623
                'previous_item_id' => $row['previous_item_id'],
7624
                'next_item_id' => $row['next_item_id'],
7625
                'display_order' => $row['display_order'],
7626
                'max_score' => $row['max_score'],
7627
                'min_score' => $row['min_score'],
7628
                'mastery_score' => $row['mastery_score'],
7629
                'prerequisite' => $row['prerequisite'],
7630
                'max_time_allowed' => $row['max_time_allowed'],
7631
            ];
7632
        }
7633
7634
        $this->tree_array($arrLP);
7635
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7636
        unset($this->arrMenu);
7637
7638
        $form = new FormValidator(
7639
            'quiz_form',
7640
            'POST',
7641
            $this->getCurrentBuildingModeURL()
7642
        );
7643
        $defaults = [];
7644
7645
        if ($action === 'add') {
7646
            $legend = get_lang('CreateTheExercise');
7647
        } elseif ($action === 'move') {
7648
            $legend = get_lang('MoveTheCurrentExercise');
7649
        } else {
7650
            $legend = get_lang('EditCurrentExecice');
7651
        }
7652
7653
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7654
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7655
        }
7656
7657
        $form->addHeader($legend);
7658
7659
        if ($action != 'move') {
7660
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7661
            $defaults['title'] = $item_title;
7662
        }
7663
7664
        // Select for Parent item, root or chapter
7665
        $selectParent = $form->addSelect(
7666
            'parent',
7667
            get_lang('Parent'),
7668
            [],
7669
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7670
        );
7671
        $selectParent->addOption($this->name, 0);
7672
7673
        $arrHide = [
7674
            $id,
7675
        ];
7676
        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...
7677
            if ($action != 'add') {
7678
                if (
7679
                    ($arrLP[$i]['item_type'] == 'dir') &&
7680
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7681
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7682
                ) {
7683
                    $selectParent->addOption(
7684
                        $arrLP[$i]['title'],
7685
                        $arrLP[$i]['id'],
7686
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7687
                    );
7688
7689
                    if ($parent == $arrLP[$i]['id']) {
7690
                        $selectParent->setSelected($arrLP[$i]['id']);
7691
                    }
7692
                } else {
7693
                    $arrHide[] = $arrLP[$i]['id'];
7694
                }
7695
            } else {
7696
                if ($arrLP[$i]['item_type'] == 'dir') {
7697
                    $selectParent->addOption(
7698
                        $arrLP[$i]['title'],
7699
                        $arrLP[$i]['id'],
7700
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7701
                    );
7702
7703
                    if ($parent == $arrLP[$i]['id']) {
7704
                        $selectParent->setSelected($arrLP[$i]['id']);
7705
                    }
7706
                }
7707
            }
7708
        }
7709
7710
        if (is_array($arrLP)) {
7711
            reset($arrLP);
7712
        }
7713
7714
        $selectPrevious = $form->addSelect(
7715
            'previous',
7716
            get_lang('Position'),
7717
            [],
7718
            ['id' => 'previous']
7719
        );
7720
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7721
7722
        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...
7723
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7724
                $arrLP[$i]['id'] != $id
7725
            ) {
7726
                $selectPrevious->addOption(
7727
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7728
                    $arrLP[$i]['id']
7729
                );
7730
7731
                if (is_array($extra_info)) {
7732
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7733
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7734
                    }
7735
                } elseif ($action == 'add') {
7736
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7737
                }
7738
            }
7739
        }
7740
7741
        if ($action != 'move') {
7742
            $arrHide = [];
7743
            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...
7744
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7745
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7746
                }
7747
            }
7748
        }
7749
7750
        if ($action == 'add') {
7751
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7752
        } else {
7753
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7754
        }
7755
7756
        if ($action == 'move') {
7757
            $form->addHidden('title', $item_title);
7758
            $form->addHidden('description', $item_description);
7759
        }
7760
7761
        if (is_numeric($extra_info)) {
7762
            $form->addHidden('path', $extra_info);
7763
        } elseif (is_array($extra_info)) {
7764
            $form->addHidden('path', $extra_info['path']);
7765
        }
7766
7767
        $form->addHidden('type', TOOL_QUIZ);
7768
        $form->addHidden('post_time', time());
7769
        $form->setDefaults($defaults);
7770
7771
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7772
    }
7773
7774
    /**
7775
     * Addition of Hotpotatoes tests.
7776
     *
7777
     * @param string $action
7778
     * @param int    $id         Internal ID of the item
7779
     * @param string $extra_info
7780
     *
7781
     * @return string HTML structure to display the hotpotatoes addition formular
7782
     */
7783
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7784
    {
7785
        $course_id = api_get_course_int_id();
7786
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7787
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7788
7789
        if ($id != 0 && is_array($extra_info)) {
7790
            $item_title = stripslashes($extra_info['title']);
7791
            $item_description = stripslashes($extra_info['description']);
7792
        } elseif (is_numeric($extra_info)) {
7793
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7794
7795
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7796
                    WHERE
7797
                        c_id = ".$course_id." AND
7798
                        path LIKE '".$uploadPath."/%/%htm%' AND
7799
                        iid = ".(int) $extra_info."
7800
                    ORDER BY iid ASC";
7801
7802
            $res_hot = Database::query($sql);
7803
            $row = Database::fetch_array($res_hot);
7804
7805
            $item_title = $row['title'];
7806
            $item_description = $row['description'];
7807
7808
            if (!empty($row['comment'])) {
7809
                $item_title = $row['comment'];
7810
            }
7811
        } else {
7812
            $item_title = '';
7813
            $item_description = '';
7814
        }
7815
7816
        if ($id != 0 && is_array($extra_info)) {
7817
            $parent = $extra_info['parent_item_id'];
7818
        } else {
7819
            $parent = 0;
7820
        }
7821
7822
        $sql = "SELECT * FROM $tbl_lp_item
7823
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7824
        $result = Database::query($sql);
7825
        $arrLP = [];
7826
        while ($row = Database::fetch_array($result)) {
7827
            $arrLP[] = [
7828
                'id' => $row['id'],
7829
                'item_type' => $row['item_type'],
7830
                'title' => $row['title'],
7831
                'path' => $row['path'],
7832
                'description' => $row['description'],
7833
                'parent_item_id' => $row['parent_item_id'],
7834
                'previous_item_id' => $row['previous_item_id'],
7835
                'next_item_id' => $row['next_item_id'],
7836
                'display_order' => $row['display_order'],
7837
                'max_score' => $row['max_score'],
7838
                'min_score' => $row['min_score'],
7839
                'mastery_score' => $row['mastery_score'],
7840
                'prerequisite' => $row['prerequisite'],
7841
                'max_time_allowed' => $row['max_time_allowed'],
7842
            ];
7843
        }
7844
7845
        $legend = '<legend>';
7846
        if ($action == 'add') {
7847
            $legend .= get_lang('CreateTheExercise');
7848
        } elseif ($action == 'move') {
7849
            $legend .= get_lang('MoveTheCurrentExercise');
7850
        } else {
7851
            $legend .= get_lang('EditCurrentExecice');
7852
        }
7853
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7854
            $legend .= Display:: return_message(
7855
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7856
            );
7857
        }
7858
        $legend .= '</legend>';
7859
7860
        $return = '<form method="POST">';
7861
        $return .= $legend;
7862
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7863
        $return .= '<tr>';
7864
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7865
        $return .= '<td class="input">';
7866
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7867
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7868
        $arrHide = [
7869
            $id,
7870
        ];
7871
7872
        if (count($arrLP) > 0) {
7873
            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...
7874
                if ($action != 'add') {
7875
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7876
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7877
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7878
                    ) {
7879
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7880
                    } else {
7881
                        $arrHide[] = $arrLP[$i]['id'];
7882
                    }
7883
                } else {
7884
                    if ($arrLP[$i]['item_type'] == 'dir') {
7885
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7886
                    }
7887
                }
7888
            }
7889
            reset($arrLP);
7890
        }
7891
7892
        $return .= '</select>';
7893
        $return .= '</td>';
7894
        $return .= '</tr>';
7895
        $return .= '<tr>';
7896
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7897
        $return .= '<td class="input">';
7898
        $return .= '<select id="previous" name="previous" size="1">';
7899
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7900
7901
        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...
7902
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7903
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7904
                    $selected = 'selected="selected" ';
7905
                } elseif ($action == 'add') {
7906
                    $selected = 'selected="selected" ';
7907
                } else {
7908
                    $selected = '';
7909
                }
7910
7911
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7912
            }
7913
        }
7914
7915
        $return .= '</select>';
7916
        $return .= '</td>';
7917
        $return .= '</tr>';
7918
7919
        if ($action != 'move') {
7920
            $return .= '<tr>';
7921
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7922
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7923
            $return .= '</tr>';
7924
            $id_prerequisite = 0;
7925
            if (is_array($arrLP) && count($arrLP) > 0) {
7926
                foreach ($arrLP as $key => $value) {
7927
                    if ($value['id'] == $id) {
7928
                        $id_prerequisite = $value['prerequisite'];
7929
                        break;
7930
                    }
7931
                }
7932
7933
                $arrHide = [];
7934
                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...
7935
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7936
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7937
                    }
7938
                }
7939
            }
7940
        }
7941
7942
        $return .= '<tr>';
7943
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7944
            get_lang('SaveHotpotatoes').'</button></td>';
7945
        $return .= '</tr>';
7946
        $return .= '</table>';
7947
7948
        if ($action == 'move') {
7949
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7950
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7951
        }
7952
7953
        if (is_numeric($extra_info)) {
7954
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7955
        } elseif (is_array($extra_info)) {
7956
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7957
        }
7958
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7959
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7960
        $return .= '</form>';
7961
7962
        return $return;
7963
    }
7964
7965
    /**
7966
     * Return the form to display the forum edit/add option.
7967
     *
7968
     * @param string $action
7969
     * @param int    $id         ID of the lp_item if already exists
7970
     * @param string $extra_info
7971
     *
7972
     * @throws Exception
7973
     *
7974
     * @return string HTML form
7975
     */
7976
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7977
    {
7978
        $course_id = api_get_course_int_id();
7979
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7980
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7981
7982
        if ($id != 0 && is_array($extra_info)) {
7983
            $item_title = stripslashes($extra_info['title']);
7984
        } elseif (is_numeric($extra_info)) {
7985
            $sql = "SELECT forum_title as title, forum_comment as comment
7986
                    FROM $tbl_forum
7987
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7988
7989
            $result = Database::query($sql);
7990
            $row = Database::fetch_array($result);
7991
7992
            $item_title = $row['title'];
7993
            $item_description = $row['comment'];
7994
        } else {
7995
            $item_title = '';
7996
            $item_description = '';
7997
        }
7998
7999
        if ($id != 0 && is_array($extra_info)) {
8000
            $parent = $extra_info['parent_item_id'];
8001
        } else {
8002
            $parent = 0;
8003
        }
8004
8005
        $sql = "SELECT * FROM $tbl_lp_item
8006
                WHERE
8007
                    c_id = $course_id AND
8008
                    lp_id = ".$this->lp_id;
8009
        $result = Database::query($sql);
8010
        $arrLP = [];
8011
        while ($row = Database::fetch_array($result)) {
8012
            $arrLP[] = [
8013
                'id' => $row['iid'],
8014
                'item_type' => $row['item_type'],
8015
                'title' => $row['title'],
8016
                'path' => $row['path'],
8017
                'description' => $row['description'],
8018
                'parent_item_id' => $row['parent_item_id'],
8019
                'previous_item_id' => $row['previous_item_id'],
8020
                'next_item_id' => $row['next_item_id'],
8021
                'display_order' => $row['display_order'],
8022
                'max_score' => $row['max_score'],
8023
                'min_score' => $row['min_score'],
8024
                'mastery_score' => $row['mastery_score'],
8025
                'prerequisite' => $row['prerequisite'],
8026
            ];
8027
        }
8028
8029
        $this->tree_array($arrLP);
8030
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8031
        unset($this->arrMenu);
8032
8033
        if ($action == 'add') {
8034
            $legend = get_lang('CreateTheForum');
8035
        } elseif ($action == 'move') {
8036
            $legend = get_lang('MoveTheCurrentForum');
8037
        } else {
8038
            $legend = get_lang('EditCurrentForum');
8039
        }
8040
8041
        $form = new FormValidator(
8042
            'forum_form',
8043
            'POST',
8044
            $this->getCurrentBuildingModeURL()
8045
        );
8046
        $defaults = [];
8047
8048
        $form->addHeader($legend);
8049
8050
        if ($action != 'move') {
8051
            $form->addText(
8052
                'title',
8053
                get_lang('Title'),
8054
                true,
8055
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8056
            );
8057
            $defaults['title'] = $item_title;
8058
        }
8059
8060
        $selectParent = $form->addSelect(
8061
            'parent',
8062
            get_lang('Parent'),
8063
            [],
8064
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8065
        );
8066
        $selectParent->addOption($this->name, 0);
8067
        $arrHide = [
8068
            $id,
8069
        ];
8070
        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...
8071
            if ($action != 'add') {
8072
                if ($arrLP[$i]['item_type'] == 'dir' &&
8073
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8074
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8075
                ) {
8076
                    $selectParent->addOption(
8077
                        $arrLP[$i]['title'],
8078
                        $arrLP[$i]['id'],
8079
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8080
                    );
8081
8082
                    if ($parent == $arrLP[$i]['id']) {
8083
                        $selectParent->setSelected($arrLP[$i]['id']);
8084
                    }
8085
                } else {
8086
                    $arrHide[] = $arrLP[$i]['id'];
8087
                }
8088
            } else {
8089
                if ($arrLP[$i]['item_type'] == 'dir') {
8090
                    $selectParent->addOption(
8091
                        $arrLP[$i]['title'],
8092
                        $arrLP[$i]['id'],
8093
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8094
                    );
8095
8096
                    if ($parent == $arrLP[$i]['id']) {
8097
                        $selectParent->setSelected($arrLP[$i]['id']);
8098
                    }
8099
                }
8100
            }
8101
        }
8102
8103
        if (is_array($arrLP)) {
8104
            reset($arrLP);
8105
        }
8106
8107
        $selectPrevious = $form->addSelect(
8108
            'previous',
8109
            get_lang('Position'),
8110
            [],
8111
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8112
        );
8113
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8114
8115
        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...
8116
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8117
                $arrLP[$i]['id'] != $id
8118
            ) {
8119
                $selectPrevious->addOption(
8120
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8121
                    $arrLP[$i]['id']
8122
                );
8123
8124
                if (isset($extra_info['previous_item_id']) &&
8125
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8126
                ) {
8127
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8128
                } elseif ($action == 'add') {
8129
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8130
                }
8131
            }
8132
        }
8133
8134
        if ($action != 'move') {
8135
            $id_prerequisite = 0;
8136
            if (is_array($arrLP)) {
8137
                foreach ($arrLP as $key => $value) {
8138
                    if ($value['id'] == $id) {
8139
                        $id_prerequisite = $value['prerequisite'];
8140
                        break;
8141
                    }
8142
                }
8143
            }
8144
8145
            $arrHide = [];
8146
            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...
8147
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8148
                    if (isset($extra_info['previous_item_id']) &&
8149
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8150
                    ) {
8151
                        $s_selected_position = $arrLP[$i]['id'];
8152
                    } elseif ($action == 'add') {
8153
                        $s_selected_position = 0;
8154
                    }
8155
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8156
                }
8157
            }
8158
        }
8159
8160
        if ($action == 'add') {
8161
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8162
        } else {
8163
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8164
        }
8165
8166
        if ($action == 'move') {
8167
            $form->addHidden('title', $item_title);
8168
            $form->addHidden('description', $item_description);
8169
        }
8170
8171
        if (is_numeric($extra_info)) {
8172
            $form->addHidden('path', $extra_info);
8173
        } elseif (is_array($extra_info)) {
8174
            $form->addHidden('path', $extra_info['path']);
8175
        }
8176
        $form->addHidden('type', TOOL_FORUM);
8177
        $form->addHidden('post_time', time());
8178
        $form->setDefaults($defaults);
8179
8180
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8181
    }
8182
8183
    /**
8184
     * Return HTML form to add/edit forum threads.
8185
     *
8186
     * @param string $action
8187
     * @param int    $id         Item ID if already exists in learning path
8188
     * @param string $extra_info
8189
     *
8190
     * @throws Exception
8191
     *
8192
     * @return string HTML form
8193
     */
8194
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8195
    {
8196
        $course_id = api_get_course_int_id();
8197
        if (empty($course_id)) {
8198
            return null;
8199
        }
8200
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8201
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8202
8203
        if ($id != 0 && is_array($extra_info)) {
8204
            $item_title = stripslashes($extra_info['title']);
8205
        } elseif (is_numeric($extra_info)) {
8206
            $sql = "SELECT thread_title as title FROM $tbl_forum
8207
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8208
8209
            $result = Database::query($sql);
8210
            $row = Database::fetch_array($result);
8211
8212
            $item_title = $row['title'];
8213
            $item_description = '';
8214
        } else {
8215
            $item_title = '';
8216
            $item_description = '';
8217
        }
8218
8219
        if ($id != 0 && is_array($extra_info)) {
8220
            $parent = $extra_info['parent_item_id'];
8221
        } else {
8222
            $parent = 0;
8223
        }
8224
8225
        $sql = "SELECT * FROM $tbl_lp_item
8226
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8227
        $result = Database::query($sql);
8228
8229
        $arrLP = [];
8230
        while ($row = Database::fetch_array($result)) {
8231
            $arrLP[] = [
8232
                'id' => $row['iid'],
8233
                'item_type' => $row['item_type'],
8234
                'title' => $row['title'],
8235
                'path' => $row['path'],
8236
                'description' => $row['description'],
8237
                'parent_item_id' => $row['parent_item_id'],
8238
                'previous_item_id' => $row['previous_item_id'],
8239
                'next_item_id' => $row['next_item_id'],
8240
                'display_order' => $row['display_order'],
8241
                'max_score' => $row['max_score'],
8242
                'min_score' => $row['min_score'],
8243
                'mastery_score' => $row['mastery_score'],
8244
                'prerequisite' => $row['prerequisite'],
8245
            ];
8246
        }
8247
8248
        $this->tree_array($arrLP);
8249
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8250
        unset($this->arrMenu);
8251
8252
        $form = new FormValidator(
8253
            'thread_form',
8254
            'POST',
8255
            $this->getCurrentBuildingModeURL()
8256
        );
8257
        $defaults = [];
8258
8259
        if ($action == 'add') {
8260
            $legend = get_lang('CreateTheForum');
8261
        } elseif ($action == 'move') {
8262
            $legend = get_lang('MoveTheCurrentForum');
8263
        } else {
8264
            $legend = get_lang('EditCurrentForum');
8265
        }
8266
8267
        $form->addHeader($legend);
8268
        $selectParent = $form->addSelect(
8269
            'parent',
8270
            get_lang('Parent'),
8271
            [],
8272
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8273
        );
8274
        $selectParent->addOption($this->name, 0);
8275
8276
        $arrHide = [
8277
            $id,
8278
        ];
8279
8280
        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...
8281
            if ($action != 'add') {
8282
                if (
8283
                    ($arrLP[$i]['item_type'] == 'dir') &&
8284
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8285
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8286
                ) {
8287
                    $selectParent->addOption(
8288
                        $arrLP[$i]['title'],
8289
                        $arrLP[$i]['id'],
8290
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8291
                    );
8292
8293
                    if ($parent == $arrLP[$i]['id']) {
8294
                        $selectParent->setSelected($arrLP[$i]['id']);
8295
                    }
8296
                } else {
8297
                    $arrHide[] = $arrLP[$i]['id'];
8298
                }
8299
            } else {
8300
                if ($arrLP[$i]['item_type'] == 'dir') {
8301
                    $selectParent->addOption(
8302
                        $arrLP[$i]['title'],
8303
                        $arrLP[$i]['id'],
8304
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8305
                    );
8306
8307
                    if ($parent == $arrLP[$i]['id']) {
8308
                        $selectParent->setSelected($arrLP[$i]['id']);
8309
                    }
8310
                }
8311
            }
8312
        }
8313
8314
        if ($arrLP != null) {
8315
            reset($arrLP);
8316
        }
8317
8318
        $selectPrevious = $form->addSelect(
8319
            'previous',
8320
            get_lang('Position'),
8321
            [],
8322
            ['id' => 'previous']
8323
        );
8324
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8325
8326
        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...
8327
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8328
                $selectPrevious->addOption(
8329
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8330
                    $arrLP[$i]['id']
8331
                );
8332
8333
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8334
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8335
                } elseif ($action == 'add') {
8336
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8337
                }
8338
            }
8339
        }
8340
8341
        if ($action != 'move') {
8342
            $form->addText(
8343
                'title',
8344
                get_lang('Title'),
8345
                true,
8346
                ['id' => 'idTitle']
8347
            );
8348
            $defaults['title'] = $item_title;
8349
8350
            $id_prerequisite = 0;
8351
            if ($arrLP != null) {
8352
                foreach ($arrLP as $key => $value) {
8353
                    if ($value['id'] == $id) {
8354
                        $id_prerequisite = $value['prerequisite'];
8355
                        break;
8356
                    }
8357
                }
8358
            }
8359
8360
            $arrHide = [];
8361
            $s_selected_position = 0;
8362
            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...
8363
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8364
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8365
                        $s_selected_position = $arrLP[$i]['id'];
8366
                    } elseif ($action == 'add') {
8367
                        $s_selected_position = 0;
8368
                    }
8369
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8370
                }
8371
            }
8372
8373
            $selectPrerequisites = $form->addSelect(
8374
                'prerequisites',
8375
                get_lang('LearnpathPrerequisites'),
8376
                [],
8377
                ['id' => 'prerequisites']
8378
            );
8379
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8380
8381
            foreach ($arrHide as $key => $value) {
8382
                $selectPrerequisites->addOption($value['value'], $key);
8383
8384
                if ($key == $s_selected_position && $action == 'add') {
8385
                    $selectPrerequisites->setSelected($key);
8386
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8387
                    $selectPrerequisites->setSelected($key);
8388
                }
8389
            }
8390
        }
8391
8392
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8393
8394
        if ($action == 'move') {
8395
            $form->addHidden('title', $item_title);
8396
            $form->addHidden('description', $item_description);
8397
        }
8398
8399
        if (is_numeric($extra_info)) {
8400
            $form->addHidden('path', $extra_info);
8401
        } elseif (is_array($extra_info)) {
8402
            $form->addHidden('path', $extra_info['path']);
8403
        }
8404
8405
        $form->addHidden('type', TOOL_THREAD);
8406
        $form->addHidden('post_time', time());
8407
        $form->setDefaults($defaults);
8408
8409
        return $form->returnForm();
8410
    }
8411
8412
    /**
8413
     * Return the HTML form to display an item (generally a dir item).
8414
     *
8415
     * @param string $item_type
8416
     * @param string $title
8417
     * @param string $action
8418
     * @param int    $id
8419
     * @param string $extra_info
8420
     *
8421
     * @throws Exception
8422
     * @throws HTML_QuickForm_Error
8423
     *
8424
     * @return string HTML form
8425
     */
8426
    public function display_item_form(
8427
        $item_type,
8428
        $title = '',
8429
        $action = 'add_item',
8430
        $id = 0,
8431
        $extra_info = 'new'
8432
    ) {
8433
        $_course = api_get_course_info();
8434
8435
        global $charset;
8436
8437
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8438
        $item_title = '';
8439
        $item_description = '';
8440
        $item_path_fck = '';
8441
8442
        if ($id != 0 && is_array($extra_info)) {
8443
            $item_title = $extra_info['title'];
8444
            $item_description = $extra_info['description'];
8445
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8446
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8447
        }
8448
        $parent = 0;
8449
        if ($id != 0 && is_array($extra_info)) {
8450
            $parent = $extra_info['parent_item_id'];
8451
        }
8452
8453
        $id = (int) $id;
8454
        $sql = "SELECT * FROM $tbl_lp_item
8455
                WHERE
8456
                    lp_id = ".$this->lp_id." AND
8457
                    iid != $id";
8458
8459
        if ($item_type == 'dir') {
8460
            $sql .= " AND parent_item_id = 0";
8461
        }
8462
8463
        $result = Database::query($sql);
8464
        $arrLP = [];
8465
        while ($row = Database::fetch_array($result)) {
8466
            $arrLP[] = [
8467
                'id' => $row['iid'],
8468
                'item_type' => $row['item_type'],
8469
                'title' => $row['title'],
8470
                'path' => $row['path'],
8471
                'description' => $row['description'],
8472
                'parent_item_id' => $row['parent_item_id'],
8473
                'previous_item_id' => $row['previous_item_id'],
8474
                'next_item_id' => $row['next_item_id'],
8475
                'max_score' => $row['max_score'],
8476
                'min_score' => $row['min_score'],
8477
                'mastery_score' => $row['mastery_score'],
8478
                'prerequisite' => $row['prerequisite'],
8479
                'display_order' => $row['display_order'],
8480
            ];
8481
        }
8482
8483
        $this->tree_array($arrLP);
8484
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8485
        unset($this->arrMenu);
8486
8487
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8488
8489
        $form = new FormValidator('form', 'POST', $url);
8490
        $defaults['title'] = api_html_entity_decode(
8491
            $item_title,
8492
            ENT_QUOTES,
8493
            $charset
8494
        );
8495
        $defaults['description'] = $item_description;
8496
8497
        $form->addHeader($title);
8498
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8499
        $arrHide[0]['padding'] = 20;
8500
        $charset = api_get_system_encoding();
8501
        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...
8502
            if ($action != 'add') {
8503
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8504
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8505
                ) {
8506
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8507
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8508
                    if ($parent == $arrLP[$i]['id']) {
8509
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8510
                    }
8511
                }
8512
            } else {
8513
                if ($arrLP[$i]['item_type'] == 'dir') {
8514
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8515
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8516
                    if ($parent == $arrLP[$i]['id']) {
8517
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8518
                    }
8519
                }
8520
            }
8521
        }
8522
8523
        if ($action != 'move') {
8524
            $form->addElement('text', 'title', get_lang('Title'));
8525
            $form->applyFilter('title', 'html_filter');
8526
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8527
        } else {
8528
            $form->addElement('hidden', 'title');
8529
        }
8530
8531
        $parentSelect = $form->addElement(
8532
            'select',
8533
            'parent',
8534
            get_lang('Parent'),
8535
            '',
8536
            [
8537
                'id' => 'idParent',
8538
                'onchange' => "javascript: load_cbo(this.value);",
8539
            ]
8540
        );
8541
8542
        foreach ($arrHide as $key => $value) {
8543
            $parentSelect->addOption(
8544
                $value['value'],
8545
                $key,
8546
                'style="padding-left:'.$value['padding'].'px;"'
8547
            );
8548
            $lastPosition = $key;
8549
        }
8550
8551
        if (!empty($s_selected_parent)) {
8552
            $parentSelect->setSelected($s_selected_parent);
8553
        }
8554
8555
        if (is_array($arrLP)) {
8556
            reset($arrLP);
8557
        }
8558
        $arrHide = [];
8559
        // POSITION
8560
        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...
8561
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8562
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8563
                //this is the same!
8564
                if (isset($extra_info['previous_item_id']) &&
8565
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8566
                ) {
8567
                    $s_selected_position = $arrLP[$i]['id'];
8568
                } elseif ($action == 'add') {
8569
                    $s_selected_position = $arrLP[$i]['id'];
8570
                }
8571
8572
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8573
            }
8574
        }
8575
8576
        $position = $form->addElement(
8577
            'select',
8578
            'previous',
8579
            get_lang('Position'),
8580
            '',
8581
            ['id' => 'previous']
8582
        );
8583
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8584
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8585
8586
        $lastPosition = null;
8587
        foreach ($arrHide as $key => $value) {
8588
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8589
            $lastPosition = $key;
8590
        }
8591
8592
        if (!empty($s_selected_position)) {
8593
            $position->setSelected($s_selected_position);
8594
        }
8595
8596
        // When new chapter add at the end
8597
        if ($action == 'add_item') {
8598
            $position->setSelected($lastPosition);
8599
        }
8600
8601
        if (is_array($arrLP)) {
8602
            reset($arrLP);
8603
        }
8604
8605
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8606
8607
        //fix in order to use the tab
8608
        if ($item_type == 'dir') {
8609
            $form->addElement('hidden', 'type', 'dir');
8610
        }
8611
8612
        $extension = null;
8613
        if (!empty($item_path)) {
8614
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8615
        }
8616
8617
        //assets can't be modified
8618
        //$item_type == 'asset' ||
8619
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8620
            if ($item_type == 'sco') {
8621
                $form->addElement(
8622
                    'html',
8623
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8624
                );
8625
            }
8626
            $renderer = $form->defaultRenderer();
8627
            $renderer->setElementTemplate(
8628
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8629
                'content_lp'
8630
            );
8631
8632
            $relative_prefix = '';
8633
8634
            $editor_config = [
8635
                'ToolbarSet' => 'LearningPathDocuments',
8636
                'Width' => '100%',
8637
                'Height' => '500',
8638
                'FullPage' => true,
8639
                'CreateDocumentDir' => $relative_prefix,
8640
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8641
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8642
            ];
8643
8644
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8645
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8646
            $defaults['content_lp'] = file_get_contents($content_path);
8647
        }
8648
8649
        if (!empty($id)) {
8650
            $form->addHidden('id', $id);
8651
        }
8652
8653
        $form->addElement('hidden', 'type', $item_type);
8654
        $form->addElement('hidden', 'post_time', time());
8655
        $form->setDefaults($defaults);
8656
8657
        return $form->returnForm();
8658
    }
8659
8660
    /**
8661
     * @return string
8662
     */
8663
    public function getCurrentBuildingModeURL()
8664
    {
8665
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8666
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8667
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8668
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8669
8670
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8671
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8672
8673
        return $currentUrl;
8674
    }
8675
8676
    /**
8677
     * Returns the form to update or create a document.
8678
     *
8679
     * @param string $action     (add/edit)
8680
     * @param int    $id         ID of the lp_item (if already exists)
8681
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8682
     *
8683
     * @throws Exception
8684
     * @throws HTML_QuickForm_Error
8685
     *
8686
     * @return string HTML form
8687
     */
8688
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8689
    {
8690
        $course_id = api_get_course_int_id();
8691
        $_course = api_get_course_info();
8692
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8693
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8694
8695
        $no_display_edit_textarea = false;
8696
        $item_description = '';
8697
        //If action==edit document
8698
        //We don't display the document form if it's not an editable document (html or txt file)
8699
        if ($action === 'edit') {
8700
            if (is_array($extra_info)) {
8701
                $path_parts = pathinfo($extra_info['dir']);
8702
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8703
                    $no_display_edit_textarea = true;
8704
                }
8705
            }
8706
        }
8707
        $no_display_add = false;
8708
8709
        // If action==add an existing document
8710
        // We don't display the document form if it's not an editable document (html or txt file).
8711
        if ($action === 'add') {
8712
            if (is_numeric($extra_info)) {
8713
                $extra_info = (int) $extra_info;
8714
                $sql_doc = "SELECT path FROM $tbl_doc
8715
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8716
                $result = Database::query($sql_doc);
8717
                $path_file = Database::result($result, 0, 0);
8718
                $path_parts = pathinfo($path_file);
8719
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8720
                    $no_display_add = true;
8721
                }
8722
            }
8723
        }
8724
        if ($id != 0 && is_array($extra_info)) {
8725
            $item_title = stripslashes($extra_info['title']);
8726
            $item_description = stripslashes($extra_info['description']);
8727
            if (empty($item_title)) {
8728
                $path_parts = pathinfo($extra_info['path']);
8729
                $item_title = stripslashes($path_parts['filename']);
8730
            }
8731
        } elseif (is_numeric($extra_info)) {
8732
            $sql = "SELECT path, title FROM $tbl_doc
8733
                    WHERE
8734
                        c_id = ".$course_id." AND
8735
                        iid = ".intval($extra_info);
8736
            $result = Database::query($sql);
8737
            $row = Database::fetch_array($result);
8738
            $item_title = $row['title'];
8739
            $item_title = str_replace('_', ' ', $item_title);
8740
            if (empty($item_title)) {
8741
                $path_parts = pathinfo($row['path']);
8742
                $item_title = stripslashes($path_parts['filename']);
8743
            }
8744
        } else {
8745
            $item_title = '';
8746
            $item_description = '';
8747
        }
8748
        $return = '<legend>';
8749
        $parent = 0;
8750
        if ($id != 0 && is_array($extra_info)) {
8751
            $parent = $extra_info['parent_item_id'];
8752
        }
8753
8754
        $sql = "SELECT * FROM $tbl_lp_item
8755
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8756
        $result = Database::query($sql);
8757
        $arrLP = [];
8758
8759
        while ($row = Database::fetch_array($result)) {
8760
            $arrLP[] = [
8761
                'id' => $row['iid'],
8762
                'item_type' => $row['item_type'],
8763
                'title' => $row['title'],
8764
                'path' => $row['path'],
8765
                'description' => $row['description'],
8766
                'parent_item_id' => $row['parent_item_id'],
8767
                'previous_item_id' => $row['previous_item_id'],
8768
                'next_item_id' => $row['next_item_id'],
8769
                'display_order' => $row['display_order'],
8770
                'max_score' => $row['max_score'],
8771
                'min_score' => $row['min_score'],
8772
                'mastery_score' => $row['mastery_score'],
8773
                'prerequisite' => $row['prerequisite'],
8774
            ];
8775
        }
8776
8777
        $this->tree_array($arrLP);
8778
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8779
        unset($this->arrMenu);
8780
8781
        if ($action == 'add') {
8782
            $return .= get_lang('CreateTheDocument');
8783
        } elseif ($action == 'move') {
8784
            $return .= get_lang('MoveTheCurrentDocument');
8785
        } else {
8786
            $return .= get_lang('EditTheCurrentDocument');
8787
        }
8788
        $return .= '</legend>';
8789
8790
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8791
            $return .= Display::return_message(
8792
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8793
                false
8794
            );
8795
        }
8796
        $form = new FormValidator(
8797
            'form',
8798
            'POST',
8799
            $this->getCurrentBuildingModeURL(),
8800
            '',
8801
            ['enctype' => 'multipart/form-data']
8802
        );
8803
        $defaults['title'] = Security::remove_XSS($item_title);
8804
        if (empty($item_title)) {
8805
            $defaults['title'] = Security::remove_XSS($item_title);
8806
        }
8807
        $defaults['description'] = $item_description;
8808
        $form->addElement('html', $return);
8809
8810
        if ($action != 'move') {
8811
            $data = $this->generate_lp_folder($_course);
8812
            if ($action != 'edit') {
8813
                $folders = DocumentManager::get_all_document_folders(
8814
                    $_course,
8815
                    0,
8816
                    true
8817
                );
8818
                DocumentManager::build_directory_selector(
8819
                    $folders,
8820
                    '',
8821
                    [],
8822
                    true,
8823
                    $form,
8824
                    'directory_parent_id'
8825
                );
8826
            }
8827
8828
            if (isset($data['id'])) {
8829
                $defaults['directory_parent_id'] = $data['id'];
8830
            }
8831
8832
            $form->addElement(
8833
                'text',
8834
                'title',
8835
                get_lang('Title'),
8836
                ['id' => 'idTitle', 'class' => 'col-md-4']
8837
            );
8838
            $form->applyFilter('title', 'html_filter');
8839
        }
8840
8841
        $arrHide[0]['value'] = $this->name;
8842
        $arrHide[0]['padding'] = 20;
8843
8844
        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...
8845
            if ($action != 'add') {
8846
                if ($arrLP[$i]['item_type'] == 'dir' &&
8847
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8848
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8849
                ) {
8850
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8851
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8852
                }
8853
            } else {
8854
                if ($arrLP[$i]['item_type'] == 'dir') {
8855
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8856
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8857
                }
8858
            }
8859
        }
8860
8861
        $parentSelect = $form->addSelect(
8862
            'parent',
8863
            get_lang('Parent'),
8864
            [],
8865
            [
8866
                'id' => 'idParent',
8867
                'onchange' => 'javascript: load_cbo(this.value);',
8868
            ]
8869
        );
8870
8871
        $my_count = 0;
8872
        foreach ($arrHide as $key => $value) {
8873
            if ($my_count != 0) {
8874
                // The LP name is also the first section and is not in the same charset like the other sections.
8875
                $value['value'] = Security::remove_XSS($value['value']);
8876
                $parentSelect->addOption(
8877
                    $value['value'],
8878
                    $key,
8879
                    'style="padding-left:'.$value['padding'].'px;"'
8880
                );
8881
            } else {
8882
                $value['value'] = Security::remove_XSS($value['value']);
8883
                $parentSelect->addOption(
8884
                    $value['value'],
8885
                    $key,
8886
                    'style="padding-left:'.$value['padding'].'px;"'
8887
                );
8888
            }
8889
            $my_count++;
8890
        }
8891
8892
        if (!empty($id)) {
8893
            $parentSelect->setSelected($parent);
8894
        } else {
8895
            $parent_item_id = Session::read('parent_item_id', 0);
8896
            $parentSelect->setSelected($parent_item_id);
8897
        }
8898
8899
        if (is_array($arrLP)) {
8900
            reset($arrLP);
8901
        }
8902
8903
        $arrHide = [];
8904
        // POSITION
8905
        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...
8906
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8907
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8908
            ) {
8909
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8910
            }
8911
        }
8912
8913
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8914
8915
        $position = $form->addSelect(
8916
            'previous',
8917
            get_lang('Position'),
8918
            [],
8919
            ['id' => 'previous']
8920
        );
8921
8922
        $position->addOption(get_lang('FirstPosition'), 0);
8923
        foreach ($arrHide as $key => $value) {
8924
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8925
            $position->addOption(
8926
                $value['value'],
8927
                $key,
8928
                'style="padding-left:'.$padding.'px;"'
8929
            );
8930
        }
8931
8932
        $position->setSelected($selectedPosition);
8933
8934
        if (is_array($arrLP)) {
8935
            reset($arrLP);
8936
        }
8937
8938
        if ($action != 'move') {
8939
            $arrHide = [];
8940
            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...
8941
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8942
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8943
                ) {
8944
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8945
                }
8946
            }
8947
8948
            if (!$no_display_add) {
8949
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8950
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8951
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8952
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8953
                ) {
8954
                    if (isset($_POST['content'])) {
8955
                        $content = stripslashes($_POST['content']);
8956
                    } elseif (is_array($extra_info)) {
8957
                        //If it's an html document or a text file
8958
                        if (!$no_display_edit_textarea) {
8959
                            $content = $this->display_document(
8960
                                $extra_info['path'],
8961
                                false,
8962
                                false
8963
                            );
8964
                        }
8965
                    } elseif (is_numeric($extra_info)) {
8966
                        $content = $this->display_document(
8967
                            $extra_info,
8968
                            false,
8969
                            false
8970
                        );
8971
                    } else {
8972
                        $content = '';
8973
                    }
8974
8975
                    if (!$no_display_edit_textarea) {
8976
                        // We need to calculate here some specific settings for the online editor.
8977
                        // The calculated settings work for documents in the Documents tool
8978
                        // (on the root or in subfolders).
8979
                        // For documents in native scorm packages it is unclear whether the
8980
                        // online editor should be activated or not.
8981
8982
                        // A new document, it is in the root of the repository.
8983
                        $relative_path = '';
8984
                        $relative_prefix = '';
8985
                        if (is_array($extra_info) && $extra_info != 'new') {
8986
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8987
                            $relative_path = explode('/', $extra_info['dir']);
8988
                            $cnt = count($relative_path) - 2;
8989
                            if ($cnt < 0) {
8990
                                $cnt = 0;
8991
                            }
8992
                            $relative_prefix = str_repeat('../', $cnt);
8993
                            $relative_path = array_slice($relative_path, 1, $cnt);
8994
                            $relative_path = implode('/', $relative_path);
8995
                            if (strlen($relative_path) > 0) {
8996
                                $relative_path = $relative_path.'/';
8997
                            }
8998
                        } else {
8999
                            $result = $this->generate_lp_folder($_course);
9000
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9001
                            $relative_prefix = '../../';
9002
                        }
9003
9004
                        $editor_config = [
9005
                            'ToolbarSet' => 'LearningPathDocuments',
9006
                            'Width' => '100%',
9007
                            'Height' => '500',
9008
                            'FullPage' => true,
9009
                            'CreateDocumentDir' => $relative_prefix,
9010
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9011
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9012
                        ];
9013
9014
                        if ($_GET['action'] == 'add_item') {
9015
                            $class = 'add';
9016
                            $text = get_lang('LPCreateDocument');
9017
                        } else {
9018
                            if ($_GET['action'] == 'edit_item') {
9019
                                $class = 'save';
9020
                                $text = get_lang('SaveDocument');
9021
                            }
9022
                        }
9023
9024
                        $form->addButtonSave($text, 'submit_button');
9025
                        $renderer = $form->defaultRenderer();
9026
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9027
                        $form->addElement('html', '<div class="editor-lp">');
9028
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9029
                        $form->addElement('html', '</div>');
9030
                        $defaults['content_lp'] = $content;
9031
                    }
9032
                } elseif (is_numeric($extra_info)) {
9033
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9034
9035
                    $return = $this->display_document($extra_info, true, true, true);
9036
                    $form->addElement('html', $return);
9037
                }
9038
            }
9039
        }
9040
        if (isset($extra_info['item_type']) &&
9041
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9042
        ) {
9043
            $parentSelect->freeze();
9044
            $position->freeze();
9045
        }
9046
9047
        if ($action == 'move') {
9048
            $form->addElement('hidden', 'title', $item_title);
9049
            $form->addElement('hidden', 'description', $item_description);
9050
        }
9051
        if (is_numeric($extra_info)) {
9052
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9053
            $form->addElement('hidden', 'path', $extra_info);
9054
        } elseif (is_array($extra_info)) {
9055
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9056
            $form->addElement('hidden', 'path', $extra_info['path']);
9057
        }
9058
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9059
        $form->addElement('hidden', 'post_time', time());
9060
        $form->setDefaults($defaults);
9061
9062
        return $form->returnForm();
9063
    }
9064
9065
    /**
9066
     * Returns the form to update or create a read-out text.
9067
     *
9068
     * @param string $action     "add" or "edit"
9069
     * @param int    $id         ID of the lp_item (if already exists)
9070
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9071
     *
9072
     * @throws Exception
9073
     * @throws HTML_QuickForm_Error
9074
     *
9075
     * @return string HTML form
9076
     */
9077
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9078
    {
9079
        $course_id = api_get_course_int_id();
9080
        $_course = api_get_course_info();
9081
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9082
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9083
9084
        $no_display_edit_textarea = false;
9085
        $item_description = '';
9086
        //If action==edit document
9087
        //We don't display the document form if it's not an editable document (html or txt file)
9088
        if ($action == 'edit') {
9089
            if (is_array($extra_info)) {
9090
                $path_parts = pathinfo($extra_info['dir']);
9091
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9092
                    $no_display_edit_textarea = true;
9093
                }
9094
            }
9095
        }
9096
        $no_display_add = false;
9097
9098
        if ($id != 0 && is_array($extra_info)) {
9099
            $item_title = stripslashes($extra_info['title']);
9100
            $item_description = stripslashes($extra_info['description']);
9101
            $item_terms = stripslashes($extra_info['terms']);
9102
            if (empty($item_title)) {
9103
                $path_parts = pathinfo($extra_info['path']);
9104
                $item_title = stripslashes($path_parts['filename']);
9105
            }
9106
        } elseif (is_numeric($extra_info)) {
9107
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9108
            $result = Database::query($sql);
9109
            $row = Database::fetch_array($result);
9110
            $item_title = $row['title'];
9111
            $item_title = str_replace('_', ' ', $item_title);
9112
            if (empty($item_title)) {
9113
                $path_parts = pathinfo($row['path']);
9114
                $item_title = stripslashes($path_parts['filename']);
9115
            }
9116
        } else {
9117
            $item_title = '';
9118
            $item_description = '';
9119
        }
9120
9121
        if ($id != 0 && is_array($extra_info)) {
9122
            $parent = $extra_info['parent_item_id'];
9123
        } else {
9124
            $parent = 0;
9125
        }
9126
9127
        $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9128
        $result = Database::query($sql);
9129
        $arrLP = [];
9130
9131
        while ($row = Database::fetch_array($result)) {
9132
            $arrLP[] = [
9133
                'id' => $row['iid'],
9134
                'item_type' => $row['item_type'],
9135
                'title' => $row['title'],
9136
                'path' => $row['path'],
9137
                'description' => $row['description'],
9138
                'parent_item_id' => $row['parent_item_id'],
9139
                'previous_item_id' => $row['previous_item_id'],
9140
                'next_item_id' => $row['next_item_id'],
9141
                'display_order' => $row['display_order'],
9142
                'max_score' => $row['max_score'],
9143
                'min_score' => $row['min_score'],
9144
                'mastery_score' => $row['mastery_score'],
9145
                'prerequisite' => $row['prerequisite'],
9146
            ];
9147
        }
9148
9149
        $this->tree_array($arrLP);
9150
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9151
        unset($this->arrMenu);
9152
9153
        if ($action == 'add') {
9154
            $formHeader = get_lang('CreateTheDocument');
9155
        } else {
9156
            $formHeader = get_lang('EditTheCurrentDocument');
9157
        }
9158
9159
        if ('edit' === $action) {
9160
            $urlAudioIcon = Display::url(
9161
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9162
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9163
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9164
            );
9165
        } else {
9166
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9167
        }
9168
9169
        $form = new FormValidator(
9170
            'frm_add_reading',
9171
            'POST',
9172
            $this->getCurrentBuildingModeURL(),
9173
            '',
9174
            ['enctype' => 'multipart/form-data']
9175
        );
9176
        $form->addHeader($formHeader);
9177
        $form->addHtml(
9178
            Display::return_message(
9179
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9180
                'normal',
9181
                false
9182
            )
9183
        );
9184
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9185
        $defaults['description'] = $item_description;
9186
9187
        $data = $this->generate_lp_folder($_course);
9188
9189
        if ($action != 'edit') {
9190
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9191
            DocumentManager::build_directory_selector(
9192
                $folders,
9193
                '',
9194
                [],
9195
                true,
9196
                $form,
9197
                'directory_parent_id'
9198
            );
9199
        }
9200
9201
        if (isset($data['id'])) {
9202
            $defaults['directory_parent_id'] = $data['id'];
9203
        }
9204
9205
        $form->addElement(
9206
            'text',
9207
            'title',
9208
            get_lang('Title')
9209
        );
9210
        $form->applyFilter('title', 'trim');
9211
        $form->applyFilter('title', 'html_filter');
9212
9213
        $arrHide[0]['value'] = $this->name;
9214
        $arrHide[0]['padding'] = 20;
9215
9216
        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...
9217
            if ($action != 'add') {
9218
                if ($arrLP[$i]['item_type'] == 'dir' &&
9219
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9220
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9221
                ) {
9222
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9223
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9224
                }
9225
            } else {
9226
                if ($arrLP[$i]['item_type'] == 'dir') {
9227
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9228
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9229
                }
9230
            }
9231
        }
9232
9233
        $parent_select = $form->addSelect(
9234
            'parent',
9235
            get_lang('Parent'),
9236
            [],
9237
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9238
        );
9239
9240
        $my_count = 0;
9241
        foreach ($arrHide as $key => $value) {
9242
            if ($my_count != 0) {
9243
                // The LP name is also the first section and is not in the same charset like the other sections.
9244
                $value['value'] = Security::remove_XSS($value['value']);
9245
                $parent_select->addOption(
9246
                    $value['value'],
9247
                    $key,
9248
                    'style="padding-left:'.$value['padding'].'px;"'
9249
                );
9250
            } else {
9251
                $value['value'] = Security::remove_XSS($value['value']);
9252
                $parent_select->addOption(
9253
                    $value['value'],
9254
                    $key,
9255
                    'style="padding-left:'.$value['padding'].'px;"'
9256
                );
9257
            }
9258
            $my_count++;
9259
        }
9260
9261
        if (!empty($id)) {
9262
            $parent_select->setSelected($parent);
9263
        } else {
9264
            $parent_item_id = Session::read('parent_item_id', 0);
9265
            $parent_select->setSelected($parent_item_id);
9266
        }
9267
9268
        if (is_array($arrLP)) {
9269
            reset($arrLP);
9270
        }
9271
9272
        $arrHide = [];
9273
        $s_selected_position = null;
9274
9275
        // POSITION
9276
        $lastPosition = null;
9277
9278
        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...
9279
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9280
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9281
            ) {
9282
                if ((isset($extra_info['previous_item_id']) &&
9283
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9284
                ) {
9285
                    $s_selected_position = $arrLP[$i]['id'];
9286
                }
9287
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9288
            }
9289
            $lastPosition = $arrLP[$i]['id'];
9290
        }
9291
9292
        if (empty($s_selected_position)) {
9293
            $s_selected_position = $lastPosition;
9294
        }
9295
9296
        $position = $form->addSelect(
9297
            'previous',
9298
            get_lang('Position'),
9299
            []
9300
        );
9301
        $position->addOption(get_lang('FirstPosition'), 0);
9302
9303
        foreach ($arrHide as $key => $value) {
9304
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9305
            $position->addOption(
9306
                $value['value'],
9307
                $key,
9308
                'style="padding-left:'.$padding.'px;"'
9309
            );
9310
        }
9311
        $position->setSelected($s_selected_position);
9312
9313
        if (is_array($arrLP)) {
9314
            reset($arrLP);
9315
        }
9316
9317
        $arrHide = [];
9318
9319
        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...
9320
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9321
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9322
            ) {
9323
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9324
            }
9325
        }
9326
9327
        if (!$no_display_add) {
9328
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9329
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9330
9331
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9332
                if (!$no_display_edit_textarea) {
9333
                    $content = '';
9334
9335
                    if (isset($_POST['content'])) {
9336
                        $content = stripslashes($_POST['content']);
9337
                    } elseif (is_array($extra_info)) {
9338
                        $content = $this->display_document($extra_info['path'], false, false);
9339
                    } elseif (is_numeric($extra_info)) {
9340
                        $content = $this->display_document($extra_info, false, false);
9341
                    }
9342
9343
                    // A new document, it is in the root of the repository.
9344
                    if (is_array($extra_info) && $extra_info != 'new') {
9345
                    } else {
9346
                        $this->generate_lp_folder($_course);
9347
                    }
9348
9349
                    if ($_GET['action'] == 'add_item') {
9350
                        $text = get_lang('LPCreateDocument');
9351
                    } else {
9352
                        $text = get_lang('SaveDocument');
9353
                    }
9354
9355
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9356
                    $form
9357
                        ->defaultRenderer()
9358
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9359
                    $form->addButtonSave($text, 'submit_button');
9360
                    $defaults['content_lp'] = $content;
9361
                }
9362
            } elseif (is_numeric($extra_info)) {
9363
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9364
9365
                $return = $this->display_document($extra_info, true, true, true);
9366
                $form->addElement('html', $return);
9367
            }
9368
        }
9369
9370
        if (is_numeric($extra_info)) {
9371
            $form->addElement('hidden', 'path', $extra_info);
9372
        } elseif (is_array($extra_info)) {
9373
            $form->addElement('hidden', 'path', $extra_info['path']);
9374
        }
9375
9376
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9377
        $form->addElement('hidden', 'post_time', time());
9378
        $form->setDefaults($defaults);
9379
9380
        return $form->returnForm();
9381
    }
9382
9383
    /**
9384
     * @param array  $courseInfo
9385
     * @param string $content
9386
     * @param string $title
9387
     * @param int    $parentId
9388
     *
9389
     * @throws \Doctrine\ORM\ORMException
9390
     * @throws \Doctrine\ORM\OptimisticLockException
9391
     * @throws \Doctrine\ORM\TransactionRequiredException
9392
     *
9393
     * @return int
9394
     */
9395
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9396
    {
9397
        $creatorId = api_get_user_id();
9398
        $sessionId = api_get_session_id();
9399
9400
        // Generates folder
9401
        $result = $this->generate_lp_folder($courseInfo);
9402
        $dir = $result['dir'];
9403
9404
        if (empty($parentId) || $parentId == '/') {
9405
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9406
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9407
9408
            if ($parentId === '/') {
9409
                $dir = '/';
9410
            }
9411
9412
            // Please, do not modify this dirname formatting.
9413
            if (strstr($dir, '..')) {
9414
                $dir = '/';
9415
            }
9416
9417
            if (!empty($dir[0]) && $dir[0] == '.') {
9418
                $dir = substr($dir, 1);
9419
            }
9420
            if (!empty($dir[0]) && $dir[0] != '/') {
9421
                $dir = '/'.$dir;
9422
            }
9423
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9424
                $dir .= '/';
9425
            }
9426
        } else {
9427
            $parentInfo = DocumentManager::get_document_data_by_id(
9428
                $parentId,
9429
                $courseInfo['code']
9430
            );
9431
            if (!empty($parentInfo)) {
9432
                $dir = $parentInfo['path'].'/';
9433
            }
9434
        }
9435
9436
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9437
9438
        if (!is_dir($filepath)) {
9439
            $dir = '/';
9440
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9441
        }
9442
9443
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9444
9445
        if (!empty($title)) {
9446
            $title = api_replace_dangerous_char(stripslashes($title));
9447
        } else {
9448
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9449
        }
9450
9451
        $title = disable_dangerous_file($title);
9452
        $filename = $title;
9453
        $content = !empty($content) ? $content : $_POST['content_lp'];
9454
        $tmpFileName = $filename;
9455
9456
        $i = 0;
9457
        while (file_exists($filepath.$tmpFileName.'.html')) {
9458
            $tmpFileName = $filename.'_'.++$i;
9459
        }
9460
9461
        $filename = $tmpFileName.'.html';
9462
        $content = stripslashes($content);
9463
9464
        if (file_exists($filepath.$filename)) {
9465
            return 0;
9466
        }
9467
9468
        $putContent = file_put_contents($filepath.$filename, $content);
9469
9470
        if ($putContent === false) {
9471
            return 0;
9472
        }
9473
9474
        $fileSize = filesize($filepath.$filename);
9475
        $saveFilePath = $dir.$filename;
9476
9477
        $documentId = add_document(
9478
            $courseInfo,
9479
            $saveFilePath,
9480
            'file',
9481
            $fileSize,
9482
            $tmpFileName,
9483
            '',
9484
            0, //readonly
9485
            true,
9486
            null,
9487
            $sessionId,
9488
            $creatorId
9489
        );
9490
9491
        if (!$documentId) {
9492
            return 0;
9493
        }
9494
9495
        api_item_property_update(
9496
            $courseInfo,
9497
            TOOL_DOCUMENT,
9498
            $documentId,
9499
            'DocumentAdded',
9500
            $creatorId,
9501
            null,
9502
            null,
9503
            null,
9504
            null,
9505
            $sessionId
9506
        );
9507
9508
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9509
        $newTitle = $originalTitle;
9510
9511
        if ($newComment || $newTitle) {
9512
            $em = Database::getManager();
9513
9514
            /** @var CDocument $doc */
9515
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9516
9517
            if ($newComment) {
9518
                $doc->setComment($newComment);
9519
            }
9520
9521
            if ($newTitle) {
9522
                $doc->setTitle($newTitle);
9523
            }
9524
9525
            $em->persist($doc);
9526
            $em->flush();
9527
        }
9528
9529
        return $documentId;
9530
    }
9531
9532
    /**
9533
     * Return HTML form to add/edit a link item.
9534
     *
9535
     * @param string $action     (add/edit)
9536
     * @param int    $id         Item ID if exists
9537
     * @param mixed  $extra_info
9538
     *
9539
     * @throws Exception
9540
     * @throws HTML_QuickForm_Error
9541
     *
9542
     * @return string HTML form
9543
     */
9544
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9545
    {
9546
        $course_id = api_get_course_int_id();
9547
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9548
        $tbl_link = Database::get_course_table(TABLE_LINK);
9549
9550
        $item_title = '';
9551
        $item_description = '';
9552
        $item_url = '';
9553
9554
        if ($id != 0 && is_array($extra_info)) {
9555
            $item_title = stripslashes($extra_info['title']);
9556
            $item_description = stripslashes($extra_info['description']);
9557
            $item_url = stripslashes($extra_info['url']);
9558
        } elseif (is_numeric($extra_info)) {
9559
            $extra_info = (int) $extra_info;
9560
            $sql = "SELECT title, description, url
9561
                    FROM $tbl_link
9562
                    WHERE c_id = $course_id AND iid = $extra_info";
9563
            $result = Database::query($sql);
9564
            $row = Database::fetch_array($result);
9565
            $item_title = $row['title'];
9566
            $item_description = $row['description'];
9567
            $item_url = $row['url'];
9568
        }
9569
9570
        $form = new FormValidator(
9571
            'edit_link',
9572
            'POST',
9573
            $this->getCurrentBuildingModeURL()
9574
        );
9575
        $defaults = [];
9576
        $parent = 0;
9577
        if ($id != 0 && is_array($extra_info)) {
9578
            $parent = $extra_info['parent_item_id'];
9579
        }
9580
9581
        $sql = "SELECT * FROM $tbl_lp_item
9582
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9583
        $result = Database::query($sql);
9584
        $arrLP = [];
9585
9586
        while ($row = Database::fetch_array($result)) {
9587
            $arrLP[] = [
9588
                'id' => $row['id'],
9589
                'item_type' => $row['item_type'],
9590
                'title' => $row['title'],
9591
                'path' => $row['path'],
9592
                'description' => $row['description'],
9593
                'parent_item_id' => $row['parent_item_id'],
9594
                'previous_item_id' => $row['previous_item_id'],
9595
                'next_item_id' => $row['next_item_id'],
9596
                'display_order' => $row['display_order'],
9597
                'max_score' => $row['max_score'],
9598
                'min_score' => $row['min_score'],
9599
                'mastery_score' => $row['mastery_score'],
9600
                'prerequisite' => $row['prerequisite'],
9601
            ];
9602
        }
9603
9604
        $this->tree_array($arrLP);
9605
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9606
        unset($this->arrMenu);
9607
9608
        if ($action == 'add') {
9609
            $legend = get_lang('CreateTheLink');
9610
        } elseif ($action == 'move') {
9611
            $legend = get_lang('MoveCurrentLink');
9612
        } else {
9613
            $legend = get_lang('EditCurrentLink');
9614
        }
9615
9616
        $form->addHeader($legend);
9617
9618
        if ($action != 'move') {
9619
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9620
            $defaults['title'] = $item_title;
9621
        }
9622
9623
        $selectParent = $form->addSelect(
9624
            'parent',
9625
            get_lang('Parent'),
9626
            [],
9627
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9628
        );
9629
        $selectParent->addOption($this->name, 0);
9630
        $arrHide = [
9631
            $id,
9632
        ];
9633
9634
        $parent_item_id = Session::read('parent_item_id', 0);
9635
9636
        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...
9637
            if ($action != 'add') {
9638
                if (
9639
                    ($arrLP[$i]['item_type'] == 'dir') &&
9640
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9641
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9642
                ) {
9643
                    $selectParent->addOption(
9644
                        $arrLP[$i]['title'],
9645
                        $arrLP[$i]['id'],
9646
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9647
                    );
9648
9649
                    if ($parent == $arrLP[$i]['id']) {
9650
                        $selectParent->setSelected($arrLP[$i]['id']);
9651
                    }
9652
                } else {
9653
                    $arrHide[] = $arrLP[$i]['id'];
9654
                }
9655
            } else {
9656
                if ($arrLP[$i]['item_type'] == 'dir') {
9657
                    $selectParent->addOption(
9658
                        $arrLP[$i]['title'],
9659
                        $arrLP[$i]['id'],
9660
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9661
                    );
9662
9663
                    if ($parent_item_id == $arrLP[$i]['id']) {
9664
                        $selectParent->setSelected($arrLP[$i]['id']);
9665
                    }
9666
                }
9667
            }
9668
        }
9669
9670
        if (is_array($arrLP)) {
9671
            reset($arrLP);
9672
        }
9673
9674
        $selectPrevious = $form->addSelect(
9675
            'previous',
9676
            get_lang('Position'),
9677
            [],
9678
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9679
        );
9680
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9681
9682
        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...
9683
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9684
                $selectPrevious->addOption(
9685
                    $arrLP[$i]['title'],
9686
                    $arrLP[$i]['id']
9687
                );
9688
9689
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9690
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9691
                } elseif ($action == 'add') {
9692
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9693
                }
9694
            }
9695
        }
9696
9697
        if ($action != 'move') {
9698
            $urlAttributes = ['class' => 'learnpath_item_form'];
9699
9700
            if (is_numeric($extra_info)) {
9701
                $urlAttributes['disabled'] = 'disabled';
9702
            }
9703
9704
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9705
            $defaults['url'] = $item_url;
9706
            $arrHide = [];
9707
            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...
9708
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9709
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9710
                }
9711
            }
9712
        }
9713
9714
        if ($action == 'add') {
9715
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9716
        } else {
9717
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9718
        }
9719
9720
        if ($action == 'move') {
9721
            $form->addHidden('title', $item_title);
9722
            $form->addHidden('description', $item_description);
9723
        }
9724
9725
        if (is_numeric($extra_info)) {
9726
            $form->addHidden('path', $extra_info);
9727
        } elseif (is_array($extra_info)) {
9728
            $form->addHidden('path', $extra_info['path']);
9729
        }
9730
        $form->addHidden('type', TOOL_LINK);
9731
        $form->addHidden('post_time', time());
9732
        $form->setDefaults($defaults);
9733
9734
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9735
    }
9736
9737
    /**
9738
     * Return HTML form to add/edit a student publication (work).
9739
     *
9740
     * @param string $action
9741
     * @param int    $id         Item ID if already exists
9742
     * @param string $extra_info
9743
     *
9744
     * @throws Exception
9745
     *
9746
     * @return string HTML form
9747
     */
9748
    public function display_student_publication_form(
9749
        $action = 'add',
9750
        $id = 0,
9751
        $extra_info = ''
9752
    ) {
9753
        $course_id = api_get_course_int_id();
9754
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9755
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9756
9757
        $item_title = get_lang('Student_publication');
9758
        if ($id != 0 && is_array($extra_info)) {
9759
            $item_title = stripslashes($extra_info['title']);
9760
            $item_description = stripslashes($extra_info['description']);
9761
        } elseif (is_numeric($extra_info)) {
9762
            $extra_info = (int) $extra_info;
9763
            $sql = "SELECT title, description
9764
                    FROM $tbl_publication
9765
                    WHERE c_id = $course_id AND id = ".$extra_info;
9766
9767
            $result = Database::query($sql);
9768
            $row = Database::fetch_array($result);
9769
            if ($row) {
9770
                $item_title = $row['title'];
9771
            }
9772
        }
9773
9774
        $parent = 0;
9775
        if ($id != 0 && is_array($extra_info)) {
9776
            $parent = $extra_info['parent_item_id'];
9777
        }
9778
9779
        $sql = "SELECT * FROM $tbl_lp_item
9780
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9781
        $result = Database::query($sql);
9782
        $arrLP = [];
9783
9784
        while ($row = Database::fetch_array($result)) {
9785
            $arrLP[] = [
9786
                'id' => $row['iid'],
9787
                'item_type' => $row['item_type'],
9788
                'title' => $row['title'],
9789
                'path' => $row['path'],
9790
                'description' => $row['description'],
9791
                'parent_item_id' => $row['parent_item_id'],
9792
                'previous_item_id' => $row['previous_item_id'],
9793
                'next_item_id' => $row['next_item_id'],
9794
                'display_order' => $row['display_order'],
9795
                'max_score' => $row['max_score'],
9796
                'min_score' => $row['min_score'],
9797
                'mastery_score' => $row['mastery_score'],
9798
                'prerequisite' => $row['prerequisite'],
9799
            ];
9800
        }
9801
9802
        $this->tree_array($arrLP);
9803
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9804
        unset($this->arrMenu);
9805
9806
        $form = new FormValidator('frm_student_publication', 'post', '#');
9807
9808
        if ($action == 'add') {
9809
            $form->addHeader(get_lang('Student_publication'));
9810
        } elseif ($action == 'move') {
9811
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9812
        } else {
9813
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9814
        }
9815
9816
        if ($action != 'move') {
9817
            $form->addText(
9818
                'title',
9819
                get_lang('Title'),
9820
                true,
9821
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9822
            );
9823
        }
9824
9825
        $parentSelect = $form->addSelect(
9826
            'parent',
9827
            get_lang('Parent'),
9828
            ['0' => $this->name],
9829
            [
9830
                'onchange' => 'javascript: load_cbo(this.value);',
9831
                'class' => 'learnpath_item_form',
9832
                'id' => 'idParent',
9833
            ]
9834
        );
9835
9836
        $arrHide = [$id];
9837
        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...
9838
            if ($action != 'add') {
9839
                if (
9840
                    ($arrLP[$i]['item_type'] == 'dir') &&
9841
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9842
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9843
                ) {
9844
                    $parentSelect->addOption(
9845
                        $arrLP[$i]['title'],
9846
                        $arrLP[$i]['id'],
9847
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9848
                    );
9849
9850
                    if ($parent == $arrLP[$i]['id']) {
9851
                        $parentSelect->setSelected($arrLP[$i]['id']);
9852
                    }
9853
                } else {
9854
                    $arrHide[] = $arrLP[$i]['id'];
9855
                }
9856
            } else {
9857
                if ($arrLP[$i]['item_type'] == 'dir') {
9858
                    $parentSelect->addOption(
9859
                        $arrLP[$i]['title'],
9860
                        $arrLP[$i]['id'],
9861
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9862
                    );
9863
9864
                    if ($parent == $arrLP[$i]['id']) {
9865
                        $parentSelect->setSelected($arrLP[$i]['id']);
9866
                    }
9867
                }
9868
            }
9869
        }
9870
9871
        if (is_array($arrLP)) {
9872
            reset($arrLP);
9873
        }
9874
9875
        $previousSelect = $form->addSelect(
9876
            'previous',
9877
            get_lang('Position'),
9878
            ['0' => get_lang('FirstPosition')],
9879
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9880
        );
9881
9882
        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...
9883
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9884
                $previousSelect->addOption(
9885
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9886
                    $arrLP[$i]['id']
9887
                );
9888
9889
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9890
                    $previousSelect->setSelected($arrLP[$i]['id']);
9891
                } elseif ($action == 'add') {
9892
                    $previousSelect->setSelected($arrLP[$i]['id']);
9893
                }
9894
            }
9895
        }
9896
9897
        if ($action == 'add') {
9898
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9899
        } else {
9900
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9901
        }
9902
9903
        if ($action == 'move') {
9904
            $form->addHidden('title', $item_title);
9905
            $form->addHidden('description', $item_description);
9906
        }
9907
9908
        if (is_numeric($extra_info)) {
9909
            $form->addHidden('path', $extra_info);
9910
        } elseif (is_array($extra_info)) {
9911
            $form->addHidden('path', $extra_info['path']);
9912
        }
9913
9914
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9915
        $form->addHidden('post_time', time());
9916
        $form->setDefaults(['title' => $item_title]);
9917
9918
        $return = '<div class="sectioncomment">';
9919
        $return .= $form->returnForm();
9920
        $return .= '</div>';
9921
9922
        return $return;
9923
    }
9924
9925
    /**
9926
     * Displays the menu for manipulating a step.
9927
     *
9928
     * @param id     $item_id
9929
     * @param string $item_type
9930
     *
9931
     * @return string
9932
     */
9933
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9934
    {
9935
        $_course = api_get_course_info();
9936
        $course_code = api_get_course_id();
9937
        $item_id = (int) $item_id;
9938
9939
        $return = '<div class="actions">';
9940
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9941
        $sql = "SELECT * FROM $tbl_lp_item
9942
                WHERE iid = ".$item_id;
9943
        $result = Database::query($sql);
9944
        $row = Database::fetch_assoc($result);
9945
9946
        $audio_player = null;
9947
        // We display an audio player if needed.
9948
        if (!empty($row['audio'])) {
9949
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9950
9951
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9952
                .'<audio src="'.$webAudioPath.'" controls>'
9953
                .'</div><br>';
9954
        }
9955
9956
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9957
9958
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9959
            $return .= Display::url(
9960
                Display::return_icon(
9961
                    'edit.png',
9962
                    get_lang('Edit'),
9963
                    [],
9964
                    ICON_SIZE_SMALL
9965
                ),
9966
                $url.'&action=edit_item&path_item='.$row['path']
9967
            );
9968
9969
            $return .= Display::url(
9970
                Display::return_icon(
9971
                    'move.png',
9972
                    get_lang('Move'),
9973
                    [],
9974
                    ICON_SIZE_SMALL
9975
                ),
9976
                $url.'&action=move_item'
9977
            );
9978
        }
9979
9980
        // Commented for now as prerequisites cannot be added to chapters.
9981
        if ($item_type != 'dir') {
9982
            $return .= Display::url(
9983
                Display::return_icon(
9984
                    'accept.png',
9985
                    get_lang('LearnpathPrerequisites'),
9986
                    [],
9987
                    ICON_SIZE_SMALL
9988
                ),
9989
                $url.'&action=edit_item_prereq'
9990
            );
9991
        }
9992
        $return .= Display::url(
9993
            Display::return_icon(
9994
                'delete.png',
9995
                get_lang('Delete'),
9996
                [],
9997
                ICON_SIZE_SMALL
9998
            ),
9999
            $url.'&action=delete_item'
10000
        );
10001
10002
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10003
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10004
            if (empty($documentData)) {
10005
                // Try with iid
10006
                $table = Database::get_course_table(TABLE_DOCUMENT);
10007
                $sql = "SELECT path FROM $table
10008
                        WHERE
10009
                              c_id = ".api_get_course_int_id()." AND
10010
                              iid = ".$row['path']." AND
10011
                              path NOT LIKE '%_DELETED_%'";
10012
                $result = Database::query($sql);
10013
                $documentData = Database::fetch_array($result);
10014
                if ($documentData) {
10015
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10016
                }
10017
            }
10018
            if (isset($documentData['absolute_path_from_document'])) {
10019
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10020
            }
10021
        }
10022
10023
        $return .= '</div>';
10024
10025
        if (!empty($audio_player)) {
10026
            $return .= $audio_player;
10027
        }
10028
10029
        return $return;
10030
    }
10031
10032
    /**
10033
     * Creates the javascript needed for filling up the checkboxes without page reload.
10034
     *
10035
     * @return string
10036
     */
10037
    public function get_js_dropdown_array()
10038
    {
10039
        $course_id = api_get_course_int_id();
10040
        $return = 'var child_name = new Array();'."\n";
10041
        $return .= 'var child_value = new Array();'."\n\n";
10042
        $return .= 'child_name[0] = new Array();'."\n";
10043
        $return .= 'child_value[0] = new Array();'."\n\n";
10044
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10045
        $sql = "SELECT * FROM ".$tbl_lp_item."
10046
                WHERE
10047
                    c_id = $course_id AND
10048
                    lp_id = ".$this->lp_id." AND
10049
                    parent_item_id = 0
10050
                ORDER BY display_order ASC";
10051
        $res_zero = Database::query($sql);
10052
        $i = 0;
10053
10054
        while ($row_zero = Database::fetch_array($res_zero)) {
10055
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10056
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10057
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10058
                }
10059
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10060
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10061
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10062
            }
10063
        }
10064
        $return .= "\n";
10065
        $sql = "SELECT * FROM $tbl_lp_item
10066
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10067
        $res = Database::query($sql);
10068
        while ($row = Database::fetch_array($res)) {
10069
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10070
                           WHERE
10071
                                c_id = ".$course_id." AND
10072
                                parent_item_id = ".$row['iid']."
10073
                           ORDER BY display_order ASC";
10074
            $res_parent = Database::query($sql_parent);
10075
            $i = 0;
10076
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10077
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10078
10079
            while ($row_parent = Database::fetch_array($res_parent)) {
10080
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
10081
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10082
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10083
            }
10084
            $return .= "\n";
10085
        }
10086
10087
        $return .= "
10088
            function load_cbo(id) {
10089
                if (!id) {
10090
                    return false;
10091
                }
10092
10093
                var cbo = document.getElementById('previous');
10094
                for(var i = cbo.length - 1; i > 0; i--) {
10095
                    cbo.options[i] = null;
10096
                }
10097
10098
                var k=0;
10099
                for(var i = 1; i <= child_name[id].length; i++){
10100
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10101
                    option.style.paddingLeft = '40px';
10102
                    cbo.options[i] = option;
10103
                    k = i;
10104
                }
10105
10106
                cbo.options[k].selected = true;
10107
                $('#previous').selectpicker('refresh');
10108
            }";
10109
10110
        return $return;
10111
    }
10112
10113
    /**
10114
     * Display the form to allow moving an item.
10115
     *
10116
     * @param int $item_id Item ID
10117
     *
10118
     * @throws Exception
10119
     * @throws HTML_QuickForm_Error
10120
     *
10121
     * @return string HTML form
10122
     */
10123
    public function display_move_item($item_id)
10124
    {
10125
        $return = '';
10126
        if (is_numeric($item_id)) {
10127
            $item_id = (int) $item_id;
10128
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10129
10130
            $sql = "SELECT * FROM $tbl_lp_item
10131
                    WHERE iid = $item_id";
10132
            $res = Database::query($sql);
10133
            $row = Database::fetch_array($res);
10134
10135
            switch ($row['item_type']) {
10136
                case 'dir':
10137
                case 'asset':
10138
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10139
                    $return .= $this->display_item_form(
10140
                        $row['item_type'],
10141
                        get_lang('MoveCurrentChapter'),
10142
                        'move',
10143
                        $item_id,
10144
                        $row
10145
                    );
10146
                    break;
10147
                case TOOL_DOCUMENT:
10148
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10149
                    $return .= $this->display_document_form('move', $item_id, $row);
10150
                    break;
10151
                case TOOL_LINK:
10152
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10153
                    $return .= $this->display_link_form('move', $item_id, $row);
10154
                    break;
10155
                case TOOL_HOTPOTATOES:
10156
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10157
                    $return .= $this->display_link_form('move', $item_id, $row);
10158
                    break;
10159
                case TOOL_QUIZ:
10160
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10161
                    $return .= $this->display_quiz_form('move', $item_id, $row);
10162
                    break;
10163
                case TOOL_STUDENTPUBLICATION:
10164
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10165
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
10166
                    break;
10167
                case TOOL_FORUM:
10168
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10169
                    $return .= $this->display_forum_form('move', $item_id, $row);
10170
                    break;
10171
                case TOOL_THREAD:
10172
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10173
                    $return .= $this->display_forum_form('move', $item_id, $row);
10174
                    break;
10175
            }
10176
        }
10177
10178
        return $return;
10179
    }
10180
10181
    /**
10182
     * Return HTML form to allow prerequisites selection.
10183
     *
10184
     * @todo use FormValidator
10185
     *
10186
     * @param int Item ID
10187
     *
10188
     * @return string HTML form
10189
     */
10190
    public function display_item_prerequisites_form($item_id = 0)
10191
    {
10192
        $course_id = api_get_course_int_id();
10193
        $item_id = (int) $item_id;
10194
10195
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10196
10197
        /* Current prerequisite */
10198
        $sql = "SELECT * FROM $tbl_lp_item
10199
                WHERE iid = $item_id";
10200
        $result = Database::query($sql);
10201
        $row = Database::fetch_array($result);
10202
        $prerequisiteId = $row['prerequisite'];
10203
        $return = '<legend>';
10204
        $return .= get_lang('AddEditPrerequisites');
10205
        $return .= '</legend>';
10206
        $return .= '<form method="POST">';
10207
        $return .= '<div class="table-responsive">';
10208
        $return .= '<table class="table table-hover">';
10209
        $return .= '<thead>';
10210
        $return .= '<tr>';
10211
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10212
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10213
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10214
        $return .= '</tr>';
10215
        $return .= '</thead>';
10216
10217
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10218
        $return .= '<tbody>';
10219
        $return .= '<tr>';
10220
        $return .= '<td colspan="3">';
10221
        $return .= '<div class="radio learnpath"><label for="idNone">';
10222
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10223
        $return .= get_lang('None').'</label>';
10224
        $return .= '</div>';
10225
        $return .= '</tr>';
10226
10227
        $sql = "SELECT * FROM $tbl_lp_item
10228
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10229
        $result = Database::query($sql);
10230
        $arrLP = [];
10231
10232
        $selectedMinScore = [];
10233
        $selectedMaxScore = [];
10234
        $masteryScore = [];
10235
        while ($row = Database::fetch_array($result)) {
10236
            if ($row['iid'] == $item_id) {
10237
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10238
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10239
            }
10240
            $masteryScore[$row['iid']] = $row['mastery_score'];
10241
10242
            $arrLP[] = [
10243
                'id' => $row['iid'],
10244
                'item_type' => $row['item_type'],
10245
                'title' => $row['title'],
10246
                'ref' => $row['ref'],
10247
                'description' => $row['description'],
10248
                'parent_item_id' => $row['parent_item_id'],
10249
                'previous_item_id' => $row['previous_item_id'],
10250
                'next_item_id' => $row['next_item_id'],
10251
                'max_score' => $row['max_score'],
10252
                'min_score' => $row['min_score'],
10253
                'mastery_score' => $row['mastery_score'],
10254
                'prerequisite' => $row['prerequisite'],
10255
                'display_order' => $row['display_order'],
10256
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10257
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10258
            ];
10259
        }
10260
10261
        $this->tree_array($arrLP);
10262
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10263
        unset($this->arrMenu);
10264
10265
        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...
10266
            $item = $arrLP[$i];
10267
10268
            if ($item['id'] == $item_id) {
10269
                break;
10270
            }
10271
10272
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10273
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10274
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10275
10276
            $return .= '<tr>';
10277
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10278
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10279
            $return .= '<label for="id'.$item['id'].'">';
10280
            $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'].'" />';
10281
10282
            $icon_name = str_replace(' ', '', $item['item_type']);
10283
10284
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10285
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10286
            } else {
10287
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10288
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10289
                } else {
10290
                    $return .= Display::return_icon('folder_document.png');
10291
                }
10292
            }
10293
10294
            $return .= $item['title'].'</label>';
10295
            $return .= '</div>';
10296
            $return .= '</td>';
10297
10298
            if ($item['item_type'] == TOOL_QUIZ) {
10299
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10300
                $lpItemObj = new LpItem($course_id, $item['id']);
10301
                $exercise = new Exercise($course_id);
10302
                $exercise->read($lpItemObj->path);
10303
                $lpItemObj->max_score = $exercise->get_max_score();
10304
                $lpItemObj->update();
10305
                $item['max_score'] = $lpItemObj->max_score;
10306
10307
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10308
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10309
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10310
                }
10311
10312
                $return .= '<td>';
10313
                $return .= '<input
10314
                    class="form-control"
10315
                    size="4" maxlength="3"
10316
                    name="min_'.$item['id'].'"
10317
                    type="number"
10318
                    min="0"
10319
                    step="1"
10320
                    max="'.$item['max_score'].'"
10321
                    value="'.$selectedMinScoreValue.'"
10322
                />';
10323
                $return .= '</td>';
10324
                $return .= '<td>';
10325
                $return .= '<input
10326
                    class="form-control"
10327
                    size="4"
10328
                    maxlength="3"
10329
                    name="max_'.$item['id'].'"
10330
                    type="number"
10331
                    min="0"
10332
                    step="1"
10333
                    max="'.$item['max_score'].'"
10334
                    value="'.$selectedMaxScoreValue.'"
10335
                />';
10336
                $return .= '</td>';
10337
            }
10338
10339
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10340
                $return .= '<td>';
10341
                $return .= '<input
10342
                    size="4"
10343
                    maxlength="3"
10344
                    name="min_'.$item['id'].'"
10345
                    type="number"
10346
                    min="0"
10347
                    step="1"
10348
                    max="'.$item['max_score'].'"
10349
                    value="'.$selectedMinScoreValue.'"
10350
                />';
10351
                $return .= '</td>';
10352
                $return .= '<td>';
10353
                $return .= '<input
10354
                    size="4"
10355
                    maxlength="3"
10356
                    name="max_'.$item['id'].'"
10357
                    type="number"
10358
                    min="0"
10359
                    step="1"
10360
                    max="'.$item['max_score'].'"
10361
                    value="'.$selectedMaxScoreValue.'"
10362
                />';
10363
                $return .= '</td>';
10364
            }
10365
            $return .= '</tr>';
10366
        }
10367
        $return .= '<tr>';
10368
        $return .= '</tr>';
10369
        $return .= '</tbody>';
10370
        $return .= '</table>';
10371
        $return .= '</div>';
10372
        $return .= '<div class="form-group">';
10373
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10374
            get_lang('ModifyPrerequisites').'</button>';
10375
        $return .= '</form>';
10376
10377
        return $return;
10378
    }
10379
10380
    /**
10381
     * Return HTML list to allow prerequisites selection for lp.
10382
     *
10383
     * @return string HTML form
10384
     */
10385
    public function display_lp_prerequisites_list()
10386
    {
10387
        $course_id = api_get_course_int_id();
10388
        $lp_id = $this->lp_id;
10389
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10390
10391
        // get current prerequisite
10392
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10393
        $result = Database::query($sql);
10394
        $row = Database::fetch_array($result);
10395
        $prerequisiteId = $row['prerequisite'];
10396
        $session_id = api_get_session_id();
10397
        $session_condition = api_get_session_condition($session_id, true, true);
10398
        $sql = "SELECT * FROM $tbl_lp
10399
                WHERE c_id = $course_id $session_condition
10400
                ORDER BY display_order ";
10401
        $rs = Database::query($sql);
10402
        $return = '';
10403
        $return .= '<select name="prerequisites" class="form-control">';
10404
        $return .= '<option value="0">'.get_lang('None').'</option>';
10405
        if (Database::num_rows($rs) > 0) {
10406
            while ($row = Database::fetch_array($rs)) {
10407
                if ($row['id'] == $lp_id) {
10408
                    continue;
10409
                }
10410
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10411
            }
10412
        }
10413
        $return .= '</select>';
10414
10415
        return $return;
10416
    }
10417
10418
    /**
10419
     * Creates a list with all the documents in it.
10420
     *
10421
     * @param bool $showInvisibleFiles
10422
     *
10423
     * @throws Exception
10424
     * @throws HTML_QuickForm_Error
10425
     *
10426
     * @return string
10427
     */
10428
    public function get_documents($showInvisibleFiles = false)
10429
    {
10430
        $course_info = api_get_course_info();
10431
        $sessionId = api_get_session_id();
10432
        $documentTree = DocumentManager::get_document_preview(
10433
            $course_info,
10434
            $this->lp_id,
10435
            null,
10436
            $sessionId,
10437
            true,
10438
            null,
10439
            null,
10440
            $showInvisibleFiles,
10441
            true
10442
        );
10443
10444
        $headers = [
10445
            get_lang('Files'),
10446
            get_lang('CreateTheDocument'),
10447
            get_lang('CreateReadOutText'),
10448
            get_lang('Upload'),
10449
        ];
10450
10451
        $form = new FormValidator(
10452
            'form_upload',
10453
            'POST',
10454
            $this->getCurrentBuildingModeURL(),
10455
            '',
10456
            ['enctype' => 'multipart/form-data']
10457
        );
10458
10459
        $folders = DocumentManager::get_all_document_folders(
10460
            api_get_course_info(),
10461
            0,
10462
            true
10463
        );
10464
10465
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10466
10467
        DocumentManager::build_directory_selector(
10468
            $folders,
10469
            $lpPathInfo['id'],
10470
            [],
10471
            true,
10472
            $form,
10473
            'directory_parent_id'
10474
        );
10475
10476
        $group = [
10477
            $form->createElement(
10478
                'radio',
10479
                'if_exists',
10480
                get_lang('UplWhatIfFileExists'),
10481
                get_lang('UplDoNothing'),
10482
                'nothing'
10483
            ),
10484
            $form->createElement(
10485
                'radio',
10486
                'if_exists',
10487
                null,
10488
                get_lang('UplOverwriteLong'),
10489
                'overwrite'
10490
            ),
10491
            $form->createElement(
10492
                'radio',
10493
                'if_exists',
10494
                null,
10495
                get_lang('UplRenameLong'),
10496
                'rename'
10497
            ),
10498
        ];
10499
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10500
10501
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10502
        $defaultFileExistsOption = 'rename';
10503
        if (!empty($fileExistsOption)) {
10504
            $defaultFileExistsOption = $fileExistsOption;
10505
        }
10506
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10507
10508
        // Check box options
10509
        $form->addElement(
10510
            'checkbox',
10511
            'unzip',
10512
            get_lang('Options'),
10513
            get_lang('Uncompress')
10514
        );
10515
10516
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10517
        $form->addMultipleUpload($url);
10518
        $new = $this->display_document_form('add', 0);
10519
        $frmReadOutText = $this->displayFrmReadOutText('add');
10520
        $tabs = Display::tabs(
10521
            $headers,
10522
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10523
            'subtab'
10524
        );
10525
10526
        return $tabs;
10527
    }
10528
10529
    /**
10530
     * Creates a list with all the exercises (quiz) in it.
10531
     *
10532
     * @return string
10533
     */
10534
    public function get_exercises()
10535
    {
10536
        $course_id = api_get_course_int_id();
10537
        $session_id = api_get_session_id();
10538
        $userInfo = api_get_user_info();
10539
10540
        // New for hotpotatoes.
10541
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10542
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10543
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10544
        $condition_session = api_get_session_condition($session_id, true, true);
10545
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10546
10547
        $activeCondition = ' active <> -1 ';
10548
        if ($setting) {
10549
            $activeCondition = ' active = 1 ';
10550
        }
10551
10552
        $categoryCondition = '';
10553
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10554
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10555
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10556
        }
10557
10558
        $keywordCondition = '';
10559
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10560
10561
        if (!empty($keyword)) {
10562
            $keyword = Database::escape_string($keyword);
10563
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10564
        }
10565
10566
        $sql_quiz = "SELECT * FROM $tbl_quiz
10567
                     WHERE
10568
                            c_id = $course_id AND
10569
                            $activeCondition
10570
                            $condition_session
10571
                            $categoryCondition
10572
                            $keywordCondition
10573
                     ORDER BY title ASC";
10574
10575
        $sql_hot = "SELECT * FROM $tbl_doc
10576
                     WHERE
10577
                        c_id = $course_id AND
10578
                        path LIKE '".$uploadPath."/%/%htm%'
10579
                        $condition_session
10580
                     ORDER BY id ASC";
10581
10582
        $res_quiz = Database::query($sql_quiz);
10583
        $res_hot = Database::query($sql_hot);
10584
10585
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10586
10587
        // Create a search-box
10588
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10589
        $form->addHidden('action', 'add_item');
10590
        $form->addHidden('type', 'step');
10591
        $form->addHidden('lp_id', $this->lp_id);
10592
        $form->addHidden('lp_build_selected', '2');
10593
10594
        $form->addCourseHiddenParams();
10595
        $form->addText(
10596
            'keyword',
10597
            get_lang('Search'),
10598
            false,
10599
            [
10600
                'aria-label' => get_lang('Search'),
10601
            ]
10602
        );
10603
10604
        if (api_get_configuration_value('allow_exercise_categories')) {
10605
            $manager = new ExerciseCategoryManager();
10606
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10607
            if (!empty($options)) {
10608
                $form->addSelect(
10609
                    'category_id',
10610
                    get_lang('Category'),
10611
                    $options,
10612
                    ['placeholder' => get_lang('SelectAnOption')]
10613
                );
10614
            }
10615
        }
10616
10617
        $form->addButtonSearch(get_lang('Search'));
10618
        $return = $form->returnForm();
10619
10620
        $return .= '<ul class="lp_resource">';
10621
10622
        $return .= '<li class="lp_resource_element">';
10623
        $return .= Display::return_icon('new_exercice.png');
10624
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10625
            get_lang('NewExercise').'</a>';
10626
        $return .= '</li>';
10627
10628
        $previewIcon = Display::return_icon(
10629
            'preview_view.png',
10630
            get_lang('Preview')
10631
        );
10632
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10633
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10634
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10635
10636
        // Display hotpotatoes
10637
        while ($row_hot = Database::fetch_array($res_hot)) {
10638
            $link = Display::url(
10639
                $previewIcon,
10640
                $exerciseUrl.'&file='.$row_hot['path'],
10641
                ['target' => '_blank']
10642
            );
10643
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10644
            $return .= '<a class="moved" href="#">';
10645
            $return .= Display::return_icon(
10646
                'move_everywhere.png',
10647
                get_lang('Move'),
10648
                [],
10649
                ICON_SIZE_TINY
10650
            );
10651
            $return .= '</a> ';
10652
            $return .= Display::return_icon('hotpotatoes_s.png');
10653
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10654
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10655
            $return .= '</li>';
10656
        }
10657
10658
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10659
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10660
            $title = strip_tags(
10661
                api_html_entity_decode($row_quiz['title'])
10662
            );
10663
10664
            $visibility = api_get_item_visibility(
10665
                ['real_id' => $course_id],
10666
                TOOL_QUIZ,
10667
                $row_quiz['iid'],
10668
                $session_id
10669
            );
10670
10671
            $link = Display::url(
10672
                $previewIcon,
10673
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10674
                ['target' => '_blank']
10675
            );
10676
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10677
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10678
            $return .= $quizIcon;
10679
            $sessionStar = api_get_session_image(
10680
                $row_quiz['session_id'],
10681
                $userInfo['status']
10682
            );
10683
            $return .= Display::url(
10684
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10685
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10686
                [
10687
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10688
                ]
10689
            );
10690
            $return .= '</li>';
10691
        }
10692
10693
        $return .= '</ul>';
10694
10695
        return $return;
10696
    }
10697
10698
    /**
10699
     * Creates a list with all the links in it.
10700
     *
10701
     * @return string
10702
     */
10703
    public function get_links()
10704
    {
10705
        $selfUrl = api_get_self();
10706
        $courseIdReq = api_get_cidreq();
10707
        $course = api_get_course_info();
10708
        $userInfo = api_get_user_info();
10709
10710
        $course_id = $course['real_id'];
10711
        $tbl_link = Database::get_course_table(TABLE_LINK);
10712
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10713
        $moveEverywhereIcon = Display::return_icon(
10714
            'move_everywhere.png',
10715
            get_lang('Move'),
10716
            [],
10717
            ICON_SIZE_TINY
10718
        );
10719
10720
        $session_id = api_get_session_id();
10721
        $condition_session = api_get_session_condition(
10722
            $session_id,
10723
            true,
10724
            true,
10725
            'link.session_id'
10726
        );
10727
10728
        $sql = "SELECT
10729
                    link.id as link_id,
10730
                    link.title as link_title,
10731
                    link.session_id as link_session_id,
10732
                    link.category_id as category_id,
10733
                    link_category.category_title as category_title
10734
                FROM $tbl_link as link
10735
                LEFT JOIN $linkCategoryTable as link_category
10736
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10737
                WHERE link.c_id = $course_id $condition_session
10738
                ORDER BY link_category.category_title ASC, link.title ASC";
10739
        $result = Database::query($sql);
10740
        $categorizedLinks = [];
10741
        $categories = [];
10742
10743
        while ($link = Database::fetch_array($result)) {
10744
            if (!$link['category_id']) {
10745
                $link['category_title'] = get_lang('Uncategorized');
10746
            }
10747
            $categories[$link['category_id']] = $link['category_title'];
10748
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10749
        }
10750
10751
        $linksHtmlCode =
10752
            '<script>
10753
            function toggle_tool(tool, id) {
10754
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10755
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10756
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10757
                } else {
10758
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10759
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10760
                }
10761
            }
10762
        </script>
10763
10764
        <ul class="lp_resource">
10765
            <li class="lp_resource_element">
10766
                '.Display::return_icon('linksnew.gif').'
10767
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10768
                get_lang('LinkAdd').'
10769
                </a>
10770
            </li>';
10771
10772
        foreach ($categorizedLinks as $categoryId => $links) {
10773
            $linkNodes = null;
10774
            foreach ($links as $key => $linkInfo) {
10775
                $title = $linkInfo['link_title'];
10776
                $linkSessionId = $linkInfo['link_session_id'];
10777
10778
                $link = Display::url(
10779
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10780
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10781
                    ['target' => '_blank']
10782
                );
10783
10784
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10785
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10786
                    $linkNodes .=
10787
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10788
                        <a class="moved" href="#">'.
10789
                            $moveEverywhereIcon.
10790
                        '</a>
10791
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10792
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10793
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10794
                        Security::remove_XSS($title).$sessionStar.$link.
10795
                        '</a>
10796
                    </li>';
10797
                }
10798
            }
10799
            $linksHtmlCode .=
10800
                '<li>
10801
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10802
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10803
                    align="absbottom" />
10804
                </a>
10805
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10806
            </li>
10807
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10808
        }
10809
        $linksHtmlCode .= '</ul>';
10810
10811
        return $linksHtmlCode;
10812
    }
10813
10814
    /**
10815
     * Creates a list with all the student publications in it.
10816
     *
10817
     * @return string
10818
     */
10819
    public function get_student_publications()
10820
    {
10821
        $return = '<ul class="lp_resource">';
10822
        $return .= '<li class="lp_resource_element">';
10823
        $return .= Display::return_icon('works_new.gif');
10824
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10825
            get_lang('AddAssignmentPage').'</a>';
10826
        $return .= '</li>';
10827
10828
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10829
        $works = getWorkListTeacher(0, 100, null, null, null);
10830
        if (!empty($works)) {
10831
            foreach ($works as $work) {
10832
                $link = Display::url(
10833
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10834
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10835
                    ['target' => '_blank']
10836
                );
10837
10838
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10839
                $return .= '<a class="moved" href="#">';
10840
                $return .= Display::return_icon(
10841
                    'move_everywhere.png',
10842
                    get_lang('Move'),
10843
                    [],
10844
                    ICON_SIZE_TINY
10845
                );
10846
                $return .= '</a> ';
10847
10848
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10849
                $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.'">'.
10850
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10851
                </a>';
10852
10853
                $return .= '</li>';
10854
            }
10855
        }
10856
10857
        $return .= '</ul>';
10858
10859
        return $return;
10860
    }
10861
10862
    /**
10863
     * Creates a list with all the forums in it.
10864
     *
10865
     * @return string
10866
     */
10867
    public function get_forums()
10868
    {
10869
        require_once '../forum/forumfunction.inc.php';
10870
10871
        $forumCategories = get_forum_categories();
10872
        $forumsInNoCategory = get_forums_in_category(0);
10873
        if (!empty($forumsInNoCategory)) {
10874
            $forumCategories = array_merge(
10875
                $forumCategories,
10876
                [
10877
                    [
10878
                        'cat_id' => 0,
10879
                        'session_id' => 0,
10880
                        'visibility' => 1,
10881
                        'cat_comment' => null,
10882
                    ],
10883
                ]
10884
            );
10885
        }
10886
10887
        $forumList = get_forums();
10888
        $a_forums = [];
10889
        foreach ($forumCategories as $forumCategory) {
10890
            // The forums in this category.
10891
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10892
            if (!empty($forumsInCategory)) {
10893
                foreach ($forumList as $forum) {
10894
                    if (isset($forum['forum_category']) &&
10895
                        $forum['forum_category'] == $forumCategory['cat_id']
10896
                    ) {
10897
                        $a_forums[] = $forum;
10898
                    }
10899
                }
10900
            }
10901
        }
10902
10903
        $return = '<ul class="lp_resource">';
10904
10905
        // First add link
10906
        $return .= '<li class="lp_resource_element">';
10907
        $return .= Display::return_icon('new_forum.png');
10908
        $return .= Display::url(
10909
            get_lang('CreateANewForum'),
10910
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10911
                'action' => 'add',
10912
                'content' => 'forum',
10913
                'lp_id' => $this->lp_id,
10914
            ]),
10915
            ['title' => get_lang('CreateANewForum')]
10916
        );
10917
        $return .= '</li>';
10918
10919
        $return .= '<script>
10920
            function toggle_forum(forum_id) {
10921
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10922
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10923
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10924
                } else {
10925
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10926
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10927
                }
10928
            }
10929
        </script>';
10930
10931
        foreach ($a_forums as $forum) {
10932
            if (!empty($forum['forum_id'])) {
10933
                $link = Display::url(
10934
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10935
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10936
                    ['target' => '_blank']
10937
                );
10938
10939
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10940
                $return .= '<a class="moved" href="#">';
10941
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10942
                $return .= ' </a>';
10943
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10944
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10945
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10946
                            </a>
10947
                            <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">'.
10948
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10949
10950
                $return .= '</li>';
10951
10952
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10953
                $a_threads = get_threads($forum['forum_id']);
10954
                if (is_array($a_threads)) {
10955
                    foreach ($a_threads as $thread) {
10956
                        $link = Display::url(
10957
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10958
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10959
                            ['target' => '_blank']
10960
                        );
10961
10962
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10963
                        $return .= '&nbsp;<a class="moved" href="#">';
10964
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10965
                        $return .= ' </a>';
10966
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10967
                        $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.'">'.
10968
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10969
                        $return .= '</li>';
10970
                    }
10971
                }
10972
                $return .= '</div>';
10973
            }
10974
        }
10975
        $return .= '</ul>';
10976
10977
        return $return;
10978
    }
10979
10980
    /**
10981
     * // TODO: The output encoding should be equal to the system encoding.
10982
     *
10983
     * Exports the learning path as a SCORM package. This is the main function that
10984
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10985
     * whole thing and returns the zip.
10986
     *
10987
     * This method needs to be called in PHP5, as it will fail with non-adequate
10988
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10989
     * you need to call it on a learnpath object.
10990
     *
10991
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10992
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10993
     * path has been modified, it should use the generic method here below.
10994
     *
10995
     * @return string Returns the zip package string, or null if error
10996
     */
10997
    public function scormExport()
10998
    {
10999
        api_set_more_memory_and_time_limits();
11000
11001
        $_course = api_get_course_info();
11002
        $course_id = $_course['real_id'];
11003
        // Create the zip handler (this will remain available throughout the method).
11004
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11005
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11006
        $temp_dir_short = uniqid('scorm_export', true);
11007
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11008
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11009
        $zip_folder = new PclZip($temp_zip_file);
11010
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11011
        $root_path = $main_path = api_get_path(SYS_PATH);
11012
        $files_cleanup = [];
11013
11014
        // Place to temporarily stash the zip file.
11015
        // create the temp dir if it doesn't exist
11016
        // or do a cleanup before creating the zip file.
11017
        if (!is_dir($temp_zip_dir)) {
11018
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11019
        } else {
11020
            // Cleanup: Check the temp dir for old files and delete them.
11021
            $handle = opendir($temp_zip_dir);
11022
            while (false !== ($file = readdir($handle))) {
11023
                if ($file != '.' && $file != '..') {
11024
                    unlink("$temp_zip_dir/$file");
11025
                }
11026
            }
11027
            closedir($handle);
11028
        }
11029
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11030
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11031
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11032
        ) {
11033
            // Remove the possible . at the end of the path.
11034
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11035
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11036
            mkdir(
11037
                $dest_path_to_scorm_folder,
11038
                api_get_permissions_for_new_directories(),
11039
                true
11040
            );
11041
            copyr(
11042
                $current_course_path.'/scorm/'.$this->path,
11043
                $dest_path_to_scorm_folder,
11044
                ['imsmanifest'],
11045
                $zip_files
11046
            );
11047
        }
11048
11049
        // Build a dummy imsmanifest structure.
11050
        // Do not add to the zip yet (we still need it).
11051
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11052
        // Aggregation Model official document, section "2.3 Content Packaging".
11053
        // We are going to build a UTF-8 encoded manifest.
11054
        // Later we will recode it to the desired (and supported) encoding.
11055
        $xmldoc = new DOMDocument('1.0');
11056
        $root = $xmldoc->createElement('manifest');
11057
        $root->setAttribute('identifier', 'SingleCourseManifest');
11058
        $root->setAttribute('version', '1.1');
11059
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11060
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11061
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11062
        $root->setAttribute(
11063
            'xsi:schemaLocation',
11064
            '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'
11065
        );
11066
        // Build mandatory sub-root container elements.
11067
        $metadata = $xmldoc->createElement('metadata');
11068
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11069
        $metadata->appendChild($md_schema);
11070
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11071
        $metadata->appendChild($md_schemaversion);
11072
        $root->appendChild($metadata);
11073
11074
        $organizations = $xmldoc->createElement('organizations');
11075
        $resources = $xmldoc->createElement('resources');
11076
11077
        // Build the only organization we will use in building our learnpaths.
11078
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11079
        $organization = $xmldoc->createElement('organization');
11080
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11081
        // To set the title of the SCORM entity (=organization), we take the name given
11082
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11083
        // learning path charset) as it is the encoding that defines how it is stored
11084
        // in the database. Then we convert it to HTML entities again as the "&" character
11085
        // alone is not authorized in XML (must be &amp;).
11086
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11087
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11088
        $organization->appendChild($org_title);
11089
        $folder_name = 'document';
11090
11091
        // Removes the learning_path/scorm_folder path when exporting see #4841
11092
        $path_to_remove = '';
11093
        $path_to_replace = '';
11094
        $result = $this->generate_lp_folder($_course);
11095
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11096
            $path_to_remove = 'document'.$result['dir'];
11097
            $path_to_replace = $folder_name.'/';
11098
        }
11099
11100
        // Fixes chamilo scorm exports
11101
        if ($this->ref === 'chamilo_scorm_export') {
11102
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11103
        }
11104
11105
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11106
        $link_updates = [];
11107
        $links_to_create = [];
11108
        foreach ($this->ordered_items as $index => $itemId) {
11109
            /** @var learnpathItem $item */
11110
            $item = $this->items[$itemId];
11111
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11112
                // Get included documents from this item.
11113
                if ($item->type === 'sco') {
11114
                    $inc_docs = $item->get_resources_from_source(
11115
                        null,
11116
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11117
                    );
11118
                } else {
11119
                    $inc_docs = $item->get_resources_from_source();
11120
                }
11121
11122
                // Give a child element <item> to the <organization> element.
11123
                $my_item_id = $item->get_id();
11124
                $my_item = $xmldoc->createElement('item');
11125
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11126
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11127
                $my_item->setAttribute('isvisible', 'true');
11128
                // Give a child element <title> to the <item> element.
11129
                $my_title = $xmldoc->createElement(
11130
                    'title',
11131
                    htmlspecialchars(
11132
                        api_utf8_encode($item->get_title()),
11133
                        ENT_QUOTES,
11134
                        'UTF-8'
11135
                    )
11136
                );
11137
                $my_item->appendChild($my_title);
11138
                // Give a child element <adlcp:prerequisites> to the <item> element.
11139
                $my_prereqs = $xmldoc->createElement(
11140
                    'adlcp:prerequisites',
11141
                    $this->get_scorm_prereq_string($my_item_id)
11142
                );
11143
                $my_prereqs->setAttribute('type', 'aicc_script');
11144
                $my_item->appendChild($my_prereqs);
11145
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11146
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11147
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11148
                //$xmldoc->createElement('adlcp:timelimitaction','');
11149
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11150
                //$xmldoc->createElement('adlcp:datafromlms','');
11151
                // Give a child element <adlcp:masteryscore> to the <item> element.
11152
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11153
                $my_item->appendChild($my_masteryscore);
11154
11155
                // Attach this item to the organization element or hits parent if there is one.
11156
                if (!empty($item->parent) && $item->parent != 0) {
11157
                    $children = $organization->childNodes;
11158
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11159
                    if (is_object($possible_parent)) {
11160
                        $possible_parent->appendChild($my_item);
11161
                    } else {
11162
                        if ($this->debug > 0) {
11163
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11164
                        }
11165
                    }
11166
                } else {
11167
                    if ($this->debug > 0) {
11168
                        error_log('No parent');
11169
                    }
11170
                    $organization->appendChild($my_item);
11171
                }
11172
11173
                // Get the path of the file(s) from the course directory root.
11174
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11175
                $my_xml_file_path = $my_file_path;
11176
                if (!empty($path_to_remove)) {
11177
                    // From docs
11178
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11179
11180
                    // From quiz
11181
                    if ($this->ref === 'chamilo_scorm_export') {
11182
                        $path_to_remove = 'scorm/'.$this->path.'/';
11183
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11184
                    }
11185
                }
11186
11187
                $my_sub_dir = dirname($my_file_path);
11188
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11189
                $my_xml_sub_dir = $my_sub_dir;
11190
                // Give a <resource> child to the <resources> element
11191
                $my_resource = $xmldoc->createElement('resource');
11192
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11193
                $my_resource->setAttribute('type', 'webcontent');
11194
                $my_resource->setAttribute('href', $my_xml_file_path);
11195
                // adlcp:scormtype can be either 'sco' or 'asset'.
11196
                if ($item->type === 'sco') {
11197
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11198
                } else {
11199
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11200
                }
11201
                // xml:base is the base directory to find the files declared in this resource.
11202
                $my_resource->setAttribute('xml:base', '');
11203
                // Give a <file> child to the <resource> element.
11204
                $my_file = $xmldoc->createElement('file');
11205
                $my_file->setAttribute('href', $my_xml_file_path);
11206
                $my_resource->appendChild($my_file);
11207
11208
                // Dependency to other files - not yet supported.
11209
                $i = 1;
11210
                if ($inc_docs) {
11211
                    foreach ($inc_docs as $doc_info) {
11212
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11213
                            continue;
11214
                        }
11215
                        $my_dep = $xmldoc->createElement('resource');
11216
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11217
                        $my_dep->setAttribute('identifier', $res_id);
11218
                        $my_dep->setAttribute('type', 'webcontent');
11219
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11220
                        $my_dep_file = $xmldoc->createElement('file');
11221
                        // Check type of URL.
11222
                        if ($doc_info[1] == 'remote') {
11223
                            // Remote file. Save url as is.
11224
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11225
                            $my_dep->setAttribute('xml:base', '');
11226
                        } elseif ($doc_info[1] === 'local') {
11227
                            switch ($doc_info[2]) {
11228
                                case 'url':
11229
                                    // Local URL - save path as url for now, don't zip file.
11230
                                    $abs_path = api_get_path(SYS_PATH).
11231
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11232
                                    $current_dir = dirname($abs_path);
11233
                                    $current_dir = str_replace('\\', '/', $current_dir);
11234
                                    $file_path = realpath($abs_path);
11235
                                    $file_path = str_replace('\\', '/', $file_path);
11236
                                    $my_dep_file->setAttribute('href', $file_path);
11237
                                    $my_dep->setAttribute('xml:base', '');
11238
                                    if (strstr($file_path, $main_path) !== false) {
11239
                                        // The calculated real path is really inside Chamilo's root path.
11240
                                        // Reduce file path to what's under the DocumentRoot.
11241
                                        $replace = $file_path;
11242
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11243
                                        $destinationFile = $file_path;
11244
11245
                                        if (strstr($file_path, 'upload/users') !== false) {
11246
                                            $pos = strpos($file_path, 'my_files/');
11247
                                            if ($pos !== false) {
11248
                                                $onlyDirectory = str_replace(
11249
                                                    'upload/users/',
11250
                                                    '',
11251
                                                    substr($file_path, $pos, strlen($file_path))
11252
                                                );
11253
                                            }
11254
                                            $replace = $onlyDirectory;
11255
                                            $destinationFile = $replace;
11256
                                        }
11257
                                        $zip_files_abs[] = $file_path;
11258
                                        $link_updates[$my_file_path][] = [
11259
                                            'orig' => $doc_info[0],
11260
                                            'dest' => $destinationFile,
11261
                                            'replace' => $replace,
11262
                                        ];
11263
                                        $my_dep_file->setAttribute('href', $file_path);
11264
                                        $my_dep->setAttribute('xml:base', '');
11265
                                    } elseif (empty($file_path)) {
11266
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11267
                                        $file_path = str_replace('//', '/', $file_path);
11268
                                        if (file_exists($file_path)) {
11269
                                            // We get the relative path.
11270
                                            $file_path = substr($file_path, strlen($current_dir));
11271
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11272
                                            $link_updates[$my_file_path][] = [
11273
                                                'orig' => $doc_info[0],
11274
                                                'dest' => $file_path,
11275
                                            ];
11276
                                            $my_dep_file->setAttribute('href', $file_path);
11277
                                            $my_dep->setAttribute('xml:base', '');
11278
                                        }
11279
                                    }
11280
                                    break;
11281
                                case 'abs':
11282
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11283
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11284
                                    $my_dep->setAttribute('xml:base', '');
11285
11286
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11287
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11288
                                    $abs_img_path_without_subdir = $doc_info[0];
11289
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11290
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11291
                                    if ($pos === 0) {
11292
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11293
                                    }
11294
11295
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11296
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11297
11298
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11299
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11300
                                    // Check if the current document is in that path.
11301
                                    if (strstr($file_path, $cur_path) !== false) {
11302
                                        $destinationFile = substr($file_path, strlen($cur_path));
11303
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11304
11305
                                        $fileToTest = $cur_path.$my_file_path;
11306
                                        if (!empty($path_to_remove)) {
11307
                                            $fileToTest = str_replace(
11308
                                                $path_to_remove.'/',
11309
                                                $path_to_replace,
11310
                                                $cur_path.$my_file_path
11311
                                            );
11312
                                        }
11313
11314
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11315
11316
                                        // Put the current document in the zip (this array is the array
11317
                                        // that will manage documents already in the course folder - relative).
11318
                                        $zip_files[] = $filePathNoCoursePart;
11319
                                        // Update the links to the current document in the
11320
                                        // containing document (make them relative).
11321
                                        $link_updates[$my_file_path][] = [
11322
                                            'orig' => $doc_info[0],
11323
                                            'dest' => $destinationFile,
11324
                                            'replace' => $relative_path,
11325
                                        ];
11326
11327
                                        $my_dep_file->setAttribute('href', $file_path);
11328
                                        $my_dep->setAttribute('xml:base', '');
11329
                                    } elseif (strstr($file_path, $main_path) !== false) {
11330
                                        // The calculated real path is really inside Chamilo's root path.
11331
                                        // Reduce file path to what's under the DocumentRoot.
11332
                                        $file_path = substr($file_path, strlen($root_path));
11333
                                        $zip_files_abs[] = $file_path;
11334
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11335
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11336
                                        $my_dep->setAttribute('xml:base', '');
11337
                                    } elseif (empty($file_path)) {
11338
                                        // Probably this is an image inside "/main" directory
11339
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11340
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11341
11342
                                        if (file_exists($file_path)) {
11343
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11344
                                                // We get the relative path.
11345
                                                $pos = strpos($file_path, 'main/default_course_document/');
11346
                                                if ($pos !== false) {
11347
                                                    $onlyDirectory = str_replace(
11348
                                                        'main/default_course_document/',
11349
                                                        '',
11350
                                                        substr($file_path, $pos, strlen($file_path))
11351
                                                    );
11352
                                                }
11353
11354
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11355
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11356
                                                $zip_files_abs[] = $fileAbs;
11357
                                                $link_updates[$my_file_path][] = [
11358
                                                    'orig' => $doc_info[0],
11359
                                                    'dest' => $destinationFile,
11360
                                                ];
11361
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11362
                                                $my_dep->setAttribute('xml:base', '');
11363
                                            }
11364
                                        }
11365
                                    }
11366
                                    break;
11367
                                case 'rel':
11368
                                    // Path relative to the current document.
11369
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11370
                                    if (substr($doc_info[0], 0, 2) === '..') {
11371
                                        // Relative path going up.
11372
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11373
                                        $current_dir = str_replace('\\', '/', $current_dir);
11374
                                        $file_path = realpath($current_dir.$doc_info[0]);
11375
                                        $file_path = str_replace('\\', '/', $file_path);
11376
                                        if (strstr($file_path, $main_path) !== false) {
11377
                                            // The calculated real path is really inside Chamilo's root path.
11378
                                            // Reduce file path to what's under the DocumentRoot.
11379
                                            $file_path = substr($file_path, strlen($root_path));
11380
                                            $zip_files_abs[] = $file_path;
11381
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11382
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11383
                                            $my_dep->setAttribute('xml:base', '');
11384
                                        }
11385
                                    } else {
11386
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11387
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11388
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11389
                                    }
11390
                                    break;
11391
                                default:
11392
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11393
                                    $my_dep->setAttribute('xml:base', '');
11394
                                    break;
11395
                            }
11396
                        }
11397
                        $my_dep->appendChild($my_dep_file);
11398
                        $resources->appendChild($my_dep);
11399
                        $dependency = $xmldoc->createElement('dependency');
11400
                        $dependency->setAttribute('identifierref', $res_id);
11401
                        $my_resource->appendChild($dependency);
11402
                        $i++;
11403
                    }
11404
                }
11405
                $resources->appendChild($my_resource);
11406
                $zip_files[] = $my_file_path;
11407
            } else {
11408
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11409
                switch ($item->type) {
11410
                    case TOOL_LINK:
11411
                        $my_item = $xmldoc->createElement('item');
11412
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11413
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11414
                        $my_item->setAttribute('isvisible', 'true');
11415
                        // Give a child element <title> to the <item> element.
11416
                        $my_title = $xmldoc->createElement(
11417
                            'title',
11418
                            htmlspecialchars(
11419
                                api_utf8_encode($item->get_title()),
11420
                                ENT_QUOTES,
11421
                                'UTF-8'
11422
                            )
11423
                        );
11424
                        $my_item->appendChild($my_title);
11425
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11426
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11427
                        $my_prereqs->setAttribute('type', 'aicc_script');
11428
                        $my_item->appendChild($my_prereqs);
11429
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11430
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11431
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11432
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11433
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11434
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11435
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11436
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11437
                        $my_item->appendChild($my_masteryscore);
11438
11439
                        // Attach this item to the organization element or its parent if there is one.
11440
                        if (!empty($item->parent) && $item->parent != 0) {
11441
                            $children = $organization->childNodes;
11442
                            for ($i = 0; $i < $children->length; $i++) {
11443
                                $item_temp = $children->item($i);
11444
                                if ($item_temp->nodeName == 'item') {
11445
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11446
                                        $item_temp->appendChild($my_item);
11447
                                    }
11448
                                }
11449
                            }
11450
                        } else {
11451
                            $organization->appendChild($my_item);
11452
                        }
11453
11454
                        $my_file_path = 'link_'.$item->get_id().'.html';
11455
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11456
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11457
                        $rs = Database::query($sql);
11458
                        if ($link = Database::fetch_array($rs)) {
11459
                            $url = $link['url'];
11460
                            $title = stripslashes($link['title']);
11461
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11462
                            $my_xml_file_path = $my_file_path;
11463
                            $my_sub_dir = dirname($my_file_path);
11464
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11465
                            $my_xml_sub_dir = $my_sub_dir;
11466
                            // Give a <resource> child to the <resources> element.
11467
                            $my_resource = $xmldoc->createElement('resource');
11468
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11469
                            $my_resource->setAttribute('type', 'webcontent');
11470
                            $my_resource->setAttribute('href', $my_xml_file_path);
11471
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11472
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11473
                            // xml:base is the base directory to find the files declared in this resource.
11474
                            $my_resource->setAttribute('xml:base', '');
11475
                            // give a <file> child to the <resource> element.
11476
                            $my_file = $xmldoc->createElement('file');
11477
                            $my_file->setAttribute('href', $my_xml_file_path);
11478
                            $my_resource->appendChild($my_file);
11479
                            $resources->appendChild($my_resource);
11480
                        }
11481
                        break;
11482
                    case TOOL_QUIZ:
11483
                        $exe_id = $item->path;
11484
                        // Should be using ref when everything will be cleaned up in this regard.
11485
                        $exe = new Exercise();
11486
                        $exe->read($exe_id);
11487
                        $my_item = $xmldoc->createElement('item');
11488
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11489
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11490
                        $my_item->setAttribute('isvisible', 'true');
11491
                        // Give a child element <title> to the <item> element.
11492
                        $my_title = $xmldoc->createElement(
11493
                            'title',
11494
                            htmlspecialchars(
11495
                                api_utf8_encode($item->get_title()),
11496
                                ENT_QUOTES,
11497
                                'UTF-8'
11498
                            )
11499
                        );
11500
                        $my_item->appendChild($my_title);
11501
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11502
                        $my_item->appendChild($my_max_score);
11503
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11504
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11505
                        $my_prereqs->setAttribute('type', 'aicc_script');
11506
                        $my_item->appendChild($my_prereqs);
11507
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11508
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11509
                        $my_item->appendChild($my_masteryscore);
11510
11511
                        // Attach this item to the organization element or hits parent if there is one.
11512
                        if (!empty($item->parent) && $item->parent != 0) {
11513
                            $children = $organization->childNodes;
11514
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11515
                            if ($possible_parent) {
11516
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11517
                                    $possible_parent->appendChild($my_item);
11518
                                }
11519
                            }
11520
                        } else {
11521
                            $organization->appendChild($my_item);
11522
                        }
11523
11524
                        // Get the path of the file(s) from the course directory root
11525
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11526
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11527
                        // Write the contents of the exported exercise into a (big) html file
11528
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11529
                        $scormExercise = new ScormExercise($exe, true);
11530
                        $contents = $scormExercise->export();
11531
11532
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11533
                        $res = file_put_contents($tmp_file_path, $contents);
11534
                        if ($res === false) {
11535
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11536
                        }
11537
                        $files_cleanup[] = $tmp_file_path;
11538
                        $my_xml_file_path = $my_file_path;
11539
                        $my_sub_dir = dirname($my_file_path);
11540
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11541
                        $my_xml_sub_dir = $my_sub_dir;
11542
                        // Give a <resource> child to the <resources> element.
11543
                        $my_resource = $xmldoc->createElement('resource');
11544
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11545
                        $my_resource->setAttribute('type', 'webcontent');
11546
                        $my_resource->setAttribute('href', $my_xml_file_path);
11547
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11548
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11549
                        // xml:base is the base directory to find the files declared in this resource.
11550
                        $my_resource->setAttribute('xml:base', '');
11551
                        // Give a <file> child to the <resource> element.
11552
                        $my_file = $xmldoc->createElement('file');
11553
                        $my_file->setAttribute('href', $my_xml_file_path);
11554
                        $my_resource->appendChild($my_file);
11555
11556
                        // Get included docs.
11557
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11558
11559
                        // Dependency to other files - not yet supported.
11560
                        $i = 1;
11561
                        foreach ($inc_docs as $doc_info) {
11562
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11563
                                continue;
11564
                            }
11565
                            $my_dep = $xmldoc->createElement('resource');
11566
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11567
                            $my_dep->setAttribute('identifier', $res_id);
11568
                            $my_dep->setAttribute('type', 'webcontent');
11569
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11570
                            $my_dep_file = $xmldoc->createElement('file');
11571
                            // Check type of URL.
11572
                            if ($doc_info[1] == 'remote') {
11573
                                // Remote file. Save url as is.
11574
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11575
                                $my_dep->setAttribute('xml:base', '');
11576
                            } elseif ($doc_info[1] == 'local') {
11577
                                switch ($doc_info[2]) {
11578
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11579
                                        // Save file but as local file (retrieve from URL).
11580
                                        $abs_path = api_get_path(SYS_PATH).
11581
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11582
                                        $current_dir = dirname($abs_path);
11583
                                        $current_dir = str_replace('\\', '/', $current_dir);
11584
                                        $file_path = realpath($abs_path);
11585
                                        $file_path = str_replace('\\', '/', $file_path);
11586
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11587
                                        $my_dep->setAttribute('xml:base', '');
11588
                                        if (strstr($file_path, $main_path) !== false) {
11589
                                            // The calculated real path is really inside the chamilo root path.
11590
                                            // Reduce file path to what's under the DocumentRoot.
11591
                                            $file_path = substr($file_path, strlen($root_path));
11592
                                            $zip_files_abs[] = $file_path;
11593
                                            $link_updates[$my_file_path][] = [
11594
                                                'orig' => $doc_info[0],
11595
                                                'dest' => 'document/'.$file_path,
11596
                                            ];
11597
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11598
                                            $my_dep->setAttribute('xml:base', '');
11599
                                        } elseif (empty($file_path)) {
11600
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11601
                                            $file_path = str_replace('//', '/', $file_path);
11602
                                            if (file_exists($file_path)) {
11603
                                                $file_path = substr($file_path, strlen($current_dir));
11604
                                                // We get the relative path.
11605
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11606
                                                $link_updates[$my_file_path][] = [
11607
                                                    'orig' => $doc_info[0],
11608
                                                    'dest' => 'document/'.$file_path,
11609
                                                ];
11610
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11611
                                                $my_dep->setAttribute('xml:base', '');
11612
                                            }
11613
                                        }
11614
                                        break;
11615
                                    case 'abs':
11616
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11617
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11618
                                        $current_dir = str_replace('\\', '/', $current_dir);
11619
                                        $file_path = realpath($doc_info[0]);
11620
                                        $file_path = str_replace('\\', '/', $file_path);
11621
                                        $my_dep_file->setAttribute('href', $file_path);
11622
                                        $my_dep->setAttribute('xml:base', '');
11623
11624
                                        if (strstr($file_path, $main_path) !== false) {
11625
                                            // The calculated real path is really inside the chamilo root path.
11626
                                            // Reduce file path to what's under the DocumentRoot.
11627
                                            $file_path = substr($file_path, strlen($root_path));
11628
                                            $zip_files_abs[] = $file_path;
11629
                                            $link_updates[$my_file_path][] = [
11630
                                                'orig' => $doc_info[0],
11631
                                                'dest' => $file_path,
11632
                                            ];
11633
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11634
                                            $my_dep->setAttribute('xml:base', '');
11635
                                        } elseif (empty($file_path)) {
11636
                                            $docSysPartPath = str_replace(
11637
                                                api_get_path(REL_COURSE_PATH),
11638
                                                '',
11639
                                                $doc_info[0]
11640
                                            );
11641
11642
                                            $docSysPartPathNoCourseCode = str_replace(
11643
                                                $_course['directory'].'/',
11644
                                                '',
11645
                                                $docSysPartPath
11646
                                            );
11647
11648
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11649
                                            if (file_exists($docSysPath)) {
11650
                                                $file_path = $docSysPartPathNoCourseCode;
11651
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11652
                                                $link_updates[$my_file_path][] = [
11653
                                                    'orig' => $doc_info[0],
11654
                                                    'dest' => $file_path,
11655
                                                ];
11656
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11657
                                                $my_dep->setAttribute('xml:base', '');
11658
                                            }
11659
                                        }
11660
                                        break;
11661
                                    case 'rel':
11662
                                        // Path relative to the current document. Save xml:base as current document's
11663
                                        // directory and save file in zip as subdir.file_path
11664
                                        if (substr($doc_info[0], 0, 2) === '..') {
11665
                                            // Relative path going up.
11666
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11667
                                            $current_dir = str_replace('\\', '/', $current_dir);
11668
                                            $file_path = realpath($current_dir.$doc_info[0]);
11669
                                            $file_path = str_replace('\\', '/', $file_path);
11670
                                            if (strstr($file_path, $main_path) !== false) {
11671
                                                // The calculated real path is really inside Chamilo's root path.
11672
                                                // Reduce file path to what's under the DocumentRoot.
11673
11674
                                                $file_path = substr($file_path, strlen($root_path));
11675
                                                $file_path_dest = $file_path;
11676
11677
                                                // File path is courses/CHAMILO/document/....
11678
                                                $info_file_path = explode('/', $file_path);
11679
                                                if ($info_file_path[0] == 'courses') {
11680
                                                    // Add character "/" in file path.
11681
                                                    $file_path_dest = 'document/'.$file_path;
11682
                                                }
11683
                                                $zip_files_abs[] = $file_path;
11684
11685
                                                $link_updates[$my_file_path][] = [
11686
                                                    'orig' => $doc_info[0],
11687
                                                    'dest' => $file_path_dest,
11688
                                                ];
11689
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11690
                                                $my_dep->setAttribute('xml:base', '');
11691
                                            }
11692
                                        } else {
11693
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11694
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11695
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11696
                                        }
11697
                                        break;
11698
                                    default:
11699
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11700
                                        $my_dep->setAttribute('xml:base', '');
11701
                                        break;
11702
                                }
11703
                            }
11704
                            $my_dep->appendChild($my_dep_file);
11705
                            $resources->appendChild($my_dep);
11706
                            $dependency = $xmldoc->createElement('dependency');
11707
                            $dependency->setAttribute('identifierref', $res_id);
11708
                            $my_resource->appendChild($dependency);
11709
                            $i++;
11710
                        }
11711
                        $resources->appendChild($my_resource);
11712
                        $zip_files[] = $my_file_path;
11713
                        break;
11714
                    default:
11715
                        // Get the path of the file(s) from the course directory root
11716
                        $my_file_path = 'non_exportable.html';
11717
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11718
                        $my_xml_file_path = $my_file_path;
11719
                        $my_sub_dir = dirname($my_file_path);
11720
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11721
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11722
                        $my_xml_sub_dir = $my_sub_dir;
11723
                        // Give a <resource> child to the <resources> element.
11724
                        $my_resource = $xmldoc->createElement('resource');
11725
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11726
                        $my_resource->setAttribute('type', 'webcontent');
11727
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11728
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11729
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11730
                        // xml:base is the base directory to find the files declared in this resource.
11731
                        $my_resource->setAttribute('xml:base', '');
11732
                        // Give a <file> child to the <resource> element.
11733
                        $my_file = $xmldoc->createElement('file');
11734
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11735
                        $my_resource->appendChild($my_file);
11736
                        $resources->appendChild($my_resource);
11737
                        break;
11738
                }
11739
            }
11740
        }
11741
        $organizations->appendChild($organization);
11742
        $root->appendChild($organizations);
11743
        $root->appendChild($resources);
11744
        $xmldoc->appendChild($root);
11745
11746
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11747
11748
        // then add the file to the zip, then destroy the file (this is done automatically).
11749
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11750
        foreach ($zip_files as $file_path) {
11751
            if (empty($file_path)) {
11752
                continue;
11753
            }
11754
11755
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11756
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11757
11758
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11759
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11760
            }
11761
11762
            $this->create_path($dest_file);
11763
            @copy($filePath, $dest_file);
11764
11765
            // Check if the file needs a link update.
11766
            if (in_array($file_path, array_keys($link_updates))) {
11767
                $string = file_get_contents($dest_file);
11768
                unlink($dest_file);
11769
                foreach ($link_updates[$file_path] as $old_new) {
11770
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11771
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11772
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11773
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11774
                    if (substr($old_new['dest'], -3) === 'flv' &&
11775
                        substr($old_new['dest'], 0, 5) === 'main/'
11776
                    ) {
11777
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11778
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11779
                        substr($old_new['dest'], 0, 6) === 'video/'
11780
                    ) {
11781
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11782
                    }
11783
11784
                    // Fix to avoid problems with default_course_document
11785
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11786
                        $newDestination = $old_new['dest'];
11787
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11788
                            $newDestination = $old_new['replace'];
11789
                        }
11790
                    } else {
11791
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11792
                    }
11793
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11794
11795
                    // Add files inside the HTMLs
11796
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11797
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11798
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11799
                        copy(
11800
                            $sys_course_path.$new_path,
11801
                            $destinationFile
11802
                        );
11803
                    }
11804
                }
11805
                file_put_contents($dest_file, $string);
11806
            }
11807
11808
            if (file_exists($filePath) && $copyAll) {
11809
                $extension = $this->get_extension($filePath);
11810
                if (in_array($extension, ['html', 'html'])) {
11811
                    $containerOrigin = dirname($filePath);
11812
                    $containerDestination = dirname($dest_file);
11813
11814
                    $finder = new Finder();
11815
                    $finder->files()->in($containerOrigin)
11816
                        ->notName('*_DELETED_*')
11817
                        ->exclude('share_folder')
11818
                        ->exclude('chat_files')
11819
                        ->exclude('certificates')
11820
                    ;
11821
11822
                    if (is_dir($containerOrigin) &&
11823
                        is_dir($containerDestination)
11824
                    ) {
11825
                        $fs = new Filesystem();
11826
                        $fs->mirror(
11827
                            $containerOrigin,
11828
                            $containerDestination,
11829
                            $finder
11830
                        );
11831
                    }
11832
                }
11833
            }
11834
        }
11835
11836
        foreach ($zip_files_abs as $file_path) {
11837
            if (empty($file_path)) {
11838
                continue;
11839
            }
11840
11841
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11842
                continue;
11843
            }
11844
11845
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11846
            if (strstr($file_path, 'upload/users') !== false) {
11847
                $pos = strpos($file_path, 'my_files/');
11848
                if ($pos !== false) {
11849
                    $onlyDirectory = str_replace(
11850
                        'upload/users/',
11851
                        '',
11852
                        substr($file_path, $pos, strlen($file_path))
11853
                    );
11854
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11855
                }
11856
            }
11857
11858
            if (strstr($file_path, 'default_course_document/') !== false) {
11859
                $replace = str_replace('/main', '', $file_path);
11860
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11861
            }
11862
11863
            if (empty($dest_file)) {
11864
                continue;
11865
            }
11866
11867
            $this->create_path($dest_file);
11868
            copy($main_path.$file_path, $dest_file);
11869
            // Check if the file needs a link update.
11870
            if (in_array($file_path, array_keys($link_updates))) {
11871
                $string = file_get_contents($dest_file);
11872
                unlink($dest_file);
11873
                foreach ($link_updates[$file_path] as $old_new) {
11874
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11875
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11876
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11877
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11878
                    if (substr($old_new['dest'], -3) == 'flv' &&
11879
                        substr($old_new['dest'], 0, 5) == 'main/'
11880
                    ) {
11881
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11882
                    }
11883
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11884
                }
11885
                file_put_contents($dest_file, $string);
11886
            }
11887
        }
11888
11889
        if (is_array($links_to_create)) {
11890
            foreach ($links_to_create as $file => $link) {
11891
                $content = '<!DOCTYPE html><head>
11892
                            <meta charset="'.api_get_language_isocode().'" />
11893
                            <title>'.$link['title'].'</title>
11894
                            </head>
11895
                            <body dir="'.api_get_text_direction().'">
11896
                            <div style="text-align:center">
11897
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11898
                            </body>
11899
                            </html>';
11900
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11901
            }
11902
        }
11903
11904
        // Add non exportable message explanation.
11905
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11906
        $file_content = '<!DOCTYPE html><head>
11907
                        <meta charset="'.api_get_language_isocode().'" />
11908
                        <title>'.$lang_not_exportable.'</title>
11909
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11910
                        </head>
11911
                        <body dir="'.api_get_text_direction().'">';
11912
        $file_content .=
11913
            <<<EOD
11914
                    <style>
11915
            .error-message {
11916
                font-family: arial, verdana, helvetica, sans-serif;
11917
                border-width: 1px;
11918
                border-style: solid;
11919
                left: 50%;
11920
                margin: 10px auto;
11921
                min-height: 30px;
11922
                padding: 5px;
11923
                right: 50%;
11924
                width: 500px;
11925
                background-color: #FFD1D1;
11926
                border-color: #FF0000;
11927
                color: #000;
11928
            }
11929
        </style>
11930
    <body>
11931
        <div class="error-message">
11932
            $lang_not_exportable
11933
        </div>
11934
    </body>
11935
</html>
11936
EOD;
11937
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11938
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11939
        }
11940
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11941
11942
        // Add the extra files that go along with a SCORM package.
11943
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11944
11945
        $fs = new Filesystem();
11946
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11947
11948
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11949
        $manifest = @$xmldoc->saveXML();
11950
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11951
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11952
        $zip_folder->add(
11953
            $archivePath.'/'.$temp_dir_short,
11954
            PCLZIP_OPT_REMOVE_PATH,
11955
            $archivePath.'/'.$temp_dir_short.'/'
11956
        );
11957
11958
        // Clean possible temporary files.
11959
        foreach ($files_cleanup as $file) {
11960
            $res = unlink($file);
11961
            if ($res === false) {
11962
                error_log(
11963
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11964
                    0
11965
                );
11966
            }
11967
        }
11968
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11969
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11970
    }
11971
11972
    /**
11973
     * @param int $lp_id
11974
     *
11975
     * @return bool
11976
     */
11977
    public function scorm_export_to_pdf($lp_id)
11978
    {
11979
        $lp_id = (int) $lp_id;
11980
        $files_to_export = [];
11981
11982
        $sessionId = api_get_session_id();
11983
        $course_data = api_get_course_info($this->cc);
11984
11985
        if (!empty($course_data)) {
11986
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11987
            $list = self::get_flat_ordered_items_list($lp_id);
11988
            if (!empty($list)) {
11989
                foreach ($list as $item_id) {
11990
                    $item = $this->items[$item_id];
11991
                    switch ($item->type) {
11992
                        case 'document':
11993
                            // Getting documents from a LP with chamilo documents
11994
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11995
                            // Try loading document from the base course.
11996
                            if (empty($file_data) && !empty($sessionId)) {
11997
                                $file_data = DocumentManager::get_document_data_by_id(
11998
                                    $item->path,
11999
                                    $this->cc,
12000
                                    false,
12001
                                    0
12002
                                );
12003
                            }
12004
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12005
                            if (file_exists($file_path)) {
12006
                                $files_to_export[] = [
12007
                                    'title' => $item->get_title(),
12008
                                    'path' => $file_path,
12009
                                ];
12010
                            }
12011
                            break;
12012
                        case 'asset': //commes from a scorm package generated by chamilo
12013
                        case 'sco':
12014
                            $file_path = $scorm_path.'/'.$item->path;
12015
                            if (file_exists($file_path)) {
12016
                                $files_to_export[] = [
12017
                                    'title' => $item->get_title(),
12018
                                    'path' => $file_path,
12019
                                ];
12020
                            }
12021
                            break;
12022
                        case 'dir':
12023
                            $files_to_export[] = [
12024
                                'title' => $item->get_title(),
12025
                                'path' => null,
12026
                            ];
12027
                            break;
12028
                    }
12029
                }
12030
            }
12031
12032
            $pdf = new PDF();
12033
            $result = $pdf->html_to_pdf(
12034
                $files_to_export,
12035
                $this->name,
12036
                $this->cc,
12037
                true,
12038
                true,
12039
                true,
12040
                $this->get_name()
12041
            );
12042
12043
            return $result;
12044
        }
12045
12046
        return false;
12047
    }
12048
12049
    /**
12050
     * Temp function to be moved in main_api or the best place around for this.
12051
     * Creates a file path if it doesn't exist.
12052
     *
12053
     * @param string $path
12054
     */
12055
    public function create_path($path)
12056
    {
12057
        $path_bits = explode('/', dirname($path));
12058
12059
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12060
        $path_built = IS_WINDOWS_OS ? '' : '/';
12061
        foreach ($path_bits as $bit) {
12062
            if (!empty($bit)) {
12063
                $new_path = $path_built.$bit;
12064
                if (is_dir($new_path)) {
12065
                    $path_built = $new_path.'/';
12066
                } else {
12067
                    mkdir($new_path, api_get_permissions_for_new_directories());
12068
                    $path_built = $new_path.'/';
12069
                }
12070
            }
12071
        }
12072
    }
12073
12074
    /**
12075
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12076
     *
12077
     * @return bool The results of the unlink function, or false if there was no image to start with
12078
     */
12079
    public function delete_lp_image()
12080
    {
12081
        $img = $this->get_preview_image();
12082
        if ($img != '') {
12083
            $del_file = $this->get_preview_image_path(null, 'sys');
12084
            if (isset($del_file) && file_exists($del_file)) {
12085
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12086
                if (file_exists($del_file_2)) {
12087
                    unlink($del_file_2);
12088
                }
12089
                $this->set_preview_image('');
12090
12091
                return @unlink($del_file);
12092
            }
12093
        }
12094
12095
        return false;
12096
    }
12097
12098
    /**
12099
     * Uploads an author image to the upload/learning_path/images directory.
12100
     *
12101
     * @param array    The image array, coming from the $_FILES superglobal
12102
     *
12103
     * @return bool True on success, false on error
12104
     */
12105
    public function upload_image($image_array)
12106
    {
12107
        if (!empty($image_array['name'])) {
12108
            $upload_ok = process_uploaded_file($image_array);
12109
            $has_attachment = true;
12110
        }
12111
12112
        if ($upload_ok && $has_attachment) {
12113
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12114
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12115
            $updir = $sys_course_path.$courseDir;
12116
            // Try to add an extension to the file if it hasn't one.
12117
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12118
12119
            if (filter_extension($new_file_name)) {
12120
                $file_extension = explode('.', $image_array['name']);
12121
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
12122
                $filename = uniqid('');
12123
                $new_file_name = $filename.'.'.$file_extension;
12124
                $new_path = $updir.'/'.$new_file_name;
12125
12126
                // Resize the image.
12127
                $temp = new Image($image_array['tmp_name']);
12128
                $temp->resize(104);
12129
                $result = $temp->send_image($new_path);
12130
12131
                // Storing the image filename.
12132
                if ($result) {
12133
                    $this->set_preview_image($new_file_name);
12134
12135
                    //Resize to 64px to use on course homepage
12136
                    $temp->resize(64);
12137
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12138
12139
                    return true;
12140
                }
12141
            }
12142
        }
12143
12144
        return false;
12145
    }
12146
12147
    /**
12148
     * @param int    $lp_id
12149
     * @param string $status
12150
     */
12151
    public function set_autolaunch($lp_id, $status)
12152
    {
12153
        $course_id = api_get_course_int_id();
12154
        $lp_id = (int) $lp_id;
12155
        $status = (int) $status;
12156
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12157
12158
        // Setting everything to autolaunch = 0
12159
        $attributes['autolaunch'] = 0;
12160
        $where = [
12161
            'session_id = ? AND c_id = ? ' => [
12162
                api_get_session_id(),
12163
                $course_id,
12164
            ],
12165
        ];
12166
        Database::update($lp_table, $attributes, $where);
12167
        if ($status == 1) {
12168
            //Setting my lp_id to autolaunch = 1
12169
            $attributes['autolaunch'] = 1;
12170
            $where = [
12171
                'iid = ? AND session_id = ? AND c_id = ?' => [
12172
                    $lp_id,
12173
                    api_get_session_id(),
12174
                    $course_id,
12175
                ],
12176
            ];
12177
            Database::update($lp_table, $attributes, $where);
12178
        }
12179
    }
12180
12181
    /**
12182
     * Gets previous_item_id for the next element of the lp_item table.
12183
     *
12184
     * @author Isaac flores paz
12185
     *
12186
     * @return int Previous item ID
12187
     */
12188
    public function select_previous_item_id()
12189
    {
12190
        $course_id = api_get_course_int_id();
12191
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12192
12193
        // Get the max order of the items
12194
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12195
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12196
        $rs_max_order = Database::query($sql);
12197
        $row_max_order = Database::fetch_object($rs_max_order);
12198
        $max_order = $row_max_order->display_order;
12199
        // Get the previous item ID
12200
        $sql = "SELECT iid as previous FROM $table_lp_item
12201
                WHERE
12202
                    c_id = $course_id AND
12203
                    lp_id = ".$this->lp_id." AND
12204
                    display_order = '$max_order' ";
12205
        $rs_max = Database::query($sql);
12206
        $row_max = Database::fetch_object($rs_max);
12207
12208
        // Return the previous item ID
12209
        return $row_max->previous;
12210
    }
12211
12212
    /**
12213
     * Copies an LP.
12214
     */
12215
    public function copy()
12216
    {
12217
        // Course builder
12218
        $cb = new CourseBuilder();
12219
12220
        //Setting tools that will be copied
12221
        $cb->set_tools_to_build(['learnpaths']);
12222
12223
        //Setting elements that will be copied
12224
        $cb->set_tools_specific_id_list(
12225
            ['learnpaths' => [$this->lp_id]]
12226
        );
12227
12228
        $course = $cb->build();
12229
12230
        //Course restorer
12231
        $course_restorer = new CourseRestorer($course);
12232
        $course_restorer->set_add_text_in_items(true);
12233
        $course_restorer->set_tool_copy_settings(
12234
            ['learnpaths' => ['reset_dates' => true]]
12235
        );
12236
        $course_restorer->restore(
12237
            api_get_course_id(),
12238
            api_get_session_id(),
12239
            false,
12240
            false
12241
        );
12242
    }
12243
12244
    /**
12245
     * Verify document size.
12246
     *
12247
     * @param string $s
12248
     *
12249
     * @return bool
12250
     */
12251
    public static function verify_document_size($s)
12252
    {
12253
        $post_max = ini_get('post_max_size');
12254
        if (substr($post_max, -1, 1) == 'M') {
12255
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12256
        } elseif (substr($post_max, -1, 1) == 'G') {
12257
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12258
        }
12259
        $upl_max = ini_get('upload_max_filesize');
12260
        if (substr($upl_max, -1, 1) == 'M') {
12261
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12262
        } elseif (substr($upl_max, -1, 1) == 'G') {
12263
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12264
        }
12265
        $documents_total_space = DocumentManager::documents_total_space();
12266
        $course_max_space = DocumentManager::get_course_quota();
12267
        $total_size = filesize($s) + $documents_total_space;
12268
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12269
            return true;
12270
        }
12271
12272
        return false;
12273
    }
12274
12275
    /**
12276
     * Clear LP prerequisites.
12277
     */
12278
    public function clear_prerequisites()
12279
    {
12280
        $course_id = $this->get_course_int_id();
12281
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12282
        $lp_id = $this->get_id();
12283
        // Cleaning prerequisites
12284
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12285
                WHERE c_id = $course_id AND lp_id = $lp_id";
12286
        Database::query($sql);
12287
12288
        // Cleaning mastery score for exercises
12289
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12290
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12291
        Database::query($sql);
12292
    }
12293
12294
    public function set_previous_step_as_prerequisite_for_all_items()
12295
    {
12296
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12297
        $course_id = $this->get_course_int_id();
12298
        $lp_id = $this->get_id();
12299
12300
        if (!empty($this->items)) {
12301
            $previous_item_id = null;
12302
            $previous_item_max = 0;
12303
            $previous_item_type = null;
12304
            $last_item_not_dir = null;
12305
            $last_item_not_dir_type = null;
12306
            $last_item_not_dir_max = null;
12307
12308
            foreach ($this->ordered_items as $itemId) {
12309
                $item = $this->getItem($itemId);
12310
                // if there was a previous item... (otherwise jump to set it)
12311
                if (!empty($previous_item_id)) {
12312
                    $current_item_id = $item->get_id(); //save current id
12313
                    if ($item->get_type() != 'dir') {
12314
                        // Current item is not a folder, so it qualifies to get a prerequisites
12315
                        if ($last_item_not_dir_type == 'quiz') {
12316
                            // if previous is quiz, mark its max score as default score to be achieved
12317
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12318
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12319
                            Database::query($sql);
12320
                        }
12321
                        // now simply update the prerequisite to set it to the last non-chapter item
12322
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12323
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12324
                        Database::query($sql);
12325
                        // record item as 'non-chapter' reference
12326
                        $last_item_not_dir = $item->get_id();
12327
                        $last_item_not_dir_type = $item->get_type();
12328
                        $last_item_not_dir_max = $item->get_max();
12329
                    }
12330
                } else {
12331
                    if ($item->get_type() != 'dir') {
12332
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12333
                        $last_item_not_dir = $item->get_id();
12334
                        $last_item_not_dir_type = $item->get_type();
12335
                        $last_item_not_dir_max = $item->get_max();
12336
                    }
12337
                }
12338
                // Saving the item as "previous item" for the next loop
12339
                $previous_item_id = $item->get_id();
12340
                $previous_item_max = $item->get_max();
12341
                $previous_item_type = $item->get_type();
12342
            }
12343
        }
12344
    }
12345
12346
    /**
12347
     * @param array $params
12348
     *
12349
     * @throws \Doctrine\ORM\OptimisticLockException
12350
     *
12351
     * @return int
12352
     */
12353
    public static function createCategory($params)
12354
    {
12355
        $em = Database::getManager();
12356
        $item = new CLpCategory();
12357
        $item->setName($params['name']);
12358
        $item->setCId($params['c_id']);
12359
        $em->persist($item);
12360
        $em->flush();
12361
12362
        api_item_property_update(
12363
            api_get_course_info(),
12364
            TOOL_LEARNPATH_CATEGORY,
12365
            $item->getId(),
12366
            'visible',
12367
            api_get_user_id()
12368
        );
12369
12370
        return $item->getId();
12371
    }
12372
12373
    /**
12374
     * @param array $params
12375
     *
12376
     * @throws \Doctrine\ORM\ORMException
12377
     * @throws \Doctrine\ORM\OptimisticLockException
12378
     * @throws \Doctrine\ORM\TransactionRequiredException
12379
     */
12380
    public static function updateCategory($params)
12381
    {
12382
        $em = Database::getManager();
12383
        /** @var CLpCategory $item */
12384
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12385
        if ($item) {
12386
            $item->setName($params['name']);
12387
            $em->merge($item);
12388
            $em->flush();
12389
        }
12390
    }
12391
12392
    /**
12393
     * @param int $id
12394
     *
12395
     * @throws \Doctrine\ORM\ORMException
12396
     * @throws \Doctrine\ORM\OptimisticLockException
12397
     * @throws \Doctrine\ORM\TransactionRequiredException
12398
     */
12399
    public static function moveUpCategory($id)
12400
    {
12401
        $id = (int) $id;
12402
        $em = Database::getManager();
12403
        /** @var CLpCategory $item */
12404
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12405
        if ($item) {
12406
            $position = $item->getPosition() - 1;
12407
            $item->setPosition($position);
12408
            $em->persist($item);
12409
            $em->flush();
12410
        }
12411
    }
12412
12413
    /**
12414
     * @param int $id
12415
     *
12416
     * @throws \Doctrine\ORM\ORMException
12417
     * @throws \Doctrine\ORM\OptimisticLockException
12418
     * @throws \Doctrine\ORM\TransactionRequiredException
12419
     */
12420
    public static function moveDownCategory($id)
12421
    {
12422
        $id = (int) $id;
12423
        $em = Database::getManager();
12424
        /** @var CLpCategory $item */
12425
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12426
        if ($item) {
12427
            $position = $item->getPosition() + 1;
12428
            $item->setPosition($position);
12429
            $em->persist($item);
12430
            $em->flush();
12431
        }
12432
    }
12433
12434
    public static function getLpList($courseId, $onlyActiveLp = true)
12435
    {
12436
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12437
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12438
        $courseId = (int) $courseId;
12439
12440
        $sql = "SELECT lp.id, lp.name
12441
                FROM $TABLE_LP lp
12442
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12443
                ON lp.id = ip.ref
12444
                WHERE lp.c_id = $courseId ";
12445
12446
        if ($onlyActiveLp) {
12447
            $sql .= "AND ip.tool = 'learnpath' ";
12448
            $sql .= "AND ip.visibility = 1 ";
12449
        }
12450
12451
        $sql .= "GROUP BY lp.id";
12452
12453
        $result = Database::query($sql);
12454
12455
        return Database::store_result($result, 'ASSOC');
12456
    }
12457
12458
    /**
12459
     * @param int $courseId
12460
     *
12461
     * @throws \Doctrine\ORM\Query\QueryException
12462
     *
12463
     * @return int|mixed
12464
     */
12465
    public static function getCountCategories($courseId)
12466
    {
12467
        if (empty($courseId)) {
12468
            return 0;
12469
        }
12470
        $em = Database::getManager();
12471
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12472
        $query->setParameter('id', $courseId);
12473
12474
        return $query->getSingleScalarResult();
12475
    }
12476
12477
    /**
12478
     * @param int $courseId
12479
     *
12480
     * @return mixed
12481
     */
12482
    public static function getCategories($courseId)
12483
    {
12484
        $em = Database::getManager();
12485
12486
        // Using doctrine extensions
12487
        /** @var SortableRepository $repo */
12488
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12489
        $items = $repo
12490
            ->getBySortableGroupsQuery(['cId' => $courseId])
12491
            ->getResult();
12492
12493
        return $items;
12494
    }
12495
12496
    /**
12497
     * @param int $id
12498
     *
12499
     * @throws \Doctrine\ORM\ORMException
12500
     * @throws \Doctrine\ORM\OptimisticLockException
12501
     * @throws \Doctrine\ORM\TransactionRequiredException
12502
     *
12503
     * @return CLpCategory
12504
     */
12505
    public static function getCategory($id)
12506
    {
12507
        $id = (int) $id;
12508
        $em = Database::getManager();
12509
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12510
12511
        return $item;
12512
    }
12513
12514
    /**
12515
     * @param int $courseId
12516
     *
12517
     * @return array
12518
     */
12519
    public static function getCategoryByCourse($courseId)
12520
    {
12521
        $em = Database::getManager();
12522
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12523
            ['cId' => $courseId]
12524
        );
12525
12526
        return $items;
12527
    }
12528
12529
    /**
12530
     * @param int $id
12531
     *
12532
     * @throws \Doctrine\ORM\ORMException
12533
     * @throws \Doctrine\ORM\OptimisticLockException
12534
     * @throws \Doctrine\ORM\TransactionRequiredException
12535
     *
12536
     * @return mixed
12537
     */
12538
    public static function deleteCategory($id)
12539
    {
12540
        $em = Database::getManager();
12541
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12542
        if ($item) {
12543
            $courseId = $item->getCId();
12544
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12545
            $query->setParameter('id', $courseId);
12546
            $query->setParameter('catId', $item->getId());
12547
            $lps = $query->getResult();
12548
12549
            // Setting category = 0.
12550
            if ($lps) {
12551
                foreach ($lps as $lpItem) {
12552
                    $lpItem->setCategoryId(0);
12553
                }
12554
            }
12555
12556
            // Removing category.
12557
            $em->remove($item);
12558
            $em->flush();
12559
12560
            $courseInfo = api_get_course_info_by_id($courseId);
12561
            $sessionId = api_get_session_id();
12562
12563
            // Delete link tool
12564
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12565
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12566
            // Delete tools
12567
            $sql = "DELETE FROM $tbl_tool
12568
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12569
            Database::query($sql);
12570
12571
            return true;
12572
        }
12573
12574
        return false;
12575
    }
12576
12577
    /**
12578
     * @param int  $courseId
12579
     * @param bool $addSelectOption
12580
     *
12581
     * @return mixed
12582
     */
12583
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12584
    {
12585
        $items = self::getCategoryByCourse($courseId);
12586
        $cats = [];
12587
        if ($addSelectOption) {
12588
            $cats = [get_lang('SelectACategory')];
12589
        }
12590
12591
        if (!empty($items)) {
12592
            foreach ($items as $cat) {
12593
                $cats[$cat->getId()] = $cat->getName();
12594
            }
12595
        }
12596
12597
        return $cats;
12598
    }
12599
12600
    /**
12601
     * @param string $courseCode
12602
     * @param int    $lpId
12603
     * @param int    $user_id
12604
     *
12605
     * @return learnpath
12606
     */
12607
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12608
    {
12609
        $debug = 0;
12610
        $learnPath = null;
12611
        $lpObject = Session::read('lpobject');
12612
        if ($lpObject !== null) {
12613
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12614
            if ($debug) {
12615
                error_log('getLpFromSession: unserialize');
12616
                error_log('------getLpFromSession------');
12617
                error_log('------unserialize------');
12618
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12619
                error_log("api_get_sessionid: ".api_get_session_id());
12620
            }
12621
        }
12622
12623
        if (!is_object($learnPath)) {
12624
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12625
            if ($debug) {
12626
                error_log('------getLpFromSession------');
12627
                error_log('getLpFromSession: create new learnpath');
12628
                error_log("create new LP with $courseCode - $lpId - $user_id");
12629
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12630
                error_log("api_get_sessionid: ".api_get_session_id());
12631
            }
12632
        }
12633
12634
        return $learnPath;
12635
    }
12636
12637
    /**
12638
     * @param int $itemId
12639
     *
12640
     * @return learnpathItem|false
12641
     */
12642
    public function getItem($itemId)
12643
    {
12644
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12645
            return $this->items[$itemId];
12646
        }
12647
12648
        return false;
12649
    }
12650
12651
    /**
12652
     * @return int
12653
     */
12654
    public function getCurrentAttempt()
12655
    {
12656
        $attempt = $this->getItem($this->get_current_item_id());
12657
        if ($attempt) {
12658
            $attemptId = $attempt->get_attempt_id();
12659
12660
            return $attemptId;
12661
        }
12662
12663
        return 0;
12664
    }
12665
12666
    /**
12667
     * @return int
12668
     */
12669
    public function getCategoryId()
12670
    {
12671
        return (int) $this->categoryId;
12672
    }
12673
12674
    /**
12675
     * @param int $categoryId
12676
     *
12677
     * @return bool
12678
     */
12679
    public function setCategoryId($categoryId)
12680
    {
12681
        $this->categoryId = (int) $categoryId;
12682
        $table = Database::get_course_table(TABLE_LP_MAIN);
12683
        $lp_id = $this->get_id();
12684
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12685
                WHERE iid = $lp_id";
12686
        Database::query($sql);
12687
12688
        return true;
12689
    }
12690
12691
    /**
12692
     * Get whether this is a learning path with the possibility to subscribe
12693
     * users or not.
12694
     *
12695
     * @return int
12696
     */
12697
    public function getSubscribeUsers()
12698
    {
12699
        return $this->subscribeUsers;
12700
    }
12701
12702
    /**
12703
     * Set whether this is a learning path with the possibility to subscribe
12704
     * users or not.
12705
     *
12706
     * @param int $value (0 = false, 1 = true)
12707
     *
12708
     * @return bool
12709
     */
12710
    public function setSubscribeUsers($value)
12711
    {
12712
        $this->subscribeUsers = (int) $value;
12713
        $table = Database::get_course_table(TABLE_LP_MAIN);
12714
        $lp_id = $this->get_id();
12715
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12716
                WHERE iid = $lp_id";
12717
        Database::query($sql);
12718
12719
        return true;
12720
    }
12721
12722
    /**
12723
     * Calculate the count of stars for a user in this LP
12724
     * This calculation is based on the following rules:
12725
     * - the student gets one star when he gets to 50% of the learning path
12726
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12727
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12728
     * - the student gets the final star when the score for the *last* test is >= 80%.
12729
     *
12730
     * @param int $sessionId Optional. The session ID
12731
     *
12732
     * @return int The count of stars
12733
     */
12734
    public function getCalculateStars($sessionId = 0)
12735
    {
12736
        $stars = 0;
12737
        $progress = self::getProgress(
12738
            $this->lp_id,
12739
            $this->user_id,
12740
            $this->course_int_id,
12741
            $sessionId
12742
        );
12743
12744
        if ($progress >= 50) {
12745
            $stars++;
12746
        }
12747
12748
        // Calculate stars chapters evaluation
12749
        $exercisesItems = $this->getExercisesItems();
12750
12751
        if (!empty($exercisesItems)) {
12752
            $totalResult = 0;
12753
12754
            foreach ($exercisesItems as $exerciseItem) {
12755
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12756
                    $this->user_id,
12757
                    $exerciseItem->path,
12758
                    $this->course_int_id,
12759
                    $sessionId,
12760
                    $this->lp_id,
12761
                    $exerciseItem->db_id
12762
                );
12763
12764
                $exerciseResultInfo = end($exerciseResultInfo);
12765
12766
                if (!$exerciseResultInfo) {
12767
                    continue;
12768
                }
12769
12770
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12771
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12772
                } else {
12773
                    $exerciseResult = 0;
12774
                }
12775
                $totalResult += $exerciseResult;
12776
            }
12777
12778
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12779
12780
            if ($totalExerciseAverage >= 50) {
12781
                $stars++;
12782
            }
12783
12784
            if ($totalExerciseAverage >= 80) {
12785
                $stars++;
12786
            }
12787
        }
12788
12789
        // Calculate star for final evaluation
12790
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12791
12792
        if (!empty($finalEvaluationItem)) {
12793
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12794
                $this->user_id,
12795
                $finalEvaluationItem->path,
12796
                $this->course_int_id,
12797
                $sessionId,
12798
                $this->lp_id,
12799
                $finalEvaluationItem->db_id
12800
            );
12801
12802
            $evaluationResultInfo = end($evaluationResultInfo);
12803
12804
            if ($evaluationResultInfo) {
12805
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12806
12807
                if ($evaluationResult >= 80) {
12808
                    $stars++;
12809
                }
12810
            }
12811
        }
12812
12813
        return $stars;
12814
    }
12815
12816
    /**
12817
     * Get the items of exercise type.
12818
     *
12819
     * @return array The items. Otherwise return false
12820
     */
12821
    public function getExercisesItems()
12822
    {
12823
        $exercises = [];
12824
        foreach ($this->items as $item) {
12825
            if ($item->type != 'quiz') {
12826
                continue;
12827
            }
12828
            $exercises[] = $item;
12829
        }
12830
12831
        array_pop($exercises);
12832
12833
        return $exercises;
12834
    }
12835
12836
    /**
12837
     * Get the item of exercise type (evaluation type).
12838
     *
12839
     * @return array The final evaluation. Otherwise return false
12840
     */
12841
    public function getFinalEvaluationItem()
12842
    {
12843
        $exercises = [];
12844
        foreach ($this->items as $item) {
12845
            if ($item->type != 'quiz') {
12846
                continue;
12847
            }
12848
12849
            $exercises[] = $item;
12850
        }
12851
12852
        return array_pop($exercises);
12853
    }
12854
12855
    /**
12856
     * Calculate the total points achieved for the current user in this learning path.
12857
     *
12858
     * @param int $sessionId Optional. The session Id
12859
     *
12860
     * @return int
12861
     */
12862
    public function getCalculateScore($sessionId = 0)
12863
    {
12864
        // Calculate stars chapters evaluation
12865
        $exercisesItems = $this->getExercisesItems();
12866
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12867
        $totalExercisesResult = 0;
12868
        $totalEvaluationResult = 0;
12869
12870
        if ($exercisesItems !== false) {
12871
            foreach ($exercisesItems as $exerciseItem) {
12872
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12873
                    $this->user_id,
12874
                    $exerciseItem->path,
12875
                    $this->course_int_id,
12876
                    $sessionId,
12877
                    $this->lp_id,
12878
                    $exerciseItem->db_id
12879
                );
12880
12881
                $exerciseResultInfo = end($exerciseResultInfo);
12882
12883
                if (!$exerciseResultInfo) {
12884
                    continue;
12885
                }
12886
12887
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12888
            }
12889
        }
12890
12891
        if (!empty($finalEvaluationItem)) {
12892
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12893
                $this->user_id,
12894
                $finalEvaluationItem->path,
12895
                $this->course_int_id,
12896
                $sessionId,
12897
                $this->lp_id,
12898
                $finalEvaluationItem->db_id
12899
            );
12900
12901
            $evaluationResultInfo = end($evaluationResultInfo);
12902
12903
            if ($evaluationResultInfo) {
12904
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12905
            }
12906
        }
12907
12908
        return $totalExercisesResult + $totalEvaluationResult;
12909
    }
12910
12911
    /**
12912
     * Check if URL is not allowed to be show in a iframe.
12913
     *
12914
     * @param string $src
12915
     *
12916
     * @return string
12917
     */
12918
    public function fixBlockedLinks($src)
12919
    {
12920
        $urlInfo = parse_url($src);
12921
12922
        $platformProtocol = 'https';
12923
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12924
            $platformProtocol = 'http';
12925
        }
12926
12927
        $protocolFixApplied = false;
12928
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12929
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12930
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12931
12932
        if ($platformProtocol != $scheme) {
12933
            Session::write('x_frame_source', $src);
12934
            $src = 'blank.php?error=x_frames_options';
12935
            $protocolFixApplied = true;
12936
        }
12937
12938
        if ($protocolFixApplied == false) {
12939
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12940
                // Check X-Frame-Options
12941
                $ch = curl_init();
12942
                $options = [
12943
                    CURLOPT_URL => $src,
12944
                    CURLOPT_RETURNTRANSFER => true,
12945
                    CURLOPT_HEADER => true,
12946
                    CURLOPT_FOLLOWLOCATION => true,
12947
                    CURLOPT_ENCODING => "",
12948
                    CURLOPT_AUTOREFERER => true,
12949
                    CURLOPT_CONNECTTIMEOUT => 120,
12950
                    CURLOPT_TIMEOUT => 120,
12951
                    CURLOPT_MAXREDIRS => 10,
12952
                ];
12953
12954
                $proxySettings = api_get_configuration_value('proxy_settings');
12955
                if (!empty($proxySettings) &&
12956
                    isset($proxySettings['curl_setopt_array'])
12957
                ) {
12958
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12959
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12960
                }
12961
12962
                curl_setopt_array($ch, $options);
12963
                $response = curl_exec($ch);
12964
                $httpCode = curl_getinfo($ch);
12965
                $headers = substr($response, 0, $httpCode['header_size']);
12966
12967
                $error = false;
12968
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12969
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12970
                ) {
12971
                    $error = true;
12972
                }
12973
12974
                if ($error) {
12975
                    Session::write('x_frame_source', $src);
12976
                    $src = 'blank.php?error=x_frames_options';
12977
                }
12978
            }
12979
        }
12980
12981
        return $src;
12982
    }
12983
12984
    /**
12985
     * Check if this LP has a created forum in the basis course.
12986
     *
12987
     * @return bool
12988
     */
12989
    public function lpHasForum()
12990
    {
12991
        $forumTable = Database::get_course_table(TABLE_FORUM);
12992
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12993
12994
        $fakeFrom = "
12995
            $forumTable f
12996
            INNER JOIN $itemProperty ip
12997
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12998
        ";
12999
13000
        $resultData = Database::select(
13001
            'COUNT(f.iid) AS qty',
13002
            $fakeFrom,
13003
            [
13004
                'where' => [
13005
                    'ip.visibility != ? AND ' => 2,
13006
                    'ip.tool = ? AND ' => TOOL_FORUM,
13007
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13008
                    'f.lp_id = ?' => intval($this->lp_id),
13009
                ],
13010
            ],
13011
            'first'
13012
        );
13013
13014
        return $resultData['qty'] > 0;
13015
    }
13016
13017
    /**
13018
     * Get the forum for this learning path.
13019
     *
13020
     * @param int $sessionId
13021
     *
13022
     * @return bool
13023
     */
13024
    public function getForum($sessionId = 0)
13025
    {
13026
        $forumTable = Database::get_course_table(TABLE_FORUM);
13027
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13028
13029
        $fakeFrom = "$forumTable f
13030
            INNER JOIN $itemProperty ip ";
13031
13032
        if ($this->lp_session_id == 0) {
13033
            $fakeFrom .= "
13034
                ON (
13035
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13036
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13037
                    )
13038
                )
13039
            ";
13040
        } else {
13041
            $fakeFrom .= "
13042
                ON (
13043
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13044
                )
13045
            ";
13046
        }
13047
13048
        $resultData = Database::select(
13049
            'f.*',
13050
            $fakeFrom,
13051
            [
13052
                'where' => [
13053
                    'ip.visibility != ? AND ' => 2,
13054
                    'ip.tool = ? AND ' => TOOL_FORUM,
13055
                    'f.session_id = ? AND ' => $sessionId,
13056
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13057
                    'f.lp_id = ?' => intval($this->lp_id),
13058
                ],
13059
            ],
13060
            'first'
13061
        );
13062
13063
        if (empty($resultData)) {
13064
            return false;
13065
        }
13066
13067
        return $resultData;
13068
    }
13069
13070
    /**
13071
     * Create a forum for this learning path.
13072
     *
13073
     * @param int $forumCategoryId
13074
     *
13075
     * @return int The forum ID if was created. Otherwise return false
13076
     */
13077
    public function createForum($forumCategoryId)
13078
    {
13079
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13080
13081
        $forumId = store_forum(
13082
            [
13083
                'lp_id' => $this->lp_id,
13084
                'forum_title' => $this->name,
13085
                'forum_comment' => null,
13086
                'forum_category' => (int) $forumCategoryId,
13087
                'students_can_edit_group' => ['students_can_edit' => 0],
13088
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13089
                'default_view_type_group' => ['default_view_type' => 'flat'],
13090
                'group_forum' => 0,
13091
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13092
            ],
13093
            [],
13094
            true
13095
        );
13096
13097
        return $forumId;
13098
    }
13099
13100
    /**
13101
     * Get the LP Final Item form.
13102
     *
13103
     * @throws Exception
13104
     * @throws HTML_QuickForm_Error
13105
     *
13106
     * @return string
13107
     */
13108
    public function getFinalItemForm()
13109
    {
13110
        $finalItem = $this->getFinalItem();
13111
        $title = '';
13112
13113
        if ($finalItem) {
13114
            $title = $finalItem->get_title();
13115
            $buttonText = get_lang('Save');
13116
            $content = $this->getSavedFinalItem();
13117
        } else {
13118
            $buttonText = get_lang('LPCreateDocument');
13119
            $content = $this->getFinalItemTemplate();
13120
        }
13121
13122
        $courseInfo = api_get_course_info();
13123
        $result = $this->generate_lp_folder($courseInfo);
13124
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13125
        $relative_prefix = '../../';
13126
13127
        $editorConfig = [
13128
            'ToolbarSet' => 'LearningPathDocuments',
13129
            'Width' => '100%',
13130
            'Height' => '500',
13131
            'FullPage' => true,
13132
            'CreateDocumentDir' => $relative_prefix,
13133
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13134
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13135
        ];
13136
13137
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13138
            'type' => 'document',
13139
            'lp_id' => $this->lp_id,
13140
        ]);
13141
13142
        $form = new FormValidator('final_item', 'POST', $url);
13143
        $form->addText('title', get_lang('Title'));
13144
        $form->addButtonSave($buttonText);
13145
        $form->addHtml(
13146
            Display::return_message(
13147
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13148
                'normal',
13149
                false
13150
            )
13151
        );
13152
13153
        $renderer = $form->defaultRenderer();
13154
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13155
13156
        $form->addHtmlEditor(
13157
            'content_lp_certificate',
13158
            null,
13159
            true,
13160
            false,
13161
            $editorConfig,
13162
            true
13163
        );
13164
        $form->addHidden('action', 'add_final_item');
13165
        $form->addHidden('path', Session::read('pathItem'));
13166
        $form->addHidden('previous', $this->get_last());
13167
        $form->setDefaults(
13168
            ['title' => $title, 'content_lp_certificate' => $content]
13169
        );
13170
13171
        if ($form->validate()) {
13172
            $values = $form->exportValues();
13173
            $lastItemId = $this->getLastInFirstLevel();
13174
13175
            if (!$finalItem) {
13176
                $documentId = $this->create_document(
13177
                    $this->course_info,
13178
                    $values['content_lp_certificate'],
13179
                    $values['title']
13180
                );
13181
                $this->add_item(
13182
                    0,
13183
                    $lastItemId,
13184
                    'final_item',
13185
                    $documentId,
13186
                    $values['title'],
13187
                    ''
13188
                );
13189
13190
                Display::addFlash(
13191
                    Display::return_message(get_lang('Added'))
13192
                );
13193
            } else {
13194
                $this->edit_document($this->course_info);
13195
            }
13196
        }
13197
13198
        return $form->returnForm();
13199
    }
13200
13201
    /**
13202
     * Check if the current lp item is first, both, last or none from lp list.
13203
     *
13204
     * @param int $currentItemId
13205
     *
13206
     * @return string
13207
     */
13208
    public function isFirstOrLastItem($currentItemId)
13209
    {
13210
        $lpItemId = [];
13211
        $typeListNotToVerify = self::getChapterTypes();
13212
13213
        // Using get_toc() function instead $this->items because returns the correct order of the items
13214
        foreach ($this->get_toc() as $item) {
13215
            if (!in_array($item['type'], $typeListNotToVerify)) {
13216
                $lpItemId[] = $item['id'];
13217
            }
13218
        }
13219
13220
        $lastLpItemIndex = count($lpItemId) - 1;
13221
        $position = array_search($currentItemId, $lpItemId);
13222
13223
        switch ($position) {
13224
            case 0:
13225
                if (!$lastLpItemIndex) {
13226
                    $answer = 'both';
13227
                    break;
13228
                }
13229
13230
                $answer = 'first';
13231
                break;
13232
            case $lastLpItemIndex:
13233
                $answer = 'last';
13234
                break;
13235
            default:
13236
                $answer = 'none';
13237
        }
13238
13239
        return $answer;
13240
    }
13241
13242
    /**
13243
     * Get whether this is a learning path with the accumulated SCORM time or not.
13244
     *
13245
     * @return int
13246
     */
13247
    public function getAccumulateScormTime()
13248
    {
13249
        return $this->accumulateScormTime;
13250
    }
13251
13252
    /**
13253
     * Set whether this is a learning path with the accumulated SCORM time or not.
13254
     *
13255
     * @param int $value (0 = false, 1 = true)
13256
     *
13257
     * @return bool Always returns true
13258
     */
13259
    public function setAccumulateScormTime($value)
13260
    {
13261
        $this->accumulateScormTime = (int) $value;
13262
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13263
        $lp_id = $this->get_id();
13264
        $sql = "UPDATE $lp_table
13265
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13266
                WHERE iid = $lp_id";
13267
        Database::query($sql);
13268
13269
        return true;
13270
    }
13271
13272
    /**
13273
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13274
     * the new learning path tool.
13275
     *
13276
     * The function is a big switch on tool type.
13277
     * In each case, we query the corresponding table for information and build the link
13278
     * with that information.
13279
     *
13280
     * @author Yannick Warnier <[email protected]> - rebranding based on
13281
     * previous work (display_addedresource_link_in_learnpath())
13282
     *
13283
     * @param int $course_id      Course code
13284
     * @param int $learningPathId The learning path ID (in lp table)
13285
     * @param int $id_in_path     the unique index in the items table
13286
     * @param int $lpViewId
13287
     *
13288
     * @return string
13289
     */
13290
    public static function rl_get_resource_link_for_learnpath(
13291
        $course_id,
13292
        $learningPathId,
13293
        $id_in_path,
13294
        $lpViewId
13295
    ) {
13296
        $session_id = api_get_session_id();
13297
        $course_info = api_get_course_info_by_id($course_id);
13298
13299
        $learningPathId = (int) $learningPathId;
13300
        $id_in_path = (int) $id_in_path;
13301
        $lpViewId = (int) $lpViewId;
13302
13303
        $em = Database::getManager();
13304
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13305
13306
        /** @var CLpItem $rowItem */
13307
        $rowItem = $lpItemRepo->findOneBy([
13308
            'cId' => $course_id,
13309
            'lpId' => $learningPathId,
13310
            'iid' => $id_in_path,
13311
        ]);
13312
13313
        if (!$rowItem) {
13314
            // Try one more time with "id"
13315
            /** @var CLpItem $rowItem */
13316
            $rowItem = $lpItemRepo->findOneBy([
13317
                'cId' => $course_id,
13318
                'lpId' => $learningPathId,
13319
                'id' => $id_in_path,
13320
            ]);
13321
13322
            if (!$rowItem) {
13323
                return -1;
13324
            }
13325
        }
13326
13327
        $course_code = $course_info['code'];
13328
        $type = $rowItem->getItemType();
13329
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13330
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13331
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13332
        $link = '';
13333
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13334
13335
        switch ($type) {
13336
            case 'dir':
13337
                return $main_dir_path.'lp/blank.php';
13338
            case TOOL_CALENDAR_EVENT:
13339
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13340
            case TOOL_ANNOUNCEMENT:
13341
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13342
            case TOOL_LINK:
13343
                $linkInfo = Link::getLinkInfo($id);
13344
                if (isset($linkInfo['url'])) {
13345
                    return $linkInfo['url'];
13346
                }
13347
13348
                return '';
13349
            case TOOL_QUIZ:
13350
                if (empty($id)) {
13351
                    return '';
13352
                }
13353
13354
                // Get the lp_item_view with the highest view_count.
13355
                $learnpathItemViewResult = $em
13356
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13357
                    ->findBy(
13358
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13359
                        ['viewCount' => 'DESC'],
13360
                        1
13361
                    );
13362
                /** @var CLpItemView $learnpathItemViewData */
13363
                $learnpathItemViewData = current($learnpathItemViewResult);
13364
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13365
13366
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13367
                    .http_build_query([
13368
                        'lp_init' => 1,
13369
                        'learnpath_item_view_id' => $learnpathItemViewId,
13370
                        'learnpath_id' => $learningPathId,
13371
                        'learnpath_item_id' => $id_in_path,
13372
                        'exerciseId' => $id,
13373
                    ]);
13374
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13375
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13376
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13377
                $myrow = Database::fetch_array($result);
13378
                $path = $myrow['path'];
13379
13380
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13381
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13382
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13383
            case TOOL_FORUM:
13384
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13385
            case TOOL_THREAD:
13386
                // forum post
13387
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13388
                if (empty($id)) {
13389
                    return '';
13390
                }
13391
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13392
                $result = Database::query($sql);
13393
                $myrow = Database::fetch_array($result);
13394
13395
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13396
                    .$extraParams;
13397
            case TOOL_POST:
13398
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13399
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13400
                $myrow = Database::fetch_array($result);
13401
13402
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13403
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13404
            case TOOL_READOUT_TEXT:
13405
                return api_get_path(WEB_CODE_PATH).
13406
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13407
            case TOOL_DOCUMENT:
13408
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13409
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13410
13411
                if (empty($document)) {
13412
                    // Try with normal id
13413
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13414
13415
                    if (empty($document)) {
13416
                        return '';
13417
                    }
13418
                }
13419
13420
                $documentPathInfo = pathinfo($document->getPath());
13421
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
13422
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13423
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13424
13425
                $openmethod = 2;
13426
                $officedoc = false;
13427
                Session::write('openmethod', $openmethod);
13428
                Session::write('officedoc', $officedoc);
13429
13430
                if ($showDirectUrl) {
13431
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13432
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13433
                        if (Link::isPdfLink($file)) {
13434
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13435
13436
                            return $pdfUrl;
13437
                        }
13438
                    }
13439
13440
                    return $file;
13441
                }
13442
13443
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13444
            case TOOL_LP_FINAL_ITEM:
13445
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13446
                    .$extraParams;
13447
            case 'assignments':
13448
                return $main_dir_path.'work/work.php?'.$extraParams;
13449
            case TOOL_DROPBOX:
13450
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13451
            case 'introduction_text': //DEPRECATED
13452
                return '';
13453
            case TOOL_COURSE_DESCRIPTION:
13454
                return $main_dir_path.'course_description?'.$extraParams;
13455
            case TOOL_GROUP:
13456
                return $main_dir_path.'group/group.php?'.$extraParams;
13457
            case TOOL_USER:
13458
                return $main_dir_path.'user/user.php?'.$extraParams;
13459
            case TOOL_STUDENTPUBLICATION:
13460
                if (!empty($rowItem->getPath())) {
13461
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13462
                }
13463
13464
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13465
        }
13466
13467
        return $link;
13468
    }
13469
13470
    /**
13471
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13472
     *
13473
     * @author Yannick Warnier <[email protected]>
13474
     *
13475
     * @param string $course_code    Course code
13476
     * @param int    $learningPathId
13477
     * @param int    $id_in_path     The resource ID
13478
     *
13479
     * @return string
13480
     */
13481
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13482
    {
13483
        $_course = api_get_course_info($course_code);
13484
        if (empty($_course)) {
13485
            return '';
13486
        }
13487
        $course_id = $_course['real_id'];
13488
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13489
        $learningPathId = (int) $learningPathId;
13490
        $id_in_path = (int) $id_in_path;
13491
13492
        $sql = "SELECT item_type, title, ref
13493
                FROM $tbl_lp_item
13494
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13495
        $res_item = Database::query($sql);
13496
13497
        if (Database::num_rows($res_item) < 1) {
13498
            return '';
13499
        }
13500
        $row_item = Database::fetch_array($res_item);
13501
        $type = strtolower($row_item['item_type']);
13502
        $id = $row_item['ref'];
13503
        $output = '';
13504
13505
        switch ($type) {
13506
            case TOOL_CALENDAR_EVENT:
13507
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13508
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13509
                $myrow = Database::fetch_array($result);
13510
                $output = $myrow['title'];
13511
                break;
13512
            case TOOL_ANNOUNCEMENT:
13513
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13514
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13515
                $myrow = Database::fetch_array($result);
13516
                $output = $myrow['title'];
13517
                break;
13518
            case TOOL_LINK:
13519
                // Doesn't take $target into account.
13520
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13521
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13522
                $myrow = Database::fetch_array($result);
13523
                $output = $myrow['title'];
13524
                break;
13525
            case TOOL_QUIZ:
13526
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13527
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13528
                $myrow = Database::fetch_array($result);
13529
                $output = $myrow['title'];
13530
                break;
13531
            case TOOL_FORUM:
13532
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13533
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13534
                $myrow = Database::fetch_array($result);
13535
                $output = $myrow['forum_name'];
13536
                break;
13537
            case TOOL_THREAD:
13538
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13539
                // Grabbing the title of the post.
13540
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13541
                $result_title = Database::query($sql_title);
13542
                $myrow_title = Database::fetch_array($result_title);
13543
                $output = $myrow_title['post_title'];
13544
                break;
13545
            case TOOL_POST:
13546
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13547
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13548
                $result = Database::query($sql);
13549
                $post = Database::fetch_array($result);
13550
                $output = $post['post_title'];
13551
                break;
13552
            case 'dir':
13553
            case TOOL_DOCUMENT:
13554
                $title = $row_item['title'];
13555
                $output = '-';
13556
                if (!empty($title)) {
13557
                    $output = $title;
13558
                }
13559
                break;
13560
            case 'hotpotatoes':
13561
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13562
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13563
                $myrow = Database::fetch_array($result);
13564
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13565
                $last = count($pathname) - 1; // Making a correct name for the link.
13566
                $filename = $pathname[$last]; // Making a correct name for the link.
13567
                $myrow['path'] = rawurlencode($myrow['path']);
13568
                $output = $filename;
13569
                break;
13570
        }
13571
13572
        return stripslashes($output);
13573
    }
13574
13575
    /**
13576
     * Get the parent names for the current item.
13577
     *
13578
     * @param int $newItemId Optional. The item ID
13579
     *
13580
     * @return array
13581
     */
13582
    public function getCurrentItemParentNames($newItemId = 0)
13583
    {
13584
        $newItemId = $newItemId ?: $this->get_current_item_id();
13585
        $return = [];
13586
        $item = $this->getItem($newItemId);
13587
        $parent = $this->getItem($item->get_parent());
13588
13589
        while ($parent) {
13590
            $return[] = $parent->get_title();
13591
            $parent = $this->getItem($parent->get_parent());
13592
        }
13593
13594
        return array_reverse($return);
13595
    }
13596
13597
    /**
13598
     * Reads and process "lp_subscription_settings" setting.
13599
     *
13600
     * @return array
13601
     */
13602
    public static function getSubscriptionSettings()
13603
    {
13604
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13605
        if (empty($subscriptionSettings)) {
13606
            // By default allow both settings
13607
            $subscriptionSettings = [
13608
                'allow_add_users_to_lp' => true,
13609
                'allow_add_users_to_lp_category' => true,
13610
            ];
13611
        } else {
13612
            $subscriptionSettings = $subscriptionSettings['options'];
13613
        }
13614
13615
        return $subscriptionSettings;
13616
    }
13617
13618
    /**
13619
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13620
     */
13621
    public function exportToCourseBuildFormat()
13622
    {
13623
        if (!api_is_allowed_to_edit()) {
13624
            return false;
13625
        }
13626
13627
        $courseBuilder = new CourseBuilder();
13628
        $itemList = [];
13629
        /** @var learnpathItem $item */
13630
        foreach ($this->items as $item) {
13631
            $itemList[$item->get_type()][] = $item->get_path();
13632
        }
13633
13634
        if (empty($itemList)) {
13635
            return false;
13636
        }
13637
13638
        if (isset($itemList['document'])) {
13639
            // Get parents
13640
            foreach ($itemList['document'] as $documentId) {
13641
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13642
                if (!empty($documentInfo['parents'])) {
13643
                    foreach ($documentInfo['parents'] as $parentInfo) {
13644
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13645
                            continue;
13646
                        }
13647
                        $itemList['document'][] = $parentInfo['iid'];
13648
                    }
13649
                }
13650
            }
13651
13652
            $courseInfo = api_get_course_info();
13653
            foreach ($itemList['document'] as $documentId) {
13654
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13655
                $items = DocumentManager::get_resources_from_source_html(
13656
                    $documentInfo['absolute_path'],
13657
                    true,
13658
                    TOOL_DOCUMENT
13659
                );
13660
13661
                if (!empty($items)) {
13662
                    foreach ($items as $item) {
13663
                        // Get information about source url
13664
                        $url = $item[0]; // url
13665
                        $scope = $item[1]; // scope (local, remote)
13666
                        $type = $item[2]; // type (rel, abs, url)
13667
13668
                        $origParseUrl = parse_url($url);
13669
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13670
13671
                        if ($scope == 'local') {
13672
                            if ($type == 'abs' || $type == 'rel') {
13673
                                $documentFile = strstr($realOrigPath, 'document');
13674
                                if (strpos($realOrigPath, $documentFile) !== false) {
13675
                                    $documentFile = str_replace('document', '', $documentFile);
13676
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13677
                                    // Document found! Add it to the list
13678
                                    if ($itemDocumentId) {
13679
                                        $itemList['document'][] = $itemDocumentId;
13680
                                    }
13681
                                }
13682
                            }
13683
                        }
13684
                    }
13685
                }
13686
            }
13687
13688
            $courseBuilder->build_documents(
13689
                api_get_session_id(),
13690
                $this->get_course_int_id(),
13691
                true,
13692
                $itemList['document']
13693
            );
13694
        }
13695
13696
        if (isset($itemList['quiz'])) {
13697
            $courseBuilder->build_quizzes(
13698
                api_get_session_id(),
13699
                $this->get_course_int_id(),
13700
                true,
13701
                $itemList['quiz']
13702
            );
13703
        }
13704
13705
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13706
13707
        /*if (!empty($itemList['thread'])) {
13708
            $postList = [];
13709
            foreach ($itemList['thread'] as $postId) {
13710
                $post = get_post_information($postId);
13711
                if ($post) {
13712
                    if (!isset($itemList['forum'])) {
13713
                        $itemList['forum'] = [];
13714
                    }
13715
                    $itemList['forum'][] = $post['forum_id'];
13716
                    $postList[] = $postId;
13717
                }
13718
            }
13719
13720
            if (!empty($postList)) {
13721
                $courseBuilder->build_forum_posts(
13722
                    $this->get_course_int_id(),
13723
                    null,
13724
                    null,
13725
                    $postList
13726
                );
13727
            }
13728
        }*/
13729
13730
        if (!empty($itemList['thread'])) {
13731
            $threadList = [];
13732
            $em = Database::getManager();
13733
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13734
            foreach ($itemList['thread'] as $threadId) {
13735
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13736
                $thread = $repo->find($threadId);
13737
                if ($thread) {
13738
                    $itemList['forum'][] = $thread->getForumId();
13739
                    $threadList[] = $thread->getIid();
13740
                }
13741
            }
13742
13743
            if (!empty($threadList)) {
13744
                $courseBuilder->build_forum_topics(
13745
                    api_get_session_id(),
13746
                    $this->get_course_int_id(),
13747
                    null,
13748
                    $threadList
13749
                );
13750
            }
13751
        }
13752
13753
        $forumCategoryList = [];
13754
        if (isset($itemList['forum'])) {
13755
            foreach ($itemList['forum'] as $forumId) {
13756
                $forumInfo = get_forums($forumId);
13757
                $forumCategoryList[] = $forumInfo['forum_category'];
13758
            }
13759
        }
13760
13761
        if (!empty($forumCategoryList)) {
13762
            $courseBuilder->build_forum_category(
13763
                api_get_session_id(),
13764
                $this->get_course_int_id(),
13765
                true,
13766
                $forumCategoryList
13767
            );
13768
        }
13769
13770
        if (!empty($itemList['forum'])) {
13771
            $courseBuilder->build_forums(
13772
                api_get_session_id(),
13773
                $this->get_course_int_id(),
13774
                true,
13775
                $itemList['forum']
13776
            );
13777
        }
13778
13779
        if (isset($itemList['link'])) {
13780
            $courseBuilder->build_links(
13781
                api_get_session_id(),
13782
                $this->get_course_int_id(),
13783
                true,
13784
                $itemList['link']
13785
            );
13786
        }
13787
13788
        if (!empty($itemList['student_publication'])) {
13789
            $courseBuilder->build_works(
13790
                api_get_session_id(),
13791
                $this->get_course_int_id(),
13792
                true,
13793
                $itemList['student_publication']
13794
            );
13795
        }
13796
13797
        $courseBuilder->build_learnpaths(
13798
            api_get_session_id(),
13799
            $this->get_course_int_id(),
13800
            true,
13801
            [$this->get_id()],
13802
            false
13803
        );
13804
13805
        $courseBuilder->restoreDocumentsFromList();
13806
13807
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13808
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13809
        $result = DocumentManager::file_send_for_download(
13810
            $zipPath,
13811
            true,
13812
            $this->get_name().'.zip'
13813
        );
13814
13815
        if ($result) {
13816
            api_not_allowed();
13817
        }
13818
13819
        return true;
13820
    }
13821
13822
    /**
13823
     * Get whether this is a learning path with the accumulated work time or not.
13824
     *
13825
     * @return int
13826
     */
13827
    public function getAccumulateWorkTime()
13828
    {
13829
        return (int) $this->accumulateWorkTime;
13830
    }
13831
13832
    /**
13833
     * Get whether this is a learning path with the accumulated work time or not.
13834
     *
13835
     * @return int
13836
     */
13837
    public function getAccumulateWorkTimeTotalCourse()
13838
    {
13839
        $table = Database::get_course_table(TABLE_LP_MAIN);
13840
        $sql = "SELECT SUM(accumulate_work_time) AS total
13841
                FROM $table
13842
                WHERE c_id = ".$this->course_int_id;
13843
        $result = Database::query($sql);
13844
        $row = Database::fetch_array($result);
13845
13846
        return (int) $row['total'];
13847
    }
13848
13849
    /**
13850
     * Set whether this is a learning path with the accumulated work time or not.
13851
     *
13852
     * @param int $value (0 = false, 1 = true)
13853
     *
13854
     * @return bool
13855
     */
13856
    public function setAccumulateWorkTime($value)
13857
    {
13858
        if (!api_get_configuration_value('lp_minimum_time')) {
13859
            return false;
13860
        }
13861
13862
        $this->accumulateWorkTime = (int) $value;
13863
        $table = Database::get_course_table(TABLE_LP_MAIN);
13864
        $lp_id = $this->get_id();
13865
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13866
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13867
        Database::query($sql);
13868
13869
        return true;
13870
    }
13871
13872
    /**
13873
     * @param int $lpId
13874
     * @param int $courseId
13875
     *
13876
     * @return mixed
13877
     */
13878
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13879
    {
13880
        $lpId = (int) $lpId;
13881
        $courseId = (int) $courseId;
13882
13883
        $table = Database::get_course_table(TABLE_LP_MAIN);
13884
        $sql = "SELECT accumulate_work_time
13885
                FROM $table
13886
                WHERE c_id = $courseId AND id = $lpId";
13887
        $result = Database::query($sql);
13888
        $row = Database::fetch_array($result);
13889
13890
        return $row['accumulate_work_time'];
13891
    }
13892
13893
    /**
13894
     * @param int $courseId
13895
     *
13896
     * @return int
13897
     */
13898
    public static function getAccumulateWorkTimeTotal($courseId)
13899
    {
13900
        $table = Database::get_course_table(TABLE_LP_MAIN);
13901
        $courseId = (int) $courseId;
13902
        $sql = "SELECT SUM(accumulate_work_time) AS total
13903
                FROM $table
13904
                WHERE c_id = $courseId";
13905
        $result = Database::query($sql);
13906
        $row = Database::fetch_array($result);
13907
13908
        return (int) $row['total'];
13909
    }
13910
13911
    /**
13912
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13913
     * and put the images in.
13914
     *
13915
     * @return array
13916
     */
13917
    public static function getIconSelect()
13918
    {
13919
        $theme = api_get_visual_theme();
13920
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13921
        $icons = ['' => get_lang('SelectAnOption')];
13922
13923
        if (is_dir($path)) {
13924
            $finder = new Finder();
13925
            $finder->files()->in($path);
13926
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13927
            /** @var SplFileInfo $file */
13928
            foreach ($finder as $file) {
13929
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13930
                    $icons[$file->getFilename()] = $file->getFilename();
13931
                }
13932
            }
13933
        }
13934
13935
        return $icons;
13936
    }
13937
13938
    /**
13939
     * @param int $lpId
13940
     *
13941
     * @return string
13942
     */
13943
    public static function getSelectedIcon($lpId)
13944
    {
13945
        $extraFieldValue = new ExtraFieldValue('lp');
13946
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13947
        $icon = '';
13948
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13949
            $icon = $lpIcon['value'];
13950
        }
13951
13952
        return $icon;
13953
    }
13954
13955
    /**
13956
     * @param int $lpId
13957
     *
13958
     * @return string
13959
     */
13960
    public static function getSelectedIconHtml($lpId)
13961
    {
13962
        $icon = self::getSelectedIcon($lpId);
13963
13964
        if (empty($icon)) {
13965
            return '';
13966
        }
13967
13968
        $theme = api_get_visual_theme();
13969
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13970
13971
        return Display::img($path);
13972
    }
13973
13974
    /**
13975
     * Get the depth level of LP item.
13976
     *
13977
     * @param array $items
13978
     * @param int   $currentItemId
13979
     *
13980
     * @return int
13981
     */
13982
    private static function get_level_for_item($items, $currentItemId)
13983
    {
13984
        $parentItemId = 0;
13985
        if (isset($items[$currentItemId])) {
13986
            $parentItemId = $items[$currentItemId]->parent;
13987
        }
13988
13989
        if ($parentItemId == 0) {
13990
            return 0;
13991
        } else {
13992
            return self::get_level_for_item($items, $parentItemId) + 1;
13993
        }
13994
    }
13995
13996
    /**
13997
     * Generate the link for a learnpath category as course tool.
13998
     *
13999
     * @param int $categoryId
14000
     *
14001
     * @return string
14002
     */
14003
    private static function getCategoryLinkForTool($categoryId)
14004
    {
14005
        $categoryId = (int) $categoryId;
14006
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14007
            .http_build_query(
14008
                [
14009
                    'action' => 'view_category',
14010
                    'id' => $categoryId,
14011
                ]
14012
            );
14013
14014
        return $link;
14015
    }
14016
14017
    /**
14018
     * Return the scorm item type object with spaces replaced with _
14019
     * The return result is use to build a css classname like scorm_type_$return.
14020
     *
14021
     * @param $in_type
14022
     *
14023
     * @return mixed
14024
     */
14025
    private static function format_scorm_type_item($in_type)
14026
    {
14027
        return str_replace(' ', '_', $in_type);
14028
    }
14029
14030
    /**
14031
     * Check and obtain the lp final item if exist.
14032
     *
14033
     * @return learnpathItem
14034
     */
14035
    private function getFinalItem()
14036
    {
14037
        if (empty($this->items)) {
14038
            return null;
14039
        }
14040
14041
        foreach ($this->items as $item) {
14042
            if ($item->type !== 'final_item') {
14043
                continue;
14044
            }
14045
14046
            return $item;
14047
        }
14048
    }
14049
14050
    /**
14051
     * Get the LP Final Item Template.
14052
     *
14053
     * @return string
14054
     */
14055
    private function getFinalItemTemplate()
14056
    {
14057
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14058
    }
14059
14060
    /**
14061
     * Get the LP Final Item Url.
14062
     *
14063
     * @return string
14064
     */
14065
    private function getSavedFinalItem()
14066
    {
14067
        $finalItem = $this->getFinalItem();
14068
        $doc = DocumentManager::get_document_data_by_id(
14069
            $finalItem->path,
14070
            $this->cc
14071
        );
14072
        if ($doc && file_exists($doc['absolute_path'])) {
14073
            return file_get_contents($doc['absolute_path']);
14074
        }
14075
14076
        return '';
14077
    }
14078
}
14079