Completed
Push — master ( 255b9b...7e1a98 )
by Julito
09:17
created

learnpath::set_terms_by_prefix()   B

Complexity

Conditions 10
Paths 13

Size

Total Lines 68
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 40
nc 13
nop 2
dl 0
loc 68
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Framework\Container;
5
use Chamilo\CoreBundle\Repository\CourseRepository;
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\CItemProperty;
10
use Chamilo\CourseBundle\Entity\CLp;
11
use Chamilo\CourseBundle\Entity\CLpCategory;
12
use Chamilo\CourseBundle\Entity\CLpItem;
13
use Chamilo\CourseBundle\Entity\CLpItemView;
14
use Chamilo\CourseBundle\Entity\CTool;
15
use Chamilo\UserBundle\Entity\User;
16
use ChamiloSession as Session;
17
use Gedmo\Sortable\Entity\Repository\SortableRepository;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\Finder\Finder;
20
21
/**
22
 * Class learnpath
23
 * This class defines the parent attributes and methods for Chamilo learnpaths
24
 * and SCORM learnpaths. It is used by the scorm class.
25
 *
26
 * @todo decouple class
27
 *
28
 * @package chamilo.learnpath
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
37
    public $attempt = 0; // The number for the current ID view.
38
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
39
    public $current; // Id of the current item the user is viewing.
40
    public $current_score; // The score of the current item.
41
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
42
    public $current_time_stop; // The time the user closed this resource.
43
    public $default_status = 'not attempted';
44
    public $encoding = 'UTF-8';
45
    public $error = '';
46
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
47
    public $index; // The index of the active learnpath_item in $ordered_items array.
48
    public $items = [];
49
    public $last; // item_id of last item viewed in the learning path.
50
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
51
    public $license; // Which license this course has been given - not used yet on 20060522.
52
    public $lp_id; // DB iid for this learnpath.
53
    public $lp_view_id; // DB ID for lp_view
54
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
55
    public $message = '';
56
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
57
    public $name; // Learnpath name (they generally have one).
58
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
59
    public $path = ''; // Path inside the scorm directory (if scorm).
60
    public $theme; // The current theme of the learning path.
61
    public $preview_image; // The current image of the learning path.
62
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
63
    public $accumulateWorkTime; // The min time of learnpath
64
65
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
66
    public $prevent_reinit = 1;
67
68
    // Describes the mode of progress bar display.
69
    public $seriousgame_mode = 0;
70
    public $progress_bar_mode = '%';
71
72
    // Percentage progress as saved in the db.
73
    public $progress_db = 0;
74
    public $proximity; // Wether the content is distant or local or unknown.
75
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
76
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
77
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
78
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
79
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
80
    public $user_id; //ID of the user that is viewing/using the course
81
    public $update_queue = [];
82
    public $scorm_debug = 0;
83
    public $arrMenu = []; // Array for the menu items.
84
    public $debug = 0; // Logging level.
85
    public $lp_session_id = 0;
86
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
87
    public $prerequisite = 0;
88
    public $use_max_score = 1; // 1 or 0
89
    public $subscribeUsers = 0; // Subscribe users or not
90
    public $created_on = '';
91
    public $modified_on = '';
92
    public $publicated_on = '';
93
    public $expired_on = '';
94
    public $ref = null;
95
    public $course_int_id;
96
    public $course_info = [];
97
    public $categoryId;
98
99
    /**
100
     * Constructor.
101
     * Needs a database handler, a course code and a learnpath id from the database.
102
     * Also builds the list of items into $this->items.
103
     *
104
     * @param string $course  Course code
105
     * @param int    $lp_id   c_lp.iid
106
     * @param int    $user_id
107
     */
108
    public function __construct($course, $lp_id, $user_id)
109
    {
110
        $debug = $this->debug;
111
        $this->encoding = api_get_system_encoding();
112
        if ($debug) {
113
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
114
        }
115
        if (empty($course)) {
116
            $course = api_get_course_id();
117
        }
118
        $course_info = api_get_course_info($course);
119
        if (!empty($course_info)) {
120
            $this->cc = $course_info['code'];
121
            $this->course_info = $course_info;
122
            $course_id = $course_info['real_id'];
123
        } else {
124
            $this->error = 'Course code does not exist in database.';
125
        }
126
127
        $lp_id = (int) $lp_id;
128
        $course_id = (int) $course_id;
129
        $this->set_course_int_id($course_id);
130
        // Check learnpath ID.
131
        if (empty($lp_id) || empty($course_id)) {
132
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
133
        } else {
134
            // TODO: Make it flexible to use any course_code (still using env course code here).
135
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
136
            $sql = "SELECT * FROM $lp_table
137
                    WHERE iid = $lp_id";
138
            if ($debug) {
139
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
140
            }
141
            $res = Database::query($sql);
142
            if (Database::num_rows($res) > 0) {
143
                $this->lp_id = $lp_id;
144
                $row = Database::fetch_array($res);
145
                $this->type = $row['lp_type'];
146
                $this->name = stripslashes($row['name']);
147
                $this->proximity = $row['content_local'];
148
                $this->theme = $row['theme'];
149
                $this->maker = $row['content_maker'];
150
                $this->prevent_reinit = $row['prevent_reinit'];
151
                $this->seriousgame_mode = $row['seriousgame_mode'];
152
                $this->license = $row['content_license'];
153
                $this->scorm_debug = $row['debug'];
154
                $this->js_lib = $row['js_lib'];
155
                $this->path = $row['path'];
156
                $this->preview_image = $row['preview_image'];
157
                $this->author = $row['author'];
158
                $this->hide_toc_frame = $row['hide_toc_frame'];
159
                $this->lp_session_id = $row['session_id'];
160
                $this->use_max_score = $row['use_max_score'];
161
                $this->subscribeUsers = $row['subscribe_users'];
162
                $this->created_on = $row['created_on'];
163
                $this->modified_on = $row['modified_on'];
164
                $this->ref = $row['ref'];
165
                $this->categoryId = $row['category_id'];
166
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
167
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
168
169
                if (!empty($row['publicated_on'])) {
170
                    $this->publicated_on = $row['publicated_on'];
171
                }
172
173
                if (!empty($row['expired_on'])) {
174
                    $this->expired_on = $row['expired_on'];
175
                }
176
                if ($this->type == 2) {
177
                    if ($row['force_commit'] == 1) {
178
                        $this->force_commit = true;
179
                    }
180
                }
181
                $this->mode = $row['default_view_mod'];
182
183
                // Check user ID.
184
                if (empty($user_id)) {
185
                    $this->error = 'User ID is empty';
186
                } else {
187
                    $userInfo = api_get_user_info($user_id);
188
                    if (!empty($userInfo)) {
189
                        $this->user_id = $userInfo['user_id'];
190
                    } else {
191
                        $this->error = 'User ID does not exist in database #'.$user_id;
192
                    }
193
                }
194
195
                // End of variables checking.
196
                $session_id = api_get_session_id();
197
                //  Get the session condition for learning paths of the base + session.
198
                $session = api_get_session_condition($session_id);
199
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
200
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
201
202
                // Selecting by view_count descending allows to get the highest view_count first.
203
                $sql = "SELECT * FROM $lp_table
204
                        WHERE 
205
                            c_id = $course_id AND 
206
                            lp_id = $lp_id AND 
207
                            user_id = $user_id 
208
                            $session
209
                        ORDER BY view_count DESC";
210
                $res = Database::query($sql);
211
                if ($debug) {
212
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
213
                }
214
215
                if (Database::num_rows($res) > 0) {
216
                    if ($debug) {
217
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
218
                    }
219
                    $row = Database::fetch_array($res);
220
                    $this->attempt = $row['view_count'];
221
                    $this->lp_view_id = $row['id'];
222
                    $this->last_item_seen = $row['last_item'];
223
                    $this->progress_db = $row['progress'];
224
                    $this->lp_view_session_id = $row['session_id'];
225
                } elseif (!api_is_invitee()) {
226
                    if ($debug) {
227
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
228
                    }
229
                    $this->attempt = 1;
230
                    $params = [
231
                        'c_id' => $course_id,
232
                        'lp_id' => $lp_id,
233
                        'user_id' => $user_id,
234
                        'view_count' => 1,
235
                        'session_id' => $session_id,
236
                        'last_item' => 0,
237
                    ];
238
                    $this->last_item_seen = 0;
239
                    $this->lp_view_session_id = $session_id;
240
                    $this->lp_view_id = Database::insert($lp_table, $params);
241
                    if (!empty($this->lp_view_id)) {
242
                        $sql = "UPDATE $lp_table SET id = iid
243
                                WHERE iid = ".$this->lp_view_id;
244
                        Database::query($sql);
245
                    }
246
                }
247
248
                // Initialise items.
249
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
250
                $sql = "SELECT * FROM $lp_item_table
251
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
252
                        ORDER BY parent_item_id, display_order";
253
                $res = Database::query($sql);
254
255
                $lp_item_id_list = [];
256
                while ($row = Database::fetch_array($res)) {
257
                    $lp_item_id_list[] = $row['iid'];
258
                    switch ($this->type) {
259
                        case 3: //aicc
260
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
261
                            if (is_object($oItem)) {
262
                                $my_item_id = $oItem->get_id();
263
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
264
                                $oItem->set_prevent_reinit($this->prevent_reinit);
265
                                // Don't use reference here as the next loop will make the pointed object change.
266
                                $this->items[$my_item_id] = $oItem;
267
                                $this->refs_list[$oItem->ref] = $my_item_id;
268
                                if ($debug) {
269
                                    error_log(
270
                                        'learnpath::__construct() - '.
271
                                        'aicc object with id '.$my_item_id.
272
                                        ' set in items[]',
273
                                        0
274
                                    );
275
                                }
276
                            }
277
                            break;
278
                        case 2:
279
                            $oItem = new scormItem('db', $row['iid'], $course_id);
280
                            if (is_object($oItem)) {
281
                                $my_item_id = $oItem->get_id();
282
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
283
                                $oItem->set_prevent_reinit($this->prevent_reinit);
284
                                // Don't use reference here as the next loop will make the pointed object change.
285
                                $this->items[$my_item_id] = $oItem;
286
                                $this->refs_list[$oItem->ref] = $my_item_id;
287
                                if ($debug) {
288
                                    error_log('object with id '.$my_item_id.' set in items[]');
289
                                }
290
                            }
291
                            break;
292
                        case 1:
293
                        default:
294
                            if ($debug) {
295
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
296
                            }
297
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
298
299
                            if ($debug) {
300
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
301
                            }
302
                            if (is_object($oItem)) {
303
                                $my_item_id = $oItem->get_id();
304
                                // Moved down to when we are sure the item_view exists.
305
                                //$oItem->set_lp_view($this->lp_view_id);
306
                                $oItem->set_prevent_reinit($this->prevent_reinit);
307
                                // Don't use reference here as the next loop will make the pointed object change.
308
                                $this->items[$my_item_id] = $oItem;
309
                                $this->refs_list[$my_item_id] = $my_item_id;
310
                                if ($debug) {
311
                                    error_log(
312
                                        'learnpath::__construct() '.__LINE__.
313
                                        ' - object with id '.$my_item_id.' set in items[]'
314
                                    );
315
                                }
316
                            }
317
                            break;
318
                    }
319
320
                    // Setting the object level with variable $this->items[$i][parent]
321
                    foreach ($this->items as $itemLPObject) {
322
                        $level = self::get_level_for_item(
323
                            $this->items,
324
                            $itemLPObject->db_id
325
                        );
326
                        $itemLPObject->level = $level;
327
                    }
328
329
                    // Setting the view in the item object.
330
                    if (is_object($this->items[$row['iid']])) {
331
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
332
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
333
                            $this->items[$row['iid']]->current_start_time = 0;
334
                            $this->items[$row['iid']]->current_stop_time = 0;
335
                        }
336
                    }
337
                }
338
339
                if (!empty($lp_item_id_list)) {
340
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
341
                    if (!empty($lp_item_id_list_to_string)) {
342
                        // Get last viewing vars.
343
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
344
                        // This query should only return one or zero result.
345
                        $sql = "SELECT lp_item_id, status
346
                                FROM $itemViewTable
347
                                WHERE
348
                                    c_id = $course_id AND
349
                                    lp_view_id = ".$this->get_view_id()." AND
350
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
351
                                ORDER BY view_count DESC ";
352
                        $status_list = [];
353
                        $res = Database::query($sql);
354
                        while ($row = Database:: fetch_array($res)) {
355
                            $status_list[$row['lp_item_id']] = $row['status'];
356
                        }
357
358
                        foreach ($lp_item_id_list as $item_id) {
359
                            if (isset($status_list[$item_id])) {
360
                                $status = $status_list[$item_id];
361
                                if (is_object($this->items[$item_id])) {
362
                                    $this->items[$item_id]->set_status($status);
363
                                    if (empty($status)) {
364
                                        $this->items[$item_id]->set_status(
365
                                            $this->default_status
366
                                        );
367
                                    }
368
                                }
369
                            } else {
370
                                if (!api_is_invitee()) {
371
                                    if (is_object($this->items[$item_id])) {
372
                                        $this->items[$item_id]->set_status(
373
                                            $this->default_status
374
                                        );
375
                                    }
376
377
                                    if (!empty($this->lp_view_id)) {
378
                                        // Add that row to the lp_item_view table so that
379
                                        // we have something to show in the stats page.
380
                                        $params = [
381
                                            'c_id' => $course_id,
382
                                            'lp_item_id' => $item_id,
383
                                            'lp_view_id' => $this->lp_view_id,
384
                                            'view_count' => 1,
385
                                            'status' => 'not attempted',
386
                                            'start_time' => time(),
387
                                            'total_time' => 0,
388
                                            'score' => 0,
389
                                        ];
390
                                        $insertId = Database::insert($itemViewTable, $params);
391
392
                                        if ($insertId) {
393
                                            $sql = "UPDATE $itemViewTable SET id = iid
394
                                                    WHERE iid = $insertId";
395
                                            Database::query($sql);
396
                                        }
397
398
                                        $this->items[$item_id]->set_lp_view(
399
                                            $this->lp_view_id,
400
                                            $course_id
401
                                        );
402
                                    }
403
                                }
404
                            }
405
                        }
406
                    }
407
                }
408
409
                $this->ordered_items = self::get_flat_ordered_items_list(
410
                    $this->get_id(),
411
                    0,
412
                    $course_id
413
                );
414
                $this->max_ordered_items = 0;
415
                foreach ($this->ordered_items as $index => $dummy) {
416
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
417
                        $this->max_ordered_items = $index;
418
                    }
419
                }
420
                // TODO: Define the current item better.
421
                $this->first();
422
                if ($debug) {
423
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
424
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
425
                }
426
            } else {
427
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
428
            }
429
        }
430
    }
431
432
    /**
433
     * @return string
434
     */
435
    public function getCourseCode()
436
    {
437
        return $this->cc;
438
    }
439
440
    /**
441
     * @return int
442
     */
443
    public function get_course_int_id()
444
    {
445
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
446
    }
447
448
    /**
449
     * @param $course_id
450
     *
451
     * @return int
452
     */
453
    public function set_course_int_id($course_id)
454
    {
455
        return $this->course_int_id = (int) $course_id;
456
    }
457
458
    /**
459
     * Function rewritten based on old_add_item() from Yannick Warnier.
460
     * Due the fact that users can decide where the item should come, I had to overlook this function and
461
     * I found it better to rewrite it. Old function is still available.
462
     * Added also the possibility to add a description.
463
     *
464
     * @param int    $parent
465
     * @param int    $previous
466
     * @param string $type
467
     * @param int    $id               resource ID (ref)
468
     * @param string $title
469
     * @param string $description
470
     * @param int    $prerequisites
471
     * @param int    $max_time_allowed
472
     * @param int    $userId
473
     *
474
     * @return int
475
     */
476
    public function add_item(
477
        $parent,
478
        $previous,
479
        $type = 'dir',
480
        $id,
481
        $title,
482
        $description,
483
        $prerequisites = 0,
484
        $max_time_allowed = 0,
485
        $userId = 0
486
    ) {
487
        $course_id = $this->course_info['real_id'];
488
        if (empty($course_id)) {
489
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
490
            $this->course_info = api_get_course_info($this->cc);
491
            $course_id = $this->course_info['real_id'];
492
        }
493
        $userId = empty($userId) ? api_get_user_id() : $userId;
494
        $sessionId = api_get_session_id();
495
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
496
        $_course = $this->course_info;
497
        $parent = (int) $parent;
498
        $previous = (int) $previous;
499
        $id = (int) $id;
500
        $max_time_allowed = htmlentities($max_time_allowed);
501
        if (empty($max_time_allowed)) {
502
            $max_time_allowed = 0;
503
        }
504
        $sql = "SELECT COUNT(iid) AS num
505
                FROM $tbl_lp_item
506
                WHERE
507
                    c_id = $course_id AND
508
                    lp_id = ".$this->get_id()." AND
509
                    parent_item_id = $parent ";
510
511
        $res_count = Database::query($sql);
512
        $row = Database::fetch_array($res_count);
513
        $num = $row['num'];
514
515
        $tmp_previous = 0;
516
        $display_order = 0;
517
        $next = 0;
518
        if ($num > 0) {
519
            if (empty($previous)) {
520
                $sql = "SELECT iid, next_item_id, display_order
521
                        FROM $tbl_lp_item
522
                        WHERE
523
                            c_id = $course_id AND
524
                            lp_id = ".$this->get_id()." AND
525
                            parent_item_id = $parent AND
526
                            previous_item_id = 0 OR
527
                            previous_item_id = $parent";
528
                $result = Database::query($sql);
529
                $row = Database::fetch_array($result);
530
                if ($row) {
531
                    $next = $row['iid'];
532
                }
533
            } else {
534
                $previous = (int) $previous;
535
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
536
						FROM $tbl_lp_item
537
                        WHERE
538
                            c_id = $course_id AND
539
                            lp_id = ".$this->get_id()." AND
540
                            id = $previous";
541
                $result = Database::query($sql);
542
                $row = Database::fetch_array($result);
543
                if ($row) {
544
                    $tmp_previous = $row['iid'];
545
                    $next = $row['next_item_id'];
546
                    $display_order = $row['display_order'];
547
                }
548
            }
549
        }
550
551
        $id = (int) $id;
552
        $typeCleaned = Database::escape_string($type);
553
        $max_score = 100;
554
        if ($type === 'quiz') {
555
            $sql = 'SELECT SUM(ponderation)
556
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
557
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
558
                    ON
559
                        quiz_question.id = quiz_rel_question.question_id AND
560
                        quiz_question.c_id = quiz_rel_question.c_id
561
                    WHERE
562
                        quiz_rel_question.exercice_id = '.$id." AND
563
                        quiz_question.c_id = $course_id AND
564
                        quiz_rel_question.c_id = $course_id ";
565
            $rsQuiz = Database::query($sql);
566
            $max_score = Database::result($rsQuiz, 0, 0);
567
568
            // Disabling the exercise if we add it inside a LP
569
            $exercise = new Exercise($course_id);
570
            $exercise->read($id);
571
            $exercise->disable();
572
            $exercise->save();
573
        }
574
575
        $params = [
576
            'c_id' => $course_id,
577
            'lp_id' => $this->get_id(),
578
            'item_type' => $typeCleaned,
579
            'ref' => '',
580
            'title' => $title,
581
            'description' => $description,
582
            'path' => $id,
583
            'max_score' => $max_score,
584
            'parent_item_id' => $parent,
585
            'previous_item_id' => $previous,
586
            'next_item_id' => (int) $next,
587
            'display_order' => $display_order + 1,
588
            'prerequisite' => $prerequisites,
589
            'max_time_allowed' => $max_time_allowed,
590
            'min_score' => 0,
591
            'launch_data' => '',
592
        ];
593
594
        if ($prerequisites != 0) {
595
            $params['prerequisite'] = $prerequisites;
596
        }
597
598
        $new_item_id = Database::insert($tbl_lp_item, $params);
599
        if ($new_item_id) {
600
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
601
            Database::query($sql);
602
603
            if (!empty($next)) {
604
                $sql = "UPDATE $tbl_lp_item
605
                        SET previous_item_id = $new_item_id 
606
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
607
                Database::query($sql);
608
            }
609
610
            // Update the item that should be before the new item.
611
            if (!empty($tmp_previous)) {
612
                $sql = "UPDATE $tbl_lp_item
613
                        SET next_item_id = $new_item_id
614
                        WHERE c_id = $course_id AND id = $tmp_previous";
615
                Database::query($sql);
616
            }
617
618
            // Update all the items after the new item.
619
            $sql = "UPDATE $tbl_lp_item
620
                        SET display_order = display_order + 1
621
                    WHERE
622
                        c_id = $course_id AND
623
                        lp_id = ".$this->get_id()." AND
624
                        iid <> $new_item_id AND
625
                        parent_item_id = $parent AND
626
                        display_order > $display_order";
627
            Database::query($sql);
628
629
            // Update the item that should come after the new item.
630
            $sql = "UPDATE $tbl_lp_item
631
                    SET ref = $new_item_id
632
                    WHERE c_id = $course_id AND iid = $new_item_id";
633
            Database::query($sql);
634
635
            $sql = "UPDATE $tbl_lp_item
636
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
637
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
638
            Database::query($sql);
639
640
            // Upload audio.
641
            if (!empty($_FILES['mp3']['name'])) {
642
                // Create the audio folder if it does not exist yet.
643
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
644
                if (!is_dir($filepath.'audio')) {
645
                    mkdir(
646
                        $filepath.'audio',
647
                        api_get_permissions_for_new_directories()
648
                    );
649
                    $audio_id = DocumentManager::addDocument(
650
                        $_course,
651
                        '/audio',
652
                        'folder',
653
                        0,
654
                        'audio',
655
                        '',
656
                        0,
657
                        true,
658
                        null,
659
                        $sessionId,
660
                        $userId
661
                    );
662
                }
663
664
                $file_path = handle_uploaded_document(
665
                    $_course,
666
                    $_FILES['mp3'],
667
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
668
                    '/audio',
669
                    $userId,
670
                    '',
671
                    '',
672
                    '',
673
                    '',
674
                    false
675
                );
676
677
                // Getting the filename only.
678
                $file_components = explode('/', $file_path);
679
                $file = $file_components[count($file_components) - 1];
680
681
                // Store the mp3 file in the lp_item table.
682
                $sql = "UPDATE $tbl_lp_item SET
683
                          audio = '".Database::escape_string($file)."'
684
                        WHERE iid = '".intval($new_item_id)."'";
685
                Database::query($sql);
686
            }
687
        }
688
689
        return $new_item_id;
690
    }
691
692
    /**
693
     * Static admin function allowing addition of a learnpath to a course.
694
     *
695
     * @param string $courseCode
696
     * @param string $name
697
     * @param string $description
698
     * @param string $learnpath
699
     * @param string $origin
700
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
701
     * @param string $publicated_on
702
     * @param string $expired_on
703
     * @param int    $categoryId
704
     * @param int    $userId
705
     *
706
     * @return int The new learnpath ID on success, 0 on failure
707
     */
708
    public static function add_lp(
709
        $courseCode,
710
        $name,
711
        $description = '',
712
        $learnpath = 'guess',
713
        $origin = 'zip',
714
        $zipname = '',
715
        $publicated_on = '',
716
        $expired_on = '',
717
        $categoryId = 0,
718
        $userId = 0
719
    ) {
720
        global $charset;
721
722
        if (!empty($courseCode)) {
723
            $courseInfo = api_get_course_info($courseCode);
724
            $course_id = $courseInfo['real_id'];
725
        } else {
726
            $course_id = api_get_course_int_id();
727
            $courseInfo = api_get_course_info();
728
        }
729
730
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
731
        // Check course code exists.
732
        // Check lp_name doesn't exist, otherwise append something.
733
        $i = 0;
734
        $categoryId = (int) $categoryId;
735
        // Session id.
736
        $session_id = api_get_session_id();
737
        $userId = empty($userId) ? api_get_user_id() : $userId;
738
739
        if (empty($publicated_on)) {
740
            $publicated_on = null;
741
        } else {
742
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
743
        }
744
745
        if (empty($expired_on)) {
746
            $expired_on = null;
747
        } else {
748
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
749
        }
750
751
        $check_name = "SELECT * FROM $tbl_lp
752
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
753
        $res_name = Database::query($check_name);
754
755
        while (Database::num_rows($res_name)) {
756
            // There is already one such name, update the current one a bit.
757
            $i++;
758
            $name = $name.' - '.$i;
759
            $check_name = "SELECT * FROM $tbl_lp 
760
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
761
            $res_name = Database::query($check_name);
762
        }
763
        // New name does not exist yet; keep it.
764
        // Escape description.
765
        // Kevin: added htmlentities().
766
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
767
        $type = 1;
768
        switch ($learnpath) {
769
            case 'guess':
770
                break;
771
            case 'dokeos':
772
            case 'chamilo':
773
                $type = 1;
774
                break;
775
            case 'aicc':
776
                break;
777
        }
778
779
        switch ($origin) {
780
            case 'zip':
781
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
782
                break;
783
            case 'manual':
784
            default:
785
                $get_max = "SELECT MAX(display_order) 
786
                            FROM $tbl_lp WHERE c_id = $course_id";
787
                $res_max = Database::query($get_max);
788
                if (Database::num_rows($res_max) < 1) {
789
                    $dsp = 1;
790
                } else {
791
                    $row = Database::fetch_array($res_max);
792
                    $dsp = $row[0] + 1;
793
                }
794
795
                $params = [
796
                    'c_id' => $course_id,
797
                    'lp_type' => $type,
798
                    'name' => $name,
799
                    'description' => $description,
800
                    'path' => '',
801
                    'default_view_mod' => 'embedded',
802
                    'default_encoding' => 'UTF-8',
803
                    'display_order' => $dsp,
804
                    'content_maker' => 'Chamilo',
805
                    'content_local' => 'local',
806
                    'js_lib' => '',
807
                    'session_id' => $session_id,
808
                    'created_on' => api_get_utc_datetime(),
809
                    'modified_on' => api_get_utc_datetime(),
810
                    'publicated_on' => $publicated_on,
811
                    'expired_on' => $expired_on,
812
                    'category_id' => $categoryId,
813
                    'force_commit' => 0,
814
                    'content_license' => '',
815
                    'debug' => 0,
816
                    'theme' => '',
817
                    'preview_image' => '',
818
                    'author' => '',
819
                    'prerequisite' => 0,
820
                    'hide_toc_frame' => 0,
821
                    'seriousgame_mode' => 0,
822
                    'autolaunch' => 0,
823
                    'max_attempts' => 0,
824
                    'subscribe_users' => 0,
825
                    'accumulate_scorm_time' => 1,
826
                ];
827
                $id = Database::insert($tbl_lp, $params);
828
829
                if ($id > 0) {
830
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
831
                    Database::query($sql);
832
833
                    // Insert into item_property.
834
                    api_item_property_update(
835
                        $courseInfo,
836
                        TOOL_LEARNPATH,
837
                        $id,
838
                        'LearnpathAdded',
839
                        $userId
840
                    );
841
                    api_set_default_visibility(
842
                        $id,
843
                        TOOL_LEARNPATH,
844
                        0,
845
                        $courseInfo,
846
                        $session_id,
847
                        $userId
848
                    );
849
850
                    return $id;
851
                }
852
                break;
853
        }
854
    }
855
856
    /**
857
     * Auto completes the parents of an item in case it's been completed or passed.
858
     *
859
     * @param int $item Optional ID of the item from which to look for parents
860
     */
861
    public function autocomplete_parents($item)
862
    {
863
        $debug = $this->debug;
864
865
        if (empty($item)) {
866
            $item = $this->current;
867
        }
868
869
        $currentItem = $this->getItem($item);
870
        if ($currentItem) {
871
            $parent_id = $currentItem->get_parent();
872
            $parent = $this->getItem($parent_id);
873
            if ($parent) {
874
                // if $item points to an object and there is a parent.
875
                if ($debug) {
876
                    error_log(
877
                        'Autocompleting parent of item '.$item.' '.
878
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
879
                        0
880
                    );
881
                }
882
883
                // New experiment including failed and browsed in completed status.
884
                //$current_status = $currentItem->get_status();
885
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
886
                // Fixes chapter auto complete
887
                if (true) {
888
                    // If the current item is completed or passes or succeeded.
889
                    $updateParentStatus = true;
890
                    if ($debug) {
891
                        error_log('Status of current item is alright');
892
                    }
893
894
                    foreach ($parent->get_children() as $childItemId) {
895
                        $childItem = $this->getItem($childItemId);
896
897
                        // If children was not set try to get the info
898
                        if (empty($childItem->db_item_view_id)) {
899
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
900
                        }
901
902
                        // Check all his brothers (parent's children) for completion status.
903
                        if ($childItemId != $item) {
904
                            if ($debug) {
905
                                error_log(
906
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
907
                                    0
908
                                );
909
                            }
910
                            // Trying completing parents of failed and browsed items as well.
911
                            if ($childItem->status_is(
912
                                [
913
                                    'completed',
914
                                    'passed',
915
                                    'succeeded',
916
                                    'browsed',
917
                                    'failed',
918
                                ]
919
                            )
920
                            ) {
921
                                // Keep completion status to true.
922
                                continue;
923
                            } else {
924
                                if ($debug > 2) {
925
                                    error_log(
926
                                        '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,
927
                                        0
928
                                    );
929
                                }
930
                                $updateParentStatus = false;
931
                                break;
932
                            }
933
                        }
934
                    }
935
936
                    if ($updateParentStatus) {
937
                        // If all the children were completed:
938
                        $parent->set_status('completed');
939
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
940
                        // Force the status to "completed"
941
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
942
                        $this->update_queue[$parent->get_id()] = 'completed';
943
                        if ($debug) {
944
                            error_log(
945
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
946
                                print_r($this->update_queue, 1),
947
                                0
948
                            );
949
                        }
950
                        // Recursive call.
951
                        $this->autocomplete_parents($parent->get_id());
952
                    }
953
                }
954
            } else {
955
                if ($debug) {
956
                    error_log("Parent #$parent_id does not exists");
957
                }
958
            }
959
        } else {
960
            if ($debug) {
961
                error_log("#$item is an item that doesn't have parents");
962
            }
963
        }
964
    }
965
966
    /**
967
     * Closes the current resource.
968
     *
969
     * Stops the timer
970
     * Saves into the database if required
971
     * Clears the current resource data from this object
972
     *
973
     * @return bool True on success, false on failure
974
     */
975
    public function close()
976
    {
977
        if (empty($this->lp_id)) {
978
            $this->error = 'Trying to close this learnpath but no ID is set';
979
980
            return false;
981
        }
982
        $this->current_time_stop = time();
983
        $this->ordered_items = [];
984
        $this->index = 0;
985
        unset($this->lp_id);
986
        //unset other stuff
987
        return true;
988
    }
989
990
    /**
991
     * Static admin function allowing removal of a learnpath.
992
     *
993
     * @param array  $courseInfo
994
     * @param int    $id         Learnpath ID
995
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
996
     *
997
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
998
     */
999
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1000
    {
1001
        $course_id = api_get_course_int_id();
1002
        if (!empty($courseInfo)) {
1003
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1004
        }
1005
1006
        // TODO: Implement a way of getting this to work when the current object is not set.
1007
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1008
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1009
        if (!empty($id) && ($id != $this->lp_id)) {
1010
            return false;
1011
        }
1012
1013
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1014
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1015
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1016
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1017
1018
        // Delete lp item id.
1019
        foreach ($this->items as $lpItemId => $dummy) {
1020
            $sql = "DELETE FROM $lp_item_view
1021
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1022
            Database::query($sql);
1023
        }
1024
1025
        // Proposed by Christophe (nickname: clefevre)
1026
        $sql = "DELETE FROM $lp_item
1027
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1028
        Database::query($sql);
1029
1030
        $sql = "DELETE FROM $lp_view 
1031
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1032
        Database::query($sql);
1033
1034
        self::toggle_publish($this->lp_id, 'i');
1035
1036
        if ($this->type == 2 || $this->type == 3) {
1037
            // This is a scorm learning path, delete the files as well.
1038
            $sql = "SELECT path FROM $lp
1039
                    WHERE iid = ".$this->lp_id;
1040
            $res = Database::query($sql);
1041
            if (Database::num_rows($res) > 0) {
1042
                $row = Database::fetch_array($res);
1043
                $path = $row['path'];
1044
                $sql = "SELECT id FROM $lp
1045
                        WHERE 
1046
                            c_id = $course_id AND
1047
                            path = '$path' AND 
1048
                            iid != ".$this->lp_id;
1049
                $res = Database::query($sql);
1050
                if (Database::num_rows($res) > 0) {
1051
                    // Another learning path uses this directory, so don't delete it.
1052
                    if ($this->debug > 2) {
1053
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1054
                    }
1055
                } else {
1056
                    // No other LP uses that directory, delete it.
1057
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1058
                    // The absolute system path for this course.
1059
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1060
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1061
                        if ($this->debug > 2) {
1062
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1063
                        }
1064
                        // Proposed by Christophe (clefevre).
1065
                        if (strcmp(substr($path, -2), "/.") == 0) {
1066
                            $path = substr($path, 0, -1); // Remove "." at the end.
1067
                        }
1068
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1069
                        rmdirr($course_scorm_dir.$path);
1070
                    }
1071
                }
1072
            }
1073
        }
1074
1075
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1076
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1077
        // Delete tools
1078
        $sql = "DELETE FROM $tbl_tool
1079
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1080
        Database::query($sql);
1081
1082
        $sql = "DELETE FROM $lp 
1083
                WHERE iid = ".$this->lp_id;
1084
        Database::query($sql);
1085
        // Updates the display order of all lps.
1086
        $this->update_display_order();
1087
1088
        api_item_property_update(
1089
            api_get_course_info(),
1090
            TOOL_LEARNPATH,
1091
            $this->lp_id,
1092
            'delete',
1093
            api_get_user_id()
1094
        );
1095
1096
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1097
            api_get_course_id(),
1098
            4,
1099
            $id,
1100
            api_get_session_id()
1101
        );
1102
1103
        if ($link_info !== false) {
1104
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1105
        }
1106
1107
        if (api_get_setting('search_enabled') == 'true') {
1108
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1109
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1110
        }
1111
    }
1112
1113
    /**
1114
     * Removes all the children of one item - dangerous!
1115
     *
1116
     * @param int $id Element ID of which children have to be removed
1117
     *
1118
     * @return int Total number of children removed
1119
     */
1120
    public function delete_children_items($id)
1121
    {
1122
        $course_id = $this->course_info['real_id'];
1123
1124
        $num = 0;
1125
        $id = (int) $id;
1126
        if (empty($id) || empty($course_id)) {
1127
            return false;
1128
        }
1129
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1130
        $sql = "SELECT * FROM $lp_item 
1131
                WHERE c_id = $course_id AND parent_item_id = $id";
1132
        $res = Database::query($sql);
1133
        while ($row = Database::fetch_array($res)) {
1134
            $num += $this->delete_children_items($row['iid']);
1135
            $sql = "DELETE FROM $lp_item 
1136
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1137
            Database::query($sql);
1138
            $num++;
1139
        }
1140
1141
        return $num;
1142
    }
1143
1144
    /**
1145
     * Removes an item from the current learnpath.
1146
     *
1147
     * @param int $id Elem ID (0 if first)
1148
     *
1149
     * @return int Number of elements moved
1150
     *
1151
     * @todo implement resource removal
1152
     */
1153
    public function delete_item($id)
1154
    {
1155
        $course_id = api_get_course_int_id();
1156
        $id = (int) $id;
1157
        // TODO: Implement the resource removal.
1158
        if (empty($id) || empty($course_id)) {
1159
            return false;
1160
        }
1161
        // First select item to get previous, next, and display order.
1162
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1163
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1164
        $res_sel = Database::query($sql_sel);
1165
        if (Database::num_rows($res_sel) < 1) {
1166
            return false;
1167
        }
1168
        $row = Database::fetch_array($res_sel);
1169
        $previous = $row['previous_item_id'];
1170
        $next = $row['next_item_id'];
1171
        $display = $row['display_order'];
1172
        $parent = $row['parent_item_id'];
1173
        $lp = $row['lp_id'];
1174
        // Delete children items.
1175
        $this->delete_children_items($id);
1176
        // Now delete the item.
1177
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1178
        Database::query($sql_del);
1179
        // Now update surrounding items.
1180
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1181
                    WHERE iid = $previous";
1182
        Database::query($sql_upd);
1183
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1184
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1185
        Database::query($sql_upd);
1186
        // Now update all following items with new display order.
1187
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1188
                    WHERE 
1189
                        c_id = $course_id AND 
1190
                        lp_id = $lp AND 
1191
                        parent_item_id = $parent AND 
1192
                        display_order > $display";
1193
        Database::query($sql_all);
1194
1195
        //Removing prerequisites since the item will not longer exist
1196
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1197
                    WHERE c_id = $course_id AND prerequisite = $id";
1198
        Database::query($sql_all);
1199
1200
        $sql = "UPDATE $lp_item
1201
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1202
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1203
        Database::query($sql);
1204
1205
        // Remove from search engine if enabled.
1206
        if (api_get_setting('search_enabled') === 'true') {
1207
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1208
            $sql = 'SELECT * FROM %s 
1209
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1210
                    LIMIT 1';
1211
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1212
            $res = Database::query($sql);
1213
            if (Database::num_rows($res) > 0) {
1214
                $row2 = Database::fetch_array($res);
1215
                $di = new ChamiloIndexer();
1216
                $di->remove_document($row2['search_did']);
1217
            }
1218
            $sql = 'DELETE FROM %s 
1219
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1220
                    LIMIT 1';
1221
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1222
            Database::query($sql);
1223
        }
1224
    }
1225
1226
    /**
1227
     * Updates an item's content in place.
1228
     *
1229
     * @param int    $id               Element ID
1230
     * @param int    $parent           Parent item ID
1231
     * @param int    $previous         Previous item ID
1232
     * @param string $title            Item title
1233
     * @param string $description      Item description
1234
     * @param string $prerequisites    Prerequisites (optional)
1235
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1236
     * @param int    $max_time_allowed
1237
     * @param string $url
1238
     *
1239
     * @return bool True on success, false on error
1240
     */
1241
    public function edit_item(
1242
        $id,
1243
        $parent,
1244
        $previous,
1245
        $title,
1246
        $description,
1247
        $prerequisites = '0',
1248
        $audio = [],
1249
        $max_time_allowed = 0,
1250
        $url = ''
1251
    ) {
1252
        $course_id = api_get_course_int_id();
1253
        $_course = api_get_course_info();
1254
        $id = (int) $id;
1255
1256
        if (empty($max_time_allowed)) {
1257
            $max_time_allowed = 0;
1258
        }
1259
1260
        if (empty($id) || empty($_course)) {
1261
            return false;
1262
        }
1263
1264
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1265
        $sql = "SELECT * FROM $tbl_lp_item 
1266
                WHERE iid = $id";
1267
        $res_select = Database::query($sql);
1268
        $row_select = Database::fetch_array($res_select);
1269
        $audio_update_sql = '';
1270
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1271
            // Create the audio folder if it does not exist yet.
1272
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1273
            if (!is_dir($filepath.'audio')) {
1274
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1275
                $audio_id = DocumentManager::addDocument(
1276
                    $_course,
1277
                    '/audio',
1278
                    'folder',
1279
                    0,
1280
                    'audio'
1281
                );
1282
            }
1283
1284
            // Upload file in documents.
1285
            $pi = pathinfo($audio['name']);
1286
            if ($pi['extension'] === 'mp3') {
1287
                $c_det = api_get_course_info($this->cc);
1288
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1289
                $path = handle_uploaded_document(
1290
                    $c_det,
1291
                    $audio,
1292
                    $bp,
1293
                    '/audio',
1294
                    api_get_user_id(),
1295
                    0,
1296
                    null,
1297
                    0,
1298
                    'rename',
1299
                    false,
1300
                    0
1301
                );
1302
                $path = substr($path, 7);
1303
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1304
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1305
            }
1306
        }
1307
1308
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1309
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1310
1311
        // TODO: htmlspecialchars to be checked for encoding related problems.
1312
        if ($same_parent && $same_previous) {
1313
            // Only update title and description.
1314
            $sql = "UPDATE $tbl_lp_item
1315
                    SET title = '".Database::escape_string($title)."',
1316
                        prerequisite = '".$prerequisites."',
1317
                        description = '".Database::escape_string($description)."'
1318
                        ".$audio_update_sql.",
1319
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1320
                    WHERE iid = $id";
1321
            Database::query($sql);
1322
        } else {
1323
            $old_parent = $row_select['parent_item_id'];
1324
            $old_previous = $row_select['previous_item_id'];
1325
            $old_next = $row_select['next_item_id'];
1326
            $old_order = $row_select['display_order'];
1327
            $old_prerequisite = $row_select['prerequisite'];
1328
            $old_max_time_allowed = $row_select['max_time_allowed'];
1329
1330
            /* BEGIN -- virtually remove the current item id */
1331
            /* for the next and previous item it is like the current item doesn't exist anymore */
1332
            if ($old_previous != 0) {
1333
                // Next
1334
                $sql = "UPDATE $tbl_lp_item
1335
                        SET next_item_id = $old_next
1336
                        WHERE iid = $old_previous";
1337
                Database::query($sql);
1338
            }
1339
1340
            if (!empty($old_next)) {
1341
                // Previous
1342
                $sql = "UPDATE $tbl_lp_item
1343
                        SET previous_item_id = $old_previous
1344
                        WHERE iid = $old_next";
1345
                Database::query($sql);
1346
            }
1347
1348
            // display_order - 1 for every item with a display_order
1349
            // bigger then the display_order of the current item.
1350
            $sql = "UPDATE $tbl_lp_item
1351
                    SET display_order = display_order - 1
1352
                    WHERE
1353
                        c_id = $course_id AND
1354
                        display_order > $old_order AND
1355
                        lp_id = ".$this->lp_id." AND
1356
                        parent_item_id = $old_parent";
1357
            Database::query($sql);
1358
            /* END -- virtually remove the current item id */
1359
1360
            /* BEGIN -- update the current item id to his new location */
1361
            if ($previous == 0) {
1362
                // Select the data of the item that should come after the current item.
1363
                $sql = "SELECT id, display_order
1364
                        FROM $tbl_lp_item
1365
                        WHERE
1366
                            c_id = $course_id AND
1367
                            lp_id = ".$this->lp_id." AND
1368
                            parent_item_id = $parent AND
1369
                            previous_item_id = $previous";
1370
                $res_select_old = Database::query($sql);
1371
                $row_select_old = Database::fetch_array($res_select_old);
1372
1373
                // If the new parent didn't have children before.
1374
                if (Database::num_rows($res_select_old) == 0) {
1375
                    $new_next = 0;
1376
                    $new_order = 1;
1377
                } else {
1378
                    $new_next = $row_select_old['id'];
1379
                    $new_order = $row_select_old['display_order'];
1380
                }
1381
            } else {
1382
                // Select the data of the item that should come before the current item.
1383
                $sql = "SELECT next_item_id, display_order
1384
                        FROM $tbl_lp_item
1385
                        WHERE iid = $previous";
1386
                $res_select_old = Database::query($sql);
1387
                $row_select_old = Database::fetch_array($res_select_old);
1388
                $new_next = $row_select_old['next_item_id'];
1389
                $new_order = $row_select_old['display_order'] + 1;
1390
            }
1391
1392
            // TODO: htmlspecialchars to be checked for encoding related problems.
1393
            // Update the current item with the new data.
1394
            $sql = "UPDATE $tbl_lp_item
1395
                    SET
1396
                        title = '".Database::escape_string($title)."',
1397
                        description = '".Database::escape_string($description)."',
1398
                        parent_item_id = $parent,
1399
                        previous_item_id = $previous,
1400
                        next_item_id = $new_next,
1401
                        display_order = $new_order
1402
                        $audio_update_sql
1403
                    WHERE iid = $id";
1404
            Database::query($sql);
1405
1406
            if ($previous != 0) {
1407
                // Update the previous item's next_item_id.
1408
                $sql = "UPDATE $tbl_lp_item
1409
                        SET next_item_id = $id
1410
                        WHERE iid = $previous";
1411
                Database::query($sql);
1412
            }
1413
1414
            if (!empty($new_next)) {
1415
                // Update the next item's previous_item_id.
1416
                $sql = "UPDATE $tbl_lp_item
1417
                        SET previous_item_id = $id
1418
                        WHERE iid = $new_next";
1419
                Database::query($sql);
1420
            }
1421
1422
            if ($old_prerequisite != $prerequisites) {
1423
                $sql = "UPDATE $tbl_lp_item
1424
                        SET prerequisite = '$prerequisites'
1425
                        WHERE iid = $id";
1426
                Database::query($sql);
1427
            }
1428
1429
            if ($old_max_time_allowed != $max_time_allowed) {
1430
                // update max time allowed
1431
                $sql = "UPDATE $tbl_lp_item
1432
                        SET max_time_allowed = $max_time_allowed
1433
                        WHERE iid = $id";
1434
                Database::query($sql);
1435
            }
1436
1437
            // Update all the items with the same or a bigger display_order than the current item.
1438
            $sql = "UPDATE $tbl_lp_item
1439
                    SET display_order = display_order + 1
1440
                    WHERE
1441
                       c_id = $course_id AND
1442
                       lp_id = ".$this->get_id()." AND
1443
                       iid <> $id AND
1444
                       parent_item_id = $parent AND
1445
                       display_order >= $new_order";
1446
            Database::query($sql);
1447
        }
1448
1449
        if ($row_select['item_type'] == 'link') {
1450
            $link = new Link();
1451
            $linkId = $row_select['path'];
1452
            $link->updateLink($linkId, $url);
1453
        }
1454
    }
1455
1456
    /**
1457
     * Updates an item's prereq in place.
1458
     *
1459
     * @param int    $id              Element ID
1460
     * @param string $prerequisite_id Prerequisite Element ID
1461
     * @param int    $minScore        Prerequisite min score
1462
     * @param int    $maxScore        Prerequisite max score
1463
     *
1464
     * @return bool True on success, false on error
1465
     */
1466
    public function edit_item_prereq(
1467
        $id,
1468
        $prerequisite_id,
1469
        $minScore = 0,
1470
        $maxScore = 100
1471
    ) {
1472
        $id = (int) $id;
1473
        $prerequisite_id = (int) $prerequisite_id;
1474
1475
        if (empty($id)) {
1476
            return false;
1477
        }
1478
1479
        if (empty($minScore) || $minScore < 0) {
1480
            $minScore = 0;
1481
        }
1482
1483
        if (empty($maxScore) || $maxScore < 0) {
1484
            $maxScore = 100;
1485
        }
1486
1487
        $minScore = floatval($minScore);
1488
        $maxScore = floatval($maxScore);
1489
1490
        if (empty($prerequisite_id)) {
1491
            $prerequisite_id = 'NULL';
1492
            $minScore = 0;
1493
            $maxScore = 100;
1494
        }
1495
1496
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1497
        $sql = " UPDATE $tbl_lp_item
1498
                 SET
1499
                    prerequisite = $prerequisite_id ,
1500
                    prerequisite_min_score = $minScore ,
1501
                    prerequisite_max_score = $maxScore
1502
                 WHERE iid = $id";
1503
1504
        Database::query($sql);
1505
1506
        return true;
1507
    }
1508
1509
    /**
1510
     * Get the specific prefix index terms of this learning path.
1511
     *
1512
     * @param string $prefix
1513
     *
1514
     * @return array Array of terms
1515
     */
1516
    public function get_common_index_terms_by_prefix($prefix)
1517
    {
1518
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1519
        $terms = get_specific_field_values_list_by_prefix(
1520
            $prefix,
1521
            $this->cc,
1522
            TOOL_LEARNPATH,
1523
            $this->lp_id
1524
        );
1525
        $prefix_terms = [];
1526
        if (!empty($terms)) {
1527
            foreach ($terms as $term) {
1528
                $prefix_terms[] = $term['value'];
1529
            }
1530
        }
1531
1532
        return $prefix_terms;
1533
    }
1534
1535
    /**
1536
     * Gets the number of items currently completed.
1537
     *
1538
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1539
     *
1540
     * @return int The number of items currently completed
1541
     */
1542
    public function get_complete_items_count($failedStatusException = false)
1543
    {
1544
        $i = 0;
1545
        $completedStatusList = [
1546
            'completed',
1547
            'passed',
1548
            'succeeded',
1549
            'browsed',
1550
        ];
1551
1552
        if (!$failedStatusException) {
1553
            $completedStatusList[] = 'failed';
1554
        }
1555
1556
        foreach ($this->items as $id => $dummy) {
1557
            // Trying failed and browsed considered "progressed" as well.
1558
            if ($this->items[$id]->status_is($completedStatusList) &&
1559
                $this->items[$id]->get_type() != 'dir'
1560
            ) {
1561
                $i++;
1562
            }
1563
        }
1564
1565
        return $i;
1566
    }
1567
1568
    /**
1569
     * Gets the current item ID.
1570
     *
1571
     * @return int The current learnpath item id
1572
     */
1573
    public function get_current_item_id()
1574
    {
1575
        $current = 0;
1576
        if (!empty($this->current)) {
1577
            $current = (int) $this->current;
1578
        }
1579
1580
        return $current;
1581
    }
1582
1583
    /**
1584
     * Force to get the first learnpath item id.
1585
     *
1586
     * @return int The current learnpath item id
1587
     */
1588
    public function get_first_item_id()
1589
    {
1590
        $current = 0;
1591
        if (is_array($this->ordered_items)) {
1592
            $current = $this->ordered_items[0];
1593
        }
1594
1595
        return $current;
1596
    }
1597
1598
    /**
1599
     * Gets the total number of items available for viewing in this SCORM.
1600
     *
1601
     * @return int The total number of items
1602
     */
1603
    public function get_total_items_count()
1604
    {
1605
        return count($this->items);
1606
    }
1607
1608
    /**
1609
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1610
     *
1611
     * @return int The total no-chapters number of items
1612
     */
1613
    public function getTotalItemsCountWithoutDirs()
1614
    {
1615
        $total = 0;
1616
        $typeListNotToCount = self::getChapterTypes();
1617
        foreach ($this->items as $temp2) {
1618
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1619
                $total++;
1620
            }
1621
        }
1622
1623
        return $total;
1624
    }
1625
1626
    /**
1627
     *  Sets the first element URL.
1628
     */
1629
    public function first()
1630
    {
1631
        if ($this->debug > 0) {
1632
            error_log('In learnpath::first()', 0);
1633
            error_log('$this->last_item_seen '.$this->last_item_seen);
1634
        }
1635
1636
        // Test if the last_item_seen exists and is not a dir.
1637
        if (count($this->ordered_items) == 0) {
1638
            $this->index = 0;
1639
        }
1640
1641
        if (!empty($this->last_item_seen) &&
1642
            !empty($this->items[$this->last_item_seen]) &&
1643
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1644
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1645
            //&& !$this->items[$this->last_item_seen]->is_done()
1646
        ) {
1647
            if ($this->debug > 2) {
1648
                error_log(
1649
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1650
                    $this->items[$this->last_item_seen]->get_type()
1651
                );
1652
            }
1653
            $index = -1;
1654
            foreach ($this->ordered_items as $myindex => $item_id) {
1655
                if ($item_id == $this->last_item_seen) {
1656
                    $index = $myindex;
1657
                    break;
1658
                }
1659
            }
1660
            if ($index == -1) {
1661
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1662
                if ($this->debug > 2) {
1663
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1664
                }
1665
1666
                return false;
1667
            } else {
1668
                $this->last = $this->last_item_seen;
1669
                $this->current = $this->last_item_seen;
1670
                $this->index = $index;
1671
            }
1672
        } else {
1673
            if ($this->debug > 2) {
1674
                error_log('In learnpath::first() - No last item seen', 0);
1675
            }
1676
            $index = 0;
1677
            // Loop through all ordered items and stop at the first item that is
1678
            // not a directory *and* that has not been completed yet.
1679
            while (!empty($this->ordered_items[$index]) &&
1680
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1681
                (
1682
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1683
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1684
                ) && $index < $this->max_ordered_items) {
1685
                $index++;
1686
            }
1687
1688
            $this->last = $this->current;
1689
            // current is
1690
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1691
            $this->index = $index;
1692
            if ($this->debug > 2) {
1693
                error_log('$index '.$index);
1694
                error_log('In learnpath::first() - No last item seen');
1695
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1696
            }
1697
        }
1698
        if ($this->debug > 2) {
1699
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1700
        }
1701
    }
1702
1703
    /**
1704
     * Gets the js library from the database.
1705
     *
1706
     * @return string The name of the javascript library to be used
1707
     */
1708
    public function get_js_lib()
1709
    {
1710
        $lib = '';
1711
        if (!empty($this->js_lib)) {
1712
            $lib = $this->js_lib;
1713
        }
1714
1715
        return $lib;
1716
    }
1717
1718
    /**
1719
     * Gets the learnpath database ID.
1720
     *
1721
     * @return int Learnpath ID in the lp table
1722
     */
1723
    public function get_id()
1724
    {
1725
        if (!empty($this->lp_id)) {
1726
            return (int) $this->lp_id;
1727
        }
1728
1729
        return 0;
1730
    }
1731
1732
    /**
1733
     * Gets the last element URL.
1734
     *
1735
     * @return string URL to load into the viewer
1736
     */
1737
    public function get_last()
1738
    {
1739
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1740
        if (count($this->ordered_items) > 0) {
1741
            $this->index = count($this->ordered_items) - 1;
1742
1743
            return $this->ordered_items[$this->index];
1744
        }
1745
1746
        return false;
1747
    }
1748
1749
    /**
1750
     * Get the last element in the first level.
1751
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1752
     *
1753
     * @return mixed
1754
     */
1755
    public function getLastInFirstLevel()
1756
    {
1757
        try {
1758
            $lastId = Database::getManager()
1759
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1760
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1761
                ->setMaxResults(1)
1762
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1763
                ->getSingleScalarResult();
1764
1765
            return $lastId;
1766
        } catch (Exception $exception) {
1767
            return 0;
1768
        }
1769
    }
1770
1771
    /**
1772
     * Gets the navigation bar for the learnpath display screen.
1773
     *
1774
     * @param string $barId
1775
     *
1776
     * @return string The HTML string to use as a navigation bar
1777
     */
1778
    public function get_navigation_bar($barId = '')
1779
    {
1780
        if (empty($barId)) {
1781
            $barId = 'control-top';
1782
        }
1783
        $lpId = $this->lp_id;
1784
        $mycurrentitemid = $this->get_current_item_id();
1785
1786
        $reportingText = get_lang('Reporting');
1787
        $previousText = get_lang('Previous');
1788
        $nextText = get_lang('Next');
1789
        $fullScreenText = get_lang('Back to normal screen');
1790
1791
        $settings = api_get_configuration_value('lp_view_settings');
1792
        $display = isset($settings['display']) ? $settings['display'] : false;
1793
        $reportingIcon = '
1794
            <a class="icon-toolbar" 
1795
                id="stats_link"
1796
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
1797
                onclick="window.parent.API.save_asset(); return true;" 
1798
                target="content_name" title="'.$reportingText.'">
1799
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1800
            </a>';
1801
1802
        if (!empty($display)) {
1803
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1804
            if ($showReporting === false) {
1805
                $reportingIcon = '';
1806
            }
1807
        }
1808
1809
        $hideArrows = false;
1810
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1811
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1812
        }
1813
1814
        $previousIcon = '';
1815
        $nextIcon = '';
1816
        if ($hideArrows === false) {
1817
            $previousIcon = '
1818
                <a class="icon-toolbar" id="scorm-previous" href="#" 
1819
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1820
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1821
                </a>';
1822
1823
            $nextIcon = '
1824
                <a class="icon-toolbar" id="scorm-next" href="#" 
1825
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1826
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1827
                </a>';
1828
        }
1829
1830
        if ($this->mode === 'fullscreen') {
1831
            $navbar = '
1832
                  <span id="'.$barId.'" class="buttons">
1833
                    '.$reportingIcon.'
1834
                    '.$previousIcon.'                    
1835
                    '.$nextIcon.'
1836
                    <a class="icon-toolbar" id="view-embedded" 
1837
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1838
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1839
                    </a>
1840
                  </span>';
1841
        } else {
1842
            $navbar = '
1843
                 <span id="'.$barId.'" class="buttons text-right">
1844
                    '.$reportingIcon.'
1845
                    '.$previousIcon.'
1846
                    '.$nextIcon.'               
1847
                </span>';
1848
        }
1849
1850
        return $navbar;
1851
    }
1852
1853
    /**
1854
     * Gets the next resource in queue (url).
1855
     *
1856
     * @return string URL to load into the viewer
1857
     */
1858
    public function get_next_index()
1859
    {
1860
        // TODO
1861
        $index = $this->index;
1862
        $index++;
1863
        while (
1864
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1865
            $index < $this->max_ordered_items
1866
        ) {
1867
            $index++;
1868
            if ($index == $this->max_ordered_items) {
1869
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1870
                    return $this->index;
1871
                }
1872
1873
                return $index;
1874
            }
1875
        }
1876
        if (empty($this->ordered_items[$index])) {
1877
            return $this->index;
1878
        }
1879
1880
        return $index;
1881
    }
1882
1883
    /**
1884
     * Gets item_id for the next element.
1885
     *
1886
     * @return int Next item (DB) ID
1887
     */
1888
    public function get_next_item_id()
1889
    {
1890
        $new_index = $this->get_next_index();
1891
        if (!empty($new_index)) {
1892
            if (isset($this->ordered_items[$new_index])) {
1893
                return $this->ordered_items[$new_index];
1894
            }
1895
        }
1896
1897
        return 0;
1898
    }
1899
1900
    /**
1901
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1902
     *
1903
     * Generally, the package provided is in the form of a zip file, so the function
1904
     * has been written to test a zip file. If not a zip, the function will return the
1905
     * default return value: ''
1906
     *
1907
     * @param string $file_path the path to the file
1908
     * @param string $file_name the original name of the file
1909
     *
1910
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1911
     */
1912
    public static function get_package_type($file_path, $file_name)
1913
    {
1914
        // Get name of the zip file without the extension.
1915
        $file_info = pathinfo($file_name);
1916
        $extension = $file_info['extension']; // Extension only.
1917
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1918
                'dll',
1919
                'exe',
1920
            ])) {
1921
            return 'oogie';
1922
        }
1923
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1924
                'dll',
1925
                'exe',
1926
            ])) {
1927
            return 'woogie';
1928
        }
1929
1930
        $zipFile = new PclZip($file_path);
1931
        // Check the zip content (real size and file extension).
1932
        $zipContentArray = $zipFile->listContent();
1933
        $package_type = '';
1934
        $manifest = '';
1935
        $aicc_match_crs = 0;
1936
        $aicc_match_au = 0;
1937
        $aicc_match_des = 0;
1938
        $aicc_match_cst = 0;
1939
1940
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1941
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
1942
            foreach ($zipContentArray as $thisContent) {
1943
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1944
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1945
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
1946
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1947
                    $package_type = 'scorm';
1948
                    break; // Exit the foreach loop.
1949
                } elseif (
1950
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1951
                    in_array(
1952
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1953
                        ['crs', 'au', 'des', 'cst']
1954
                    )
1955
                ) {
1956
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1957
                    switch ($ext) {
1958
                        case 'crs':
1959
                            $aicc_match_crs = 1;
1960
                            break;
1961
                        case 'au':
1962
                            $aicc_match_au = 1;
1963
                            break;
1964
                        case 'des':
1965
                            $aicc_match_des = 1;
1966
                            break;
1967
                        case 'cst':
1968
                            $aicc_match_cst = 1;
1969
                            break;
1970
                        default:
1971
                            break;
1972
                    }
1973
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1974
                } else {
1975
                    $package_type = '';
1976
                }
1977
            }
1978
        }
1979
1980
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1981
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1982
            $package_type = 'aicc';
1983
        }
1984
1985
        // Try with chamilo course builder
1986
        if (empty($package_type)) {
1987
            $package_type = 'chamilo';
1988
        }
1989
1990
        return $package_type;
1991
    }
1992
1993
    /**
1994
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1995
     *
1996
     * @return string URL to load into the viewer
1997
     */
1998
    public function get_previous_index()
1999
    {
2000
        $index = $this->index;
2001
        if (isset($this->ordered_items[$index - 1])) {
2002
            $index--;
2003
            while (isset($this->ordered_items[$index]) &&
2004
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2005
            ) {
2006
                $index--;
2007
                if ($index < 0) {
2008
                    return $this->index;
2009
                }
2010
            }
2011
        }
2012
2013
        return $index;
2014
    }
2015
2016
    /**
2017
     * Gets item_id for the next element.
2018
     *
2019
     * @return int Previous item (DB) ID
2020
     */
2021
    public function get_previous_item_id()
2022
    {
2023
        $index = $this->get_previous_index();
2024
2025
        return $this->ordered_items[$index];
2026
    }
2027
2028
    /**
2029
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2030
     *
2031
     * @param int    $lpItemId
2032
     * @param string $autostart
2033
     *
2034
     * @return string The mediaplayer HTML
2035
     */
2036
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2037
    {
2038
        $course_id = api_get_course_int_id();
2039
        $_course = api_get_course_info();
2040
        if (empty($_course)) {
2041
            return '';
2042
        }
2043
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2044
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2045
        $lpItemId = (int) $lpItemId;
2046
2047
        /** @var learnpathItem $item */
2048
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2049
        $itemViewId = 0;
2050
        if ($item) {
2051
            $itemViewId = (int) $item->db_item_view_id;
2052
        }
2053
2054
        // Getting all the information about the item.
2055
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status 
2056
                FROM $tbl_lp_item as lpi
2057
                INNER JOIN $tbl_lp_item_view as lp_view
2058
                ON (lpi.iid = lp_view.lp_item_id)
2059
                WHERE
2060
                    lp_view.iid = $itemViewId AND
2061
                    lpi.iid = $lpItemId AND
2062
                    lp_view.c_id = $course_id";
2063
        $result = Database::query($sql);
2064
        $row = Database::fetch_assoc($result);
2065
        $output = '';
2066
2067
        if (!empty($row['audio'])) {
2068
            $list = $_SESSION['oLP']->get_toc();
2069
2070
            switch ($row['item_type']) {
2071
                case 'quiz':
2072
                    $type_quiz = false;
2073
                    foreach ($list as $toc) {
2074
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2075
                            $type_quiz = true;
2076
                        }
2077
                    }
2078
2079
                    if ($type_quiz) {
2080
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2081
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2082
                        } else {
2083
                            $autostart_audio = $autostart;
2084
                        }
2085
                    }
2086
                    break;
2087
                case TOOL_READOUT_TEXT:;
2088
                    $autostart_audio = 'false';
2089
                    break;
2090
                default:
2091
                    $autostart_audio = 'true';
2092
            }
2093
2094
            $courseInfo = api_get_course_info();
2095
            $audio = $row['audio'];
2096
2097
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2098
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2099
2100
            if (!file_exists($file)) {
2101
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2102
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2103
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2104
            }
2105
2106
            $player = Display::getMediaPlayer(
2107
                $file,
2108
                [
2109
                    'id' => 'lp_audio_media_player',
2110
                    'url' => $url,
2111
                    'autoplay' => $autostart_audio,
2112
                    'width' => '100%',
2113
                ]
2114
            );
2115
2116
            // The mp3 player.
2117
            $output = '<div id="container">';
2118
            $output .= $player;
2119
            $output .= '</div>';
2120
        }
2121
2122
        return $output;
2123
    }
2124
2125
    /**
2126
     * @param int   $studentId
2127
     * @param int   $prerequisite
2128
     * @param array $courseInfo
2129
     * @param int   $sessionId
2130
     *
2131
     * @return bool
2132
     */
2133
    public static function isBlockedByPrerequisite(
2134
        $studentId,
2135
        $prerequisite,
2136
        $courseInfo,
2137
        $sessionId
2138
    ) {
2139
        if (empty($courseInfo)) {
2140
            return false;
2141
        }
2142
2143
        $courseId = $courseInfo['real_id'];
2144
2145
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2146
        if ($allow) {
2147
            if (api_is_allowed_to_edit() ||
2148
                api_is_platform_admin(true) ||
2149
                api_is_drh() ||
2150
                api_is_coach($sessionId, $courseId, false)
2151
            ) {
2152
                return false;
2153
            }
2154
        }
2155
2156
        $isBlocked = false;
2157
        if (!empty($prerequisite)) {
2158
            $progress = self::getProgress(
2159
                $prerequisite,
2160
                $studentId,
2161
                $courseId,
2162
                $sessionId
2163
            );
2164
            if ($progress < 100) {
2165
                $isBlocked = true;
2166
            }
2167
2168
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2169
                // Block if it does not exceed minimum time
2170
                // Minimum time (in minutes) to pass the learning path
2171
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2172
2173
                if ($accumulateWorkTime > 0) {
2174
                    // Total time in course (sum of times in learning paths from course)
2175
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2176
2177
                    // Connect with the plugin_licences_course_session table
2178
                    // which indicates what percentage of the time applies
2179
                    // Minimum connection percentage
2180
                    $perc = 100;
2181
                    // Time from the course
2182
                    $tc = $accumulateWorkTimeTotal;
2183
2184
                    // Percentage of the learning paths
2185
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2186
                    // Minimum time for each learning path
2187
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2188
2189
                    // Spent time (in seconds) so far in the learning path
2190
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2191
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2192
2193
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2194
                        $isBlocked = true;
2195
                    }
2196
                }
2197
            }
2198
        }
2199
2200
        return $isBlocked;
2201
    }
2202
2203
    /**
2204
     * Checks if the learning path is visible for student after the progress
2205
     * of its prerequisite is completed, considering the time availability and
2206
     * the LP visibility.
2207
     *
2208
     * @param int   $lp_id
2209
     * @param int   $student_id
2210
     * @param array $courseInfo
2211
     * @param int   $sessionId
2212
     *
2213
     * @return bool
2214
     */
2215
    public static function is_lp_visible_for_student(
2216
        $lp_id,
2217
        $student_id,
2218
        $courseInfo = [],
2219
        $sessionId = 0
2220
    ) {
2221
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2222
        $lp_id = (int) $lp_id;
2223
        $sessionId = (int) $sessionId;
2224
2225
        if (empty($courseInfo)) {
2226
            return false;
2227
        }
2228
2229
        if (empty($sessionId)) {
2230
            $sessionId = api_get_session_id();
2231
        }
2232
2233
        $courseId = $courseInfo['real_id'];
2234
2235
        $itemInfo = api_get_item_property_info(
2236
            $courseId,
2237
            TOOL_LEARNPATH,
2238
            $lp_id,
2239
            $sessionId
2240
        );
2241
2242
        // If the item was deleted.
2243
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2244
            return false;
2245
        }
2246
2247
        // @todo remove this query and load the row info as a parameter
2248
        $table = Database::get_course_table(TABLE_LP_MAIN);
2249
        // Get current prerequisite
2250
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2251
                FROM $table
2252
                WHERE iid = $lp_id";
2253
        $rs = Database::query($sql);
2254
        $now = time();
2255
        if (Database::num_rows($rs) > 0) {
2256
            $row = Database::fetch_array($rs, 'ASSOC');
2257
2258
            if (!empty($row['category_id'])) {
2259
                $em = Database::getManager();
2260
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2261
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2262
                    return false;
2263
                }
2264
            }
2265
2266
            $prerequisite = $row['prerequisite'];
2267
            $is_visible = true;
2268
2269
            $isBlocked = self::isBlockedByPrerequisite(
2270
                $student_id,
2271
                $prerequisite,
2272
                $courseInfo,
2273
                $sessionId
2274
            );
2275
2276
            if ($isBlocked) {
2277
                $is_visible = false;
2278
            }
2279
2280
            // Also check the time availability of the LP
2281
            if ($is_visible) {
2282
                // Adding visibility restrictions
2283
                if (!empty($row['publicated_on'])) {
2284
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2285
                        $is_visible = false;
2286
                    }
2287
                }
2288
                // Blocking empty start times see BT#2800
2289
                global $_custom;
2290
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2291
                    $_custom['lps_hidden_when_no_start_date']
2292
                ) {
2293
                    if (empty($row['publicated_on'])) {
2294
                        $is_visible = false;
2295
                    }
2296
                }
2297
2298
                if (!empty($row['expired_on'])) {
2299
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2300
                        $is_visible = false;
2301
                    }
2302
                }
2303
            }
2304
2305
            if ($is_visible) {
2306
                $subscriptionSettings = self::getSubscriptionSettings();
2307
2308
                // Check if the subscription users/group to a LP is ON
2309
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2310
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2311
                ) {
2312
                    // Try group
2313
                    $is_visible = false;
2314
                    // Checking only the user visibility
2315
                    $userVisibility = api_get_item_visibility(
2316
                        $courseInfo,
2317
                        'learnpath',
2318
                        $row['id'],
2319
                        $sessionId,
2320
                        $student_id,
2321
                        'LearnpathSubscription'
2322
                    );
2323
2324
                    if ($userVisibility == 1) {
2325
                        $is_visible = true;
2326
                    } else {
2327
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2328
                        if (!empty($userGroups)) {
2329
                            foreach ($userGroups as $groupInfo) {
2330
                                $groupId = $groupInfo['iid'];
2331
                                $userVisibility = api_get_item_visibility(
2332
                                    $courseInfo,
2333
                                    'learnpath',
2334
                                    $row['id'],
2335
                                    $sessionId,
2336
                                    null,
2337
                                    'LearnpathSubscription',
2338
                                    $groupId
2339
                                );
2340
2341
                                if ($userVisibility == 1) {
2342
                                    $is_visible = true;
2343
                                    break;
2344
                                }
2345
                            }
2346
                        }
2347
                    }
2348
                }
2349
            }
2350
2351
            return $is_visible;
2352
        }
2353
2354
        return false;
2355
    }
2356
2357
    /**
2358
     * @param int $lpId
2359
     * @param int $userId
2360
     * @param int $courseId
2361
     * @param int $sessionId
2362
     *
2363
     * @return int
2364
     */
2365
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2366
    {
2367
        $lpId = (int) $lpId;
2368
        $userId = (int) $userId;
2369
        $courseId = (int) $courseId;
2370
        $sessionId = (int) $sessionId;
2371
2372
        $sessionCondition = api_get_session_condition($sessionId);
2373
        $table = Database::get_course_table(TABLE_LP_VIEW);
2374
        $sql = "SELECT progress FROM $table
2375
                WHERE
2376
                    c_id = $courseId AND
2377
                    lp_id = $lpId AND
2378
                    user_id = $userId $sessionCondition ";
2379
        $res = Database::query($sql);
2380
2381
        $progress = 0;
2382
        if (Database::num_rows($res) > 0) {
2383
            $row = Database::fetch_array($res);
2384
            $progress = (int) $row['progress'];
2385
        }
2386
2387
        return $progress;
2388
    }
2389
2390
    /**
2391
     * @param array $lpList
2392
     * @param int   $userId
2393
     * @param int   $courseId
2394
     * @param int   $sessionId
2395
     *
2396
     * @return array
2397
     */
2398
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2399
    {
2400
        $lpList = array_map('intval', $lpList);
2401
        if (empty($lpList)) {
2402
            return [];
2403
        }
2404
2405
        $lpList = implode("','", $lpList);
2406
2407
        $userId = (int) $userId;
2408
        $courseId = (int) $courseId;
2409
        $sessionId = (int) $sessionId;
2410
2411
        $sessionCondition = api_get_session_condition($sessionId);
2412
        $table = Database::get_course_table(TABLE_LP_VIEW);
2413
        $sql = "SELECT lp_id, progress FROM $table
2414
                WHERE
2415
                    c_id = $courseId AND
2416
                    lp_id IN ('".$lpList."') AND
2417
                    user_id = $userId $sessionCondition ";
2418
        $res = Database::query($sql);
2419
2420
        if (Database::num_rows($res) > 0) {
2421
            $list = [];
2422
            while ($row = Database::fetch_array($res)) {
2423
                $list[$row['lp_id']] = $row['progress'];
2424
            }
2425
2426
            return $list;
2427
        }
2428
2429
        return [];
2430
    }
2431
2432
    /**
2433
     * Displays a progress bar
2434
     * completed so far.
2435
     *
2436
     * @param int    $percentage Progress value to display
2437
     * @param string $text_add   Text to display near the progress value
2438
     *
2439
     * @return string HTML string containing the progress bar
2440
     */
2441
    public static function get_progress_bar($percentage = -1, $text_add = '')
2442
    {
2443
        $text = $percentage.$text_add;
2444
        $output = '<div class="progress">
2445
            <div id="progress_bar_value" 
2446
                class="progress-bar progress-bar-success" role="progressbar" 
2447
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2448
            '.$text.'
2449
            </div>
2450
        </div>';
2451
2452
        return $output;
2453
    }
2454
2455
    /**
2456
     * @param string $mode can be '%' or 'abs'
2457
     *                     otherwise this value will be used $this->progress_bar_mode
2458
     *
2459
     * @return string
2460
     */
2461
    public function getProgressBar($mode = null)
2462
    {
2463
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2464
2465
        return self::get_progress_bar($percentage, $text_add);
2466
    }
2467
2468
    /**
2469
     * Gets the progress bar info to display inside the progress bar.
2470
     * Also used by scorm_api.php.
2471
     *
2472
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2473
     *                     we display a number of completed elements per total elements
2474
     * @param int    $add  Additional steps to fake as completed
2475
     *
2476
     * @return array Percentage or number and symbol (% or /xx)
2477
     */
2478
    public function get_progress_bar_text($mode = '', $add = 0)
2479
    {
2480
        if (empty($mode)) {
2481
            $mode = $this->progress_bar_mode;
2482
        }
2483
        $total_items = $this->getTotalItemsCountWithoutDirs();
2484
        $completeItems = $this->get_complete_items_count();
2485
        if ($add != 0) {
2486
            $completeItems += $add;
2487
        }
2488
        $text = '';
2489
        if ($completeItems > $total_items) {
2490
            $completeItems = $total_items;
2491
        }
2492
        $percentage = 0;
2493
        if ($mode == '%') {
2494
            if ($total_items > 0) {
2495
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2496
            }
2497
            $percentage = number_format($percentage, 0);
2498
            $text = '%';
2499
        } elseif ($mode === 'abs') {
2500
            $percentage = $completeItems;
2501
            $text = '/'.$total_items;
2502
        }
2503
2504
        return [
2505
            $percentage,
2506
            $text,
2507
        ];
2508
    }
2509
2510
    /**
2511
     * Gets the progress bar mode.
2512
     *
2513
     * @return string The progress bar mode attribute
2514
     */
2515
    public function get_progress_bar_mode()
2516
    {
2517
        if (!empty($this->progress_bar_mode)) {
2518
            return $this->progress_bar_mode;
2519
        }
2520
2521
        return '%';
2522
    }
2523
2524
    /**
2525
     * Gets the learnpath theme (remote or local).
2526
     *
2527
     * @return string Learnpath theme
2528
     */
2529
    public function get_theme()
2530
    {
2531
        if (!empty($this->theme)) {
2532
            return $this->theme;
2533
        }
2534
2535
        return '';
2536
    }
2537
2538
    /**
2539
     * Gets the learnpath session id.
2540
     *
2541
     * @return int
2542
     */
2543
    public function get_lp_session_id()
2544
    {
2545
        if (!empty($this->lp_session_id)) {
2546
            return (int) $this->lp_session_id;
2547
        }
2548
2549
        return 0;
2550
    }
2551
2552
    /**
2553
     * Gets the learnpath image.
2554
     *
2555
     * @return string Web URL of the LP image
2556
     */
2557
    public function get_preview_image()
2558
    {
2559
        if (!empty($this->preview_image)) {
2560
            return $this->preview_image;
2561
        }
2562
2563
        return '';
2564
    }
2565
2566
    /**
2567
     * @param string $size
2568
     * @param string $path_type
2569
     *
2570
     * @return bool|string
2571
     */
2572
    public function get_preview_image_path($size = null, $path_type = 'web')
2573
    {
2574
        $preview_image = $this->get_preview_image();
2575
        if (isset($preview_image) && !empty($preview_image)) {
2576
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2577
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2578
2579
            if (isset($size)) {
2580
                $info = pathinfo($preview_image);
2581
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2582
2583
                if (file_exists($image_sys_path.$image_custom_size)) {
2584
                    if ($path_type == 'web') {
2585
                        return $image_path.$image_custom_size;
2586
                    } else {
2587
                        return $image_sys_path.$image_custom_size;
2588
                    }
2589
                }
2590
            } else {
2591
                if ($path_type == 'web') {
2592
                    return $image_path.$preview_image;
2593
                } else {
2594
                    return $image_sys_path.$preview_image;
2595
                }
2596
            }
2597
        }
2598
2599
        return false;
2600
    }
2601
2602
    /**
2603
     * Gets the learnpath author.
2604
     *
2605
     * @return string LP's author
2606
     */
2607
    public function get_author()
2608
    {
2609
        if (!empty($this->author)) {
2610
            return $this->author;
2611
        }
2612
2613
        return '';
2614
    }
2615
2616
    /**
2617
     * Gets hide table of contents.
2618
     *
2619
     * @return int
2620
     */
2621
    public function getHideTableOfContents()
2622
    {
2623
        return (int) $this->hide_toc_frame;
2624
    }
2625
2626
    /**
2627
     * Generate a new prerequisites string for a given item. If this item was a sco and
2628
     * its prerequisites were strings (instead of IDs), then transform those strings into
2629
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2630
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2631
     * same rule as the scormExport() method.
2632
     *
2633
     * @param int $item_id Item ID
2634
     *
2635
     * @return string Prerequisites string ready for the export as SCORM
2636
     */
2637
    public function get_scorm_prereq_string($item_id)
2638
    {
2639
        if ($this->debug > 0) {
2640
            error_log('In learnpath::get_scorm_prereq_string()');
2641
        }
2642
        if (!is_object($this->items[$item_id])) {
2643
            return false;
2644
        }
2645
        /** @var learnpathItem $oItem */
2646
        $oItem = $this->items[$item_id];
2647
        $prereq = $oItem->get_prereq_string();
2648
2649
        if (empty($prereq)) {
2650
            return '';
2651
        }
2652
        if (preg_match('/^\d+$/', $prereq) &&
2653
            isset($this->items[$prereq]) &&
2654
            is_object($this->items[$prereq])
2655
        ) {
2656
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2657
            // then simply return it (with the ITEM_ prefix).
2658
            //return 'ITEM_' . $prereq;
2659
            return $this->items[$prereq]->ref;
2660
        } else {
2661
            if (isset($this->refs_list[$prereq])) {
2662
                // It's a simple string item from which the ID can be found in the refs list,
2663
                // so we can transform it directly to an ID for export.
2664
                return $this->items[$this->refs_list[$prereq]]->ref;
2665
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2666
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2667
            } else {
2668
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2669
                // and replace them, one by one, by the internal IDs (chamilo db)
2670
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2671
                // by a space as well.
2672
                $find = [
2673
                    '&',
2674
                    '|',
2675
                    '~',
2676
                    '=',
2677
                    '<>',
2678
                    '{',
2679
                    '}',
2680
                    '*',
2681
                    '(',
2682
                    ')',
2683
                ];
2684
                $replace = [
2685
                    ' ',
2686
                    ' ',
2687
                    ' ',
2688
                    ' ',
2689
                    ' ',
2690
                    ' ',
2691
                    ' ',
2692
                    ' ',
2693
                    ' ',
2694
                    ' ',
2695
                ];
2696
                $prereq_mod = str_replace($find, $replace, $prereq);
2697
                $ids = explode(' ', $prereq_mod);
2698
                foreach ($ids as $id) {
2699
                    $id = trim($id);
2700
                    if (isset($this->refs_list[$id])) {
2701
                        $prereq = preg_replace(
2702
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2703
                            'ITEM_'.$this->refs_list[$id],
2704
                            $prereq
2705
                        );
2706
                    }
2707
                }
2708
2709
                return $prereq;
2710
            }
2711
        }
2712
    }
2713
2714
    /**
2715
     * Returns the XML DOM document's node.
2716
     *
2717
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2718
     * @param string   $id       The identifier to look for
2719
     *
2720
     * @return mixed The reference to the element found with that identifier. False if not found
2721
     */
2722
    public function get_scorm_xml_node(&$children, $id)
2723
    {
2724
        for ($i = 0; $i < $children->length; $i++) {
2725
            $item_temp = $children->item($i);
2726
            if ($item_temp->nodeName == 'item') {
2727
                if ($item_temp->getAttribute('identifier') == $id) {
2728
                    return $item_temp;
2729
                }
2730
            }
2731
            $subchildren = $item_temp->childNodes;
2732
            if ($subchildren && $subchildren->length > 0) {
2733
                $val = $this->get_scorm_xml_node($subchildren, $id);
2734
                if (is_object($val)) {
2735
                    return $val;
2736
                }
2737
            }
2738
        }
2739
2740
        return false;
2741
    }
2742
2743
    /**
2744
     * Gets the status list for all LP's items.
2745
     *
2746
     * @return array Array of [index] => [item ID => current status]
2747
     */
2748
    public function get_items_status_list()
2749
    {
2750
        $list = [];
2751
        foreach ($this->ordered_items as $item_id) {
2752
            $list[] = [
2753
                $item_id => $this->items[$item_id]->get_status(),
2754
            ];
2755
        }
2756
2757
        return $list;
2758
    }
2759
2760
    /**
2761
     * Return the number of interactions for the given learnpath Item View ID.
2762
     * This method can be used as static.
2763
     *
2764
     * @param int $lp_iv_id  Item View ID
2765
     * @param int $course_id course id
2766
     *
2767
     * @return int
2768
     */
2769
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2770
    {
2771
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2772
        $lp_iv_id = (int) $lp_iv_id;
2773
        $course_id = (int) $course_id;
2774
2775
        $sql = "SELECT count(*) FROM $table
2776
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2777
        $res = Database::query($sql);
2778
        $num = 0;
2779
        if (Database::num_rows($res)) {
2780
            $row = Database::fetch_array($res);
2781
            $num = $row[0];
2782
        }
2783
2784
        return $num;
2785
    }
2786
2787
    /**
2788
     * Return the interactions as an array for the given lp_iv_id.
2789
     * This method can be used as static.
2790
     *
2791
     * @param int $lp_iv_id Learnpath Item View ID
2792
     *
2793
     * @return array
2794
     *
2795
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2796
     */
2797
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2798
    {
2799
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2800
        $list = [];
2801
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2802
        $lp_iv_id = (int) $lp_iv_id;
2803
2804
        if (empty($lp_iv_id) || empty($course_id)) {
2805
            return [];
2806
        }
2807
2808
        $sql = "SELECT * FROM $table
2809
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2810
                ORDER BY order_id ASC";
2811
        $res = Database::query($sql);
2812
        $num = Database::num_rows($res);
2813
        if ($num > 0) {
2814
            $list[] = [
2815
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2816
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2817
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2818
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2819
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2820
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2821
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2822
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2823
                'student_response_formatted' => '',
2824
            ];
2825
            while ($row = Database::fetch_array($res)) {
2826
                $studentResponseFormatted = urldecode($row['student_response']);
2827
                $content_student_response = explode('__|', $studentResponseFormatted);
2828
                if (count($content_student_response) > 0) {
2829
                    if (count($content_student_response) >= 3) {
2830
                        // Pop the element off the end of array.
2831
                        array_pop($content_student_response);
2832
                    }
2833
                    $studentResponseFormatted = implode(',', $content_student_response);
2834
                }
2835
2836
                $list[] = [
2837
                    'order_id' => $row['order_id'] + 1,
2838
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2839
                    'type' => $row['interaction_type'],
2840
                    'time' => $row['completion_time'],
2841
                    'correct_responses' => '', // Hide correct responses from students.
2842
                    'student_response' => $row['student_response'],
2843
                    'result' => $row['result'],
2844
                    'latency' => $row['latency'],
2845
                    'student_response_formatted' => $studentResponseFormatted,
2846
                ];
2847
            }
2848
        }
2849
2850
        return $list;
2851
    }
2852
2853
    /**
2854
     * Return the number of objectives for the given learnpath Item View ID.
2855
     * This method can be used as static.
2856
     *
2857
     * @param int $lp_iv_id  Item View ID
2858
     * @param int $course_id Course ID
2859
     *
2860
     * @return int Number of objectives
2861
     */
2862
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2863
    {
2864
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2865
        $course_id = (int) $course_id;
2866
        $lp_iv_id = (int) $lp_iv_id;
2867
        $sql = "SELECT count(*) FROM $table
2868
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2869
        //@todo seems that this always returns 0
2870
        $res = Database::query($sql);
2871
        $num = 0;
2872
        if (Database::num_rows($res)) {
2873
            $row = Database::fetch_array($res);
2874
            $num = $row[0];
2875
        }
2876
2877
        return $num;
2878
    }
2879
2880
    /**
2881
     * Return the objectives as an array for the given lp_iv_id.
2882
     * This method can be used as static.
2883
     *
2884
     * @param int $lpItemViewId Learnpath Item View ID
2885
     * @param int $course_id
2886
     *
2887
     * @return array
2888
     *
2889
     * @todo    Translate labels
2890
     */
2891
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2892
    {
2893
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2894
        $lpItemViewId = (int) $lpItemViewId;
2895
2896
        if (empty($course_id) || empty($lpItemViewId)) {
2897
            return [];
2898
        }
2899
2900
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2901
        $sql = "SELECT * FROM $table
2902
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2903
                ORDER BY order_id ASC";
2904
        $res = Database::query($sql);
2905
        $num = Database::num_rows($res);
2906
        $list = [];
2907
        if ($num > 0) {
2908
            $list[] = [
2909
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2910
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2911
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2912
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2913
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2914
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2915
            ];
2916
            while ($row = Database::fetch_array($res)) {
2917
                $list[] = [
2918
                    'order_id' => $row['order_id'] + 1,
2919
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2920
                    'score_raw' => $row['score_raw'],
2921
                    'score_max' => $row['score_max'],
2922
                    'score_min' => $row['score_min'],
2923
                    'status' => $row['status'],
2924
                ];
2925
            }
2926
        }
2927
2928
        return $list;
2929
    }
2930
2931
    /**
2932
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2933
     * used by get_html_toc() to be ready to display.
2934
     *
2935
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2936
     */
2937
    public function get_toc()
2938
    {
2939
        $toc = [];
2940
        foreach ($this->ordered_items as $item_id) {
2941
            // TODO: Change this link generation and use new function instead.
2942
            $toc[] = [
2943
                'id' => $item_id,
2944
                'title' => $this->items[$item_id]->get_title(),
2945
                'status' => $this->items[$item_id]->get_status(),
2946
                'level' => $this->items[$item_id]->get_level(),
2947
                'type' => $this->items[$item_id]->get_type(),
2948
                'description' => $this->items[$item_id]->get_description(),
2949
                'path' => $this->items[$item_id]->get_path(),
2950
                'parent' => $this->items[$item_id]->get_parent(),
2951
            ];
2952
        }
2953
2954
        return $toc;
2955
    }
2956
2957
    /**
2958
     * Generate and return the table of contents for this learnpath. The JS
2959
     * table returned is used inside of scorm_api.php.
2960
     *
2961
     * @param string $varname
2962
     *
2963
     * @return string A JS array variable construction
2964
     */
2965
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2966
    {
2967
        $toc = $varname.' = new Array();';
2968
        foreach ($this->ordered_items as $item_id) {
2969
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2970
        }
2971
2972
        return $toc;
2973
    }
2974
2975
    /**
2976
     * Gets the learning path type.
2977
     *
2978
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2979
     *
2980
     * @return mixed Type ID or name, depending on the parameter
2981
     */
2982
    public function get_type($get_name = false)
2983
    {
2984
        $res = false;
2985
        if (!empty($this->type) && (!$get_name)) {
2986
            $res = $this->type;
2987
        }
2988
2989
        return $res;
2990
    }
2991
2992
    /**
2993
     * Gets the learning path type as static method.
2994
     *
2995
     * @param int $lp_id
2996
     *
2997
     * @return mixed Type ID or name, depending on the parameter
2998
     */
2999
    public static function get_type_static($lp_id = 0)
3000
    {
3001
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3002
        $lp_id = (int) $lp_id;
3003
        $sql = "SELECT lp_type FROM $tbl_lp
3004
                WHERE iid = $lp_id";
3005
        $res = Database::query($sql);
3006
        if ($res === false) {
3007
            return null;
3008
        }
3009
        if (Database::num_rows($res) <= 0) {
3010
            return null;
3011
        }
3012
        $row = Database::fetch_array($res);
3013
3014
        return $row['lp_type'];
3015
    }
3016
3017
    /**
3018
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3019
     * This method can be used as abstract and is recursive.
3020
     *
3021
     * @param int $lp        Learnpath ID
3022
     * @param int $parent    Parent ID of the items to look for
3023
     * @param int $course_id
3024
     *
3025
     * @return array Ordered list of item IDs (empty array on error)
3026
     */
3027
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3028
    {
3029
        if (empty($course_id)) {
3030
            $course_id = api_get_course_int_id();
3031
        } else {
3032
            $course_id = (int) $course_id;
3033
        }
3034
        $list = [];
3035
3036
        if (empty($lp)) {
3037
            return $list;
3038
        }
3039
3040
        $lp = (int) $lp;
3041
        $parent = (int) $parent;
3042
3043
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3044
        $sql = "SELECT iid FROM $tbl_lp_item
3045
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3046
                ORDER BY display_order";
3047
3048
        $res = Database::query($sql);
3049
        while ($row = Database::fetch_array($res)) {
3050
            $sublist = self::get_flat_ordered_items_list(
3051
                $lp,
3052
                $row['iid'],
3053
                $course_id
3054
            );
3055
            $list[] = $row['iid'];
3056
            foreach ($sublist as $item) {
3057
                $list[] = $item;
3058
            }
3059
        }
3060
3061
        return $list;
3062
    }
3063
3064
    /**
3065
     * @return array
3066
     */
3067
    public static function getChapterTypes()
3068
    {
3069
        return [
3070
            'dir',
3071
        ];
3072
    }
3073
3074
    /**
3075
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3076
     *
3077
     * @param $tree
3078
     *
3079
     * @return array HTML TOC ready to display
3080
     */
3081
    public function getParentToc($tree)
3082
    {
3083
        if (empty($tree)) {
3084
            $tree = $this->get_toc();
3085
        }
3086
        $dirTypes = self::getChapterTypes();
3087
        $myCurrentId = $this->get_current_item_id();
3088
        $listParent = [];
3089
        $listChildren = [];
3090
        $listNotParent = [];
3091
        $list = [];
3092
        foreach ($tree as $subtree) {
3093
            if (in_array($subtree['type'], $dirTypes)) {
3094
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3095
                $subtree['children'] = $listChildren;
3096
                if (!empty($subtree['children'])) {
3097
                    foreach ($subtree['children'] as $subItem) {
3098
                        if ($subItem['id'] == $this->current) {
3099
                            $subtree['parent_current'] = 'in';
3100
                            $subtree['current'] = 'on';
3101
                        }
3102
                    }
3103
                }
3104
                $listParent[] = $subtree;
3105
            }
3106
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3107
                $classStatus = [
3108
                    'not attempted' => 'scorm_not_attempted',
3109
                    'incomplete' => 'scorm_not_attempted',
3110
                    'failed' => 'scorm_failed',
3111
                    'completed' => 'scorm_completed',
3112
                    'passed' => 'scorm_completed',
3113
                    'succeeded' => 'scorm_completed',
3114
                    'browsed' => 'scorm_completed',
3115
                ];
3116
3117
                if (isset($classStatus[$subtree['status']])) {
3118
                    $cssStatus = $classStatus[$subtree['status']];
3119
                }
3120
3121
                $title = Security::remove_XSS($subtree['title']);
3122
                unset($subtree['title']);
3123
3124
                if (empty($title)) {
3125
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3126
                }
3127
                $classStyle = null;
3128
                if ($subtree['id'] == $this->current) {
3129
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3130
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3131
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3132
                }
3133
                $subtree['title'] = $title;
3134
                $subtree['class'] = $classStyle.' '.$cssStatus;
3135
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3136
                $subtree['current_id'] = $myCurrentId;
3137
                $listNotParent[] = $subtree;
3138
            }
3139
        }
3140
3141
        $list['are_parents'] = $listParent;
3142
        $list['not_parents'] = $listNotParent;
3143
3144
        return $list;
3145
    }
3146
3147
    /**
3148
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3149
     *
3150
     * @param array $tree
3151
     * @param int   $id
3152
     * @param bool  $parent
3153
     *
3154
     * @return array HTML TOC ready to display
3155
     */
3156
    public function getChildrenToc($tree, $id, $parent = true)
3157
    {
3158
        if (empty($tree)) {
3159
            $tree = $this->get_toc();
3160
        }
3161
3162
        $dirTypes = self::getChapterTypes();
3163
        $currentItemId = $this->get_current_item_id();
3164
        $list = [];
3165
        $classStatus = [
3166
            'not attempted' => 'scorm_not_attempted',
3167
            'incomplete' => 'scorm_not_attempted',
3168
            'failed' => 'scorm_failed',
3169
            'completed' => 'scorm_completed',
3170
            'passed' => 'scorm_completed',
3171
            'succeeded' => 'scorm_completed',
3172
            'browsed' => 'scorm_completed',
3173
        ];
3174
3175
        foreach ($tree as $subtree) {
3176
            $subtree['tree'] = null;
3177
3178
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3179
                if ($subtree['id'] == $this->current) {
3180
                    $subtree['current'] = 'active';
3181
                } else {
3182
                    $subtree['current'] = null;
3183
                }
3184
                if (isset($classStatus[$subtree['status']])) {
3185
                    $cssStatus = $classStatus[$subtree['status']];
3186
                }
3187
3188
                $title = Security::remove_XSS($subtree['title']);
3189
                unset($subtree['title']);
3190
                if (empty($title)) {
3191
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3192
                }
3193
3194
                $classStyle = null;
3195
                if ($subtree['id'] == $this->current) {
3196
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3197
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3198
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3199
                }
3200
3201
                if (in_array($subtree['type'], $dirTypes)) {
3202
                    $subtree['title'] = stripslashes($title);
3203
                } else {
3204
                    $subtree['title'] = $title;
3205
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3206
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3207
                    $subtree['current_id'] = $currentItemId;
3208
                }
3209
                $list[] = $subtree;
3210
            }
3211
        }
3212
3213
        return $list;
3214
    }
3215
3216
    /**
3217
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3218
     *
3219
     * @param array $toc_list
3220
     *
3221
     * @return array HTML TOC ready to display
3222
     */
3223
    public function getListArrayToc($toc_list = [])
3224
    {
3225
        if (empty($toc_list)) {
3226
            $toc_list = $this->get_toc();
3227
        }
3228
        // Temporary variables.
3229
        $currentItemId = $this->get_current_item_id();
3230
        $list = [];
3231
        $arrayList = [];
3232
        $classStatus = [
3233
            'not attempted' => 'scorm_not_attempted',
3234
            'incomplete' => 'scorm_not_attempted',
3235
            'failed' => 'scorm_failed',
3236
            'completed' => 'scorm_completed',
3237
            'passed' => 'scorm_completed',
3238
            'succeeded' => 'scorm_completed',
3239
            'browsed' => 'scorm_completed',
3240
        ];
3241
3242
        foreach ($toc_list as $item) {
3243
            $list['id'] = $item['id'];
3244
            $list['status'] = $item['status'];
3245
            $cssStatus = null;
3246
3247
            if (isset($classStatus[$item['status']])) {
3248
                $cssStatus = $classStatus[$item['status']];
3249
            }
3250
3251
            $classStyle = ' ';
3252
            $dirTypes = self::getChapterTypes();
3253
3254
            if (in_array($item['type'], $dirTypes)) {
3255
                $classStyle = 'scorm_item_section ';
3256
            }
3257
            if ($item['id'] == $this->current) {
3258
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3259
            } elseif (!in_array($item['type'], $dirTypes)) {
3260
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3261
            }
3262
            $title = $item['title'];
3263
            if (empty($title)) {
3264
                $title = self::rl_get_resource_name(
3265
                    api_get_course_id(),
3266
                    $this->get_id(),
3267
                    $item['id']
3268
                );
3269
            }
3270
            $title = Security::remove_XSS($item['title']);
3271
3272
            if (empty($item['description'])) {
3273
                $list['description'] = $title;
3274
            } else {
3275
                $list['description'] = $item['description'];
3276
            }
3277
3278
            $list['class'] = $classStyle.' '.$cssStatus;
3279
            $list['level'] = $item['level'];
3280
            $list['type'] = $item['type'];
3281
3282
            if (in_array($item['type'], $dirTypes)) {
3283
                $list['css_level'] = 'level_'.$item['level'];
3284
            } else {
3285
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3286
            }
3287
3288
            if (in_array($item['type'], $dirTypes)) {
3289
                $list['title'] = stripslashes($title);
3290
            } else {
3291
                $list['title'] = stripslashes($title);
3292
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3293
                $list['current_id'] = $currentItemId;
3294
            }
3295
            $arrayList[] = $list;
3296
        }
3297
3298
        return $arrayList;
3299
    }
3300
3301
    /**
3302
     * Returns an HTML-formatted string ready to display with teacher buttons
3303
     * in LP view menu.
3304
     *
3305
     * @return string HTML TOC ready to display
3306
     */
3307
    public function get_teacher_toc_buttons()
3308
    {
3309
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3310
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3311
        $html = '';
3312
        if ($isAllow && $hideIcons == false) {
3313
            if ($this->get_lp_session_id() == api_get_session_id()) {
3314
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3315
                $html .= '<div class="btn-group">';
3316
                $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'>".
3317
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3318
                $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'>".
3319
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3320
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3321
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3322
                $html .= '</div>';
3323
                $html .= '</div>';
3324
            }
3325
        }
3326
3327
        return $html;
3328
    }
3329
3330
    /**
3331
     * Gets the learnpath maker name - generally the editor's name.
3332
     *
3333
     * @return string Learnpath maker name
3334
     */
3335
    public function get_maker()
3336
    {
3337
        if (!empty($this->maker)) {
3338
            return $this->maker;
3339
        }
3340
3341
        return '';
3342
    }
3343
3344
    /**
3345
     * Gets the learnpath name/title.
3346
     *
3347
     * @return string Learnpath name/title
3348
     */
3349
    public function get_name()
3350
    {
3351
        if (!empty($this->name)) {
3352
            return $this->name;
3353
        }
3354
3355
        return 'N/A';
3356
    }
3357
3358
    /**
3359
     * @return string
3360
     */
3361
    public function getNameNoTags()
3362
    {
3363
        return strip_tags($this->get_name());
3364
    }
3365
3366
    /**
3367
     * Gets a link to the resource from the present location, depending on item ID.
3368
     *
3369
     * @param string $type         Type of link expected
3370
     * @param int    $item_id      Learnpath item ID
3371
     * @param bool   $provided_toc
3372
     *
3373
     * @return string $provided_toc Link to the lp_item resource
3374
     */
3375
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3376
    {
3377
        $course_id = $this->get_course_int_id();
3378
        $item_id = (int) $item_id;
3379
3380
        if (empty($item_id)) {
3381
            $item_id = $this->get_current_item_id();
3382
3383
            if (empty($item_id)) {
3384
                //still empty, this means there was no item_id given and we are not in an object context or
3385
                //the object property is empty, return empty link
3386
                $this->first();
3387
3388
                return '';
3389
            }
3390
        }
3391
3392
        $file = '';
3393
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3394
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3395
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3396
3397
        $sql = "SELECT
3398
                    l.lp_type as ltype,
3399
                    l.path as lpath,
3400
                    li.item_type as litype,
3401
                    li.path as lipath,
3402
                    li.parameters as liparams
3403
        		FROM $lp_table l
3404
                INNER JOIN $lp_item_table li
3405
                ON (li.lp_id = l.iid)
3406
        		WHERE
3407
        		    li.iid = $item_id
3408
        		";
3409
        $res = Database::query($sql);
3410
        if (Database::num_rows($res) > 0) {
3411
            $row = Database::fetch_array($res);
3412
            $lp_type = $row['ltype'];
3413
            $lp_path = $row['lpath'];
3414
            $lp_item_type = $row['litype'];
3415
            $lp_item_path = $row['lipath'];
3416
            $lp_item_params = $row['liparams'];
3417
3418
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3419
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3420
            }
3421
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3422
            if ($type === 'http') {
3423
                //web path
3424
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3425
            } else {
3426
                $course_path = $sys_course_path; //system path
3427
            }
3428
3429
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3430
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3431
            if (in_array(
3432
                $lp_item_type,
3433
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3434
            )
3435
            ) {
3436
                $lp_type = 1;
3437
            }
3438
3439
            // Now go through the specific cases to get the end of the path
3440
            // @todo Use constants instead of int values.
3441
            switch ($lp_type) {
3442
                case 1:
3443
                    $file = self::rl_get_resource_link_for_learnpath(
3444
                        $course_id,
3445
                        $this->get_id(),
3446
                        $item_id,
3447
                        $this->get_view_id()
3448
                    );
3449
                    switch ($lp_item_type) {
3450
                        case 'document':
3451
                            // Shows a button to download the file instead of just downloading the file directly.
3452
                            $documentPathInfo = pathinfo($file);
3453
                            if (isset($documentPathInfo['extension'])) {
3454
                                $parsed = parse_url($documentPathInfo['extension']);
3455
                                if (isset($parsed['path'])) {
3456
                                    $extension = $parsed['path'];
3457
                                    $extensionsToDownload = [
3458
                                        'zip',
3459
                                        'ppt',
3460
                                        'pptx',
3461
                                        'ods',
3462
                                        'xlsx',
3463
                                        'xls',
3464
                                        'csv',
3465
                                        'doc',
3466
                                        'docx',
3467
                                        'dot',
3468
                                    ];
3469
3470
                                    if (in_array($extension, $extensionsToDownload)) {
3471
                                        $file = api_get_path(WEB_CODE_PATH).
3472
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3473
                                    }
3474
                                }
3475
                            }
3476
                            break;
3477
                        case 'dir':
3478
                            $file = 'lp_content.php?type=dir';
3479
                            break;
3480
                        case 'link':
3481
                            if (Link::is_youtube_link($file)) {
3482
                                $src = Link::get_youtube_video_id($file);
3483
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3484
                            } elseif (Link::isVimeoLink($file)) {
3485
                                $src = Link::getVimeoLinkId($file);
3486
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3487
                            } else {
3488
                                // If the current site is HTTPS and the link is
3489
                                // HTTP, browsers will refuse opening the link
3490
                                $urlId = api_get_current_access_url_id();
3491
                                $url = api_get_access_url($urlId, false);
3492
                                $protocol = substr($url['url'], 0, 5);
3493
                                if ($protocol === 'https') {
3494
                                    $linkProtocol = substr($file, 0, 5);
3495
                                    if ($linkProtocol === 'http:') {
3496
                                        //this is the special intervention case
3497
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3498
                                    }
3499
                                }
3500
                            }
3501
                            break;
3502
                        case 'quiz':
3503
                            // Check how much attempts of a exercise exits in lp
3504
                            $lp_item_id = $this->get_current_item_id();
3505
                            $lp_view_id = $this->get_view_id();
3506
3507
                            $prevent_reinit = null;
3508
                            if (isset($this->items[$this->current])) {
3509
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3510
                            }
3511
3512
                            if (empty($provided_toc)) {
3513
                                if ($this->debug > 0) {
3514
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3515
                                }
3516
                                $list = $this->get_toc();
3517
                            } else {
3518
                                if ($this->debug > 0) {
3519
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3520
                                }
3521
                                $list = $provided_toc;
3522
                            }
3523
3524
                            $type_quiz = false;
3525
                            foreach ($list as $toc) {
3526
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3527
                                    $type_quiz = true;
3528
                                }
3529
                            }
3530
3531
                            if ($type_quiz) {
3532
                                $lp_item_id = (int) $lp_item_id;
3533
                                $lp_view_id = (int) $lp_view_id;
3534
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3535
                                        WHERE
3536
                                            c_id = $course_id AND
3537
                                            lp_item_id='".$lp_item_id."' AND
3538
                                            lp_view_id ='".$lp_view_id."' AND
3539
                                            status='completed'";
3540
                                $result = Database::query($sql);
3541
                                $row_count = Database:: fetch_row($result);
3542
                                $count_item_view = (int) $row_count[0];
3543
                                $not_multiple_attempt = 0;
3544
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3545
                                    $not_multiple_attempt = 1;
3546
                                }
3547
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3548
                            }
3549
                            break;
3550
                    }
3551
3552
                    $tmp_array = explode('/', $file);
3553
                    $document_name = $tmp_array[count($tmp_array) - 1];
3554
                    if (strpos($document_name, '_DELETED_')) {
3555
                        $file = 'blank.php?error=document_deleted';
3556
                    }
3557
                    break;
3558
                case 2:
3559
                    if ($this->debug > 2) {
3560
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3561
                    }
3562
3563
                    if ($lp_item_type != 'dir') {
3564
                        // Quite complex here:
3565
                        // We want to make sure 'http://' (and similar) links can
3566
                        // be loaded as is (withouth the Chamilo path in front) but
3567
                        // some contents use this form: resource.htm?resource=http://blablabla
3568
                        // which means we have to find a protocol at the path's start, otherwise
3569
                        // it should not be considered as an external URL.
3570
                        // if ($this->prerequisites_match($item_id)) {
3571
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3572
                            if ($this->debug > 2) {
3573
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3574
                            }
3575
                            // Distant url, return as is.
3576
                            $file = $lp_item_path;
3577
                        } else {
3578
                            if ($this->debug > 2) {
3579
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3580
                            }
3581
                            // Prevent getting untranslatable urls.
3582
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3583
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3584
                            // Prepare the path.
3585
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3586
                            // TODO: Fix this for urls with protocol header.
3587
                            $file = str_replace('//', '/', $file);
3588
                            $file = str_replace(':/', '://', $file);
3589
                            if (substr($lp_path, -1) == '/') {
3590
                                $lp_path = substr($lp_path, 0, -1);
3591
                            }
3592
3593
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3594
                                // if file not found.
3595
                                $decoded = html_entity_decode($lp_item_path);
3596
                                list($decoded) = explode('?', $decoded);
3597
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3598
                                    $file = self::rl_get_resource_link_for_learnpath(
3599
                                        $course_id,
3600
                                        $this->get_id(),
3601
                                        $item_id,
3602
                                        $this->get_view_id()
3603
                                    );
3604
                                    if (empty($file)) {
3605
                                        $file = 'blank.php?error=document_not_found';
3606
                                    } else {
3607
                                        $tmp_array = explode('/', $file);
3608
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3609
                                        if (strpos($document_name, '_DELETED_')) {
3610
                                            $file = 'blank.php?error=document_deleted';
3611
                                        } else {
3612
                                            $file = 'blank.php?error=document_not_found';
3613
                                        }
3614
                                    }
3615
                                } else {
3616
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3617
                                }
3618
                            }
3619
                        }
3620
3621
                        // We want to use parameters if they were defined in the imsmanifest
3622
                        if (strpos($file, 'blank.php') === false) {
3623
                            $lp_item_params = ltrim($lp_item_params, '?');
3624
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3625
                        }
3626
                    } else {
3627
                        $file = 'lp_content.php?type=dir';
3628
                    }
3629
                    break;
3630
                case 3:
3631
                    if ($this->debug > 2) {
3632
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3633
                    }
3634
                    // Formatting AICC HACP append URL.
3635
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3636
                    if (!empty($lp_item_params)) {
3637
                        $aicc_append .= $lp_item_params.'&';
3638
                    }
3639
                    if ($lp_item_type != 'dir') {
3640
                        // Quite complex here:
3641
                        // We want to make sure 'http://' (and similar) links can
3642
                        // be loaded as is (withouth the Chamilo path in front) but
3643
                        // some contents use this form: resource.htm?resource=http://blablabla
3644
                        // which means we have to find a protocol at the path's start, otherwise
3645
                        // it should not be considered as an external URL.
3646
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3647
                            if ($this->debug > 2) {
3648
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3649
                            }
3650
                            // Distant url, return as is.
3651
                            $file = $lp_item_path;
3652
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3653
                            /*
3654
                            if (stristr($file,'<servername>') !== false) {
3655
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3656
                            }
3657
                            */
3658
                            if (stripos($file, '<servername>') !== false) {
3659
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3660
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3661
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3662
                            }
3663
3664
                            $file .= $aicc_append;
3665
                        } else {
3666
                            if ($this->debug > 2) {
3667
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3668
                            }
3669
                            // Prevent getting untranslatable urls.
3670
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3671
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3672
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3673
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3674
                            // TODO: Fix this for urls with protocol header.
3675
                            $file = str_replace('//', '/', $file);
3676
                            $file = str_replace(':/', '://', $file);
3677
                            $file .= $aicc_append;
3678
                        }
3679
                    } else {
3680
                        $file = 'lp_content.php?type=dir';
3681
                    }
3682
                    break;
3683
                case 4:
3684
                    break;
3685
                default:
3686
                    break;
3687
            }
3688
            // Replace &amp; by & because &amp; will break URL with params
3689
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3690
        }
3691
        if ($this->debug > 2) {
3692
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3693
        }
3694
3695
        return $file;
3696
    }
3697
3698
    /**
3699
     * Gets the latest usable view or generate a new one.
3700
     *
3701
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3702
     *
3703
     * @return int DB lp_view id
3704
     */
3705
    public function get_view($attempt_num = 0)
3706
    {
3707
        $search = '';
3708
        // Use $attempt_num to enable multi-views management (disabled so far).
3709
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3710
            $search = 'AND view_count = '.$attempt_num;
3711
        }
3712
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3713
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3714
3715
        $course_id = api_get_course_int_id();
3716
        $sessionId = api_get_session_id();
3717
3718
        $sql = "SELECT iid, view_count FROM $lp_view_table
3719
        		WHERE
3720
        		    c_id = $course_id AND
3721
        		    lp_id = ".$this->get_id()." AND
3722
        		    user_id = ".$this->get_user_id()." AND
3723
        		    session_id = $sessionId
3724
        		    $search
3725
                ORDER BY view_count DESC";
3726
        $res = Database::query($sql);
3727
        if (Database::num_rows($res) > 0) {
3728
            $row = Database::fetch_array($res);
3729
            $this->lp_view_id = $row['iid'];
3730
        } elseif (!api_is_invitee()) {
3731
            // There is no database record, create one.
3732
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3733
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3734
            Database::query($sql);
3735
            $id = Database::insert_id();
3736
            $this->lp_view_id = $id;
3737
3738
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3739
            Database::query($sql);
3740
        }
3741
3742
        return $this->lp_view_id;
3743
    }
3744
3745
    /**
3746
     * Gets the current view id.
3747
     *
3748
     * @return int View ID (from lp_view)
3749
     */
3750
    public function get_view_id()
3751
    {
3752
        if (!empty($this->lp_view_id)) {
3753
            return (int) $this->lp_view_id;
3754
        }
3755
3756
        return 0;
3757
    }
3758
3759
    /**
3760
     * Gets the update queue.
3761
     *
3762
     * @return array Array containing IDs of items to be updated by JavaScript
3763
     */
3764
    public function get_update_queue()
3765
    {
3766
        return $this->update_queue;
3767
    }
3768
3769
    /**
3770
     * Gets the user ID.
3771
     *
3772
     * @return int User ID
3773
     */
3774
    public function get_user_id()
3775
    {
3776
        if (!empty($this->user_id)) {
3777
            return (int) $this->user_id;
3778
        }
3779
3780
        return false;
3781
    }
3782
3783
    /**
3784
     * Checks if any of the items has an audio element attached.
3785
     *
3786
     * @return bool True or false
3787
     */
3788
    public function has_audio()
3789
    {
3790
        $has = false;
3791
        foreach ($this->items as $i => $item) {
3792
            if (!empty($this->items[$i]->audio)) {
3793
                $has = true;
3794
                break;
3795
            }
3796
        }
3797
3798
        return $has;
3799
    }
3800
3801
    /**
3802
     * Moves an item up and down at its level.
3803
     *
3804
     * @param int    $id        Item to move up and down
3805
     * @param string $direction Direction 'up' or 'down'
3806
     *
3807
     * @return bool|int
3808
     */
3809
    public function move_item($id, $direction)
3810
    {
3811
        $course_id = api_get_course_int_id();
3812
        if (empty($id) || empty($direction)) {
3813
            return false;
3814
        }
3815
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3816
        $sql_sel = "SELECT *
3817
                    FROM $tbl_lp_item
3818
                    WHERE
3819
                        iid = $id
3820
                    ";
3821
        $res_sel = Database::query($sql_sel);
3822
        // Check if elem exists.
3823
        if (Database::num_rows($res_sel) < 1) {
3824
            return false;
3825
        }
3826
        // Gather data.
3827
        $row = Database::fetch_array($res_sel);
3828
        $previous = $row['previous_item_id'];
3829
        $next = $row['next_item_id'];
3830
        $display = $row['display_order'];
3831
        $parent = $row['parent_item_id'];
3832
        $lp = $row['lp_id'];
3833
        // Update the item (switch with previous/next one).
3834
        switch ($direction) {
3835
            case 'up':
3836
                if ($display > 1) {
3837
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3838
                                 WHERE iid = $previous";
3839
                    $res_sel2 = Database::query($sql_sel2);
3840
                    if (Database::num_rows($res_sel2) < 1) {
3841
                        $previous_previous = 0;
3842
                    }
3843
                    // Gather data.
3844
                    $row2 = Database::fetch_array($res_sel2);
3845
                    $previous_previous = $row2['previous_item_id'];
3846
                    // Update previous_previous item (switch "next" with current).
3847
                    if ($previous_previous != 0) {
3848
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3849
                                        next_item_id = $id
3850
                                    WHERE iid = $previous_previous";
3851
                        Database::query($sql_upd2);
3852
                    }
3853
                    // Update previous item (switch with current).
3854
                    if ($previous != 0) {
3855
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3856
                                    next_item_id = $next,
3857
                                    previous_item_id = $id,
3858
                                    display_order = display_order +1
3859
                                    WHERE iid = $previous";
3860
                        Database::query($sql_upd2);
3861
                    }
3862
3863
                    // Update current item (switch with previous).
3864
                    if ($id != 0) {
3865
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3866
                                        next_item_id = $previous,
3867
                                        previous_item_id = $previous_previous,
3868
                                        display_order = display_order-1
3869
                                    WHERE c_id = ".$course_id." AND id = $id";
3870
                        Database::query($sql_upd2);
3871
                    }
3872
                    // Update next item (new previous item).
3873
                    if (!empty($next)) {
3874
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3875
                                     WHERE iid = $next";
3876
                        Database::query($sql_upd2);
3877
                    }
3878
                    $display = $display - 1;
3879
                }
3880
                break;
3881
            case 'down':
3882
                if ($next != 0) {
3883
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3884
                                 WHERE iid = $next";
3885
                    $res_sel2 = Database::query($sql_sel2);
3886
                    if (Database::num_rows($res_sel2) < 1) {
3887
                        $next_next = 0;
3888
                    }
3889
                    // Gather data.
3890
                    $row2 = Database::fetch_array($res_sel2);
3891
                    $next_next = $row2['next_item_id'];
3892
                    // Update previous item (switch with current).
3893
                    if ($previous != 0) {
3894
                        $sql_upd2 = "UPDATE $tbl_lp_item
3895
                                     SET next_item_id = $next
3896
                                     WHERE iid = $previous";
3897
                        Database::query($sql_upd2);
3898
                    }
3899
                    // Update current item (switch with previous).
3900
                    if ($id != 0) {
3901
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3902
                                     previous_item_id = $next,
3903
                                     next_item_id = $next_next,
3904
                                     display_order = display_order + 1
3905
                                     WHERE iid = $id";
3906
                        Database::query($sql_upd2);
3907
                    }
3908
3909
                    // Update next item (new previous item).
3910
                    if ($next != 0) {
3911
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3912
                                     previous_item_id = $previous,
3913
                                     next_item_id = $id,
3914
                                     display_order = display_order-1
3915
                                     WHERE iid = $next";
3916
                        Database::query($sql_upd2);
3917
                    }
3918
3919
                    // Update next_next item (switch "previous" with current).
3920
                    if ($next_next != 0) {
3921
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3922
                                     previous_item_id = $id
3923
                                     WHERE iid = $next_next";
3924
                        Database::query($sql_upd2);
3925
                    }
3926
                    $display = $display + 1;
3927
                }
3928
                break;
3929
            default:
3930
                return false;
3931
        }
3932
3933
        return $display;
3934
    }
3935
3936
    /**
3937
     * Move a LP up (display_order).
3938
     *
3939
     * @param int $lp_id      Learnpath ID
3940
     * @param int $categoryId Category ID
3941
     *
3942
     * @return bool
3943
     */
3944
    public static function move_up($lp_id, $categoryId = 0)
3945
    {
3946
        $courseId = api_get_course_int_id();
3947
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3948
3949
        $categoryCondition = '';
3950
        if (!empty($categoryId)) {
3951
            $categoryId = (int) $categoryId;
3952
            $categoryCondition = " AND category_id = $categoryId";
3953
        }
3954
        $sql = "SELECT * FROM $lp_table
3955
                WHERE c_id = $courseId
3956
                $categoryCondition
3957
                ORDER BY display_order";
3958
        $res = Database::query($sql);
3959
        if ($res === false) {
3960
            return false;
3961
        }
3962
3963
        $lps = [];
3964
        $lp_order = [];
3965
        $num = Database::num_rows($res);
3966
        // First check the order is correct, globally (might be wrong because
3967
        // of versions < 1.8.4)
3968
        if ($num > 0) {
3969
            $i = 1;
3970
            while ($row = Database::fetch_array($res)) {
3971
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3972
                    $sql = "UPDATE $lp_table SET display_order = $i
3973
                            WHERE iid = ".$row['iid'];
3974
                    Database::query($sql);
3975
                }
3976
                $row['display_order'] = $i;
3977
                $lps[$row['iid']] = $row;
3978
                $lp_order[$i] = $row['iid'];
3979
                $i++;
3980
            }
3981
        }
3982
        if ($num > 1) { // If there's only one element, no need to sort.
3983
            $order = $lps[$lp_id]['display_order'];
3984
            if ($order > 1) { // If it's the first element, no need to move up.
3985
                $sql = "UPDATE $lp_table SET display_order = $order
3986
                        WHERE iid = ".$lp_order[$order - 1];
3987
                Database::query($sql);
3988
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3989
                        WHERE iid = $lp_id";
3990
                Database::query($sql);
3991
            }
3992
        }
3993
3994
        return true;
3995
    }
3996
3997
    /**
3998
     * Move a learnpath down (display_order).
3999
     *
4000
     * @param int $lp_id      Learnpath ID
4001
     * @param int $categoryId Category ID
4002
     *
4003
     * @return bool
4004
     */
4005
    public static function move_down($lp_id, $categoryId = 0)
4006
    {
4007
        $courseId = api_get_course_int_id();
4008
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4009
4010
        $categoryCondition = '';
4011
        if (!empty($categoryId)) {
4012
            $categoryId = (int) $categoryId;
4013
            $categoryCondition = " AND category_id = $categoryId";
4014
        }
4015
4016
        $sql = "SELECT * FROM $lp_table
4017
                WHERE c_id = $courseId
4018
                $categoryCondition
4019
                ORDER BY display_order";
4020
        $res = Database::query($sql);
4021
        if ($res === false) {
4022
            return false;
4023
        }
4024
        $lps = [];
4025
        $lp_order = [];
4026
        $num = Database::num_rows($res);
4027
        $max = 0;
4028
        // First check the order is correct, globally (might be wrong because
4029
        // of versions < 1.8.4).
4030
        if ($num > 0) {
4031
            $i = 1;
4032
            while ($row = Database::fetch_array($res)) {
4033
                $max = $i;
4034
                if ($row['display_order'] != $i) {
4035
                    // If we find a gap in the order, we need to fix it.
4036
                    $sql = "UPDATE $lp_table SET display_order = $i
4037
                              WHERE iid = ".$row['iid'];
4038
                    Database::query($sql);
4039
                }
4040
                $row['display_order'] = $i;
4041
                $lps[$row['iid']] = $row;
4042
                $lp_order[$i] = $row['iid'];
4043
                $i++;
4044
            }
4045
        }
4046
        if ($num > 1) { // If there's only one element, no need to sort.
4047
            $order = $lps[$lp_id]['display_order'];
4048
            if ($order < $max) { // If it's the first element, no need to move up.
4049
                $sql = "UPDATE $lp_table SET display_order = $order
4050
                        WHERE iid = ".$lp_order[$order + 1];
4051
                Database::query($sql);
4052
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4053
                        WHERE iid = $lp_id";
4054
                Database::query($sql);
4055
            }
4056
        }
4057
4058
        return true;
4059
    }
4060
4061
    /**
4062
     * Updates learnpath attributes to point to the next element
4063
     * The last part is similar to set_current_item but processing the other way around.
4064
     */
4065
    public function next()
4066
    {
4067
        if ($this->debug > 0) {
4068
            error_log('In learnpath::next()', 0);
4069
        }
4070
        $this->last = $this->get_current_item_id();
4071
        $this->items[$this->last]->save(
4072
            false,
4073
            $this->prerequisites_match($this->last)
4074
        );
4075
        $this->autocomplete_parents($this->last);
4076
        $new_index = $this->get_next_index();
4077
        if ($this->debug > 2) {
4078
            error_log('New index: '.$new_index, 0);
4079
        }
4080
        $this->index = $new_index;
4081
        if ($this->debug > 2) {
4082
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4083
        }
4084
        $this->current = $this->ordered_items[$new_index];
4085
        if ($this->debug > 2) {
4086
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4087
        }
4088
    }
4089
4090
    /**
4091
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4092
     * class, this might be redefined to allow several behaviours depending on the document type.
4093
     *
4094
     * @param int $id Resource ID
4095
     */
4096
    public function open($id)
4097
    {
4098
        // TODO:
4099
        // set the current resource attribute to this resource
4100
        // switch on element type (redefine in child class?)
4101
        // set status for this item to "opened"
4102
        // start timer
4103
        // initialise score
4104
        $this->index = 0; //or = the last item seen (see $this->last)
4105
    }
4106
4107
    /**
4108
     * Check that all prerequisites are fulfilled. Returns true and an
4109
     * empty string on success, returns false
4110
     * and the prerequisite string on error.
4111
     * This function is based on the rules for aicc_script language as
4112
     * described in the SCORM 1.2 CAM documentation page 108.
4113
     *
4114
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4115
     *
4116
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4117
     *              string otherwise
4118
     */
4119
    public function prerequisites_match($itemId = null)
4120
    {
4121
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4122
        if ($allow) {
4123
            if (api_is_allowed_to_edit() ||
4124
                api_is_platform_admin(true) ||
4125
                api_is_drh() ||
4126
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4127
            ) {
4128
                return true;
4129
            }
4130
        }
4131
4132
        $debug = $this->debug;
4133
        if ($debug > 0) {
4134
            error_log('In learnpath::prerequisites_match()');
4135
        }
4136
4137
        if (empty($itemId)) {
4138
            $itemId = $this->current;
4139
        }
4140
4141
        $currentItem = $this->getItem($itemId);
4142
4143
        if ($currentItem) {
4144
            if ($this->type == 2) {
4145
                // Getting prereq from scorm
4146
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4147
            } else {
4148
                $prereq_string = $currentItem->get_prereq_string();
4149
            }
4150
4151
            if (empty($prereq_string)) {
4152
                if ($debug > 0) {
4153
                    error_log('Found prereq_string is empty return true');
4154
                }
4155
4156
                return true;
4157
            }
4158
4159
            // Clean spaces.
4160
            $prereq_string = str_replace(' ', '', $prereq_string);
4161
            if ($debug > 0) {
4162
                error_log('Found prereq_string: '.$prereq_string, 0);
4163
            }
4164
4165
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4166
            $result = $currentItem->parse_prereq(
4167
                $prereq_string,
4168
                $this->items,
4169
                $this->refs_list,
4170
                $this->get_user_id()
4171
            );
4172
4173
            if ($result === false) {
4174
                $this->set_error_msg($currentItem->prereq_alert);
4175
            }
4176
        } else {
4177
            $result = true;
4178
            if ($debug > 1) {
4179
                error_log('$this->items['.$itemId.'] was not an object', 0);
4180
            }
4181
        }
4182
4183
        if ($debug > 1) {
4184
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4185
        }
4186
4187
        return $result;
4188
    }
4189
4190
    /**
4191
     * Updates learnpath attributes to point to the previous element
4192
     * The last part is similar to set_current_item but processing the other way around.
4193
     */
4194
    public function previous()
4195
    {
4196
        $this->last = $this->get_current_item_id();
4197
        $this->items[$this->last]->save(
4198
            false,
4199
            $this->prerequisites_match($this->last)
4200
        );
4201
        $this->autocomplete_parents($this->last);
4202
        $new_index = $this->get_previous_index();
4203
        $this->index = $new_index;
4204
        $this->current = $this->ordered_items[$new_index];
4205
    }
4206
4207
    /**
4208
     * Publishes a learnpath. This basically means show or hide the learnpath
4209
     * to normal users.
4210
     * Can be used as abstract.
4211
     *
4212
     * @param int $lp_id          Learnpath ID
4213
     * @param int $set_visibility New visibility
4214
     *
4215
     * @return bool
4216
     */
4217
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4218
    {
4219
        $action = 'visible';
4220
        if ($set_visibility != 1) {
4221
            $action = 'invisible';
4222
            self::toggle_publish($lp_id, 'i');
4223
        }
4224
4225
        return api_item_property_update(
4226
            api_get_course_info(),
4227
            TOOL_LEARNPATH,
4228
            $lp_id,
4229
            $action,
4230
            api_get_user_id()
4231
        );
4232
    }
4233
4234
    /**
4235
     * Publishes a learnpath category.
4236
     * This basically means show or hide the learnpath category to normal users.
4237
     *
4238
     * @param int $id
4239
     * @param int $visibility
4240
     *
4241
     * @throws \Doctrine\ORM\NonUniqueResultException
4242
     * @throws \Doctrine\ORM\ORMException
4243
     * @throws \Doctrine\ORM\OptimisticLockException
4244
     * @throws \Doctrine\ORM\TransactionRequiredException
4245
     *
4246
     * @return bool
4247
     */
4248
    public static function toggleCategoryVisibility($id, $visibility = 1)
4249
    {
4250
        $action = 'visible';
4251
        if ($visibility != 1) {
4252
            self::toggleCategoryPublish($id, 0);
4253
            $action = 'invisible';
4254
        }
4255
4256
        return api_item_property_update(
4257
            api_get_course_info(),
4258
            TOOL_LEARNPATH_CATEGORY,
4259
            $id,
4260
            $action,
4261
            api_get_user_id()
4262
        );
4263
    }
4264
4265
    /**
4266
     * Publishes a learnpath. This basically means show or hide the learnpath
4267
     * on the course homepage
4268
     * Can be used as abstract.
4269
     *
4270
     * @param int    $lp_id          Learnpath id
4271
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4272
     *
4273
     * @return bool
4274
     */
4275
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4276
    {
4277
        $course_id = api_get_course_int_id();
4278
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4279
        $lp_id = (int) $lp_id;
4280
        $sql = "SELECT * FROM $tbl_lp
4281
                WHERE iid = $lp_id";
4282
        $result = Database::query($sql);
4283
        if (Database::num_rows($result)) {
4284
            $row = Database::fetch_array($result);
4285
            $name = Database::escape_string($row['name']);
4286
            if ($set_visibility == 'i') {
4287
                $v = 0;
4288
            }
4289
            if ($set_visibility == 'v') {
4290
                $v = 1;
4291
            }
4292
4293
            $session_id = api_get_session_id();
4294
            $session_condition = api_get_session_condition($session_id);
4295
4296
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4297
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4298
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4299
4300
            $sql = "SELECT * FROM $tbl_tool
4301
                    WHERE
4302
                        c_id = $course_id AND
4303
                        (link = '$link' OR link = '$oldLink') AND
4304
                        image = 'scormbuilder.gif' AND
4305
                        (
4306
                            link LIKE '$link%' OR
4307
                            link LIKE '$oldLink%'
4308
                        )
4309
                        $session_condition
4310
                    ";
4311
4312
            $result = Database::query($sql);
4313
            $num = Database::num_rows($result);
4314
            if ($set_visibility == 'i' && $num > 0) {
4315
                $sql = "DELETE FROM $tbl_tool
4316
                        WHERE
4317
                            c_id = $course_id AND
4318
                            (link = '$link' OR link = '$oldLink') AND
4319
                            image='scormbuilder.gif'
4320
                            $session_condition";
4321
                Database::query($sql);
4322
            } elseif ($set_visibility == 'v' && $num == 0) {
4323
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4324
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4325
                Database::query($sql);
4326
                $insertId = Database::insert_id();
4327
                if ($insertId) {
4328
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4329
                    Database::query($sql);
4330
                }
4331
            } elseif ($set_visibility == 'v' && $num > 0) {
4332
                $sql = "UPDATE $tbl_tool SET
4333
                            c_id = $course_id,
4334
                            name = '$name',
4335
                            link = '$link',
4336
                            image = 'scormbuilder.gif',
4337
                            visibility = '$v',
4338
                            admin = '0',
4339
                            address = 'pastillegris.gif',
4340
                            added_tool = 0,
4341
                            session_id = $session_id
4342
                        WHERE
4343
                            c_id = ".$course_id." AND
4344
                            (link = '$link' OR link = '$oldLink') AND
4345
                            image='scormbuilder.gif'
4346
                            $session_condition
4347
                        ";
4348
                Database::query($sql);
4349
            } else {
4350
                // Parameter and database incompatible, do nothing, exit.
4351
                return false;
4352
            }
4353
        } else {
4354
            return false;
4355
        }
4356
    }
4357
4358
    /**
4359
     * Publishes a learnpath.
4360
     * Show or hide the learnpath category on the course homepage.
4361
     *
4362
     * @param int $id
4363
     * @param int $setVisibility
4364
     *
4365
     * @throws \Doctrine\ORM\NonUniqueResultException
4366
     * @throws \Doctrine\ORM\ORMException
4367
     * @throws \Doctrine\ORM\OptimisticLockException
4368
     * @throws \Doctrine\ORM\TransactionRequiredException
4369
     *
4370
     * @return bool
4371
     */
4372
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4373
    {
4374
        $courseId = api_get_course_int_id();
4375
        $sessionId = api_get_session_id();
4376
        $sessionCondition = api_get_session_condition(
4377
            $sessionId,
4378
            true,
4379
            false,
4380
            't.sessionId'
4381
        );
4382
4383
        $em = Database::getManager();
4384
4385
        /** @var CLpCategory $category */
4386
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4387
4388
        if (!$category) {
4389
            return false;
4390
        }
4391
4392
        if (empty($courseId)) {
4393
            return false;
4394
        }
4395
4396
        $link = self::getCategoryLinkForTool($id);
4397
4398
        /** @var CTool $tool */
4399
        $tool = $em->createQuery("
4400
                SELECT t FROM ChamiloCourseBundle:CTool t
4401
                WHERE
4402
                    t.course = :course AND
4403
                    t.link = :link1 AND
4404
                    t.image LIKE 'lp_category.%' AND
4405
                    t.link LIKE :link2
4406
                    $sessionCondition
4407
            ")
4408
            ->setParameters([
4409
                'course' => $courseId,
4410
                'link1' => $link,
4411
                'link2' => "$link%",
4412
            ])
4413
            ->getOneOrNullResult();
4414
4415
        if ($setVisibility == 0 && $tool) {
4416
            $em->remove($tool);
4417
            $em->flush();
4418
4419
            return true;
4420
        }
4421
4422
        if ($setVisibility == 1 && !$tool) {
4423
            $tool = new CTool();
4424
            $tool
4425
                ->setCategory('authoring')
4426
                ->setCourse(api_get_course_entity($courseId))
4427
                ->setName(strip_tags($category->getName()))
4428
                ->setLink($link)
4429
                ->setImage('lp_category.png')
4430
                ->setVisibility(1)
4431
                ->setAdmin(0)
4432
                ->setAddress('pastillegris.gif')
4433
                ->setAddedTool(0)
4434
                ->setSessionId($sessionId)
4435
                ->setTarget('_self');
4436
4437
            $em->persist($tool);
4438
            $em->flush();
4439
4440
            $tool->setId($tool->getIid());
4441
4442
            $em->persist($tool);
4443
            $em->flush();
4444
4445
            return true;
4446
        }
4447
4448
        if ($setVisibility == 1 && $tool) {
4449
            $tool
4450
                ->setName(strip_tags($category->getName()))
4451
                ->setVisibility(1);
4452
4453
            $em->persist($tool);
4454
            $em->flush();
4455
4456
            return true;
4457
        }
4458
4459
        return false;
4460
    }
4461
4462
    /**
4463
     * Check if the learnpath category is visible for a user.
4464
     *
4465
     * @param int
4466
     * @param int
4467
     *
4468
     * @return bool
4469
     */
4470
    public static function categoryIsVisibleForStudent(
4471
        CLpCategory $category,
4472
        User $user,
4473
        $courseId = 0,
4474
        $sessionId = 0
4475
    ) {
4476
        if (empty($category)) {
4477
            return false;
4478
        }
4479
4480
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4481
4482
        if ($isAllowedToEdit) {
4483
            return true;
4484
        }
4485
4486
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4487
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4488
4489
        $courseInfo = api_get_course_info_by_id($courseId);
4490
4491
        $categoryVisibility = api_get_item_visibility(
4492
            $courseInfo,
4493
            TOOL_LEARNPATH_CATEGORY,
4494
            $category->getId(),
4495
            $sessionId
4496
        );
4497
4498
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4499
            return false;
4500
        }
4501
4502
        $subscriptionSettings = self::getSubscriptionSettings();
4503
4504
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4505
            return true;
4506
        }
4507
4508
        $users = $category->getUsers();
4509
4510
        if (empty($users) || !$users->count()) {
4511
            return true;
4512
        }
4513
4514
        if ($category->hasUserAdded($user)) {
4515
            return true;
4516
        }
4517
4518
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4519
        if (!empty($groups)) {
4520
            $em = Database::getManager();
4521
4522
            /** @var ItemPropertyRepository $itemRepo */
4523
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4524
4525
            /** @var CourseRepository $courseRepo */
4526
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4527
            $session = null;
4528
            if (!empty($sessionId)) {
4529
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4530
            }
4531
4532
            $course = $courseRepo->find($courseId);
4533
4534
            // Subscribed groups to a LP
4535
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4536
                TOOL_LEARNPATH_CATEGORY,
4537
                $category->getId(),
4538
                $course,
4539
                $session
4540
            );
4541
4542
            if (!empty($subscribedGroupsInLp)) {
4543
                $groups = array_column($groups, 'iid');
4544
                /** @var CItemProperty $item */
4545
                foreach ($subscribedGroupsInLp as $item) {
4546
                    if ($item->getGroup() &&
4547
                        in_array($item->getGroup()->getId(), $groups)
4548
                    ) {
4549
                        return true;
4550
                    }
4551
                }
4552
            }
4553
        }
4554
4555
        return false;
4556
    }
4557
4558
    /**
4559
     * Check if a learnpath category is published as course tool.
4560
     *
4561
     * @param int $courseId
4562
     *
4563
     * @return bool
4564
     */
4565
    public static function categoryIsPublished(
4566
        CLpCategory $category,
4567
        $courseId
4568
    ) {
4569
        $link = self::getCategoryLinkForTool($category->getId());
4570
        $em = Database::getManager();
4571
4572
        $tools = $em
4573
            ->createQuery("
4574
                SELECT t FROM ChamiloCourseBundle:CTool t
4575
                WHERE t.course = :course AND 
4576
                    t.name = :name AND
4577
                    t.image LIKE 'lp_category.%' AND
4578
                    t.link LIKE :link
4579
            ")
4580
            ->setParameters([
4581
                'course' => $courseId,
4582
                'name' => strip_tags($category->getName()),
4583
                'link' => "$link%",
4584
            ])
4585
            ->getResult();
4586
4587
        /** @var CTool $tool */
4588
        $tool = current($tools);
4589
4590
        return $tool ? $tool->getVisibility() : false;
4591
    }
4592
4593
    /**
4594
     * Restart the whole learnpath. Return the URL of the first element.
4595
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4596
     * To use a similar method  statically, use the create_new_attempt() method.
4597
     *
4598
     * @return bool
4599
     */
4600
    public function restart()
4601
    {
4602
        if ($this->debug > 0) {
4603
            error_log('In learnpath::restart()', 0);
4604
        }
4605
        // TODO
4606
        // Call autosave method to save the current progress.
4607
        //$this->index = 0;
4608
        if (api_is_invitee()) {
4609
            return false;
4610
        }
4611
        $session_id = api_get_session_id();
4612
        $course_id = api_get_course_int_id();
4613
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4614
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4615
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4616
        if ($this->debug > 2) {
4617
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4618
        }
4619
        Database::query($sql);
4620
        $view_id = Database::insert_id();
4621
4622
        if ($view_id) {
4623
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4624
            Database::query($sql);
4625
            $this->lp_view_id = $view_id;
4626
            $this->attempt = $this->attempt + 1;
4627
        } else {
4628
            $this->error = 'Could not insert into item_view table...';
4629
4630
            return false;
4631
        }
4632
        $this->autocomplete_parents($this->current);
4633
        foreach ($this->items as $index => $dummy) {
4634
            $this->items[$index]->restart();
4635
            $this->items[$index]->set_lp_view($this->lp_view_id);
4636
        }
4637
        $this->first();
4638
4639
        return true;
4640
    }
4641
4642
    /**
4643
     * Saves the current item.
4644
     *
4645
     * @return bool
4646
     */
4647
    public function save_current()
4648
    {
4649
        $debug = $this->debug;
4650
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4651
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4652
        if ($debug) {
4653
            error_log('save_current() saving item '.$this->current, 0);
4654
            error_log(''.print_r($this->items, true), 0);
4655
        }
4656
        if (isset($this->items[$this->current]) &&
4657
            is_object($this->items[$this->current])
4658
        ) {
4659
            if ($debug) {
4660
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4661
            }
4662
4663
            $res = $this->items[$this->current]->save(
4664
                false,
4665
                $this->prerequisites_match($this->current)
4666
            );
4667
            $this->autocomplete_parents($this->current);
4668
            $status = $this->items[$this->current]->get_status();
4669
            $this->update_queue[$this->current] = $status;
4670
4671
            if ($debug) {
4672
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4673
            }
4674
4675
            return $res;
4676
        }
4677
4678
        return false;
4679
    }
4680
4681
    /**
4682
     * Saves the given item.
4683
     *
4684
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4685
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4686
     *
4687
     * @return bool
4688
     */
4689
    public function save_item($item_id = null, $from_outside = true)
4690
    {
4691
        $debug = $this->debug;
4692
        if ($debug) {
4693
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4694
        }
4695
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4696
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4697
        if (empty($item_id)) {
4698
            $item_id = (int) $_REQUEST['id'];
4699
        }
4700
4701
        if (empty($item_id)) {
4702
            $item_id = $this->get_current_item_id();
4703
        }
4704
        if (isset($this->items[$item_id]) &&
4705
            is_object($this->items[$item_id])
4706
        ) {
4707
            if ($debug) {
4708
                error_log('Object exists');
4709
            }
4710
4711
            // Saving the item.
4712
            $res = $this->items[$item_id]->save(
4713
                $from_outside,
4714
                $this->prerequisites_match($item_id)
4715
            );
4716
4717
            if ($debug) {
4718
                error_log('update_queue before:');
4719
                error_log(print_r($this->update_queue, 1));
4720
            }
4721
            $this->autocomplete_parents($item_id);
4722
4723
            $status = $this->items[$item_id]->get_status();
4724
            $this->update_queue[$item_id] = $status;
4725
4726
            if ($debug) {
4727
                error_log('get_status(): '.$status);
4728
                error_log('update_queue after:');
4729
                error_log(print_r($this->update_queue, 1));
4730
            }
4731
4732
            return $res;
4733
        }
4734
4735
        return false;
4736
    }
4737
4738
    /**
4739
     * Saves the last item seen's ID only in case.
4740
     */
4741
    public function save_last()
4742
    {
4743
        $course_id = api_get_course_int_id();
4744
        $debug = $this->debug;
4745
        if ($debug) {
4746
            error_log('In learnpath::save_last()', 0);
4747
        }
4748
        $session_condition = api_get_session_condition(
4749
            api_get_session_id(),
4750
            true,
4751
            false
4752
        );
4753
        $table = Database::get_course_table(TABLE_LP_VIEW);
4754
4755
        if (isset($this->current) && !api_is_invitee()) {
4756
            if ($debug) {
4757
                error_log('Saving current item ('.$this->current.') for later review', 0);
4758
            }
4759
            $sql = "UPDATE $table SET
4760
                        last_item = ".$this->get_current_item_id()."
4761
                    WHERE
4762
                        c_id = $course_id AND
4763
                        lp_id = ".$this->get_id()." AND
4764
                        user_id = ".$this->get_user_id()." ".$session_condition;
4765
4766
            if ($debug) {
4767
                error_log('Saving last item seen : '.$sql, 0);
4768
            }
4769
            Database::query($sql);
4770
        }
4771
4772
        if (!api_is_invitee()) {
4773
            // Save progress.
4774
            list($progress) = $this->get_progress_bar_text('%');
4775
            if ($progress >= 0 && $progress <= 100) {
4776
                $progress = (int) $progress;
4777
                $sql = "UPDATE $table SET
4778
                            progress = $progress
4779
                        WHERE
4780
                            c_id = $course_id AND
4781
                            lp_id = ".$this->get_id()." AND
4782
                            user_id = ".$this->get_user_id()." ".$session_condition;
4783
                // Ignore errors as some tables might not have the progress field just yet.
4784
                Database::query($sql);
4785
                $this->progress_db = $progress;
4786
            }
4787
        }
4788
    }
4789
4790
    /**
4791
     * Sets the current item ID (checks if valid and authorized first).
4792
     *
4793
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4794
     */
4795
    public function set_current_item($item_id = null)
4796
    {
4797
        $debug = $this->debug;
4798
        if ($debug) {
4799
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4800
        }
4801
        if (empty($item_id)) {
4802
            if ($debug) {
4803
                error_log('No new current item given, ignore...', 0);
4804
            }
4805
            // Do nothing.
4806
        } else {
4807
            if ($debug) {
4808
                error_log('New current item given is '.$item_id.'...', 0);
4809
            }
4810
            if (is_numeric($item_id)) {
4811
                $item_id = (int) $item_id;
4812
                // TODO: Check in database here.
4813
                $this->last = $this->current;
4814
                $this->current = $item_id;
4815
                // TODO: Update $this->index as well.
4816
                foreach ($this->ordered_items as $index => $item) {
4817
                    if ($item == $this->current) {
4818
                        $this->index = $index;
4819
                        break;
4820
                    }
4821
                }
4822
                if ($debug) {
4823
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4824
                }
4825
            } else {
4826
                if ($debug) {
4827
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4828
                }
4829
            }
4830
        }
4831
    }
4832
4833
    /**
4834
     * Sets the encoding.
4835
     *
4836
     * @param string $enc New encoding
4837
     *
4838
     * @return bool
4839
     *
4840
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4841
     */
4842
    public function set_encoding($enc = 'UTF-8')
4843
    {
4844
        $enc = api_refine_encoding_id($enc);
4845
        if (empty($enc)) {
4846
            $enc = api_get_system_encoding();
4847
        }
4848
        if (api_is_encoding_supported($enc)) {
4849
            $lp = $this->get_id();
4850
            if ($lp != 0) {
4851
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4852
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4853
                        WHERE iid = ".$lp;
4854
                $res = Database::query($sql);
4855
4856
                return $res;
4857
            }
4858
        }
4859
4860
        return false;
4861
    }
4862
4863
    /**
4864
     * Sets the JS lib setting in the database directly.
4865
     * This is the JavaScript library file this lp needs to load on startup.
4866
     *
4867
     * @param string $lib Proximity setting
4868
     *
4869
     * @return bool True on update success. False otherwise.
4870
     */
4871
    public function set_jslib($lib = '')
4872
    {
4873
        $lp = $this->get_id();
4874
4875
        if ($lp != 0) {
4876
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4877
            $lib = Database::escape_string($lib);
4878
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4879
                    WHERE iid = $lp";
4880
            $res = Database::query($sql);
4881
4882
            return $res;
4883
        }
4884
4885
        return false;
4886
    }
4887
4888
    /**
4889
     * Sets the name of the LP maker (publisher) (and save).
4890
     *
4891
     * @param string $name Optional string giving the new content_maker of this learnpath
4892
     *
4893
     * @return bool True
4894
     */
4895
    public function set_maker($name = '')
4896
    {
4897
        if (empty($name)) {
4898
            return false;
4899
        }
4900
        $this->maker = $name;
4901
        $table = Database::get_course_table(TABLE_LP_MAIN);
4902
        $lp_id = $this->get_id();
4903
        $sql = "UPDATE $table SET
4904
                content_maker = '".Database::escape_string($this->maker)."'
4905
                WHERE iid = $lp_id";
4906
        Database::query($sql);
4907
4908
        return true;
4909
    }
4910
4911
    /**
4912
     * Sets the name of the current learnpath (and save).
4913
     *
4914
     * @param string $name Optional string giving the new name of this learnpath
4915
     *
4916
     * @return bool True/False
4917
     */
4918
    public function set_name($name = null)
4919
    {
4920
        if (empty($name)) {
4921
            return false;
4922
        }
4923
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4924
        $name = Database::escape_string($name);
4925
4926
        $this->name = $name;
4927
4928
        $lp_id = $this->get_id();
4929
        $course_id = $this->course_info['real_id'];
4930
        $sql = "UPDATE $lp_table SET
4931
                name = '$name'
4932
                WHERE iid = $lp_id";
4933
        $result = Database::query($sql);
4934
        // If the lp is visible on the homepage, change his name there.
4935
        if (Database::affected_rows($result)) {
4936
            $session_id = api_get_session_id();
4937
            $session_condition = api_get_session_condition($session_id);
4938
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4939
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4940
            $sql = "UPDATE $tbl_tool SET name = '$name'
4941
            	    WHERE
4942
            	        c_id = $course_id AND
4943
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4944
            Database::query($sql);
4945
4946
            return true;
4947
        }
4948
4949
        return false;
4950
    }
4951
4952
    /**
4953
     * Set index specified prefix terms for all items in this path.
4954
     *
4955
     * @param string $terms_string Comma-separated list of terms
4956
     * @param string $prefix       Xapian term prefix
4957
     *
4958
     * @return bool False on error, true otherwise
4959
     */
4960
    public function set_terms_by_prefix($terms_string, $prefix)
4961
    {
4962
        $course_id = api_get_course_int_id();
4963
        if (api_get_setting('search_enabled') !== 'true') {
4964
            return false;
4965
        }
4966
4967
        if (!extension_loaded('xapian')) {
4968
            return false;
4969
        }
4970
4971
        $terms_string = trim($terms_string);
4972
        $terms = explode(',', $terms_string);
4973
        array_walk($terms, 'trim_value');
4974
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4975
4976
        // Don't do anything if no change, verify only at DB, not the search engine.
4977
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
4978
            return false;
4979
        }
4980
4981
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4982
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4983
4984
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4985
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4986
        $lp_id = (int) $_POST['lp_id'];
4987
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4988
        $result = Database::query($sql);
4989
        $di = new ChamiloIndexer();
4990
4991
        while ($lp_item = Database::fetch_array($result)) {
4992
            // Get search_did.
4993
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4994
            $sql = 'SELECT * FROM %s
4995
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4996
                    LIMIT 1';
4997
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
4998
4999
            //echo $sql; echo '<br>';
5000
            $res = Database::query($sql);
5001
            if (Database::num_rows($res) > 0) {
5002
                $se_ref = Database::fetch_array($res);
5003
                // Compare terms.
5004
                $doc = $di->get_document($se_ref['search_did']);
5005
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5006
                $xterms = [];
5007
                foreach ($xapian_terms as $xapian_term) {
5008
                    $xterms[] = substr($xapian_term['name'], 1);
5009
                }
5010
5011
                $dterms = $terms;
5012
                $missing_terms = array_diff($dterms, $xterms);
5013
                $deprecated_terms = array_diff($xterms, $dterms);
5014
5015
                // Save it to search engine.
5016
                foreach ($missing_terms as $term) {
5017
                    $doc->add_term($prefix.$term, 1);
5018
                }
5019
                foreach ($deprecated_terms as $term) {
5020
                    $doc->remove_term($prefix.$term);
5021
                }
5022
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5023
                $di->getDb()->flush();
5024
            }
5025
        }
5026
5027
        return true;
5028
    }
5029
5030
    /**
5031
     * Sets the theme of the LP (local/remote) (and save).
5032
     *
5033
     * @param string $name Optional string giving the new theme of this learnpath
5034
     *
5035
     * @return bool Returns true if theme name is not empty
5036
     */
5037
    public function set_theme($name = '')
5038
    {
5039
        $this->theme = $name;
5040
        $table = Database::get_course_table(TABLE_LP_MAIN);
5041
        $lp_id = $this->get_id();
5042
        $sql = "UPDATE $table
5043
                SET theme = '".Database::escape_string($this->theme)."'
5044
                WHERE iid = $lp_id";
5045
        Database::query($sql);
5046
5047
        return true;
5048
    }
5049
5050
    /**
5051
     * Sets the image of an LP (and save).
5052
     *
5053
     * @param string $name Optional string giving the new image of this learnpath
5054
     *
5055
     * @return bool Returns true if theme name is not empty
5056
     */
5057
    public function set_preview_image($name = '')
5058
    {
5059
        $this->preview_image = $name;
5060
        $table = Database::get_course_table(TABLE_LP_MAIN);
5061
        $lp_id = $this->get_id();
5062
        $sql = "UPDATE $table SET
5063
                preview_image = '".Database::escape_string($this->preview_image)."'
5064
                WHERE iid = $lp_id";
5065
        Database::query($sql);
5066
5067
        return true;
5068
    }
5069
5070
    /**
5071
     * Sets the author of a LP (and save).
5072
     *
5073
     * @param string $name Optional string giving the new author of this learnpath
5074
     *
5075
     * @return bool Returns true if author's name is not empty
5076
     */
5077
    public function set_author($name = '')
5078
    {
5079
        $this->author = $name;
5080
        $table = Database::get_course_table(TABLE_LP_MAIN);
5081
        $lp_id = $this->get_id();
5082
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5083
                WHERE iid = $lp_id";
5084
        Database::query($sql);
5085
5086
        return true;
5087
    }
5088
5089
    /**
5090
     * Sets the hide_toc_frame parameter of a LP (and save).
5091
     *
5092
     * @param int $hide 1 if frame is hidden 0 then else
5093
     *
5094
     * @return bool Returns true if author's name is not empty
5095
     */
5096
    public function set_hide_toc_frame($hide)
5097
    {
5098
        if (intval($hide) == $hide) {
5099
            $this->hide_toc_frame = $hide;
5100
            $table = Database::get_course_table(TABLE_LP_MAIN);
5101
            $lp_id = $this->get_id();
5102
            $sql = "UPDATE $table SET
5103
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5104
                    WHERE iid = $lp_id";
5105
            Database::query($sql);
5106
5107
            return true;
5108
        }
5109
5110
        return false;
5111
    }
5112
5113
    /**
5114
     * Sets the prerequisite of a LP (and save).
5115
     *
5116
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5117
     *
5118
     * @return bool returns true if prerequisite is not empty
5119
     */
5120
    public function set_prerequisite($prerequisite)
5121
    {
5122
        $this->prerequisite = (int) $prerequisite;
5123
        $table = Database::get_course_table(TABLE_LP_MAIN);
5124
        $lp_id = $this->get_id();
5125
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5126
                WHERE iid = $lp_id";
5127
        Database::query($sql);
5128
5129
        return true;
5130
    }
5131
5132
    /**
5133
     * Sets the location/proximity of the LP (local/remote) (and save).
5134
     *
5135
     * @param string $name Optional string giving the new location of this learnpath
5136
     *
5137
     * @return bool True on success / False on error
5138
     */
5139
    public function set_proximity($name = '')
5140
    {
5141
        if (empty($name)) {
5142
            return false;
5143
        }
5144
5145
        $this->proximity = $name;
5146
        $table = Database::get_course_table(TABLE_LP_MAIN);
5147
        $lp_id = $this->get_id();
5148
        $sql = "UPDATE $table SET
5149
                    content_local = '".Database::escape_string($name)."'
5150
                WHERE iid = $lp_id";
5151
        Database::query($sql);
5152
5153
        return true;
5154
    }
5155
5156
    /**
5157
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5158
     *
5159
     * @param int $id DB ID of the item
5160
     */
5161
    public function set_previous_item($id)
5162
    {
5163
        if ($this->debug > 0) {
5164
            error_log('In learnpath::set_previous_item()', 0);
5165
        }
5166
        $this->last = $id;
5167
    }
5168
5169
    /**
5170
     * Sets use_max_score.
5171
     *
5172
     * @param int $use_max_score Optional string giving the new location of this learnpath
5173
     *
5174
     * @return bool True on success / False on error
5175
     */
5176
    public function set_use_max_score($use_max_score = 1)
5177
    {
5178
        $use_max_score = (int) $use_max_score;
5179
        $this->use_max_score = $use_max_score;
5180
        $table = Database::get_course_table(TABLE_LP_MAIN);
5181
        $lp_id = $this->get_id();
5182
        $sql = "UPDATE $table SET
5183
                    use_max_score = '".$this->use_max_score."'
5184
                WHERE iid = $lp_id";
5185
        Database::query($sql);
5186
5187
        return true;
5188
    }
5189
5190
    /**
5191
     * Sets and saves the expired_on date.
5192
     *
5193
     * @param string $expired_on Optional string giving the new author of this learnpath
5194
     *
5195
     * @throws \Doctrine\ORM\OptimisticLockException
5196
     *
5197
     * @return bool Returns true if author's name is not empty
5198
     */
5199
    public function set_expired_on($expired_on)
5200
    {
5201
        $em = Database::getManager();
5202
        /** @var CLp $lp */
5203
        $lp = $em
5204
            ->getRepository('ChamiloCourseBundle:CLp')
5205
            ->findOneBy(
5206
                [
5207
                    'iid' => $this->get_id(),
5208
                ]
5209
            );
5210
5211
        if (!$lp) {
5212
            return false;
5213
        }
5214
5215
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5216
5217
        $lp->setExpiredOn($this->expired_on);
5218
        $em->persist($lp);
5219
        $em->flush();
5220
5221
        return true;
5222
    }
5223
5224
    /**
5225
     * Sets and saves the publicated_on date.
5226
     *
5227
     * @param string $publicated_on Optional string giving the new author of this learnpath
5228
     *
5229
     * @throws \Doctrine\ORM\OptimisticLockException
5230
     *
5231
     * @return bool Returns true if author's name is not empty
5232
     */
5233
    public function set_publicated_on($publicated_on)
5234
    {
5235
        $em = Database::getManager();
5236
        /** @var CLp $lp */
5237
        $lp = $em
5238
            ->getRepository('ChamiloCourseBundle:CLp')
5239
            ->findOneBy(
5240
                [
5241
                    'iid' => $this->get_id(),
5242
                ]
5243
            );
5244
5245
        if (!$lp) {
5246
            return false;
5247
        }
5248
5249
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5250
        $lp->setPublicatedOn($this->publicated_on);
5251
        $em->persist($lp);
5252
        $em->flush();
5253
5254
        return true;
5255
    }
5256
5257
    /**
5258
     * Sets and saves the expired_on date.
5259
     *
5260
     * @return bool Returns true if author's name is not empty
5261
     */
5262
    public function set_modified_on()
5263
    {
5264
        $this->modified_on = api_get_utc_datetime();
5265
        $table = Database::get_course_table(TABLE_LP_MAIN);
5266
        $lp_id = $this->get_id();
5267
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5268
                WHERE iid = $lp_id";
5269
        Database::query($sql);
5270
5271
        return true;
5272
    }
5273
5274
    /**
5275
     * Sets the object's error message.
5276
     *
5277
     * @param string $error Error message. If empty, reinits the error string
5278
     */
5279
    public function set_error_msg($error = '')
5280
    {
5281
        if ($this->debug > 0) {
5282
            error_log('In learnpath::set_error_msg()', 0);
5283
        }
5284
        if (empty($error)) {
5285
            $this->error = '';
5286
        } else {
5287
            $this->error .= $error;
5288
        }
5289
    }
5290
5291
    /**
5292
     * Launches the current item if not 'sco'
5293
     * (starts timer and make sure there is a record ready in the DB).
5294
     *
5295
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5296
     *
5297
     * @return bool
5298
     */
5299
    public function start_current_item($allow_new_attempt = false)
5300
    {
5301
        $debug = $this->debug;
5302
        if ($debug) {
5303
            error_log('In learnpath::start_current_item()');
5304
            error_log('current: '.$this->current);
5305
        }
5306
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5307
            $type = $this->get_type();
5308
            $item_type = $this->items[$this->current]->get_type();
5309
            if (($type == 2 && $item_type != 'sco') ||
5310
                ($type == 3 && $item_type != 'au') ||
5311
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5312
            ) {
5313
                if ($debug) {
5314
                    error_log('item type: '.$item_type);
5315
                    error_log('lp type: '.$type);
5316
                }
5317
                $this->items[$this->current]->open($allow_new_attempt);
5318
                $this->autocomplete_parents($this->current);
5319
                $prereq_check = $this->prerequisites_match($this->current);
5320
                if ($debug) {
5321
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5322
                }
5323
                $this->items[$this->current]->save(false, $prereq_check);
5324
            }
5325
            // If sco, then it is supposed to have been updated by some other call.
5326
            if ($item_type == 'sco') {
5327
                $this->items[$this->current]->restart();
5328
            }
5329
        }
5330
        if ($debug) {
5331
            error_log('lp_view_session_id');
5332
            error_log($this->lp_view_session_id);
5333
            error_log('api session id');
5334
            error_log(api_get_session_id());
5335
            error_log('End of learnpath::start_current_item()');
5336
        }
5337
5338
        return true;
5339
    }
5340
5341
    /**
5342
     * Stops the processing and counters for the old item (as held in $this->last).
5343
     *
5344
     * @return bool True/False
5345
     */
5346
    public function stop_previous_item()
5347
    {
5348
        $debug = $this->debug;
5349
        if ($debug) {
5350
            error_log('In learnpath::stop_previous_item()', 0);
5351
        }
5352
5353
        if ($this->last != 0 && $this->last != $this->current &&
5354
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5355
        ) {
5356
            if ($debug) {
5357
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5358
            }
5359
            switch ($this->get_type()) {
5360
                case '3':
5361
                    if ($this->items[$this->last]->get_type() != 'au') {
5362
                        if ($debug) {
5363
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5364
                        }
5365
                        $this->items[$this->last]->close();
5366
                    } else {
5367
                        if ($debug) {
5368
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5369
                        }
5370
                    }
5371
                    break;
5372
                case '2':
5373
                    if ($this->items[$this->last]->get_type() != 'sco') {
5374
                        if ($debug) {
5375
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5376
                        }
5377
                        $this->items[$this->last]->close();
5378
                    } else {
5379
                        if ($debug) {
5380
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5381
                        }
5382
                    }
5383
                    break;
5384
                case '1':
5385
                default:
5386
                    if ($debug) {
5387
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5388
                    }
5389
                    $this->items[$this->last]->close();
5390
                    break;
5391
            }
5392
        } else {
5393
            if ($debug) {
5394
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5395
            }
5396
5397
            return false;
5398
        }
5399
5400
        return true;
5401
    }
5402
5403
    /**
5404
     * Updates the default view mode from fullscreen to embedded and inversely.
5405
     *
5406
     * @return string The current default view mode ('fullscreen' or 'embedded')
5407
     */
5408
    public function update_default_view_mode()
5409
    {
5410
        $table = Database::get_course_table(TABLE_LP_MAIN);
5411
        $sql = "SELECT * FROM $table
5412
                WHERE iid = ".$this->get_id();
5413
        $res = Database::query($sql);
5414
        if (Database::num_rows($res) > 0) {
5415
            $row = Database::fetch_array($res);
5416
            $default_view_mode = $row['default_view_mod'];
5417
            $view_mode = $default_view_mode;
5418
            switch ($default_view_mode) {
5419
                case 'fullscreen': // default with popup
5420
                    $view_mode = 'embedded';
5421
                    break;
5422
                case 'embedded': // default view with left menu
5423
                    $view_mode = 'embedframe';
5424
                    break;
5425
                case 'embedframe': //folded menu
5426
                    $view_mode = 'impress';
5427
                    break;
5428
                case 'impress':
5429
                    $view_mode = 'fullscreen';
5430
                    break;
5431
            }
5432
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5433
                    WHERE iid = ".$this->get_id();
5434
            Database::query($sql);
5435
            $this->mode = $view_mode;
5436
5437
            return $view_mode;
5438
        }
5439
5440
        return -1;
5441
    }
5442
5443
    /**
5444
     * Updates the default behaviour about auto-commiting SCORM updates.
5445
     *
5446
     * @return bool True if auto-commit has been set to 'on', false otherwise
5447
     */
5448
    public function update_default_scorm_commit()
5449
    {
5450
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5451
        $sql = "SELECT * FROM $lp_table
5452
                WHERE iid = ".$this->get_id();
5453
        $res = Database::query($sql);
5454
        if (Database::num_rows($res) > 0) {
5455
            $row = Database::fetch_array($res);
5456
            $force = $row['force_commit'];
5457
            if ($force == 1) {
5458
                $force = 0;
5459
                $force_return = false;
5460
            } elseif ($force == 0) {
5461
                $force = 1;
5462
                $force_return = true;
5463
            }
5464
            $sql = "UPDATE $lp_table SET force_commit = $force
5465
                    WHERE iid = ".$this->get_id();
5466
            Database::query($sql);
5467
            $this->force_commit = $force_return;
5468
5469
            return $force_return;
5470
        }
5471
5472
        return -1;
5473
    }
5474
5475
    /**
5476
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5477
     *
5478
     * @return bool True on success, false on failure
5479
     */
5480
    public function update_display_order()
5481
    {
5482
        $course_id = api_get_course_int_id();
5483
        $table = Database::get_course_table(TABLE_LP_MAIN);
5484
        $sql = "SELECT * FROM $table
5485
                WHERE c_id = $course_id
5486
                ORDER BY display_order";
5487
        $res = Database::query($sql);
5488
        if ($res === false) {
5489
            return false;
5490
        }
5491
5492
        $num = Database::num_rows($res);
5493
        // First check the order is correct, globally (might be wrong because
5494
        // of versions < 1.8.4).
5495
        if ($num > 0) {
5496
            $i = 1;
5497
            while ($row = Database::fetch_array($res)) {
5498
                if ($row['display_order'] != $i) {
5499
                    // If we find a gap in the order, we need to fix it.
5500
                    $sql = "UPDATE $table SET display_order = $i
5501
                            WHERE iid = ".$row['iid'];
5502
                    Database::query($sql);
5503
                }
5504
                $i++;
5505
            }
5506
        }
5507
5508
        return true;
5509
    }
5510
5511
    /**
5512
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5513
     *
5514
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5515
     */
5516
    public function update_reinit()
5517
    {
5518
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5519
        $sql = "SELECT * FROM $lp_table
5520
                WHERE iid = ".$this->get_id();
5521
        $res = Database::query($sql);
5522
        if (Database::num_rows($res) > 0) {
5523
            $row = Database::fetch_array($res);
5524
            $force = $row['prevent_reinit'];
5525
            if ($force == 1) {
5526
                $force = 0;
5527
            } elseif ($force == 0) {
5528
                $force = 1;
5529
            }
5530
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5531
                    WHERE iid = ".$this->get_id();
5532
            Database::query($sql);
5533
            $this->prevent_reinit = $force;
5534
5535
            return $force;
5536
        }
5537
5538
        return -1;
5539
    }
5540
5541
    /**
5542
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5543
     *
5544
     * @return string 'single', 'multi' or 'seriousgame'
5545
     *
5546
     * @author ndiechburg <[email protected]>
5547
     */
5548
    public function get_attempt_mode()
5549
    {
5550
        //Set default value for seriousgame_mode
5551
        if (!isset($this->seriousgame_mode)) {
5552
            $this->seriousgame_mode = 0;
5553
        }
5554
        // Set default value for prevent_reinit
5555
        if (!isset($this->prevent_reinit)) {
5556
            $this->prevent_reinit = 1;
5557
        }
5558
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5559
            return 'seriousgame';
5560
        }
5561
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5562
            return 'single';
5563
        }
5564
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5565
            return 'multiple';
5566
        }
5567
5568
        return 'single';
5569
    }
5570
5571
    /**
5572
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5573
     *
5574
     * @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...
5575
     *
5576
     * @return bool
5577
     *
5578
     * @author ndiechburg <[email protected]>
5579
     */
5580
    public function set_attempt_mode($mode)
5581
    {
5582
        switch ($mode) {
5583
            case 'seriousgame':
5584
                $sg_mode = 1;
5585
                $prevent_reinit = 1;
5586
                break;
5587
            case 'single':
5588
                $sg_mode = 0;
5589
                $prevent_reinit = 1;
5590
                break;
5591
            case 'multiple':
5592
                $sg_mode = 0;
5593
                $prevent_reinit = 0;
5594
                break;
5595
            default:
5596
                $sg_mode = 0;
5597
                $prevent_reinit = 0;
5598
                break;
5599
        }
5600
        $this->prevent_reinit = $prevent_reinit;
5601
        $this->seriousgame_mode = $sg_mode;
5602
        $table = Database::get_course_table(TABLE_LP_MAIN);
5603
        $sql = "UPDATE $table SET
5604
                prevent_reinit = $prevent_reinit ,
5605
                seriousgame_mode = $sg_mode
5606
                WHERE iid = ".$this->get_id();
5607
        $res = Database::query($sql);
5608
        if ($res) {
5609
            return true;
5610
        } else {
5611
            return false;
5612
        }
5613
    }
5614
5615
    /**
5616
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5617
     *
5618
     * @author ndiechburg <[email protected]>
5619
     */
5620
    public function switch_attempt_mode()
5621
    {
5622
        $mode = $this->get_attempt_mode();
5623
        switch ($mode) {
5624
            case 'single':
5625
                $next_mode = 'multiple';
5626
                break;
5627
            case 'multiple':
5628
                $next_mode = 'seriousgame';
5629
                break;
5630
            case 'seriousgame':
5631
            default:
5632
                $next_mode = 'single';
5633
                break;
5634
        }
5635
        $this->set_attempt_mode($next_mode);
5636
    }
5637
5638
    /**
5639
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5640
     * but possibility to do again a completed item.
5641
     *
5642
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5643
     *
5644
     * @author ndiechburg <[email protected]>
5645
     */
5646
    public function set_seriousgame_mode()
5647
    {
5648
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5649
        $sql = "SELECT * FROM $lp_table
5650
                WHERE iid = ".$this->get_id();
5651
        $res = Database::query($sql);
5652
        if (Database::num_rows($res) > 0) {
5653
            $row = Database::fetch_array($res);
5654
            $force = $row['seriousgame_mode'];
5655
            if ($force == 1) {
5656
                $force = 0;
5657
            } elseif ($force == 0) {
5658
                $force = 1;
5659
            }
5660
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5661
			        WHERE iid = ".$this->get_id();
5662
            Database::query($sql);
5663
            $this->seriousgame_mode = $force;
5664
5665
            return $force;
5666
        }
5667
5668
        return -1;
5669
    }
5670
5671
    /**
5672
     * Updates the "scorm_debug" value that shows or hide the debug window.
5673
     *
5674
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5675
     */
5676
    public function update_scorm_debug()
5677
    {
5678
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5679
        $sql = "SELECT * FROM $lp_table
5680
                WHERE iid = ".$this->get_id();
5681
        $res = Database::query($sql);
5682
        if (Database::num_rows($res) > 0) {
5683
            $row = Database::fetch_array($res);
5684
            $force = $row['debug'];
5685
            if ($force == 1) {
5686
                $force = 0;
5687
            } elseif ($force == 0) {
5688
                $force = 1;
5689
            }
5690
            $sql = "UPDATE $lp_table SET debug = $force
5691
                    WHERE iid = ".$this->get_id();
5692
            Database::query($sql);
5693
            $this->scorm_debug = $force;
5694
5695
            return $force;
5696
        }
5697
5698
        return -1;
5699
    }
5700
5701
    /**
5702
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5703
     *
5704
     * @author Kevin Van Den Haute
5705
     *
5706
     * @param  array
5707
     */
5708
    public function tree_array($array)
5709
    {
5710
        $array = $this->sort_tree_array($array);
5711
        $this->create_tree_array($array);
5712
    }
5713
5714
    /**
5715
     * Creates an array with the elements of the learning path tree in it.
5716
     *
5717
     * @author Kevin Van Den Haute
5718
     *
5719
     * @param array $array
5720
     * @param int   $parent
5721
     * @param int   $depth
5722
     * @param array $tmp
5723
     */
5724
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5725
    {
5726
        if (is_array($array)) {
5727
            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...
5728
                if ($array[$i]['parent_item_id'] == $parent) {
5729
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5730
                        $tmp[] = $array[$i]['parent_item_id'];
5731
                        $depth++;
5732
                    }
5733
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5734
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5735
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5736
5737
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5738
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5739
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5740
                    $this->arrMenu[] = [
5741
                        'id' => $array[$i]['id'],
5742
                        'ref' => $ref,
5743
                        'item_type' => $array[$i]['item_type'],
5744
                        'title' => $array[$i]['title'],
5745
                        'title_raw' => $array[$i]['title_raw'],
5746
                        'path' => $path,
5747
                        'description' => $array[$i]['description'],
5748
                        'parent_item_id' => $array[$i]['parent_item_id'],
5749
                        'previous_item_id' => $array[$i]['previous_item_id'],
5750
                        'next_item_id' => $array[$i]['next_item_id'],
5751
                        'min_score' => $array[$i]['min_score'],
5752
                        'max_score' => $array[$i]['max_score'],
5753
                        'mastery_score' => $array[$i]['mastery_score'],
5754
                        'display_order' => $array[$i]['display_order'],
5755
                        'prerequisite' => $preq,
5756
                        'depth' => $depth,
5757
                        'audio' => $audio,
5758
                        'prerequisite_min_score' => $prerequisiteMinScore,
5759
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5760
                    ];
5761
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5762
                }
5763
            }
5764
        }
5765
    }
5766
5767
    /**
5768
     * Sorts a multi dimensional array by parent id and display order.
5769
     *
5770
     * @author Kevin Van Den Haute
5771
     *
5772
     * @param array $array (array with al the learning path items in it)
5773
     *
5774
     * @return array
5775
     */
5776
    public function sort_tree_array($array)
5777
    {
5778
        foreach ($array as $key => $row) {
5779
            $parent[$key] = $row['parent_item_id'];
5780
            $position[$key] = $row['display_order'];
5781
        }
5782
5783
        if (count($array) > 0) {
5784
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5785
        }
5786
5787
        return $array;
5788
    }
5789
5790
    /**
5791
     * Function that creates a html list of learning path items so that we can add audio files to them.
5792
     *
5793
     * @author Kevin Van Den Haute
5794
     *
5795
     * @return string
5796
     */
5797
    public function overview()
5798
    {
5799
        $return = '';
5800
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5801
5802
        // we need to start a form when we want to update all the mp3 files
5803
        if ($update_audio == 'true') {
5804
            $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">';
5805
        }
5806
        $return .= '<div id="message"></div>';
5807
        if (count($this->items) == 0) {
5808
            $return .= Display::return_message(get_lang('You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'), 'normal');
5809
        } else {
5810
            $return_audio = '<table class="data_table">';
5811
            $return_audio .= '<tr>';
5812
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5813
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5814
            $return_audio .= '</tr>';
5815
5816
            if ($update_audio != 'true') {
5817
                $return .= '<div class="col-md-12">';
5818
                $return .= self::return_new_tree($update_audio);
5819
                $return .= '</div>';
5820
                $return .= Display::div(
5821
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5822
                    ['style' => 'float:left; margin-top:15px;width:100%']
5823
                );
5824
            } else {
5825
                $return_audio .= self::return_new_tree($update_audio);
5826
                $return .= $return_audio.'</table>';
5827
            }
5828
5829
            // We need to close the form when we are updating the mp3 files.
5830
            if ($update_audio == 'true') {
5831
                $return .= '<div class="footer-audio">';
5832
                $return .= Display::button(
5833
                    'save_audio',
5834
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5835
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5836
                );
5837
                $return .= '</div>';
5838
            }
5839
        }
5840
5841
        // We need to close the form when we are updating the mp3 files.
5842
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5843
            $return .= '</form>';
5844
        }
5845
5846
        return $return;
5847
    }
5848
5849
    /**
5850
     * @param string $update_audio
5851
     *
5852
     * @return array
5853
     */
5854
    public function processBuildMenuElements($update_audio = 'false')
5855
    {
5856
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5857
        $arrLP = $this->getItemsForForm();
5858
5859
        $this->tree_array($arrLP);
5860
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5861
        unset($this->arrMenu);
5862
        $default_data = null;
5863
        $default_content = null;
5864
        $elements = [];
5865
        $return_audio = null;
5866
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5867
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5868
        $countItems = count($arrLP);
5869
5870
        $upIcon = Display::return_icon(
5871
            'up.png',
5872
            get_lang('Up'),
5873
            [],
5874
            ICON_SIZE_TINY
5875
        );
5876
5877
        $disableUpIcon = Display::return_icon(
5878
            'up_na.png',
5879
            get_lang('Up'),
5880
            [],
5881
            ICON_SIZE_TINY
5882
        );
5883
5884
        $downIcon = Display::return_icon(
5885
            'down.png',
5886
            get_lang('Down'),
5887
            [],
5888
            ICON_SIZE_TINY
5889
        );
5890
5891
        $disableDownIcon = Display::return_icon(
5892
            'down_na.png',
5893
            get_lang('Down'),
5894
            [],
5895
            ICON_SIZE_TINY
5896
        );
5897
5898
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5899
5900
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
5901
        $plugin = null;
5902
        if ($pluginCalendar) {
5903
            $plugin = LearningCalendarPlugin::create();
5904
        }
5905
5906
        for ($i = 0; $i < $countItems; $i++) {
5907
            $parent_id = $arrLP[$i]['parent_item_id'];
5908
            $title = $arrLP[$i]['title'];
5909
            $title_cut = $arrLP[$i]['title_raw'];
5910
            if ($show === false) {
5911
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5912
            }
5913
            // Link for the documents
5914
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
5915
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5916
                $title_cut = Display::url(
5917
                    $title_cut,
5918
                    $url,
5919
                    [
5920
                        'class' => 'ajax moved',
5921
                        'data-title' => $title,
5922
                        'title' => $title,
5923
                    ]
5924
                );
5925
            }
5926
5927
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5928
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5929
                Session::write('pathItem', $arrLP[$i]['path']);
5930
            }
5931
5932
            $oddClass = 'row_even';
5933
            if (($i % 2) == 0) {
5934
                $oddClass = 'row_odd';
5935
            }
5936
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5937
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5938
5939
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5940
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5941
            } else {
5942
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5943
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5944
                } else {
5945
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
5946
                        $icon = Display::return_icon('certificate.png');
5947
                    } else {
5948
                        $icon = Display::return_icon('folder_document.png');
5949
                    }
5950
                }
5951
            }
5952
5953
            // The audio column.
5954
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5955
            $audio = '';
5956
            if (!$update_audio || $update_audio != 'true') {
5957
                if (empty($arrLP[$i]['audio'])) {
5958
                    $audio .= '';
5959
                }
5960
            } else {
5961
                $types = self::getChapterTypes();
5962
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5963
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5964
                    if (!empty($arrLP[$i]['audio'])) {
5965
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5966
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5967
                    }
5968
                }
5969
            }
5970
5971
            $return_audio .= Display::span($icon.' '.$title).
5972
                Display::tag(
5973
                    'td',
5974
                    $audio,
5975
                    ['style' => '']
5976
                );
5977
            $return_audio .= '</td>';
5978
            $move_icon = '';
5979
            $move_item_icon = '';
5980
            $edit_icon = '';
5981
            $delete_icon = '';
5982
            $audio_icon = '';
5983
            $prerequisities_icon = '';
5984
            $forumIcon = '';
5985
            $previewIcon = '';
5986
            $pluginCalendarIcon = '';
5987
            $orderIcons = '';
5988
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5989
5990
            if ($is_allowed_to_edit) {
5991
                if (!$update_audio || $update_audio != 'true') {
5992
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
5993
                        $move_icon .= '<a class="moved" href="#">';
5994
                        $move_icon .= Display::return_icon(
5995
                            'move_everywhere.png',
5996
                            get_lang('Move'),
5997
                            [],
5998
                            ICON_SIZE_TINY
5999
                        );
6000
                        $move_icon .= '</a>';
6001
                    }
6002
                }
6003
6004
                // No edit for this item types
6005
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6006
                    if ($arrLP[$i]['item_type'] != 'dir') {
6007
                        $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">';
6008
                        $edit_icon .= Display::return_icon(
6009
                            'edit.png',
6010
                            get_lang('Edit section description/name'),
6011
                            [],
6012
                            ICON_SIZE_TINY
6013
                        );
6014
                        $edit_icon .= '</a>';
6015
6016
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6017
                            $forumThread = null;
6018
                            if (isset($this->items[$arrLP[$i]['id']])) {
6019
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6020
                                    $this->course_int_id,
6021
                                    $this->lp_session_id
6022
                                );
6023
                            }
6024
                            if ($forumThread) {
6025
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6026
                                        'action' => 'dissociate_forum',
6027
                                        'id' => $arrLP[$i]['id'],
6028
                                        'lp_id' => $this->lp_id,
6029
                                    ]);
6030
                                $forumIcon = Display::url(
6031
                                    Display::return_icon(
6032
                                        'forum.png',
6033
                                        get_lang('Dissociate the forum of this learning path item'),
6034
                                        [],
6035
                                        ICON_SIZE_TINY
6036
                                    ),
6037
                                    $forumIconUrl,
6038
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6039
                                );
6040
                            } else {
6041
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6042
                                        'action' => 'create_forum',
6043
                                        'id' => $arrLP[$i]['id'],
6044
                                        'lp_id' => $this->lp_id,
6045
                                    ]);
6046
                                $forumIcon = Display::url(
6047
                                    Display::return_icon(
6048
                                        'forum.png',
6049
                                        get_lang('Associate a forum to this learning path item'),
6050
                                        [],
6051
                                        ICON_SIZE_TINY
6052
                                    ),
6053
                                    $forumIconUrl,
6054
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6055
                                );
6056
                            }
6057
                        }
6058
                    } else {
6059
                        $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">';
6060
                        $edit_icon .= Display::return_icon(
6061
                            'edit.png',
6062
                            get_lang('Edit section description/name'),
6063
                            [],
6064
                            ICON_SIZE_TINY
6065
                        );
6066
                        $edit_icon .= '</a>';
6067
                    }
6068
                } else {
6069
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6070
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6071
                        $edit_icon .= Display::return_icon(
6072
                            'edit.png',
6073
                            get_lang('Edit'),
6074
                            [],
6075
                            ICON_SIZE_TINY
6076
                        );
6077
                        $edit_icon .= '</a>';
6078
                    }
6079
                }
6080
6081
                if ($pluginCalendar) {
6082
                    $pluginLink = $pluginUrl.
6083
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6084
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6085
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6086
                    if ($itemInfo && $itemInfo['value'] == 1) {
6087
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6088
                    }
6089
                    $pluginCalendarIcon = Display::url(
6090
                        $iconCalendar,
6091
                        $pluginLink,
6092
                        ['class' => 'btn btn-default']
6093
                    );
6094
                }
6095
6096
                if ($arrLP[$i]['item_type'] != 'final_item') {
6097
                    $orderIcons = Display::url(
6098
                        $upIcon,
6099
                        'javascript:void(0)',
6100
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6101
                    );
6102
                    $orderIcons .= Display::url(
6103
                        $downIcon,
6104
                        'javascript:void(0)',
6105
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6106
                    );
6107
                }
6108
6109
                $delete_icon .= ' <a 
6110
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6111
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6112
                    class="btn btn-default">';
6113
                $delete_icon .= Display::return_icon(
6114
                    'delete.png',
6115
                    get_lang('Delete section'),
6116
                    [],
6117
                    ICON_SIZE_TINY
6118
                );
6119
                $delete_icon .= '</a>';
6120
6121
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6122
                $previewImage = Display::return_icon(
6123
                    'preview_view.png',
6124
                    get_lang('Preview'),
6125
                    [],
6126
                    ICON_SIZE_TINY
6127
                );
6128
6129
                switch ($arrLP[$i]['item_type']) {
6130
                    case TOOL_DOCUMENT:
6131
                    case TOOL_LP_FINAL_ITEM:
6132
                    case TOOL_READOUT_TEXT:
6133
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6134
                        $previewIcon = Display::url(
6135
                            $previewImage,
6136
                            $urlPreviewLink,
6137
                            [
6138
                                'target' => '_blank',
6139
                                'class' => 'btn btn-default',
6140
                                'data-title' => $arrLP[$i]['title'],
6141
                                'title' => $arrLP[$i]['title'],
6142
                            ]
6143
                        );
6144
                        break;
6145
                    case TOOL_THREAD:
6146
                    case TOOL_FORUM:
6147
                    case TOOL_QUIZ:
6148
                    case TOOL_STUDENTPUBLICATION:
6149
                    case TOOL_LP_FINAL_ITEM:
6150
                    case TOOL_LINK:
6151
                        $class = 'btn btn-default';
6152
                        $target = '_blank';
6153
                        $link = self::rl_get_resource_link_for_learnpath(
6154
                            $this->course_int_id,
6155
                            $this->lp_id,
6156
                            $arrLP[$i]['id'],
6157
                            0
6158
                        );
6159
                        $previewIcon = Display::url(
6160
                            $previewImage,
6161
                            $link,
6162
                            [
6163
                                'class' => $class,
6164
                                'data-title' => $arrLP[$i]['title'],
6165
                                'title' => $arrLP[$i]['title'],
6166
                                'target' => $target,
6167
                            ]
6168
                        );
6169
                        break;
6170
                    default:
6171
                        $previewIcon = Display::url(
6172
                            $previewImage,
6173
                            $url.'&action=view_item',
6174
                            ['class' => 'btn btn-default', 'target' => '_blank']
6175
                        );
6176
                        break;
6177
                }
6178
6179
                if ($arrLP[$i]['item_type'] != 'dir') {
6180
                    $prerequisities_icon = Display::url(
6181
                        Display::return_icon(
6182
                            'accept.png',
6183
                            get_lang('Prerequisites'),
6184
                            [],
6185
                            ICON_SIZE_TINY
6186
                        ),
6187
                        $url.'&action=edit_item_prereq',
6188
                        ['class' => 'btn btn-default']
6189
                    );
6190
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6191
                        $move_item_icon = Display::url(
6192
                            Display::return_icon(
6193
                                'move.png',
6194
                                get_lang('Move'),
6195
                                [],
6196
                                ICON_SIZE_TINY
6197
                            ),
6198
                            $url.'&action=move_item',
6199
                            ['class' => 'btn btn-default']
6200
                        );
6201
                    }
6202
                    $audio_icon = Display::url(
6203
                        Display::return_icon(
6204
                            'audio.png',
6205
                            get_lang('Upload'),
6206
                            [],
6207
                            ICON_SIZE_TINY
6208
                        ),
6209
                        $url.'&action=add_audio',
6210
                        ['class' => 'btn btn-default']
6211
                    );
6212
                }
6213
            }
6214
            if ($update_audio != 'true') {
6215
                $row = $move_icon.' '.$icon.
6216
                    Display::span($title_cut).
6217
                    Display::tag(
6218
                        'div',
6219
                        "<div class=\"btn-group btn-group-xs\">
6220
                                    $previewIcon 
6221
                                    $audio 
6222
                                    $edit_icon 
6223
                                    $pluginCalendarIcon
6224
                                    $forumIcon 
6225
                                    $prerequisities_icon 
6226
                                    $move_item_icon 
6227
                                    $audio_icon 
6228
                                    $orderIcons
6229
                                    $delete_icon
6230
                                </div>",
6231
                        ['class' => 'btn-toolbar button_actions']
6232
                    );
6233
            } else {
6234
                $row =
6235
                    Display::span($title.$icon).
6236
                    Display::span($audio, ['class' => 'button_actions']);
6237
            }
6238
6239
            $default_data[$arrLP[$i]['id']] = $row;
6240
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6241
6242
            if (empty($parent_id)) {
6243
                $elements[$arrLP[$i]['id']]['data'] = $row;
6244
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6245
            } else {
6246
                $parent_arrays = [];
6247
                if ($arrLP[$i]['depth'] > 1) {
6248
                    // Getting list of parents
6249
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6250
                        foreach ($arrLP as $item) {
6251
                            if ($item['id'] == $parent_id) {
6252
                                if ($item['parent_item_id'] == 0) {
6253
                                    $parent_id = $item['id'];
6254
                                    break;
6255
                                } else {
6256
                                    $parent_id = $item['parent_item_id'];
6257
                                    if (empty($parent_arrays)) {
6258
                                        $parent_arrays[] = intval($item['id']);
6259
                                    }
6260
                                    $parent_arrays[] = $parent_id;
6261
                                    break;
6262
                                }
6263
                            }
6264
                        }
6265
                    }
6266
                }
6267
6268
                if (!empty($parent_arrays)) {
6269
                    $parent_arrays = array_reverse($parent_arrays);
6270
                    $val = '$elements';
6271
                    $x = 0;
6272
                    foreach ($parent_arrays as $item) {
6273
                        if ($x != count($parent_arrays) - 1) {
6274
                            $val .= '["'.$item.'"]["children"]';
6275
                        } else {
6276
                            $val .= '["'.$item.'"]["children"]';
6277
                        }
6278
                        $x++;
6279
                    }
6280
                    $val .= "";
6281
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6282
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6283
                } else {
6284
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6285
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6286
                }
6287
            }
6288
        }
6289
6290
        return [
6291
            'elements' => $elements,
6292
            'default_data' => $default_data,
6293
            'default_content' => $default_content,
6294
            'return_audio' => $return_audio,
6295
        ];
6296
    }
6297
6298
    /**
6299
     * @param string $updateAudio true/false strings
6300
     *
6301
     * @return string
6302
     */
6303
    public function returnLpItemList($updateAudio)
6304
    {
6305
        $result = $this->processBuildMenuElements($updateAudio);
6306
6307
        $html = self::print_recursive(
6308
            $result['elements'],
6309
            $result['default_data'],
6310
            $result['default_content']
6311
        );
6312
6313
        if (!empty($html)) {
6314
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6315
        }
6316
6317
        return $html;
6318
    }
6319
6320
    /**
6321
     * @param string $update_audio
6322
     * @param bool   $drop_element_here
6323
     *
6324
     * @return string
6325
     */
6326
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6327
    {
6328
        $result = $this->processBuildMenuElements($update_audio);
6329
6330
        $list = '<ul id="lp_item_list">';
6331
        $tree = $this->print_recursive(
6332
            $result['elements'],
6333
            $result['default_data'],
6334
            $result['default_content']
6335
        );
6336
6337
        if (!empty($tree)) {
6338
            $list .= $tree;
6339
        } else {
6340
            if ($drop_element_here) {
6341
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6342
            }
6343
        }
6344
        $list .= '</ul>';
6345
6346
        $return = Display::panelCollapse(
6347
            $this->name,
6348
            $list,
6349
            'scorm-list',
6350
            null,
6351
            'scorm-list-accordion',
6352
            'scorm-list-collapse'
6353
        );
6354
6355
        if ($update_audio === 'true') {
6356
            $return = $result['return_audio'];
6357
        }
6358
6359
        return $return;
6360
    }
6361
6362
    /**
6363
     * @param array $elements
6364
     * @param array $default_data
6365
     * @param array $default_content
6366
     *
6367
     * @return string
6368
     */
6369
    public function print_recursive($elements, $default_data, $default_content)
6370
    {
6371
        $return = '';
6372
        foreach ($elements as $key => $item) {
6373
            if (isset($item['load_data']) || empty($item['data'])) {
6374
                $item['data'] = $default_data[$item['load_data']];
6375
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6376
            }
6377
            $sub_list = '';
6378
            if (isset($item['type']) && $item['type'] === 'dir') {
6379
                // empty value
6380
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6381
            }
6382
            if (empty($item['children'])) {
6383
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6384
                $active = null;
6385
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6386
                    $active = 'active';
6387
                }
6388
                $return .= Display::tag(
6389
                    'li',
6390
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6391
                    ['id' => $key, 'class' => 'record li_container']
6392
                );
6393
            } else {
6394
                // Sections
6395
                $data = '';
6396
                if (isset($item['children'])) {
6397
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6398
                }
6399
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6400
                $return .= Display::tag(
6401
                    'li',
6402
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6403
                    ['id' => $key, 'class' => 'record li_container']
6404
                );
6405
            }
6406
        }
6407
6408
        return $return;
6409
    }
6410
6411
    /**
6412
     * This function builds the action menu.
6413
     *
6414
     * @param bool $returnContent          Optional
6415
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6416
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6417
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6418
     *
6419
     * @return string
6420
     */
6421
    public function build_action_menu(
6422
        $returnContent = false,
6423
        $showRequirementButtons = true,
6424
        $isConfigPage = false,
6425
        $allowExpand = true
6426
    ) {
6427
        $actionsRight = '';
6428
        $actionsLeft = Display::url(
6429
            Display::return_icon(
6430
                'back.png',
6431
                get_lang('Back to learning paths'),
6432
                '',
6433
                ICON_SIZE_MEDIUM
6434
            ),
6435
            'lp_controller.php?'.api_get_cidreq()
6436
        );
6437
        $actionsLeft .= Display::url(
6438
            Display::return_icon(
6439
                'preview_view.png',
6440
                get_lang('Preview'),
6441
                '',
6442
                ICON_SIZE_MEDIUM
6443
            ),
6444
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6445
                'action' => 'view',
6446
                'lp_id' => $this->lp_id,
6447
                'isStudentView' => 'true',
6448
            ])
6449
        );
6450
6451
        $actionsLeft .= Display::url(
6452
            Display::return_icon(
6453
                'upload_audio.png',
6454
                get_lang('Add audio'),
6455
                '',
6456
                ICON_SIZE_MEDIUM
6457
            ),
6458
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6459
                'action' => 'admin_view',
6460
                'lp_id' => $this->lp_id,
6461
                'updateaudio' => 'true',
6462
            ])
6463
        );
6464
6465
        $subscriptionSettings = self::getSubscriptionSettings();
6466
6467
        $request = api_request_uri();
6468
        if (strpos($request, 'edit') === false) {
6469
            $actionsLeft .= Display::url(
6470
                Display::return_icon(
6471
                    'settings.png',
6472
                    get_lang('Course settings'),
6473
                    '',
6474
                    ICON_SIZE_MEDIUM
6475
                ),
6476
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6477
                    'action' => 'edit',
6478
                    'lp_id' => $this->lp_id,
6479
                ])
6480
            );
6481
        }
6482
6483
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6484
            $actionsLeft .= Display::url(
6485
                Display::return_icon(
6486
                    'edit.png',
6487
                    get_lang('Edit'),
6488
                    '',
6489
                    ICON_SIZE_MEDIUM
6490
                ),
6491
                'lp_controller.php?'.http_build_query([
6492
                    'action' => 'build',
6493
                    'lp_id' => $this->lp_id,
6494
                ]).'&'.api_get_cidreq()
6495
            );
6496
        }
6497
6498
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6499
            if ($this->subscribeUsers == 1 &&
6500
                $subscriptionSettings['allow_add_users_to_lp']) {
6501
                $actionsLeft .= Display::url(
6502
                    Display::return_icon(
6503
                        'user.png',
6504
                        get_lang('Subscribe users to learning path'),
6505
                        '',
6506
                        ICON_SIZE_MEDIUM
6507
                    ),
6508
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6509
                );
6510
            }
6511
        }
6512
6513
        if ($allowExpand) {
6514
            $actionsLeft .= Display::url(
6515
                Display::return_icon(
6516
                    'expand.png',
6517
                    get_lang('Expand'),
6518
                    ['id' => 'expand'],
6519
                    ICON_SIZE_MEDIUM
6520
                ).
6521
                Display::return_icon(
6522
                    'contract.png',
6523
                    get_lang('Collapse'),
6524
                    ['id' => 'contract', 'class' => 'hide'],
6525
                    ICON_SIZE_MEDIUM
6526
                ),
6527
                '#',
6528
                ['role' => 'button', 'id' => 'hide_bar_template']
6529
            );
6530
        }
6531
6532
        if ($showRequirementButtons) {
6533
            $buttons = [
6534
                [
6535
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6536
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6537
                        'action' => 'set_previous_step_as_prerequisite',
6538
                        'lp_id' => $this->lp_id,
6539
                    ]),
6540
                ],
6541
                [
6542
                    'title' => get_lang('Clear all prerequisites'),
6543
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6544
                        'action' => 'clear_prerequisites',
6545
                        'lp_id' => $this->lp_id,
6546
                    ]),
6547
                ],
6548
            ];
6549
            $actionsRight = Display::groupButtonWithDropDown(
6550
                get_lang('Prerequisites options'),
6551
                $buttons,
6552
                true
6553
            );
6554
        }
6555
6556
        $toolbar = Display::toolbarAction(
6557
            'actions-lp-controller',
6558
            [$actionsLeft, $actionsRight]
6559
        );
6560
6561
        if ($returnContent) {
6562
            return $toolbar;
6563
        }
6564
6565
        echo $toolbar;
6566
    }
6567
6568
    /**
6569
     * Creates the default learning path folder.
6570
     *
6571
     * @param array $course
6572
     * @param int   $creatorId
6573
     *
6574
     * @return bool
6575
     */
6576
    public static function generate_learning_path_folder($course, $creatorId = 0)
6577
    {
6578
        // Creating learning_path folder
6579
        $dir = '/learning_path';
6580
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6581
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6582
6583
        $folder = false;
6584
        $folderData = create_unexisting_directory(
6585
            $course,
6586
            $creatorId,
6587
            0,
6588
            null,
6589
            0,
6590
            $filepath,
6591
            $dir,
6592
            get_lang('Learning paths'),
6593
            0
6594
        );
6595
6596
        if (!empty($folderData)) {
6597
            $folder = true;
6598
        }
6599
6600
        return $folder;
6601
    }
6602
6603
    /**
6604
     * @param array  $course
6605
     * @param string $lp_name
6606
     * @param int    $creatorId
6607
     *
6608
     * @return array
6609
     */
6610
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6611
    {
6612
        $filepath = '';
6613
        $dir = '/learning_path/';
6614
6615
        if (empty($lp_name)) {
6616
            $lp_name = $this->name;
6617
        }
6618
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6619
        $folder = self::generate_learning_path_folder($course, $creatorId);
6620
6621
        // Limits title size
6622
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6623
        $dir = $dir.$title;
6624
6625
        // Creating LP folder
6626
        $documentId = null;
6627
        if ($folder) {
6628
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6629
            $folderData = create_unexisting_directory(
6630
                $course,
6631
                $creatorId,
6632
                0,
6633
                0,
6634
                0,
6635
                $filepath,
6636
                $dir,
6637
                $lp_name
6638
            );
6639
            if (!empty($folderData)) {
6640
                $folder = true;
6641
            }
6642
6643
            $documentId = $folderData->getIid();
6644
            $dir = $dir.'/';
6645
            if ($folder) {
6646
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6647
            }
6648
        }
6649
6650
        if (empty($documentId)) {
6651
            $dir = api_remove_trailing_slash($dir);
6652
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6653
        }
6654
6655
        $array = [
6656
            'dir' => $dir,
6657
            'filepath' => $filepath,
6658
            'folder' => $folder,
6659
            'id' => $documentId,
6660
        ];
6661
6662
        return $array;
6663
    }
6664
6665
    /**
6666
     * Create a new document //still needs some finetuning.
6667
     *
6668
     * @param array  $courseInfo
6669
     * @param string $content
6670
     * @param string $title
6671
     * @param string $extension
6672
     * @param int    $parentId
6673
     * @param int    $creatorId  creator id
6674
     *
6675
     * @return int
6676
     */
6677
    public function create_document(
6678
        $courseInfo,
6679
        $content = '',
6680
        $title = '',
6681
        $extension = 'html',
6682
        $parentId = 0,
6683
        $creatorId = 0
6684
    ) {
6685
        if (!empty($courseInfo)) {
6686
            $course_id = $courseInfo['real_id'];
6687
        } else {
6688
            $course_id = api_get_course_int_id();
6689
        }
6690
6691
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6692
        $sessionId = api_get_session_id();
6693
6694
        // Generates folder
6695
        $result = $this->generate_lp_folder($courseInfo);
6696
        $dir = $result['dir'];
6697
6698
        if (empty($parentId) || $parentId == '/') {
6699
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6700
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6701
6702
            if ($parentId === '/') {
6703
                $dir = '/';
6704
            }
6705
6706
            // Please, do not modify this dirname formatting.
6707
            if (strstr($dir, '..')) {
6708
                $dir = '/';
6709
            }
6710
6711
            if (!empty($dir[0]) && $dir[0] == '.') {
6712
                $dir = substr($dir, 1);
6713
            }
6714
            if (!empty($dir[0]) && $dir[0] != '/') {
6715
                $dir = '/'.$dir;
6716
            }
6717
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6718
                $dir .= '/';
6719
            }
6720
        } else {
6721
            $parentInfo = DocumentManager::get_document_data_by_id(
6722
                $parentId,
6723
                $courseInfo['code']
6724
            );
6725
            if (!empty($parentInfo)) {
6726
                $dir = $parentInfo['path'].'/';
6727
            }
6728
        }
6729
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6730
        // is already escaped twice when it gets here.
6731
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6732
        if (!empty($title)) {
6733
            $title = api_replace_dangerous_char(stripslashes($title));
6734
        } else {
6735
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6736
        }
6737
6738
        $title = disable_dangerous_file($title);
6739
        $filename = $title;
6740
        $content = !empty($content) ? $content : $_POST['content_lp'];
6741
        $tmp_filename = $filename;
6742
        $filename = $tmp_filename.'.'.$extension;
6743
6744
        if ($extension === 'html') {
6745
            $content = stripslashes($content);
6746
            $content = str_replace(
6747
                api_get_path(WEB_COURSE_PATH),
6748
                api_get_path(REL_PATH).'courses/',
6749
                $content
6750
            );
6751
6752
            // Change the path of mp3 to absolute.
6753
            // The first regexp deals with :// urls.
6754
            $content = preg_replace(
6755
                "|(flashvars=\"file=)([^:/]+)/|",
6756
                "$1".api_get_path(
6757
                    REL_COURSE_PATH
6758
                ).$courseInfo['path'].'/document/',
6759
                $content
6760
            );
6761
            // The second regexp deals with audio/ urls.
6762
            $content = preg_replace(
6763
                "|(flashvars=\"file=)([^/]+)/|",
6764
                "$1".api_get_path(
6765
                    REL_COURSE_PATH
6766
                ).$courseInfo['path'].'/document/$2/',
6767
                $content
6768
            );
6769
            // For flv player: To prevent edition problem with firefox,
6770
            // we have to use a strange tip (don't blame me please).
6771
            $content = str_replace(
6772
                '</body>',
6773
                '<style type="text/css">body{}</style></body>',
6774
                $content
6775
            );
6776
        }
6777
6778
        $save_file_path = $dir.$filename;
6779
6780
        $document = DocumentManager::addDocument(
6781
            $courseInfo,
6782
            $save_file_path,
6783
            'file',
6784
            '',
6785
            $tmp_filename,
6786
            '',
6787
            0, //readonly
6788
            true,
6789
            null,
6790
            $sessionId,
6791
            $creatorId,
6792
            false,
6793
            $content,
6794
            $parentId
6795
        );
6796
6797
        $document_id = $document->getIid();
6798
        if ($document_id) {
6799
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6800
            $new_title = $originalTitle;
6801
6802
            if ($new_comment || $new_title) {
6803
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6804
                $ct = '';
6805
                if ($new_comment) {
6806
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6807
                }
6808
                if ($new_title) {
6809
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6810
                }
6811
6812
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6813
                        WHERE c_id = $course_id AND id = $document_id ";
6814
                Database::query($sql);
6815
            }
6816
        }
6817
6818
        return $document_id;
6819
    }
6820
6821
    /**
6822
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6823
     *
6824
     * @param array $_course array
6825
     */
6826
    public function edit_document($_course)
6827
    {
6828
        $course_id = api_get_course_int_id();
6829
        $urlAppend = api_get_configuration_value('url_append');
6830
        // Please, do not modify this dirname formatting.
6831
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6832
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6833
6834
        if (strstr($dir, '..')) {
6835
            $dir = '/';
6836
        }
6837
6838
        if (isset($dir[0]) && $dir[0] == '.') {
6839
            $dir = substr($dir, 1);
6840
        }
6841
6842
        if (isset($dir[0]) && $dir[0] != '/') {
6843
            $dir = '/'.$dir;
6844
        }
6845
6846
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6847
            $dir .= '/';
6848
        }
6849
6850
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6851
        if (!is_dir($filepath)) {
6852
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6853
        }
6854
6855
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6856
6857
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6858
            $document_id = (int) $_POST['path'];
6859
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
6860
            if (empty($documentInfo)) {
6861
                // Try with iid
6862
                $table = Database::get_course_table(TABLE_DOCUMENT);
6863
                $sql = "SELECT id, path FROM $table 
6864
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
6865
                $res_doc = Database::query($sql);
6866
                $row = Database::fetch_array($res_doc);
6867
                if ($row) {
6868
                    $document_id = $row['id'];
6869
                    $documentPath = $row['path'];
6870
                }
6871
            } else {
6872
                $documentPath = $documentInfo['path'];
6873
            }
6874
6875
            $content = stripslashes($_POST['content_lp']);
6876
            $file = $filepath.$documentPath;
6877
6878
            if (!file_exists($file)) {
6879
                return false;
6880
            }
6881
6882
            if ($fp = @fopen($file, 'w')) {
6883
                $content = str_replace(
6884
                    api_get_path(WEB_COURSE_PATH),
6885
                    $urlAppend.api_get_path(REL_COURSE_PATH),
6886
                    $content
6887
                );
6888
                // Change the path of mp3 to absolute.
6889
                // The first regexp deals with :// urls.
6890
                $content = preg_replace(
6891
                    "|(flashvars=\"file=)([^:/]+)/|",
6892
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
6893
                    $content
6894
                );
6895
                // The second regexp deals with audio/ urls.
6896
                $content = preg_replace(
6897
                    "|(flashvars=\"file=)([^:/]+)/|",
6898
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
6899
                    $content
6900
                );
6901
                fputs($fp, $content);
6902
                fclose($fp);
6903
6904
                $sql = "UPDATE $table_doc SET
6905
                            title='".Database::escape_string($_POST['title'])."'
6906
                        WHERE c_id = $course_id AND id = ".$document_id;
6907
                Database::query($sql);
6908
            }
6909
        }
6910
    }
6911
6912
    /**
6913
     * Displays the selected item, with a panel for manipulating the item.
6914
     *
6915
     * @param int    $item_id
6916
     * @param string $msg
6917
     * @param bool   $show_actions
6918
     *
6919
     * @return string
6920
     */
6921
    public function display_item($item_id, $msg = null, $show_actions = true)
6922
    {
6923
        $course_id = api_get_course_int_id();
6924
        $return = '';
6925
        if (is_numeric($item_id)) {
6926
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6927
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6928
                    WHERE lp.iid = ".intval($item_id);
6929
            $result = Database::query($sql);
6930
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6931
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6932
6933
                // Prevents wrong parent selection for document, see Bug#1251.
6934
                if ($row['item_type'] != 'dir') {
6935
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6936
                }
6937
6938
                if ($show_actions) {
6939
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6940
                }
6941
                $return .= '<div style="padding:10px;">';
6942
6943
                if ($msg != '') {
6944
                    $return .= $msg;
6945
                }
6946
6947
                $return .= '<h3>'.$row['title'].'</h3>';
6948
6949
                switch ($row['item_type']) {
6950
                    case TOOL_THREAD:
6951
                        $link = $this->rl_get_resource_link_for_learnpath(
6952
                            $course_id,
6953
                            $row['lp_id'],
6954
                            $item_id,
6955
                            0
6956
                        );
6957
                        $return .= Display::url(
6958
                            get_lang('Go to thread'),
6959
                            $link,
6960
                            ['class' => 'btn btn-primary']
6961
                        );
6962
                        break;
6963
                    case TOOL_FORUM:
6964
                        $return .= Display::url(
6965
                            get_lang('Go to the forum'),
6966
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6967
                            ['class' => 'btn btn-primary']
6968
                        );
6969
                        break;
6970
                    case TOOL_QUIZ:
6971
                        if (!empty($row['path'])) {
6972
                            $exercise = new Exercise();
6973
                            $exercise->read($row['path']);
6974
                            $return .= $exercise->description.'<br />';
6975
                            $return .= Display::url(
6976
                                get_lang('Go to exercise'),
6977
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6978
                                ['class' => 'btn btn-primary']
6979
                            );
6980
                        }
6981
                        break;
6982
                    case TOOL_LP_FINAL_ITEM:
6983
                        $return .= $this->getSavedFinalItem();
6984
                        break;
6985
                    case TOOL_DOCUMENT:
6986
                    case TOOL_READOUT_TEXT:
6987
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6988
                        $sql_doc = "SELECT path FROM $tbl_doc
6989
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
6990
                        $result = Database::query($sql_doc);
6991
                        $path_file = Database::result($result, 0, 0);
6992
                        $path_parts = pathinfo($path_file);
6993
                        // TODO: Correct the following naive comparisons.
6994
                        if (in_array($path_parts['extension'], [
6995
                            'html',
6996
                            'txt',
6997
                            'png',
6998
                            'jpg',
6999
                            'JPG',
7000
                            'jpeg',
7001
                            'JPEG',
7002
                            'gif',
7003
                            'swf',
7004
                            'pdf',
7005
                            'htm',
7006
                        ])) {
7007
                            $return .= $this->display_document($row['path'], true, true);
7008
                        }
7009
                        break;
7010
                    case TOOL_HOTPOTATOES:
7011
                        $return .= $this->display_document($row['path'], false, true);
7012
                        break;
7013
                }
7014
                $return .= '</div>';
7015
            }
7016
        }
7017
7018
        return $return;
7019
    }
7020
7021
    /**
7022
     * Shows the needed forms for editing a specific item.
7023
     *
7024
     * @param int $item_id
7025
     *
7026
     * @throws Exception
7027
     * @throws HTML_QuickForm_Error
7028
     *
7029
     * @return string
7030
     */
7031
    public function display_edit_item($item_id)
7032
    {
7033
        $course_id = api_get_course_int_id();
7034
        $return = '';
7035
        $item_id = (int) $item_id;
7036
7037
        if (empty($item_id)) {
7038
            return '';
7039
        }
7040
7041
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7042
        $sql = "SELECT * FROM $tbl_lp_item
7043
                WHERE iid = ".$item_id;
7044
        $res = Database::query($sql);
7045
        $row = Database::fetch_array($res);
7046
        switch ($row['item_type']) {
7047
            case 'dir':
7048
            case 'asset':
7049
            case 'sco':
7050
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7051
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7052
                    $return .= $this->display_item_form(
7053
                        $row['item_type'],
7054
                        get_lang('Edit the current section').' :',
7055
                        'edit',
7056
                        $item_id,
7057
                        $row
7058
                    );
7059
                } else {
7060
                    $return .= $this->display_item_form(
7061
                        $row['item_type'],
7062
                        get_lang('Edit the current section').' :',
7063
                        'edit_item',
7064
                        $item_id,
7065
                        $row
7066
                    );
7067
                }
7068
                break;
7069
            case TOOL_DOCUMENT:
7070
            case TOOL_READOUT_TEXT:
7071
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7072
                $sql = "SELECT lp.*, doc.path as dir
7073
                        FROM $tbl_lp_item as lp
7074
                        LEFT JOIN $tbl_doc as doc
7075
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7076
                        WHERE
7077
                            doc.c_id = $course_id AND
7078
                            lp.iid = ".$item_id;
7079
                $res_step = Database::query($sql);
7080
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7081
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7082
7083
                if ($row['item_type'] === TOOL_DOCUMENT) {
7084
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7085
                }
7086
7087
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7088
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7089
                }
7090
                break;
7091
            case TOOL_LINK:
7092
                $linkId = (int) $row['path'];
7093
                if (!empty($linkId)) {
7094
                    $table = Database::get_course_table(TABLE_LINK);
7095
                    $sql = 'SELECT url FROM '.$table.'
7096
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7097
                    $res_link = Database::query($sql);
7098
                    $row_link = Database::fetch_array($res_link);
7099
                    if (empty($row_link)) {
7100
                        // Try with id
7101
                        $sql = 'SELECT url FROM '.$table.'
7102
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7103
                        $res_link = Database::query($sql);
7104
                        $row_link = Database::fetch_array($res_link);
7105
                    }
7106
7107
                    if (is_array($row_link)) {
7108
                        $row['url'] = $row_link['url'];
7109
                    }
7110
                }
7111
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7112
                $return .= $this->display_link_form('edit', $item_id, $row);
7113
                break;
7114
            case TOOL_LP_FINAL_ITEM:
7115
                Session::write('finalItem', true);
7116
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7117
                $sql = "SELECT lp.*, doc.path as dir
7118
                        FROM $tbl_lp_item as lp
7119
                        LEFT JOIN $tbl_doc as doc
7120
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7121
                        WHERE
7122
                            doc.c_id = $course_id AND
7123
                            lp.iid = ".$item_id;
7124
                $res_step = Database::query($sql);
7125
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7126
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7127
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7128
                break;
7129
            case TOOL_QUIZ:
7130
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7131
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7132
                break;
7133
            case TOOL_HOTPOTATOES:
7134
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7135
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7136
                break;
7137
            case TOOL_STUDENTPUBLICATION:
7138
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7139
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7140
                break;
7141
            case TOOL_FORUM:
7142
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7143
                $return .= $this->display_forum_form('edit', $item_id, $row);
7144
                break;
7145
            case TOOL_THREAD:
7146
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7147
                $return .= $this->display_thread_form('edit', $item_id, $row);
7148
                break;
7149
        }
7150
7151
        return $return;
7152
    }
7153
7154
    /**
7155
     * Function that displays a list with al the resources that
7156
     * could be added to the learning path.
7157
     *
7158
     * @throws Exception
7159
     * @throws HTML_QuickForm_Error
7160
     *
7161
     * @return bool
7162
     */
7163
    public function display_resources()
7164
    {
7165
        $course_code = api_get_course_id();
7166
7167
        // Get all the docs.
7168
        $documents = $this->get_documents(true);
7169
7170
        // Get all the exercises.
7171
        $exercises = $this->get_exercises();
7172
7173
        // Get all the links.
7174
        $links = $this->get_links();
7175
7176
        // Get all the student publications.
7177
        $works = $this->get_student_publications();
7178
7179
        // Get all the forums.
7180
        $forums = $this->get_forums(null, $course_code);
7181
7182
        // Get the final item form (see BT#11048) .
7183
        $finish = $this->getFinalItemForm();
7184
7185
        $headers = [
7186
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7187
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7188
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7189
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7190
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7191
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7192
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7193
        ];
7194
7195
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7196
        $dir = $this->display_item_form('dir', get_lang('EnterDataAdd section'), 'add_item');
7197
7198
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7199
7200
        echo Display::tabs(
7201
            $headers,
7202
            [
7203
                $documents,
7204
                $exercises,
7205
                $links,
7206
                $works,
7207
                $forums,
7208
                $dir,
7209
                $finish,
7210
            ],
7211
            'resource_tab',
7212
            [],
7213
            [],
7214
            $selected
7215
        );
7216
7217
        return true;
7218
    }
7219
7220
    /**
7221
     * Returns the extension of a document.
7222
     *
7223
     * @param string $filename
7224
     *
7225
     * @return string Extension (part after the last dot)
7226
     */
7227
    public function get_extension($filename)
7228
    {
7229
        $explode = explode('.', $filename);
7230
7231
        return $explode[count($explode) - 1];
7232
    }
7233
7234
    /**
7235
     * Displays a document by id.
7236
     *
7237
     * @param int  $id
7238
     * @param bool $show_title
7239
     * @param bool $iframe
7240
     * @param bool $edit_link
7241
     *
7242
     * @return string
7243
     */
7244
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7245
    {
7246
        $_course = api_get_course_info();
7247
        $course_id = api_get_course_int_id();
7248
        $id = (int) $id;
7249
        $return = '';
7250
        $table = Database::get_course_table(TABLE_DOCUMENT);
7251
        $sql_doc = "SELECT * FROM $table
7252
                    WHERE c_id = $course_id AND iid = $id";
7253
        $res_doc = Database::query($sql_doc);
7254
        $row_doc = Database::fetch_array($res_doc);
7255
7256
        // TODO: Add a path filter.
7257
        if ($iframe) {
7258
            $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>';
7259
        } else {
7260
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7261
        }
7262
7263
        return $return;
7264
    }
7265
7266
    /**
7267
     * Return HTML form to add/edit a quiz.
7268
     *
7269
     * @param string $action     Action (add/edit)
7270
     * @param int    $id         Item ID if already exists
7271
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7272
     *
7273
     * @throws Exception
7274
     *
7275
     * @return string HTML form
7276
     */
7277
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7278
    {
7279
        $course_id = api_get_course_int_id();
7280
        $id = (int) $id;
7281
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7282
7283
        if ($id != 0 && is_array($extra_info)) {
7284
            $item_title = $extra_info['title'];
7285
            $item_description = $extra_info['description'];
7286
        } elseif (is_numeric($extra_info)) {
7287
            $sql = "SELECT title, description
7288
                    FROM $tbl_quiz
7289
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7290
7291
            $result = Database::query($sql);
7292
            $row = Database::fetch_array($result);
7293
            $item_title = $row['title'];
7294
            $item_description = $row['description'];
7295
        } else {
7296
            $item_title = '';
7297
            $item_description = '';
7298
        }
7299
        $item_title = Security::remove_XSS($item_title);
7300
        $item_description = Security::remove_XSS($item_description);
7301
7302
        $parent = 0;
7303
        if ($id != 0 && is_array($extra_info)) {
7304
            $parent = $extra_info['parent_item_id'];
7305
        }
7306
7307
        $arrLP = $this->getItemsForForm();
7308
        $this->tree_array($arrLP);
7309
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7310
        unset($this->arrMenu);
7311
7312
        $form = new FormValidator(
7313
            'quiz_form',
7314
            'POST',
7315
            $this->getCurrentBuildingModeURL()
7316
        );
7317
        $defaults = [];
7318
7319
        if ($action === 'add') {
7320
            $legend = get_lang('Adding a test to the course');
7321
        } elseif ($action === 'move') {
7322
            $legend = get_lang('Move the current test');
7323
        } else {
7324
            $legend = get_lang('Edit the current test');
7325
        }
7326
7327
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7328
            $legend .= Display::return_message(get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument'));
7329
        }
7330
7331
        $form->addHeader($legend);
7332
7333
        if ($action != 'move') {
7334
            $this->setItemTitle($form);
7335
            $defaults['title'] = $item_title;
7336
        }
7337
7338
        // Select for Parent item, root or chapter
7339
        $selectParent = $form->addSelect(
7340
            'parent',
7341
            get_lang('Parent'),
7342
            [],
7343
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7344
        );
7345
        $selectParent->addOption($this->name, 0);
7346
7347
        $arrHide = [
7348
            $id,
7349
        ];
7350
        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...
7351
            if ($action != 'add') {
7352
                if (
7353
                    ($arrLP[$i]['item_type'] == 'dir') &&
7354
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7355
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7356
                ) {
7357
                    $selectParent->addOption(
7358
                        $arrLP[$i]['title'],
7359
                        $arrLP[$i]['id'],
7360
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7361
                    );
7362
7363
                    if ($parent == $arrLP[$i]['id']) {
7364
                        $selectParent->setSelected($arrLP[$i]['id']);
7365
                    }
7366
                } else {
7367
                    $arrHide[] = $arrLP[$i]['id'];
7368
                }
7369
            } else {
7370
                if ($arrLP[$i]['item_type'] == 'dir') {
7371
                    $selectParent->addOption(
7372
                        $arrLP[$i]['title'],
7373
                        $arrLP[$i]['id'],
7374
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7375
                    );
7376
7377
                    if ($parent == $arrLP[$i]['id']) {
7378
                        $selectParent->setSelected($arrLP[$i]['id']);
7379
                    }
7380
                }
7381
            }
7382
        }
7383
7384
        if (is_array($arrLP)) {
7385
            reset($arrLP);
7386
        }
7387
7388
        $selectPrevious = $form->addSelect(
7389
            'previous',
7390
            get_lang('Position'),
7391
            [],
7392
            ['id' => 'previous']
7393
        );
7394
        $selectPrevious->addOption(get_lang('First position'), 0);
7395
7396
        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...
7397
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7398
                $arrLP[$i]['id'] != $id
7399
            ) {
7400
                $selectPrevious->addOption(
7401
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7402
                    $arrLP[$i]['id']
7403
                );
7404
7405
                if (is_array($extra_info)) {
7406
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7407
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7408
                    }
7409
                } elseif ($action == 'add') {
7410
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7411
                }
7412
            }
7413
        }
7414
7415
        if ($action != 'move') {
7416
            $arrHide = [];
7417
            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...
7418
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7419
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7420
                }
7421
            }
7422
        }
7423
7424
        if ($action === 'add') {
7425
            $form->addButtonSave(get_lang('Add test to course'), 'submit_button');
7426
        } else {
7427
            $form->addButtonSave(get_lang('Edit the current test'), 'submit_button');
7428
        }
7429
7430
        if ($action === 'move') {
7431
            $form->addHidden('title', $item_title);
7432
            $form->addHidden('description', $item_description);
7433
        }
7434
7435
        if (is_numeric($extra_info)) {
7436
            $form->addHidden('path', $extra_info);
7437
        } elseif (is_array($extra_info)) {
7438
            $form->addHidden('path', $extra_info['path']);
7439
        }
7440
7441
        $form->addHidden('type', TOOL_QUIZ);
7442
        $form->addHidden('post_time', time());
7443
        $form->setDefaults($defaults);
7444
7445
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7446
    }
7447
7448
    /**
7449
     * Addition of Hotpotatoes tests.
7450
     *
7451
     * @param string $action
7452
     * @param int    $id         Internal ID of the item
7453
     * @param string $extra_info
7454
     *
7455
     * @return string HTML structure to display the hotpotatoes addition formular
7456
     */
7457
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7458
    {
7459
        $course_id = api_get_course_int_id();
7460
        $uploadPath = DIR_HOTPOTATOES;
7461
7462
        if ($id != 0 && is_array($extra_info)) {
7463
            $item_title = stripslashes($extra_info['title']);
7464
            $item_description = stripslashes($extra_info['description']);
7465
        } elseif (is_numeric($extra_info)) {
7466
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7467
7468
            $sql = "SELECT * FROM $TBL_DOCUMENT
7469
                    WHERE
7470
                        c_id = $course_id AND
7471
                        path LIKE '".$uploadPath."/%/%htm%' AND
7472
                        iid = ".(int) $extra_info."
7473
                    ORDER BY iid ASC";
7474
7475
            $res_hot = Database::query($sql);
7476
            $row = Database::fetch_array($res_hot);
7477
7478
            $item_title = $row['title'];
7479
            $item_description = $row['description'];
7480
7481
            if (!empty($row['comment'])) {
7482
                $item_title = $row['comment'];
7483
            }
7484
        } else {
7485
            $item_title = '';
7486
            $item_description = '';
7487
        }
7488
7489
        $parent = 0;
7490
        if ($id != 0 && is_array($extra_info)) {
7491
            $parent = $extra_info['parent_item_id'];
7492
        }
7493
7494
        $arrLP = $this->getItemsForForm();
7495
        $legend = '<legend>';
7496
        if ($action == 'add') {
7497
            $legend .= get_lang('Adding a test to the course');
7498
        } elseif ($action == 'move') {
7499
            $legend .= get_lang('Move the current test');
7500
        } else {
7501
            $legend .= get_lang('Edit the current test');
7502
        }
7503
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7504
            $legend .= Display:: return_message(
7505
                get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument')
7506
            );
7507
        }
7508
        $legend .= '</legend>';
7509
7510
        $return = '<form method="POST">';
7511
        $return .= $legend;
7512
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7513
        $return .= '<tr>';
7514
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7515
        $return .= '<td class="input">';
7516
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7517
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7518
        $arrHide = [$id];
7519
7520
        if (count($arrLP) > 0) {
7521
            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...
7522
                if ($action != 'add') {
7523
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7524
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7525
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7526
                    ) {
7527
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7528
                    } else {
7529
                        $arrHide[] = $arrLP[$i]['id'];
7530
                    }
7531
                } else {
7532
                    if ($arrLP[$i]['item_type'] == 'dir') {
7533
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7534
                    }
7535
                }
7536
            }
7537
            reset($arrLP);
7538
        }
7539
7540
        $return .= '</select>';
7541
        $return .= '</td>';
7542
        $return .= '</tr>';
7543
        $return .= '<tr>';
7544
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7545
        $return .= '<td class="input">';
7546
        $return .= '<select id="previous" name="previous" size="1">';
7547
        $return .= '<option class="top" value="0">'.get_lang('First position').'</option>';
7548
7549
        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...
7550
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7551
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7552
                    $selected = 'selected="selected" ';
7553
                } elseif ($action == 'add') {
7554
                    $selected = 'selected="selected" ';
7555
                } else {
7556
                    $selected = '';
7557
                }
7558
7559
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7560
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7561
            }
7562
        }
7563
7564
        $return .= '</select>';
7565
        $return .= '</td>';
7566
        $return .= '</tr>';
7567
7568
        if ($action != 'move') {
7569
            $return .= '<tr>';
7570
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7571
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7572
            $return .= '</tr>';
7573
            $id_prerequisite = 0;
7574
            if (is_array($arrLP) && count($arrLP) > 0) {
7575
                foreach ($arrLP as $key => $value) {
7576
                    if ($value['id'] == $id) {
7577
                        $id_prerequisite = $value['prerequisite'];
7578
                        break;
7579
                    }
7580
                }
7581
7582
                $arrHide = [];
7583
                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...
7584
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7585
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7586
                    }
7587
                }
7588
            }
7589
        }
7590
7591
        $return .= '<tr>';
7592
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7593
            get_lang('Save hotpotatoes').'</button></td>';
7594
        $return .= '</tr>';
7595
        $return .= '</table>';
7596
7597
        if ($action == 'move') {
7598
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7599
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7600
        }
7601
7602
        if (is_numeric($extra_info)) {
7603
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7604
        } elseif (is_array($extra_info)) {
7605
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7606
        }
7607
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7608
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7609
        $return .= '</form>';
7610
7611
        return $return;
7612
    }
7613
7614
    /**
7615
     * Return the form to display the forum edit/add option.
7616
     *
7617
     * @param string $action
7618
     * @param int    $id         ID of the lp_item if already exists
7619
     * @param string $extra_info
7620
     *
7621
     * @throws Exception
7622
     *
7623
     * @return string HTML form
7624
     */
7625
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7626
    {
7627
        $course_id = api_get_course_int_id();
7628
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7629
7630
        $item_title = '';
7631
        $item_description = '';
7632
7633
        if ($id != 0 && is_array($extra_info)) {
7634
            $item_title = stripslashes($extra_info['title']);
7635
        } elseif (is_numeric($extra_info)) {
7636
            $sql = "SELECT forum_title as title, forum_comment as comment
7637
                    FROM $tbl_forum
7638
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7639
7640
            $result = Database::query($sql);
7641
            $row = Database::fetch_array($result);
7642
7643
            $item_title = $row['title'];
7644
            $item_description = $row['comment'];
7645
        }
7646
        $parent = 0;
7647
        if ($id != 0 && is_array($extra_info)) {
7648
            $parent = $extra_info['parent_item_id'];
7649
        }
7650
        $arrLP = $this->getItemsForForm();
7651
        $this->tree_array($arrLP);
7652
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7653
        unset($this->arrMenu);
7654
7655
        if ($action == 'add') {
7656
            $legend = get_lang('Adding a forum to the course');
7657
        } elseif ($action == 'move') {
7658
            $legend = get_lang('Move the current forum');
7659
        } else {
7660
            $legend = get_lang('Edit the current forum');
7661
        }
7662
7663
        $form = new FormValidator(
7664
            'forum_form',
7665
            'POST',
7666
            $this->getCurrentBuildingModeURL()
7667
        );
7668
        $defaults = [];
7669
7670
        $form->addHeader($legend);
7671
7672
        if ($action != 'move') {
7673
            $this->setItemTitle($form);
7674
            $defaults['title'] = $item_title;
7675
        }
7676
7677
        $selectParent = $form->addSelect(
7678
            'parent',
7679
            get_lang('Parent'),
7680
            [],
7681
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7682
        );
7683
        $selectParent->addOption($this->name, 0);
7684
        $arrHide = [
7685
            $id,
7686
        ];
7687
        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...
7688
            if ($action != 'add') {
7689
                if ($arrLP[$i]['item_type'] == 'dir' &&
7690
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7691
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7692
                ) {
7693
                    $selectParent->addOption(
7694
                        $arrLP[$i]['title'],
7695
                        $arrLP[$i]['id'],
7696
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7697
                    );
7698
7699
                    if ($parent == $arrLP[$i]['id']) {
7700
                        $selectParent->setSelected($arrLP[$i]['id']);
7701
                    }
7702
                } else {
7703
                    $arrHide[] = $arrLP[$i]['id'];
7704
                }
7705
            } else {
7706
                if ($arrLP[$i]['item_type'] == 'dir') {
7707
                    $selectParent->addOption(
7708
                        $arrLP[$i]['title'],
7709
                        $arrLP[$i]['id'],
7710
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7711
                    );
7712
7713
                    if ($parent == $arrLP[$i]['id']) {
7714
                        $selectParent->setSelected($arrLP[$i]['id']);
7715
                    }
7716
                }
7717
            }
7718
        }
7719
7720
        if (is_array($arrLP)) {
7721
            reset($arrLP);
7722
        }
7723
7724
        $selectPrevious = $form->addSelect(
7725
            'previous',
7726
            get_lang('Position'),
7727
            [],
7728
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7729
        );
7730
        $selectPrevious->addOption(get_lang('First position'), 0);
7731
7732
        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...
7733
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7734
                $arrLP[$i]['id'] != $id
7735
            ) {
7736
                $selectPrevious->addOption(
7737
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7738
                    $arrLP[$i]['id']
7739
                );
7740
7741
                if (isset($extra_info['previous_item_id']) &&
7742
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7743
                ) {
7744
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7745
                } elseif ($action == 'add') {
7746
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7747
                }
7748
            }
7749
        }
7750
7751
        if ($action != 'move') {
7752
            $id_prerequisite = 0;
7753
            if (is_array($arrLP)) {
7754
                foreach ($arrLP as $key => $value) {
7755
                    if ($value['id'] == $id) {
7756
                        $id_prerequisite = $value['prerequisite'];
7757
                        break;
7758
                    }
7759
                }
7760
            }
7761
7762
            $arrHide = [];
7763
            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...
7764
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7765
                    if (isset($extra_info['previous_item_id']) &&
7766
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7767
                    ) {
7768
                        $s_selected_position = $arrLP[$i]['id'];
7769
                    } elseif ($action == 'add') {
7770
                        $s_selected_position = 0;
7771
                    }
7772
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7773
                }
7774
            }
7775
        }
7776
7777
        if ($action == 'add') {
7778
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7779
        } else {
7780
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7781
        }
7782
7783
        if ($action == 'move') {
7784
            $form->addHidden('title', $item_title);
7785
            $form->addHidden('description', $item_description);
7786
        }
7787
7788
        if (is_numeric($extra_info)) {
7789
            $form->addHidden('path', $extra_info);
7790
        } elseif (is_array($extra_info)) {
7791
            $form->addHidden('path', $extra_info['path']);
7792
        }
7793
        $form->addHidden('type', TOOL_FORUM);
7794
        $form->addHidden('post_time', time());
7795
        $form->setDefaults($defaults);
7796
7797
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7798
    }
7799
7800
    /**
7801
     * Return HTML form to add/edit forum threads.
7802
     *
7803
     * @param string $action
7804
     * @param int    $id         Item ID if already exists in learning path
7805
     * @param string $extra_info
7806
     *
7807
     * @throws Exception
7808
     *
7809
     * @return string HTML form
7810
     */
7811
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7812
    {
7813
        $course_id = api_get_course_int_id();
7814
        if (empty($course_id)) {
7815
            return null;
7816
        }
7817
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7818
7819
        $item_title = '';
7820
        $item_description = '';
7821
        if ($id != 0 && is_array($extra_info)) {
7822
            $item_title = stripslashes($extra_info['title']);
7823
        } elseif (is_numeric($extra_info)) {
7824
            $sql = "SELECT thread_title as title FROM $tbl_forum
7825
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7826
7827
            $result = Database::query($sql);
7828
            $row = Database::fetch_array($result);
7829
7830
            $item_title = $row['title'];
7831
            $item_description = '';
7832
        }
7833
7834
        $parent = 0;
7835
        if ($id != 0 && is_array($extra_info)) {
7836
            $parent = $extra_info['parent_item_id'];
7837
        }
7838
7839
        $arrLP = $this->getItemsForForm();
7840
        $this->tree_array($arrLP);
7841
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7842
        unset($this->arrMenu);
7843
7844
        $form = new FormValidator(
7845
            'thread_form',
7846
            'POST',
7847
            $this->getCurrentBuildingModeURL()
7848
        );
7849
        $defaults = [];
7850
7851
        if ($action == 'add') {
7852
            $legend = get_lang('Adding a forum to the course');
7853
        } elseif ($action == 'move') {
7854
            $legend = get_lang('Move the current forum');
7855
        } else {
7856
            $legend = get_lang('Edit the current forum');
7857
        }
7858
7859
        $form->addHeader($legend);
7860
        $selectParent = $form->addSelect(
7861
            'parent',
7862
            get_lang('Parent'),
7863
            [],
7864
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7865
        );
7866
        $selectParent->addOption($this->name, 0);
7867
7868
        $arrHide = [
7869
            $id,
7870
        ];
7871
7872
        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...
7873
            if ($action != 'add') {
7874
                if (
7875
                    ($arrLP[$i]['item_type'] == 'dir') &&
7876
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7877
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7878
                ) {
7879
                    $selectParent->addOption(
7880
                        $arrLP[$i]['title'],
7881
                        $arrLP[$i]['id'],
7882
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7883
                    );
7884
7885
                    if ($parent == $arrLP[$i]['id']) {
7886
                        $selectParent->setSelected($arrLP[$i]['id']);
7887
                    }
7888
                } else {
7889
                    $arrHide[] = $arrLP[$i]['id'];
7890
                }
7891
            } else {
7892
                if ($arrLP[$i]['item_type'] == 'dir') {
7893
                    $selectParent->addOption(
7894
                        $arrLP[$i]['title'],
7895
                        $arrLP[$i]['id'],
7896
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7897
                    );
7898
7899
                    if ($parent == $arrLP[$i]['id']) {
7900
                        $selectParent->setSelected($arrLP[$i]['id']);
7901
                    }
7902
                }
7903
            }
7904
        }
7905
7906
        if ($arrLP != null) {
7907
            reset($arrLP);
7908
        }
7909
7910
        $selectPrevious = $form->addSelect(
7911
            'previous',
7912
            get_lang('Position'),
7913
            [],
7914
            ['id' => 'previous']
7915
        );
7916
        $selectPrevious->addOption(get_lang('First position'), 0);
7917
7918
        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...
7919
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7920
                $selectPrevious->addOption(
7921
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7922
                    $arrLP[$i]['id']
7923
                );
7924
7925
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7926
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7927
                } elseif ($action == 'add') {
7928
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7929
                }
7930
            }
7931
        }
7932
7933
        if ($action != 'move') {
7934
            $this->setItemTitle($form);
7935
            $defaults['title'] = $item_title;
7936
7937
            $id_prerequisite = 0;
7938
            if ($arrLP != null) {
7939
                foreach ($arrLP as $key => $value) {
7940
                    if ($value['id'] == $id) {
7941
                        $id_prerequisite = $value['prerequisite'];
7942
                        break;
7943
                    }
7944
                }
7945
            }
7946
7947
            $arrHide = [];
7948
            $s_selected_position = 0;
7949
            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...
7950
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7951
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7952
                        $s_selected_position = $arrLP[$i]['id'];
7953
                    } elseif ($action == 'add') {
7954
                        $s_selected_position = 0;
7955
                    }
7956
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7957
                }
7958
            }
7959
7960
            $selectPrerequisites = $form->addSelect(
7961
                'prerequisites',
7962
                get_lang('Prerequisites'),
7963
                [],
7964
                ['id' => 'prerequisites']
7965
            );
7966
            $selectPrerequisites->addOption(get_lang('No prerequisites'), 0);
7967
7968
            foreach ($arrHide as $key => $value) {
7969
                $selectPrerequisites->addOption($value['value'], $key);
7970
7971
                if ($key == $s_selected_position && $action == 'add') {
7972
                    $selectPrerequisites->setSelected($key);
7973
                } elseif ($key == $id_prerequisite && $action == 'edit') {
7974
                    $selectPrerequisites->setSelected($key);
7975
                }
7976
            }
7977
        }
7978
7979
        $form->addButtonSave(get_lang('Validate'), 'submit_button');
7980
7981
        if ($action == 'move') {
7982
            $form->addHidden('title', $item_title);
7983
            $form->addHidden('description', $item_description);
7984
        }
7985
7986
        if (is_numeric($extra_info)) {
7987
            $form->addHidden('path', $extra_info);
7988
        } elseif (is_array($extra_info)) {
7989
            $form->addHidden('path', $extra_info['path']);
7990
        }
7991
7992
        $form->addHidden('type', TOOL_THREAD);
7993
        $form->addHidden('post_time', time());
7994
        $form->setDefaults($defaults);
7995
7996
        return $form->returnForm();
7997
    }
7998
7999
    /**
8000
     * Return the HTML form to display an item (generally a dir item).
8001
     *
8002
     * @param string $item_type
8003
     * @param string $title
8004
     * @param string $action
8005
     * @param int    $id
8006
     * @param string $extra_info
8007
     *
8008
     * @throws Exception
8009
     * @throws HTML_QuickForm_Error
8010
     *
8011
     * @return string HTML form
8012
     */
8013
    public function display_item_form(
8014
        $item_type,
8015
        $title = '',
8016
        $action = 'add_item',
8017
        $id = 0,
8018
        $extra_info = 'new'
8019
    ) {
8020
        $_course = api_get_course_info();
8021
8022
        global $charset;
8023
8024
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8025
        $item_title = '';
8026
        $item_description = '';
8027
        $item_path_fck = '';
8028
8029
        if ($id != 0 && is_array($extra_info)) {
8030
            $item_title = $extra_info['title'];
8031
            $item_description = $extra_info['description'];
8032
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8033
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8034
        }
8035
        $parent = 0;
8036
        if ($id != 0 && is_array($extra_info)) {
8037
            $parent = $extra_info['parent_item_id'];
8038
        }
8039
8040
        $id = (int) $id;
8041
        $sql = "SELECT * FROM $tbl_lp_item
8042
                WHERE
8043
                    lp_id = ".$this->lp_id." AND
8044
                    iid != $id";
8045
8046
        if ($item_type == 'dir') {
8047
            $sql .= " AND parent_item_id = 0";
8048
        }
8049
8050
        $result = Database::query($sql);
8051
        $arrLP = [];
8052
        while ($row = Database::fetch_array($result)) {
8053
            $arrLP[] = [
8054
                'id' => $row['iid'],
8055
                'item_type' => $row['item_type'],
8056
                'title' => $this->cleanItemTitle($row['title']),
8057
                'title_raw' => $row['title'],
8058
                'path' => $row['path'],
8059
                'description' => $row['description'],
8060
                'parent_item_id' => $row['parent_item_id'],
8061
                'previous_item_id' => $row['previous_item_id'],
8062
                'next_item_id' => $row['next_item_id'],
8063
                'max_score' => $row['max_score'],
8064
                'min_score' => $row['min_score'],
8065
                'mastery_score' => $row['mastery_score'],
8066
                'prerequisite' => $row['prerequisite'],
8067
                'display_order' => $row['display_order'],
8068
            ];
8069
        }
8070
8071
        $this->tree_array($arrLP);
8072
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8073
        unset($this->arrMenu);
8074
8075
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8076
8077
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8078
        $defaults['title'] = api_html_entity_decode(
8079
            $item_title,
8080
            ENT_QUOTES,
8081
            $charset
8082
        );
8083
        $defaults['description'] = $item_description;
8084
8085
        $form->addHeader($title);
8086
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8087
        $arrHide[0]['padding'] = 20;
8088
        $charset = api_get_system_encoding();
8089
        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...
8090
            if ($action != 'add') {
8091
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8092
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8093
                ) {
8094
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8095
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8096
                    if ($parent == $arrLP[$i]['id']) {
8097
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8098
                    }
8099
                }
8100
            } else {
8101
                if ($arrLP[$i]['item_type'] === 'dir') {
8102
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8103
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8104
                    if ($parent == $arrLP[$i]['id']) {
8105
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8106
                    }
8107
                }
8108
            }
8109
        }
8110
8111
        if ($action != 'move') {
8112
            $this->setItemTitle($form);
8113
        } else {
8114
            $form->addElement('hidden', 'title');
8115
        }
8116
8117
        $parentSelect = $form->addElement(
8118
            'select',
8119
            'parent',
8120
            get_lang('Parent'),
8121
            '',
8122
            [
8123
                'id' => 'idParent',
8124
                'onchange' => 'javascript: load_cbo(this.value);',
8125
            ]
8126
        );
8127
8128
        foreach ($arrHide as $key => $value) {
8129
            $parentSelect->addOption(
8130
                $value['value'],
8131
                $key,
8132
                'style="padding-left:'.$value['padding'].'px;"'
8133
            );
8134
            $lastPosition = $key;
8135
        }
8136
8137
        if (!empty($s_selected_parent)) {
8138
            $parentSelect->setSelected($s_selected_parent);
8139
        }
8140
8141
        if (is_array($arrLP)) {
8142
            reset($arrLP);
8143
        }
8144
8145
        $arrHide = [];
8146
        // POSITION
8147
        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...
8148
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8149
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8150
                //this is the same!
8151
                if (isset($extra_info['previous_item_id']) &&
8152
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8153
                ) {
8154
                    $s_selected_position = $arrLP[$i]['id'];
8155
                } elseif ($action == 'add') {
8156
                    $s_selected_position = $arrLP[$i]['id'];
8157
                }
8158
8159
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8160
            }
8161
        }
8162
8163
        $position = $form->addElement(
8164
            'select',
8165
            'previous',
8166
            get_lang('Position'),
8167
            '',
8168
            ['id' => 'previous']
8169
        );
8170
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8171
        $position->addOption(get_lang('First position'), 0, 'style="padding-left:'.$padding.'px;"');
8172
8173
        $lastPosition = null;
8174
        foreach ($arrHide as $key => $value) {
8175
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8176
            $lastPosition = $key;
8177
        }
8178
8179
        if (!empty($s_selected_position)) {
8180
            $position->setSelected($s_selected_position);
8181
        }
8182
8183
        // When new chapter add at the end
8184
        if ($action === 'add_item') {
8185
            $position->setSelected($lastPosition);
8186
        }
8187
8188
        if (is_array($arrLP)) {
8189
            reset($arrLP);
8190
        }
8191
8192
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
8193
8194
        //fix in order to use the tab
8195
        if ($item_type === 'dir') {
8196
            $form->addElement('hidden', 'type', 'dir');
8197
        }
8198
8199
        $extension = null;
8200
        if (!empty($item_path)) {
8201
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8202
        }
8203
8204
        //assets can't be modified
8205
        //$item_type == 'asset' ||
8206
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8207
            if ($item_type == 'sco') {
8208
                $form->addElement(
8209
                    'html',
8210
                    '<script>alert("'.get_lang('Warning ! ! !WhenEditingScorm').'")</script>'
8211
                );
8212
            }
8213
            $renderer = $form->defaultRenderer();
8214
            $renderer->setElementTemplate(
8215
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8216
                'content_lp'
8217
            );
8218
8219
            $relative_prefix = '';
8220
            $editor_config = [
8221
                'ToolbarSet' => 'LearningPathDocuments',
8222
                'Width' => '100%',
8223
                'Height' => '500',
8224
                'FullPage' => true,
8225
                'CreateDocumentDir' => $relative_prefix,
8226
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8227
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8228
            ];
8229
8230
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8231
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8232
            $defaults['content_lp'] = file_get_contents($content_path);
8233
        }
8234
8235
        if (!empty($id)) {
8236
            $form->addHidden('id', $id);
8237
        }
8238
8239
        $form->addElement('hidden', 'type', $item_type);
8240
        $form->addElement('hidden', 'post_time', time());
8241
        $form->setDefaults($defaults);
8242
8243
        return $form->returnForm();
8244
    }
8245
8246
    /**
8247
     * @return string
8248
     */
8249
    public function getCurrentBuildingModeURL()
8250
    {
8251
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8252
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8253
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8254
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8255
8256
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8257
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8258
8259
        return $currentUrl;
8260
    }
8261
8262
    /**
8263
     * Returns the form to update or create a document.
8264
     *
8265
     * @param string $action     (add/edit)
8266
     * @param int    $id         ID of the lp_item (if already exists)
8267
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8268
     *
8269
     * @throws Exception
8270
     * @throws HTML_QuickForm_Error
8271
     *
8272
     * @return string HTML form
8273
     */
8274
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8275
    {
8276
        $course_id = api_get_course_int_id();
8277
        $_course = api_get_course_info();
8278
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8279
8280
        $no_display_edit_textarea = false;
8281
        $item_description = '';
8282
        //If action==edit document
8283
        //We don't display the document form if it's not an editable document (html or txt file)
8284
        if ($action === 'edit') {
8285
            if (is_array($extra_info)) {
8286
                $path_parts = pathinfo($extra_info['dir']);
8287
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8288
                    $no_display_edit_textarea = true;
8289
                }
8290
            }
8291
        }
8292
        $no_display_add = false;
8293
8294
        // If action==add an existing document
8295
        // We don't display the document form if it's not an editable document (html or txt file).
8296
        if ($action === 'add') {
8297
            if (is_numeric($extra_info)) {
8298
                $extra_info = (int) $extra_info;
8299
                $sql_doc = "SELECT path FROM $tbl_doc 
8300
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8301
                $result = Database::query($sql_doc);
8302
                $path_file = Database::result($result, 0, 0);
8303
                $path_parts = pathinfo($path_file);
8304
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8305
                    $no_display_add = true;
8306
                }
8307
            }
8308
        }
8309
8310
        $item_title = '';
8311
        $item_description = '';
8312
        if ($id != 0 && is_array($extra_info)) {
8313
            $item_title = stripslashes($extra_info['title']);
8314
            $item_description = stripslashes($extra_info['description']);
8315
            if (empty($item_title)) {
8316
                $path_parts = pathinfo($extra_info['path']);
8317
                $item_title = stripslashes($path_parts['filename']);
8318
            }
8319
        } elseif (is_numeric($extra_info)) {
8320
            $sql = "SELECT path, title FROM $tbl_doc
8321
                    WHERE
8322
                        c_id = ".$course_id." AND
8323
                        iid = ".intval($extra_info);
8324
            $result = Database::query($sql);
8325
            $row = Database::fetch_array($result);
8326
            $item_title = $row['title'];
8327
            $item_title = str_replace('_', ' ', $item_title);
8328
            if (empty($item_title)) {
8329
                $path_parts = pathinfo($row['path']);
8330
                $item_title = stripslashes($path_parts['filename']);
8331
            }
8332
        }
8333
8334
        $return = '<legend>';
8335
        $parent = 0;
8336
        if ($id != 0 && is_array($extra_info)) {
8337
            $parent = $extra_info['parent_item_id'];
8338
        }
8339
8340
        $arrLP = $this->getItemsForForm();
8341
        $this->tree_array($arrLP);
8342
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8343
        unset($this->arrMenu);
8344
8345
        if ($action == 'add') {
8346
            $return .= get_lang('Create a new document');
8347
        } elseif ($action == 'move') {
8348
            $return .= get_lang('Move the current document');
8349
        } else {
8350
            $return .= get_lang('Edit the current document');
8351
        }
8352
        $return .= '</legend>';
8353
8354
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8355
            $return .= Display::return_message(
8356
                '<strong>'.get_lang('Warning ! ! !').' !</strong><br />'.get_lang('Warning ! ! !EditingDocument'),
8357
                false
8358
            );
8359
        }
8360
        $form = new FormValidator(
8361
            'form',
8362
            'POST',
8363
            $this->getCurrentBuildingModeURL(),
8364
            '',
8365
            ['enctype' => 'multipart/form-data']
8366
        );
8367
        $defaults['title'] = Security::remove_XSS($item_title);
8368
        if (empty($item_title)) {
8369
            $defaults['title'] = Security::remove_XSS($item_title);
8370
        }
8371
        $defaults['description'] = $item_description;
8372
        $form->addElement('html', $return);
8373
8374
        if ($action != 'move') {
8375
            $data = $this->generate_lp_folder($_course);
8376
            if ($action != 'edit') {
8377
                $folders = DocumentManager::get_all_document_folders(
8378
                    $_course,
8379
                    0,
8380
                    true
8381
                );
8382
                DocumentManager::build_directory_selector(
8383
                    $folders,
8384
                    '',
8385
                    [],
8386
                    true,
8387
                    $form,
8388
                    'directory_parent_id'
8389
                );
8390
            }
8391
8392
            if (isset($data['id'])) {
8393
                $defaults['directory_parent_id'] = $data['id'];
8394
            }
8395
            $this->setItemTitle($form);
8396
        }
8397
8398
        $arrHide[0]['value'] = $this->name;
8399
        $arrHide[0]['padding'] = 20;
8400
8401
        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...
8402
            if ($action != 'add') {
8403
                if ($arrLP[$i]['item_type'] == 'dir' &&
8404
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8405
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8406
                ) {
8407
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8408
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8409
                }
8410
            } else {
8411
                if ($arrLP[$i]['item_type'] == 'dir') {
8412
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8413
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8414
                }
8415
            }
8416
        }
8417
8418
        $parentSelect = $form->addSelect(
8419
            'parent',
8420
            get_lang('Parent'),
8421
            [],
8422
            [
8423
                'id' => 'idParent',
8424
                'onchange' => 'javascript: load_cbo(this.value);',
8425
            ]
8426
        );
8427
8428
        $my_count = 0;
8429
        foreach ($arrHide as $key => $value) {
8430
            if ($my_count != 0) {
8431
                // The LP name is also the first section and is not in the same charset like the other sections.
8432
                $value['value'] = Security::remove_XSS($value['value']);
8433
                $parentSelect->addOption(
8434
                    $value['value'],
8435
                    $key,
8436
                    'style="padding-left:'.$value['padding'].'px;"'
8437
                );
8438
            } else {
8439
                $value['value'] = Security::remove_XSS($value['value']);
8440
                $parentSelect->addOption(
8441
                    $value['value'],
8442
                    $key,
8443
                    'style="padding-left:'.$value['padding'].'px;"'
8444
                );
8445
            }
8446
            $my_count++;
8447
        }
8448
8449
        if (!empty($id)) {
8450
            $parentSelect->setSelected($parent);
8451
        } else {
8452
            $parent_item_id = Session::read('parent_item_id', 0);
8453
            $parentSelect->setSelected($parent_item_id);
8454
        }
8455
8456
        if (is_array($arrLP)) {
8457
            reset($arrLP);
8458
        }
8459
8460
        $arrHide = [];
8461
        // POSITION
8462
        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...
8463
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8464
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8465
            ) {
8466
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8467
            }
8468
        }
8469
8470
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8471
8472
        $position = $form->addSelect(
8473
            'previous',
8474
            get_lang('Position'),
8475
            [],
8476
            ['id' => 'previous']
8477
        );
8478
8479
        $position->addOption(get_lang('First position'), 0);
8480
        foreach ($arrHide as $key => $value) {
8481
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8482
            $position->addOption(
8483
                $value['value'],
8484
                $key,
8485
                'style="padding-left:'.$padding.'px;"'
8486
            );
8487
        }
8488
8489
        $position->setSelected($selectedPosition);
8490
8491
        if (is_array($arrLP)) {
8492
            reset($arrLP);
8493
        }
8494
8495
        if ($action != 'move') {
8496
            $arrHide = [];
8497
            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...
8498
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8499
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8500
                ) {
8501
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8502
                }
8503
            }
8504
8505
            if (!$no_display_add) {
8506
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8507
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8508
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8509
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8510
                ) {
8511
                    if (isset($_POST['content'])) {
8512
                        $content = stripslashes($_POST['content']);
8513
                    } elseif (is_array($extra_info)) {
8514
                        //If it's an html document or a text file
8515
                        if (!$no_display_edit_textarea) {
8516
                            $content = $this->display_document(
8517
                                $extra_info['path'],
8518
                                false,
8519
                                false
8520
                            );
8521
                        }
8522
                    } elseif (is_numeric($extra_info)) {
8523
                        $content = $this->display_document(
8524
                            $extra_info,
8525
                            false,
8526
                            false
8527
                        );
8528
                    } else {
8529
                        $content = '';
8530
                    }
8531
8532
                    if (!$no_display_edit_textarea) {
8533
                        // We need to calculate here some specific settings for the online editor.
8534
                        // The calculated settings work for documents in the Documents tool
8535
                        // (on the root or in subfolders).
8536
                        // For documents in native scorm packages it is unclear whether the
8537
                        // online editor should be activated or not.
8538
8539
                        // A new document, it is in the root of the repository.
8540
                        $relative_path = '';
8541
                        $relative_prefix = '';
8542
                        if (is_array($extra_info) && $extra_info != 'new') {
8543
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8544
                            $relative_path = explode('/', $extra_info['dir']);
8545
                            $cnt = count($relative_path) - 2;
8546
                            if ($cnt < 0) {
8547
                                $cnt = 0;
8548
                            }
8549
                            $relative_prefix = str_repeat('../', $cnt);
8550
                            $relative_path = array_slice($relative_path, 1, $cnt);
8551
                            $relative_path = implode('/', $relative_path);
8552
                            if (strlen($relative_path) > 0) {
8553
                                $relative_path = $relative_path.'/';
8554
                            }
8555
                        } else {
8556
                            $result = $this->generate_lp_folder($_course);
8557
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8558
                            $relative_prefix = '../../';
8559
                        }
8560
8561
                        $editor_config = [
8562
                            'ToolbarSet' => 'LearningPathDocuments',
8563
                            'Width' => '100%',
8564
                            'Height' => '500',
8565
                            'FullPage' => true,
8566
                            'CreateDocumentDir' => $relative_prefix,
8567
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8568
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8569
                        ];
8570
8571
                        if ($_GET['action'] == 'add_item') {
8572
                            $class = 'add';
8573
                            $text = get_lang('Add this document to the course');
8574
                        } else {
8575
                            if ($_GET['action'] == 'edit_item') {
8576
                                $class = 'save';
8577
                                $text = get_lang('Save document');
8578
                            }
8579
                        }
8580
8581
                        $form->addButtonSave($text, 'submit_button');
8582
                        $renderer = $form->defaultRenderer();
8583
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8584
                        $form->addElement('html', '<div class="editor-lp">');
8585
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8586
                        $form->addElement('html', '</div>');
8587
                        $defaults['content_lp'] = $content;
8588
                    }
8589
                } elseif (is_numeric($extra_info)) {
8590
                    $form->addButtonSave(get_lang('Save document'), 'submit_button');
8591
8592
                    $return = $this->display_document($extra_info, true, true, true);
8593
                    $form->addElement('html', $return);
8594
                }
8595
            }
8596
        }
8597
        if (isset($extra_info['item_type']) &&
8598
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8599
        ) {
8600
            $parentSelect->freeze();
8601
            $position->freeze();
8602
        }
8603
8604
        if ($action == 'move') {
8605
            $form->addElement('hidden', 'title', $item_title);
8606
            $form->addElement('hidden', 'description', $item_description);
8607
        }
8608
        if (is_numeric($extra_info)) {
8609
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8610
            $form->addElement('hidden', 'path', $extra_info);
8611
        } elseif (is_array($extra_info)) {
8612
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8613
            $form->addElement('hidden', 'path', $extra_info['path']);
8614
        }
8615
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8616
        $form->addElement('hidden', 'post_time', time());
8617
        $form->setDefaults($defaults);
8618
8619
        return $form->returnForm();
8620
    }
8621
8622
    /**
8623
     * Returns the form to update or create a read-out text.
8624
     *
8625
     * @param string $action     "add" or "edit"
8626
     * @param int    $id         ID of the lp_item (if already exists)
8627
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8628
     *
8629
     * @throws Exception
8630
     * @throws HTML_QuickForm_Error
8631
     *
8632
     * @return string HTML form
8633
     */
8634
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8635
    {
8636
        $course_id = api_get_course_int_id();
8637
        $_course = api_get_course_info();
8638
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8639
8640
        $no_display_edit_textarea = false;
8641
        $item_description = '';
8642
        //If action==edit document
8643
        //We don't display the document form if it's not an editable document (html or txt file)
8644
        if ($action == 'edit') {
8645
            if (is_array($extra_info)) {
8646
                $path_parts = pathinfo($extra_info['dir']);
8647
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8648
                    $no_display_edit_textarea = true;
8649
                }
8650
            }
8651
        }
8652
        $no_display_add = false;
8653
8654
        $item_title = '';
8655
        $item_description = '';
8656
        if ($id != 0 && is_array($extra_info)) {
8657
            $item_title = stripslashes($extra_info['title']);
8658
            $item_description = stripslashes($extra_info['description']);
8659
            $item_terms = stripslashes($extra_info['terms']);
8660
            if (empty($item_title)) {
8661
                $path_parts = pathinfo($extra_info['path']);
8662
                $item_title = stripslashes($path_parts['filename']);
8663
            }
8664
        } elseif (is_numeric($extra_info)) {
8665
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8666
            $result = Database::query($sql);
8667
            $row = Database::fetch_array($result);
8668
            $item_title = $row['title'];
8669
            $item_title = str_replace('_', ' ', $item_title);
8670
            if (empty($item_title)) {
8671
                $path_parts = pathinfo($row['path']);
8672
                $item_title = stripslashes($path_parts['filename']);
8673
            }
8674
        }
8675
8676
        $parent = 0;
8677
        if ($id != 0 && is_array($extra_info)) {
8678
            $parent = $extra_info['parent_item_id'];
8679
        }
8680
8681
        $arrLP = $this->getItemsForForm();
8682
        $this->tree_array($arrLP);
8683
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8684
        unset($this->arrMenu);
8685
8686
        if ($action === 'add') {
8687
            $formHeader = get_lang('Create a new document');
8688
        } else {
8689
            $formHeader = get_lang('Edit the current document');
8690
        }
8691
8692
        if ('edit' === $action) {
8693
            $urlAudioIcon = Display::url(
8694
                Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY),
8695
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8696
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8697
            );
8698
        } else {
8699
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY);
8700
        }
8701
8702
        $form = new FormValidator(
8703
            'frm_add_reading',
8704
            'POST',
8705
            $this->getCurrentBuildingModeURL(),
8706
            '',
8707
            ['enctype' => 'multipart/form-data']
8708
        );
8709
        $form->addHeader($formHeader);
8710
        $form->addHtml(
8711
            Display::return_message(
8712
                sprintf(get_lang('You need attach a audio file according to the text, clicking on the %s icon.'), $urlAudioIcon),
8713
                'normal',
8714
                false
8715
            )
8716
        );
8717
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8718
        $defaults['description'] = $item_description;
8719
8720
        $data = $this->generate_lp_folder($_course);
8721
8722
        if ($action != 'edit') {
8723
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8724
            DocumentManager::build_directory_selector(
8725
                $folders,
8726
                '',
8727
                [],
8728
                true,
8729
                $form,
8730
                'directory_parent_id'
8731
            );
8732
        }
8733
8734
        if (isset($data['id'])) {
8735
            $defaults['directory_parent_id'] = $data['id'];
8736
        }
8737
        $this->setItemTitle($form);
8738
8739
        $arrHide[0]['value'] = $this->name;
8740
        $arrHide[0]['padding'] = 20;
8741
8742
        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...
8743
            if ($action != 'add') {
8744
                if ($arrLP[$i]['item_type'] == 'dir' &&
8745
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8746
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8747
                ) {
8748
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8749
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8750
                }
8751
            } else {
8752
                if ($arrLP[$i]['item_type'] == 'dir') {
8753
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8754
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8755
                }
8756
            }
8757
        }
8758
8759
        $parent_select = $form->addSelect(
8760
            'parent',
8761
            get_lang('Parent'),
8762
            [],
8763
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8764
        );
8765
8766
        $my_count = 0;
8767
        foreach ($arrHide as $key => $value) {
8768
            if ($my_count != 0) {
8769
                // The LP name is also the first section and is not in the same charset like the other sections.
8770
                $value['value'] = Security::remove_XSS($value['value']);
8771
                $parent_select->addOption(
8772
                    $value['value'],
8773
                    $key,
8774
                    'style="padding-left:'.$value['padding'].'px;"'
8775
                );
8776
            } else {
8777
                $value['value'] = Security::remove_XSS($value['value']);
8778
                $parent_select->addOption(
8779
                    $value['value'],
8780
                    $key,
8781
                    'style="padding-left:'.$value['padding'].'px;"'
8782
                );
8783
            }
8784
            $my_count++;
8785
        }
8786
8787
        if (!empty($id)) {
8788
            $parent_select->setSelected($parent);
8789
        } else {
8790
            $parent_item_id = Session::read('parent_item_id', 0);
8791
            $parent_select->setSelected($parent_item_id);
8792
        }
8793
8794
        if (is_array($arrLP)) {
8795
            reset($arrLP);
8796
        }
8797
8798
        $arrHide = [];
8799
        $s_selected_position = null;
8800
8801
        // POSITION
8802
        $lastPosition = null;
8803
8804
        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...
8805
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8806
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8807
            ) {
8808
                if ((isset($extra_info['previous_item_id']) &&
8809
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8810
                ) {
8811
                    $s_selected_position = $arrLP[$i]['id'];
8812
                }
8813
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8814
            }
8815
            $lastPosition = $arrLP[$i]['id'];
8816
        }
8817
8818
        if (empty($s_selected_position)) {
8819
            $s_selected_position = $lastPosition;
8820
        }
8821
8822
        $position = $form->addSelect(
8823
            'previous',
8824
            get_lang('Position'),
8825
            []
8826
        );
8827
        $position->addOption(get_lang('First position'), 0);
8828
8829
        foreach ($arrHide as $key => $value) {
8830
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8831
            $position->addOption(
8832
                $value['value'],
8833
                $key,
8834
                'style="padding-left:'.$padding.'px;"'
8835
            );
8836
        }
8837
        $position->setSelected($s_selected_position);
8838
8839
        if (is_array($arrLP)) {
8840
            reset($arrLP);
8841
        }
8842
8843
        $arrHide = [];
8844
8845
        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...
8846
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8847
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8848
            ) {
8849
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8850
            }
8851
        }
8852
8853
        if (!$no_display_add) {
8854
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8855
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8856
8857
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
8858
                if (!$no_display_edit_textarea) {
8859
                    $content = '';
8860
8861
                    if (isset($_POST['content'])) {
8862
                        $content = stripslashes($_POST['content']);
8863
                    } elseif (is_array($extra_info)) {
8864
                        $content = $this->display_document($extra_info['path'], false, false);
8865
                    } elseif (is_numeric($extra_info)) {
8866
                        $content = $this->display_document($extra_info, false, false);
8867
                    }
8868
8869
                    // A new document, it is in the root of the repository.
8870
                    if (is_array($extra_info) && $extra_info != 'new') {
8871
                    } else {
8872
                        $this->generate_lp_folder($_course);
8873
                    }
8874
8875
                    if ($_GET['action'] == 'add_item') {
8876
                        $text = get_lang('Add this document to the course');
8877
                    } else {
8878
                        $text = get_lang('Save document');
8879
                    }
8880
8881
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8882
                    $form
8883
                        ->defaultRenderer()
8884
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8885
                    $form->addButtonSave($text, 'submit_button');
8886
                    $defaults['content_lp'] = $content;
8887
                }
8888
            } elseif (is_numeric($extra_info)) {
8889
                $form->addButtonSave(get_lang('Save document'), 'submit_button');
8890
8891
                $return = $this->display_document($extra_info, true, true, true);
8892
                $form->addElement('html', $return);
8893
            }
8894
        }
8895
8896
        if (is_numeric($extra_info)) {
8897
            $form->addElement('hidden', 'path', $extra_info);
8898
        } elseif (is_array($extra_info)) {
8899
            $form->addElement('hidden', 'path', $extra_info['path']);
8900
        }
8901
8902
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8903
        $form->addElement('hidden', 'post_time', time());
8904
        $form->setDefaults($defaults);
8905
8906
        return $form->returnForm();
8907
    }
8908
8909
    /**
8910
     * @param array  $courseInfo
8911
     * @param string $content
8912
     * @param string $title
8913
     * @param int    $parentId
8914
     *
8915
     * @throws \Doctrine\ORM\ORMException
8916
     * @throws \Doctrine\ORM\OptimisticLockException
8917
     * @throws \Doctrine\ORM\TransactionRequiredException
8918
     *
8919
     * @return int
8920
     */
8921
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8922
    {
8923
        $creatorId = api_get_user_id();
8924
        $sessionId = api_get_session_id();
8925
8926
        // Generates folder
8927
        $result = $this->generate_lp_folder($courseInfo);
8928
        $dir = $result['dir'];
8929
8930
        if (empty($parentId) || $parentId == '/') {
8931
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8932
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8933
8934
            if ($parentId === '/') {
8935
                $dir = '/';
8936
            }
8937
8938
            // Please, do not modify this dirname formatting.
8939
            if (strstr($dir, '..')) {
8940
                $dir = '/';
8941
            }
8942
8943
            if (!empty($dir[0]) && $dir[0] == '.') {
8944
                $dir = substr($dir, 1);
8945
            }
8946
            if (!empty($dir[0]) && $dir[0] != '/') {
8947
                $dir = '/'.$dir;
8948
            }
8949
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
8950
                $dir .= '/';
8951
            }
8952
        } else {
8953
            $parentInfo = DocumentManager::get_document_data_by_id(
8954
                $parentId,
8955
                $courseInfo['code']
8956
            );
8957
            if (!empty($parentInfo)) {
8958
                $dir = $parentInfo['path'].'/';
8959
            }
8960
        }
8961
8962
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8963
8964
        if (!is_dir($filepath)) {
8965
            $dir = '/';
8966
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8967
        }
8968
8969
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8970
8971
        if (!empty($title)) {
8972
            $title = api_replace_dangerous_char(stripslashes($title));
8973
        } else {
8974
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
8975
        }
8976
8977
        $title = disable_dangerous_file($title);
8978
        $filename = $title;
8979
        $content = !empty($content) ? $content : $_POST['content_lp'];
8980
        $tmpFileName = $filename;
8981
8982
        $i = 0;
8983
        while (file_exists($filepath.$tmpFileName.'.html')) {
8984
            $tmpFileName = $filename.'_'.++$i;
8985
        }
8986
8987
        $filename = $tmpFileName.'.html';
8988
        $content = stripslashes($content);
8989
8990
        if (file_exists($filepath.$filename)) {
8991
            return 0;
8992
        }
8993
8994
        $putContent = file_put_contents($filepath.$filename, $content);
8995
8996
        if ($putContent === false) {
8997
            return 0;
8998
        }
8999
9000
        $fileSize = filesize($filepath.$filename);
9001
        $saveFilePath = $dir.$filename;
9002
9003
        $document = DocumentManager::addDocument(
9004
            $courseInfo,
9005
            $saveFilePath,
9006
            'file',
9007
            $fileSize,
9008
            $tmpFileName,
9009
            '',
9010
            0, //readonly
9011
            true,
9012
            null,
9013
            $sessionId,
9014
            $creatorId
9015
        );
9016
9017
        $documentId = $document->getId();
9018
9019
        if (!$document) {
9020
            return 0;
9021
        }
9022
9023
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9024
        $newTitle = $originalTitle;
9025
9026
        if ($newComment || $newTitle) {
9027
            $em = Database::getManager();
9028
9029
            if ($newComment) {
9030
                $document->setComment($newComment);
9031
            }
9032
9033
            if ($newTitle) {
9034
                $document->setTitle($newTitle);
9035
            }
9036
9037
            $em->persist($document);
9038
            $em->flush();
9039
        }
9040
9041
        return $documentId;
9042
    }
9043
9044
    /**
9045
     * Return HTML form to add/edit a link item.
9046
     *
9047
     * @param string $action     (add/edit)
9048
     * @param int    $id         Item ID if exists
9049
     * @param mixed  $extra_info
9050
     *
9051
     * @throws Exception
9052
     * @throws HTML_QuickForm_Error
9053
     *
9054
     * @return string HTML form
9055
     */
9056
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9057
    {
9058
        $course_id = api_get_course_int_id();
9059
        $tbl_link = Database::get_course_table(TABLE_LINK);
9060
9061
        $item_title = '';
9062
        $item_description = '';
9063
        $item_url = '';
9064
9065
        if ($id != 0 && is_array($extra_info)) {
9066
            $item_title = stripslashes($extra_info['title']);
9067
            $item_description = stripslashes($extra_info['description']);
9068
            $item_url = stripslashes($extra_info['url']);
9069
        } elseif (is_numeric($extra_info)) {
9070
            $extra_info = (int) $extra_info;
9071
            $sql = "SELECT title, description, url 
9072
                    FROM $tbl_link
9073
                    WHERE c_id = $course_id AND iid = $extra_info";
9074
            $result = Database::query($sql);
9075
            $row = Database::fetch_array($result);
9076
            $item_title = $row['title'];
9077
            $item_description = $row['description'];
9078
            $item_url = $row['url'];
9079
        }
9080
9081
        $form = new FormValidator(
9082
            'edit_link',
9083
            'POST',
9084
            $this->getCurrentBuildingModeURL()
9085
        );
9086
        $defaults = [];
9087
        $parent = 0;
9088
        if ($id != 0 && is_array($extra_info)) {
9089
            $parent = $extra_info['parent_item_id'];
9090
        }
9091
9092
        $arrLP = $this->getItemsForForm();
9093
9094
        $this->tree_array($arrLP);
9095
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9096
        unset($this->arrMenu);
9097
9098
        if ($action == 'add') {
9099
            $legend = get_lang('Adding a link to the course');
9100
        } elseif ($action == 'move') {
9101
            $legend = get_lang('Move the current link');
9102
        } else {
9103
            $legend = get_lang('Edit the current link');
9104
        }
9105
9106
        $form->addHeader($legend);
9107
9108
        if ($action != 'move') {
9109
            $this->setItemTitle($form);
9110
            $defaults['title'] = $item_title;
9111
        }
9112
9113
        $selectParent = $form->addSelect(
9114
            'parent',
9115
            get_lang('Parent'),
9116
            [],
9117
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9118
        );
9119
        $selectParent->addOption($this->name, 0);
9120
        $arrHide = [
9121
            $id,
9122
        ];
9123
9124
        $parent_item_id = Session::read('parent_item_id', 0);
9125
9126
        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...
9127
            if ($action != 'add') {
9128
                if (
9129
                    ($arrLP[$i]['item_type'] == 'dir') &&
9130
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9131
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9132
                ) {
9133
                    $selectParent->addOption(
9134
                        $arrLP[$i]['title'],
9135
                        $arrLP[$i]['id'],
9136
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9137
                    );
9138
9139
                    if ($parent == $arrLP[$i]['id']) {
9140
                        $selectParent->setSelected($arrLP[$i]['id']);
9141
                    }
9142
                } else {
9143
                    $arrHide[] = $arrLP[$i]['id'];
9144
                }
9145
            } else {
9146
                if ($arrLP[$i]['item_type'] == 'dir') {
9147
                    $selectParent->addOption(
9148
                        $arrLP[$i]['title'],
9149
                        $arrLP[$i]['id'],
9150
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9151
                    );
9152
9153
                    if ($parent_item_id == $arrLP[$i]['id']) {
9154
                        $selectParent->setSelected($arrLP[$i]['id']);
9155
                    }
9156
                }
9157
            }
9158
        }
9159
9160
        if (is_array($arrLP)) {
9161
            reset($arrLP);
9162
        }
9163
9164
        $selectPrevious = $form->addSelect(
9165
            'previous',
9166
            get_lang('Position'),
9167
            [],
9168
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9169
        );
9170
        $selectPrevious->addOption(get_lang('First position'), 0);
9171
9172
        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...
9173
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9174
                $selectPrevious->addOption(
9175
                    $arrLP[$i]['title'],
9176
                    $arrLP[$i]['id']
9177
                );
9178
9179
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9180
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9181
                } elseif ($action == 'add') {
9182
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9183
                }
9184
            }
9185
        }
9186
9187
        if ($action != 'move') {
9188
            $urlAttributes = ['class' => 'learnpath_item_form'];
9189
9190
            if (is_numeric($extra_info)) {
9191
                $urlAttributes['disabled'] = 'disabled';
9192
            }
9193
9194
            $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
9195
            $defaults['url'] = $item_url;
9196
            $arrHide = [];
9197
            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...
9198
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9199
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9200
                }
9201
            }
9202
        }
9203
9204
        if ($action == 'add') {
9205
            $form->addButtonSave(get_lang('Add link to course'), 'submit_button');
9206
        } else {
9207
            $form->addButtonSave(get_lang('Edit the current link'), 'submit_button');
9208
        }
9209
9210
        if ($action == 'move') {
9211
            $form->addHidden('title', $item_title);
9212
            $form->addHidden('description', $item_description);
9213
        }
9214
9215
        if (is_numeric($extra_info)) {
9216
            $form->addHidden('path', $extra_info);
9217
        } elseif (is_array($extra_info)) {
9218
            $form->addHidden('path', $extra_info['path']);
9219
        }
9220
        $form->addHidden('type', TOOL_LINK);
9221
        $form->addHidden('post_time', time());
9222
        $form->setDefaults($defaults);
9223
9224
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9225
    }
9226
9227
    /**
9228
     * Return HTML form to add/edit a student publication (work).
9229
     *
9230
     * @param string $action
9231
     * @param int    $id         Item ID if already exists
9232
     * @param string $extra_info
9233
     *
9234
     * @throws Exception
9235
     *
9236
     * @return string HTML form
9237
     */
9238
    public function display_student_publication_form(
9239
        $action = 'add',
9240
        $id = 0,
9241
        $extra_info = ''
9242
    ) {
9243
        $course_id = api_get_course_int_id();
9244
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9245
9246
        $item_title = get_lang('Assignments');
9247
        if ($id != 0 && is_array($extra_info)) {
9248
            $item_title = stripslashes($extra_info['title']);
9249
            $item_description = stripslashes($extra_info['description']);
9250
        } elseif (is_numeric($extra_info)) {
9251
            $extra_info = (int) $extra_info;
9252
            $sql = "SELECT title, description
9253
                    FROM $tbl_publication
9254
                    WHERE c_id = $course_id AND id = ".$extra_info;
9255
9256
            $result = Database::query($sql);
9257
            $row = Database::fetch_array($result);
9258
            if ($row) {
9259
                $item_title = $row['title'];
9260
            }
9261
        }
9262
9263
        $parent = 0;
9264
        if ($id != 0 && is_array($extra_info)) {
9265
            $parent = $extra_info['parent_item_id'];
9266
        }
9267
9268
        $arrLP = $this->getItemsForForm();
9269
9270
        $this->tree_array($arrLP);
9271
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9272
        unset($this->arrMenu);
9273
9274
        $form = new FormValidator('frm_student_publication', 'post', '#');
9275
9276
        if ($action == 'add') {
9277
            $form->addHeader(get_lang('Assignments'));
9278
        } elseif ($action == 'move') {
9279
            $form->addHeader(get_lang('Move the current assignment'));
9280
        } else {
9281
            $form->addHeader(get_lang('Edit the current assignment'));
9282
        }
9283
9284
        if ($action != 'move') {
9285
            $this->setItemTitle($form);
9286
        }
9287
9288
        $parentSelect = $form->addSelect(
9289
            'parent',
9290
            get_lang('Parent'),
9291
            ['0' => $this->name],
9292
            [
9293
                'onchange' => 'javascript: load_cbo(this.value);',
9294
                'class' => 'learnpath_item_form',
9295
                'id' => 'idParent',
9296
            ]
9297
        );
9298
9299
        $arrHide = [$id];
9300
        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...
9301
            if ($action != 'add') {
9302
                if (
9303
                    ($arrLP[$i]['item_type'] == 'dir') &&
9304
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9305
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9306
                ) {
9307
                    $parentSelect->addOption(
9308
                        $arrLP[$i]['title'],
9309
                        $arrLP[$i]['id'],
9310
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9311
                    );
9312
9313
                    if ($parent == $arrLP[$i]['id']) {
9314
                        $parentSelect->setSelected($arrLP[$i]['id']);
9315
                    }
9316
                } else {
9317
                    $arrHide[] = $arrLP[$i]['id'];
9318
                }
9319
            } else {
9320
                if ($arrLP[$i]['item_type'] == 'dir') {
9321
                    $parentSelect->addOption(
9322
                        $arrLP[$i]['title'],
9323
                        $arrLP[$i]['id'],
9324
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9325
                    );
9326
9327
                    if ($parent == $arrLP[$i]['id']) {
9328
                        $parentSelect->setSelected($arrLP[$i]['id']);
9329
                    }
9330
                }
9331
            }
9332
        }
9333
9334
        if (is_array($arrLP)) {
9335
            reset($arrLP);
9336
        }
9337
9338
        $previousSelect = $form->addSelect(
9339
            'previous',
9340
            get_lang('Position'),
9341
            ['0' => get_lang('First position')],
9342
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9343
        );
9344
9345
        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...
9346
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9347
                $previousSelect->addOption(
9348
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9349
                    $arrLP[$i]['id']
9350
                );
9351
9352
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9353
                    $previousSelect->setSelected($arrLP[$i]['id']);
9354
                } elseif ($action == 'add') {
9355
                    $previousSelect->setSelected($arrLP[$i]['id']);
9356
                }
9357
            }
9358
        }
9359
9360
        if ($action == 'add') {
9361
            $form->addButtonCreate(get_lang('Add assignment to course'), 'submit_button');
9362
        } else {
9363
            $form->addButtonCreate(get_lang('Edit the current assignment'), 'submit_button');
9364
        }
9365
9366
        if ($action == 'move') {
9367
            $form->addHidden('title', $item_title);
9368
            $form->addHidden('description', $item_description);
9369
        }
9370
9371
        if (is_numeric($extra_info)) {
9372
            $form->addHidden('path', $extra_info);
9373
        } elseif (is_array($extra_info)) {
9374
            $form->addHidden('path', $extra_info['path']);
9375
        }
9376
9377
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9378
        $form->addHidden('post_time', time());
9379
        $form->setDefaults(['title' => $item_title]);
9380
9381
        $return = '<div class="sectioncomment">';
9382
        $return .= $form->returnForm();
9383
        $return .= '</div>';
9384
9385
        return $return;
9386
    }
9387
9388
    /**
9389
     * Displays the menu for manipulating a step.
9390
     *
9391
     * @param id     $item_id
9392
     * @param string $item_type
9393
     *
9394
     * @return string
9395
     */
9396
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9397
    {
9398
        $_course = api_get_course_info();
9399
        $course_code = api_get_course_id();
9400
        $item_id = (int) $item_id;
9401
9402
        $return = '<div class="actions">';
9403
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9404
        $sql = "SELECT * FROM $tbl_lp_item 
9405
                WHERE iid = ".$item_id;
9406
        $result = Database::query($sql);
9407
        $row = Database::fetch_assoc($result);
9408
9409
        $audio_player = null;
9410
        // We display an audio player if needed.
9411
        if (!empty($row['audio'])) {
9412
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9413
9414
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9415
                .'<audio src="'.$webAudioPath.'" controls>'
9416
                .'</div><br>';
9417
        }
9418
9419
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9420
9421
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9422
            $return .= Display::url(
9423
                Display::return_icon(
9424
                    'edit.png',
9425
                    get_lang('Edit'),
9426
                    [],
9427
                    ICON_SIZE_SMALL
9428
                ),
9429
                $url.'&action=edit_item&path_item='.$row['path']
9430
            );
9431
9432
            $return .= Display::url(
9433
                Display::return_icon(
9434
                    'move.png',
9435
                    get_lang('Move'),
9436
                    [],
9437
                    ICON_SIZE_SMALL
9438
                ),
9439
                $url.'&action=move_item'
9440
            );
9441
        }
9442
9443
        // Commented for now as prerequisites cannot be added to chapters.
9444
        if ($item_type != 'dir') {
9445
            $return .= Display::url(
9446
                Display::return_icon(
9447
                    'accept.png',
9448
                    get_lang('Prerequisites'),
9449
                    [],
9450
                    ICON_SIZE_SMALL
9451
                ),
9452
                $url.'&action=edit_item_prereq'
9453
            );
9454
        }
9455
        $return .= Display::url(
9456
            Display::return_icon(
9457
                'delete.png',
9458
                get_lang('Delete'),
9459
                [],
9460
                ICON_SIZE_SMALL
9461
            ),
9462
            $url.'&action=delete_item'
9463
        );
9464
9465
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9466
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9467
            if (empty($documentData)) {
9468
                // Try with iid
9469
                $table = Database::get_course_table(TABLE_DOCUMENT);
9470
                $sql = "SELECT path FROM $table
9471
                        WHERE 
9472
                              c_id = ".api_get_course_int_id()." AND 
9473
                              iid = ".$row['path']." AND 
9474
                              path NOT LIKE '%_DELETED_%'";
9475
                $result = Database::query($sql);
9476
                $documentData = Database::fetch_array($result);
9477
                if ($documentData) {
9478
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9479
                }
9480
            }
9481
            if (isset($documentData['absolute_path_from_document'])) {
9482
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9483
            }
9484
        }
9485
9486
        $return .= '</div>';
9487
9488
        if (!empty($audio_player)) {
9489
            $return .= $audio_player;
9490
        }
9491
9492
        return $return;
9493
    }
9494
9495
    /**
9496
     * Creates the javascript needed for filling up the checkboxes without page reload.
9497
     *
9498
     * @return string
9499
     */
9500
    public function get_js_dropdown_array()
9501
    {
9502
        $course_id = api_get_course_int_id();
9503
        $return = 'var child_name = new Array();'."\n";
9504
        $return .= 'var child_value = new Array();'."\n\n";
9505
        $return .= 'child_name[0] = new Array();'."\n";
9506
        $return .= 'child_value[0] = new Array();'."\n\n";
9507
9508
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9509
        $sql = "SELECT * FROM ".$tbl_lp_item."
9510
                WHERE 
9511
                    c_id = $course_id AND 
9512
                    lp_id = ".$this->lp_id." AND 
9513
                    parent_item_id = 0
9514
                ORDER BY display_order ASC";
9515
        $res_zero = Database::query($sql);
9516
        $i = 0;
9517
9518
        $list = $this->getItemsForForm(true);
9519
9520
        foreach ($list as $row_zero) {
9521
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9522
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9523
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9524
                }
9525
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9526
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9527
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9528
            }
9529
        }
9530
9531
        $return .= "\n";
9532
        $sql = "SELECT * FROM $tbl_lp_item
9533
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9534
        $res = Database::query($sql);
9535
        while ($row = Database::fetch_array($res)) {
9536
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9537
                           WHERE
9538
                                c_id = ".$course_id." AND
9539
                                parent_item_id = ".$row['iid']."
9540
                           ORDER BY display_order ASC";
9541
            $res_parent = Database::query($sql_parent);
9542
            $i = 0;
9543
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9544
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9545
9546
            while ($row_parent = Database::fetch_array($res_parent)) {
9547
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9548
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9549
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9550
            }
9551
            $return .= "\n";
9552
        }
9553
9554
        $return .= "
9555
            function load_cbo(id) {
9556
                if (!id) {
9557
                    return false;
9558
                }
9559
            
9560
                var cbo = document.getElementById('previous');
9561
                for(var i = cbo.length - 1; i > 0; i--) {
9562
                    cbo.options[i] = null;
9563
                }
9564
            
9565
                var k=0;
9566
                for(var i = 1; i <= child_name[id].length; i++){
9567
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9568
                    option.style.paddingLeft = '40px';
9569
                    cbo.options[i] = option;
9570
                    k = i;
9571
                }
9572
            
9573
                cbo.options[k].selected = true;
9574
                $('#previous').selectpicker('refresh');
9575
            }";
9576
9577
        return $return;
9578
    }
9579
9580
    /**
9581
     * Display the form to allow moving an item.
9582
     *
9583
     * @param int $item_id Item ID
9584
     *
9585
     * @throws Exception
9586
     * @throws HTML_QuickForm_Error
9587
     *
9588
     * @return string HTML form
9589
     */
9590
    public function display_move_item($item_id)
9591
    {
9592
        $return = '';
9593
        if (is_numeric($item_id)) {
9594
            $item_id = (int) $item_id;
9595
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9596
9597
            $sql = "SELECT * FROM $tbl_lp_item
9598
                    WHERE iid = $item_id";
9599
            $res = Database::query($sql);
9600
            $row = Database::fetch_array($res);
9601
9602
            switch ($row['item_type']) {
9603
                case 'dir':
9604
                case 'asset':
9605
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9606
                    $return .= $this->display_item_form(
9607
                        $row['item_type'],
9608
                        get_lang('Move the current section'),
9609
                        'move',
9610
                        $item_id,
9611
                        $row
9612
                    );
9613
                    break;
9614
                case TOOL_DOCUMENT:
9615
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9616
                    $return .= $this->display_document_form('move', $item_id, $row);
9617
                    break;
9618
                case TOOL_LINK:
9619
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9620
                    $return .= $this->display_link_form('move', $item_id, $row);
9621
                    break;
9622
                case TOOL_HOTPOTATOES:
9623
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9624
                    $return .= $this->display_link_form('move', $item_id, $row);
9625
                    break;
9626
                case TOOL_QUIZ:
9627
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9628
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9629
                    break;
9630
                case TOOL_STUDENTPUBLICATION:
9631
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9632
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9633
                    break;
9634
                case TOOL_FORUM:
9635
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9636
                    $return .= $this->display_forum_form('move', $item_id, $row);
9637
                    break;
9638
                case TOOL_THREAD:
9639
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9640
                    $return .= $this->display_forum_form('move', $item_id, $row);
9641
                    break;
9642
            }
9643
        }
9644
9645
        return $return;
9646
    }
9647
9648
    /**
9649
     * Return HTML form to allow prerequisites selection.
9650
     *
9651
     * @todo use FormValidator
9652
     *
9653
     * @param int Item ID
9654
     *
9655
     * @return string HTML form
9656
     */
9657
    public function display_item_prerequisites_form($item_id = 0)
9658
    {
9659
        $course_id = api_get_course_int_id();
9660
        $item_id = (int) $item_id;
9661
9662
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9663
9664
        /* Current prerequisite */
9665
        $sql = "SELECT * FROM $tbl_lp_item
9666
                WHERE iid = $item_id";
9667
        $result = Database::query($sql);
9668
        $row = Database::fetch_array($result);
9669
        $prerequisiteId = $row['prerequisite'];
9670
        $return = '<legend>';
9671
        $return .= get_lang('Add/edit prerequisites');
9672
        $return .= '</legend>';
9673
        $return .= '<form method="POST">';
9674
        $return .= '<div class="table-responsive">';
9675
        $return .= '<table class="table table-hover">';
9676
        $return .= '<thead>';
9677
        $return .= '<tr>';
9678
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
9679
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
9680
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
9681
        $return .= '</tr>';
9682
        $return .= '</thead>';
9683
9684
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9685
        $return .= '<tbody>';
9686
        $return .= '<tr>';
9687
        $return .= '<td colspan="3">';
9688
        $return .= '<div class="radio learnpath"><label for="idnone">';
9689
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
9690
        $return .= get_lang('none').'</label>';
9691
        $return .= '</div>';
9692
        $return .= '</tr>';
9693
9694
        $sql = "SELECT * FROM $tbl_lp_item
9695
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9696
        $result = Database::query($sql);
9697
9698
        $selectedMinScore = [];
9699
        $selectedMaxScore = [];
9700
        $masteryScore = [];
9701
        while ($row = Database::fetch_array($result)) {
9702
            if ($row['iid'] == $item_id) {
9703
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9704
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9705
            }
9706
            $masteryScore[$row['iid']] = $row['mastery_score'];
9707
        }
9708
9709
        $arrLP = $this->getItemsForForm();
9710
        $this->tree_array($arrLP);
9711
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9712
        unset($this->arrMenu);
9713
9714
        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...
9715
            $item = $arrLP[$i];
9716
9717
            if ($item['id'] == $item_id) {
9718
                break;
9719
            }
9720
9721
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9722
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9723
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9724
9725
            $return .= '<tr>';
9726
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9727
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9728
            $return .= '<label for="id'.$item['id'].'">';
9729
            $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'].'" />';
9730
9731
            $icon_name = str_replace(' ', '', $item['item_type']);
9732
9733
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9734
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9735
            } else {
9736
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9737
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9738
                } else {
9739
                    $return .= Display::return_icon('folder_document.png');
9740
                }
9741
            }
9742
9743
            $return .= $item['title'].'</label>';
9744
            $return .= '</div>';
9745
            $return .= '</td>';
9746
9747
            if ($item['item_type'] == TOOL_QUIZ) {
9748
                // lets update max_score Tests information depending of the Tests Advanced properties
9749
                $lpItemObj = new LpItem($course_id, $item['id']);
9750
                $exercise = new Exercise($course_id);
9751
                $exercise->read($lpItemObj->path);
9752
                $lpItemObj->max_score = $exercise->get_max_score();
9753
                $lpItemObj->update();
9754
                $item['max_score'] = $lpItemObj->max_score;
9755
9756
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9757
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9758
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9759
                }
9760
9761
                $return .= '<td>';
9762
                $return .= '<input 
9763
                    class="form-control" 
9764
                    size="4" maxlength="3" 
9765
                    name="min_'.$item['id'].'" 
9766
                    type="number" 
9767
                    min="0" 
9768
                    step="1" 
9769
                    max="'.$item['max_score'].'" 
9770
                    value="'.$selectedMinScoreValue.'" 
9771
                />';
9772
                $return .= '</td>';
9773
                $return .= '<td>';
9774
                $return .= '<input 
9775
                    class="form-control" 
9776
                    size="4" 
9777
                    maxlength="3" 
9778
                    name="max_'.$item['id'].'" 
9779
                    type="number" 
9780
                    min="0" 
9781
                    step="1" 
9782
                    max="'.$item['max_score'].'" 
9783
                    value="'.$selectedMaxScoreValue.'" 
9784
                />';
9785
                $return .= '</td>';
9786
            }
9787
9788
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9789
                $return .= '<td>';
9790
                $return .= '<input 
9791
                    size="4" 
9792
                    maxlength="3" 
9793
                    name="min_'.$item['id'].'" 
9794
                    type="number" 
9795
                    min="0" 
9796
                    step="1" 
9797
                    max="'.$item['max_score'].'" 
9798
                    value="'.$selectedMinScoreValue.'" 
9799
                />';
9800
                $return .= '</td>';
9801
                $return .= '<td>';
9802
                $return .= '<input 
9803
                    size="4" 
9804
                    maxlength="3" 
9805
                    name="max_'.$item['id'].'" 
9806
                    type="number" 
9807
                    min="0" 
9808
                    step="1" 
9809
                    max="'.$item['max_score'].'" 
9810
                    value="'.$selectedMaxScoreValue.'" 
9811
                />';
9812
                $return .= '</td>';
9813
            }
9814
            $return .= '</tr>';
9815
        }
9816
        $return .= '<tr>';
9817
        $return .= '</tr>';
9818
        $return .= '</tbody>';
9819
        $return .= '</table>';
9820
        $return .= '</div>';
9821
        $return .= '<div class="form-group">';
9822
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9823
            get_lang('Save prerequisites settings').'</button>';
9824
        $return .= '</form>';
9825
9826
        return $return;
9827
    }
9828
9829
    /**
9830
     * Return HTML list to allow prerequisites selection for lp.
9831
     *
9832
     * @return string HTML form
9833
     */
9834
    public function display_lp_prerequisites_list()
9835
    {
9836
        $course_id = api_get_course_int_id();
9837
        $lp_id = $this->lp_id;
9838
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9839
9840
        // get current prerequisite
9841
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9842
        $result = Database::query($sql);
9843
        $row = Database::fetch_array($result);
9844
        $prerequisiteId = $row['prerequisite'];
9845
        $session_id = api_get_session_id();
9846
        $session_condition = api_get_session_condition($session_id, true, true);
9847
        $sql = "SELECT * FROM $tbl_lp
9848
                WHERE c_id = $course_id $session_condition
9849
                ORDER BY display_order ";
9850
        $rs = Database::query($sql);
9851
        $return = '';
9852
        $return .= '<select name="prerequisites" class="form-control">';
9853
        $return .= '<option value="0">'.get_lang('none').'</option>';
9854
        if (Database::num_rows($rs) > 0) {
9855
            while ($row = Database::fetch_array($rs)) {
9856
                if ($row['id'] == $lp_id) {
9857
                    continue;
9858
                }
9859
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9860
            }
9861
        }
9862
        $return .= '</select>';
9863
9864
        return $return;
9865
    }
9866
9867
    /**
9868
     * Creates a list with all the documents in it.
9869
     *
9870
     * @param bool $showInvisibleFiles
9871
     *
9872
     * @throws Exception
9873
     * @throws HTML_QuickForm_Error
9874
     *
9875
     * @return string
9876
     */
9877
    public function get_documents($showInvisibleFiles = false)
9878
    {
9879
        $course_info = api_get_course_info();
9880
        $sessionId = api_get_session_id();
9881
        $documentTree = DocumentManager::get_document_preview(
9882
            $course_info,
9883
            $this->lp_id,
9884
            null,
9885
            $sessionId,
9886
            true,
9887
            null,
9888
            null,
9889
            $showInvisibleFiles,
9890
            true
9891
        );
9892
9893
        $headers = [
9894
            get_lang('Files'),
9895
            get_lang('Create a new document'),
9896
            get_lang('Create read-out text'),
9897
            get_lang('Upload'),
9898
        ];
9899
9900
        $form = new FormValidator(
9901
            'form_upload',
9902
            'POST',
9903
            $this->getCurrentBuildingModeURL(),
9904
            '',
9905
            ['enctype' => 'multipart/form-data']
9906
        );
9907
9908
        $folders = DocumentManager::get_all_document_folders(
9909
            api_get_course_info(),
9910
            0,
9911
            true
9912
        );
9913
9914
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9915
9916
        DocumentManager::build_directory_selector(
9917
            $folders,
9918
            $lpPathInfo['id'],
9919
            [],
9920
            true,
9921
            $form,
9922
            'directory_parent_id'
9923
        );
9924
9925
        $group = [
9926
            $form->createElement(
9927
                'radio',
9928
                'if_exists',
9929
                get_lang('If file exists:'),
9930
                get_lang('Do nothing'),
9931
                'nothing'
9932
            ),
9933
            $form->createElement(
9934
                'radio',
9935
                'if_exists',
9936
                null,
9937
                get_lang('Overwrite the existing file'),
9938
                'overwrite'
9939
            ),
9940
            $form->createElement(
9941
                'radio',
9942
                'if_exists',
9943
                null,
9944
                get_lang('Rename the uploaded file if it exists'),
9945
                'rename'
9946
            ),
9947
        ];
9948
        $form->addGroup($group, null, get_lang('If file exists:'));
9949
9950
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9951
        $defaultFileExistsOption = 'rename';
9952
        if (!empty($fileExistsOption)) {
9953
            $defaultFileExistsOption = $fileExistsOption;
9954
        }
9955
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9956
9957
        // Check box options
9958
        $form->addElement(
9959
            'checkbox',
9960
            'unzip',
9961
            get_lang('Options'),
9962
            get_lang('Uncompress zip')
9963
        );
9964
9965
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9966
        $form->addMultipleUpload($url);
9967
        $new = $this->display_document_form('add', 0);
9968
        $frmReadOutText = $this->displayFrmReadOutText('add');
9969
        $tabs = Display::tabs(
9970
            $headers,
9971
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
9972
            'subtab'
9973
        );
9974
9975
        return $tabs;
9976
    }
9977
9978
    /**
9979
     * Creates a list with all the exercises (quiz) in it.
9980
     *
9981
     * @return string
9982
     */
9983
    public function get_exercises()
9984
    {
9985
        $course_id = api_get_course_int_id();
9986
        $session_id = api_get_session_id();
9987
        $userInfo = api_get_user_info();
9988
9989
        // New for hotpotatoes.
9990
        //$uploadPath = DIR_HOTPOTATOES; //defined in main_api
9991
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9992
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
9993
        $condition_session = api_get_session_condition($session_id, true, true);
9994
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
9995
9996
        $activeCondition = ' active <> -1 ';
9997
        if ($setting) {
9998
            $activeCondition = ' active = 1 ';
9999
        }
10000
10001
        $categoryCondition = '';
10002
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10003
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10004
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10005
        }
10006
10007
        $keywordCondition = '';
10008
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10009
10010
        if (!empty($keyword)) {
10011
            $keyword = Database::escape_string($keyword);
10012
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10013
        }
10014
10015
        $sql_quiz = "SELECT * FROM $tbl_quiz
10016
                     WHERE 
10017
                            c_id = $course_id AND 
10018
                            $activeCondition
10019
                            $condition_session 
10020
                            $categoryCondition
10021
                            $keywordCondition
10022
                     ORDER BY title ASC";
10023
        $res_quiz = Database::query($sql_quiz);
10024
10025
        /*$sql_hot = "SELECT * FROM $tbl_doc
10026
                    WHERE
10027
                        c_id = $course_id AND
10028
                        path LIKE '".$uploadPath."/%/%htm%'
10029
                        $condition_session
10030
                     ORDER BY id ASC";
10031
10032
        $res_quiz = Database::query($sql_quiz);
10033
        $res_hot = Database::query($sql_hot);*/
10034
10035
10036
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10037
10038
        // Create a search-box
10039
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10040
        $form->addHidden('action', 'add_item');
10041
        $form->addHidden('type', 'step');
10042
        $form->addHidden('lp_id', $this->lp_id);
10043
        $form->addHidden('lp_build_selected', '2');
10044
10045
        $form->addCourseHiddenParams();
10046
        $form->addText(
10047
            'keyword',
10048
            get_lang('Search'),
10049
            false,
10050
            [
10051
                'aria-label' => get_lang('Search'),
10052
            ]
10053
        );
10054
10055
        if (api_get_configuration_value('allow_exercise_categories')) {
10056
            $manager = new ExerciseCategoryManager();
10057
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10058
            if (!empty($options)) {
10059
                $form->addSelect(
10060
                    'category_id',
10061
                    get_lang('Category'),
10062
                    $options,
10063
                    ['placeholder' => get_lang('Please select an option')]
10064
                );
10065
            }
10066
        }
10067
10068
        $form->addButtonSearch(get_lang('Search'));
10069
        $return = $form->returnForm();
10070
10071
        $return .= '<ul class="lp_resource">';
10072
10073
        $return .= '<li class="lp_resource_element">';
10074
        $return .= Display::return_icon('new_exercice.png');
10075
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10076
            get_lang('New test').'</a>';
10077
        $return .= '</li>';
10078
10079
        $previewIcon = Display::return_icon(
10080
            'preview_view.png',
10081
            get_lang('Preview')
10082
        );
10083
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10084
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10085
        //$exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10086
        // Display hotpotatoes
10087
        /*while ($row_hot = Database::fetch_array($res_hot)) {
10088
            $link = Display::url(
10089
                $previewIcon,
10090
                $exerciseUrl.'&file='.$row_hot['path'],
10091
                ['target' => '_blank']
10092
            );
10093
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10094
            $return .= '<a class="moved" href="#">';
10095
            $return .= Display::return_icon(
10096
                'move_everywhere.png',
10097
                get_lang('Move'),
10098
                [],
10099
                ICON_SIZE_TINY
10100
            );
10101
            $return .= '</a> ';
10102
            $return .= Display::return_icon('hotpotatoes_s.png');
10103
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10104
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10105
            $return .= '</li>';
10106
        }*/
10107
10108
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10109
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10110
            $title = strip_tags(
10111
                api_html_entity_decode($row_quiz['title'])
10112
            );
10113
10114
            $visibility = api_get_item_visibility(
10115
                ['real_id' => $course_id],
10116
                TOOL_QUIZ,
10117
                $row_quiz['iid'],
10118
                $session_id
10119
            );
10120
10121
            $link = Display::url(
10122
                $previewIcon,
10123
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10124
                ['target' => '_blank']
10125
            );
10126
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10127
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10128
            $return .= $quizIcon;
10129
            $sessionStar = api_get_session_image(
10130
                $row_quiz['session_id'],
10131
                $userInfo['status']
10132
            );
10133
            $return .= Display::url(
10134
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10135
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10136
                [
10137
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10138
                ]
10139
            );
10140
            $return .= '</li>';
10141
        }
10142
10143
        $return .= '</ul>';
10144
10145
        return $return;
10146
    }
10147
10148
    /**
10149
     * Creates a list with all the links in it.
10150
     *
10151
     * @return string
10152
     */
10153
    public function get_links()
10154
    {
10155
        $selfUrl = api_get_self();
10156
        $courseIdReq = api_get_cidreq();
10157
        $course = api_get_course_info();
10158
        $userInfo = api_get_user_info();
10159
10160
        $course_id = $course['real_id'];
10161
        $tbl_link = Database::get_course_table(TABLE_LINK);
10162
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10163
        $moveEverywhereIcon = Display::return_icon(
10164
            'move_everywhere.png',
10165
            get_lang('Move'),
10166
            [],
10167
            ICON_SIZE_TINY
10168
        );
10169
10170
        $session_id = api_get_session_id();
10171
        $condition_session = api_get_session_condition(
10172
            $session_id,
10173
            true,
10174
            true,
10175
            'link.session_id'
10176
        );
10177
10178
        $sql = "SELECT 
10179
                    link.id as link_id,
10180
                    link.title as link_title,
10181
                    link.session_id as link_session_id,
10182
                    link.category_id as category_id,
10183
                    link_category.category_title as category_title
10184
                FROM $tbl_link as link
10185
                LEFT JOIN $linkCategoryTable as link_category
10186
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10187
                WHERE link.c_id = $course_id $condition_session
10188
                ORDER BY link_category.category_title ASC, link.title ASC";
10189
        $result = Database::query($sql);
10190
        $categorizedLinks = [];
10191
        $categories = [];
10192
10193
        while ($link = Database::fetch_array($result)) {
10194
            if (!$link['category_id']) {
10195
                $link['category_title'] = get_lang('Uncategorized');
10196
            }
10197
            $categories[$link['category_id']] = $link['category_title'];
10198
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10199
        }
10200
10201
        $linksHtmlCode =
10202
            '<script>
10203
            function toggle_tool(tool, id) {
10204
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10205
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10206
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10207
                } else {
10208
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10209
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10210
                }
10211
            }
10212
        </script>
10213
10214
        <ul class="lp_resource">
10215
            <li class="lp_resource_element">
10216
                '.Display::return_icon('linksnew.gif').'
10217
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('Add a link').'">'.
10218
                get_lang('Add a link').'
10219
                </a>
10220
            </li>';
10221
10222
        foreach ($categorizedLinks as $categoryId => $links) {
10223
            $linkNodes = null;
10224
            foreach ($links as $key => $linkInfo) {
10225
                $title = $linkInfo['link_title'];
10226
                $linkSessionId = $linkInfo['link_session_id'];
10227
10228
                $link = Display::url(
10229
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10230
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10231
                    ['target' => '_blank']
10232
                );
10233
10234
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10235
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10236
                    $linkNodes .=
10237
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10238
                        <a class="moved" href="#">'.
10239
                            $moveEverywhereIcon.
10240
                        '</a>
10241
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10242
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10243
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10244
                        Security::remove_XSS($title).$sessionStar.$link.
10245
                        '</a>
10246
                    </li>';
10247
                }
10248
            }
10249
            $linksHtmlCode .=
10250
                '<li>
10251
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10252
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10253
                    align="absbottom" />
10254
                </a>
10255
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10256
            </li>
10257
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10258
        }
10259
        $linksHtmlCode .= '</ul>';
10260
10261
        return $linksHtmlCode;
10262
    }
10263
10264
    /**
10265
     * Creates a list with all the student publications in it.
10266
     *
10267
     * @return string
10268
     */
10269
    public function get_student_publications()
10270
    {
10271
        $return = '<ul class="lp_resource">';
10272
        $return .= '<li class="lp_resource_element">';
10273
        $return .= Display::return_icon('works_new.gif');
10274
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10275
            get_lang('Add the Assignments tool to the course').'</a>';
10276
        $return .= '</li>';
10277
10278
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10279
        $works = getWorkListTeacher(0, 100, null, null, null);
10280
        if (!empty($works)) {
10281
            foreach ($works as $work) {
10282
                $link = Display::url(
10283
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10284
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10285
                    ['target' => '_blank']
10286
                );
10287
10288
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10289
                $return .= '<a class="moved" href="#">';
10290
                $return .= Display::return_icon(
10291
                    'move_everywhere.png',
10292
                    get_lang('Move'),
10293
                    [],
10294
                    ICON_SIZE_TINY
10295
                );
10296
                $return .= '</a> ';
10297
10298
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10299
                $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.'">'.
10300
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10301
                </a>';
10302
10303
                $return .= '</li>';
10304
            }
10305
        }
10306
10307
        $return .= '</ul>';
10308
10309
        return $return;
10310
    }
10311
10312
    /**
10313
     * Creates a list with all the forums in it.
10314
     *
10315
     * @return string
10316
     */
10317
    public function get_forums()
10318
    {
10319
        require_once '../forum/forumfunction.inc.php';
10320
10321
        $forumCategories = get_forum_categories();
10322
        $forumsInNoCategory = get_forums_in_category(0);
10323
        if (!empty($forumsInNoCategory)) {
10324
            $forumCategories = array_merge(
10325
                $forumCategories,
10326
                [
10327
                    [
10328
                        'cat_id' => 0,
10329
                        'session_id' => 0,
10330
                        'visibility' => 1,
10331
                        'cat_comment' => null,
10332
                    ],
10333
                ]
10334
            );
10335
        }
10336
10337
        $forumList = get_forums();
10338
        $a_forums = [];
10339
        foreach ($forumCategories as $forumCategory) {
10340
            // The forums in this category.
10341
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10342
            if (!empty($forumsInCategory)) {
10343
                foreach ($forumList as $forum) {
10344
                    if (isset($forum['forum_category']) &&
10345
                        $forum['forum_category'] == $forumCategory['cat_id']
10346
                    ) {
10347
                        $a_forums[] = $forum;
10348
                    }
10349
                }
10350
            }
10351
        }
10352
10353
        $return = '<ul class="lp_resource">';
10354
10355
        // First add link
10356
        $return .= '<li class="lp_resource_element">';
10357
        $return .= Display::return_icon('new_forum.png');
10358
        $return .= Display::url(
10359
            get_lang('Create a new forum'),
10360
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10361
                'action' => 'add',
10362
                'content' => 'forum',
10363
                'lp_id' => $this->lp_id,
10364
            ]),
10365
            ['title' => get_lang('Create a new forum')]
10366
        );
10367
        $return .= '</li>';
10368
10369
        $return .= '<script>
10370
            function toggle_forum(forum_id) {
10371
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10372
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10373
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10374
                } else {
10375
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10376
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10377
                }
10378
            }
10379
        </script>';
10380
10381
        foreach ($a_forums as $forum) {
10382
            if (!empty($forum['forum_id'])) {
10383
                $link = Display::url(
10384
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10385
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10386
                    ['target' => '_blank']
10387
                );
10388
10389
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10390
                $return .= '<a class="moved" href="#">';
10391
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10392
                $return .= ' </a>';
10393
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10394
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10395
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10396
                            </a>
10397
                            <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">'.
10398
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10399
10400
                $return .= '</li>';
10401
10402
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10403
                $a_threads = get_threads($forum['forum_id']);
10404
                if (is_array($a_threads)) {
10405
                    foreach ($a_threads as $thread) {
10406
                        $link = Display::url(
10407
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10408
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10409
                            ['target' => '_blank']
10410
                        );
10411
10412
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10413
                        $return .= '&nbsp;<a class="moved" href="#">';
10414
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10415
                        $return .= ' </a>';
10416
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10417
                        $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.'">'.
10418
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10419
                        $return .= '</li>';
10420
                    }
10421
                }
10422
                $return .= '</div>';
10423
            }
10424
        }
10425
        $return .= '</ul>';
10426
10427
        return $return;
10428
    }
10429
10430
    /**
10431
     * // TODO: The output encoding should be equal to the system encoding.
10432
     *
10433
     * Exports the learning path as a SCORM package. This is the main function that
10434
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10435
     * whole thing and returns the zip.
10436
     *
10437
     * This method needs to be called in PHP5, as it will fail with non-adequate
10438
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10439
     * you need to call it on a learnpath object.
10440
     *
10441
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10442
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10443
     * path has been modified, it should use the generic method here below.
10444
     *
10445
     * @return string Returns the zip package string, or null if error
10446
     */
10447
    public function scormExport()
10448
    {
10449
        api_set_more_memory_and_time_limits();
10450
10451
        $_course = api_get_course_info();
10452
        $course_id = $_course['real_id'];
10453
        // Create the zip handler (this will remain available throughout the method).
10454
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10455
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10456
        $temp_dir_short = uniqid('scorm_export', true);
10457
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10458
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10459
        $zip_folder = new PclZip($temp_zip_file);
10460
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10461
        $root_path = $main_path = api_get_path(SYS_PATH);
10462
        $files_cleanup = [];
10463
10464
        // Place to temporarily stash the zip file.
10465
        // create the temp dir if it doesn't exist
10466
        // or do a cleanup before creating the zip file.
10467
        if (!is_dir($temp_zip_dir)) {
10468
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10469
        } else {
10470
            // Cleanup: Check the temp dir for old files and delete them.
10471
            $handle = opendir($temp_zip_dir);
10472
            while (false !== ($file = readdir($handle))) {
10473
                if ($file != '.' && $file != '..') {
10474
                    unlink("$temp_zip_dir/$file");
10475
                }
10476
            }
10477
            closedir($handle);
10478
        }
10479
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10480
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10481
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10482
        ) {
10483
            // Remove the possible . at the end of the path.
10484
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10485
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10486
            mkdir(
10487
                $dest_path_to_scorm_folder,
10488
                api_get_permissions_for_new_directories(),
10489
                true
10490
            );
10491
            copyr(
10492
                $current_course_path.'/scorm/'.$this->path,
10493
                $dest_path_to_scorm_folder,
10494
                ['imsmanifest'],
10495
                $zip_files
10496
            );
10497
        }
10498
10499
        // Build a dummy imsmanifest structure.
10500
        // Do not add to the zip yet (we still need it).
10501
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10502
        // Aggregation Model official document, section "2.3 Content Packaging".
10503
        // We are going to build a UTF-8 encoded manifest.
10504
        // Later we will recode it to the desired (and supported) encoding.
10505
        $xmldoc = new DOMDocument('1.0');
10506
        $root = $xmldoc->createElement('manifest');
10507
        $root->setAttribute('identifier', 'SingleCourseManifest');
10508
        $root->setAttribute('version', '1.1');
10509
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10510
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10511
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10512
        $root->setAttribute(
10513
            'xsi:schemaLocation',
10514
            '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'
10515
        );
10516
        // Build mandatory sub-root container elements.
10517
        $metadata = $xmldoc->createElement('metadata');
10518
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10519
        $metadata->appendChild($md_schema);
10520
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10521
        $metadata->appendChild($md_schemaversion);
10522
        $root->appendChild($metadata);
10523
10524
        $organizations = $xmldoc->createElement('organizations');
10525
        $resources = $xmldoc->createElement('resources');
10526
10527
        // Build the only organization we will use in building our learnpaths.
10528
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10529
        $organization = $xmldoc->createElement('organization');
10530
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10531
        // To set the title of the SCORM entity (=organization), we take the name given
10532
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10533
        // learning path charset) as it is the encoding that defines how it is stored
10534
        // in the database. Then we convert it to HTML entities again as the "&" character
10535
        // alone is not authorized in XML (must be &amp;).
10536
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10537
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10538
        $organization->appendChild($org_title);
10539
        $folder_name = 'document';
10540
10541
        // Removes the learning_path/scorm_folder path when exporting see #4841
10542
        $path_to_remove = '';
10543
        $path_to_replace = '';
10544
        $result = $this->generate_lp_folder($_course);
10545
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10546
            $path_to_remove = 'document'.$result['dir'];
10547
            $path_to_replace = $folder_name.'/';
10548
        }
10549
10550
        // Fixes chamilo scorm exports
10551
        if ($this->ref === 'chamilo_scorm_export') {
10552
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10553
        }
10554
10555
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10556
        $link_updates = [];
10557
        $links_to_create = [];
10558
        foreach ($this->ordered_items as $index => $itemId) {
10559
            /** @var learnpathItem $item */
10560
            $item = $this->items[$itemId];
10561
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10562
                // Get included documents from this item.
10563
                if ($item->type === 'sco') {
10564
                    $inc_docs = $item->get_resources_from_source(
10565
                        null,
10566
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10567
                    );
10568
                } else {
10569
                    $inc_docs = $item->get_resources_from_source();
10570
                }
10571
10572
                // Give a child element <item> to the <organization> element.
10573
                $my_item_id = $item->get_id();
10574
                $my_item = $xmldoc->createElement('item');
10575
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10576
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10577
                $my_item->setAttribute('isvisible', 'true');
10578
                // Give a child element <title> to the <item> element.
10579
                $my_title = $xmldoc->createElement(
10580
                    'title',
10581
                    htmlspecialchars(
10582
                        api_utf8_encode($item->get_title()),
10583
                        ENT_QUOTES,
10584
                        'UTF-8'
10585
                    )
10586
                );
10587
                $my_item->appendChild($my_title);
10588
                // Give a child element <adlcp:prerequisites> to the <item> element.
10589
                $my_prereqs = $xmldoc->createElement(
10590
                    'adlcp:prerequisites',
10591
                    $this->get_scorm_prereq_string($my_item_id)
10592
                );
10593
                $my_prereqs->setAttribute('type', 'aicc_script');
10594
                $my_item->appendChild($my_prereqs);
10595
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10596
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10597
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10598
                //$xmldoc->createElement('adlcp:timelimitaction','');
10599
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10600
                //$xmldoc->createElement('adlcp:datafromlms','');
10601
                // Give a child element <adlcp:masteryscore> to the <item> element.
10602
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10603
                $my_item->appendChild($my_masteryscore);
10604
10605
                // Attach this item to the organization element or hits parent if there is one.
10606
                if (!empty($item->parent) && $item->parent != 0) {
10607
                    $children = $organization->childNodes;
10608
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10609
                    if (is_object($possible_parent)) {
10610
                        $possible_parent->appendChild($my_item);
10611
                    } else {
10612
                        if ($this->debug > 0) {
10613
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10614
                        }
10615
                    }
10616
                } else {
10617
                    if ($this->debug > 0) {
10618
                        error_log('No parent');
10619
                    }
10620
                    $organization->appendChild($my_item);
10621
                }
10622
10623
                // Get the path of the file(s) from the course directory root.
10624
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10625
                $my_xml_file_path = $my_file_path;
10626
                if (!empty($path_to_remove)) {
10627
                    // From docs
10628
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10629
10630
                    // From quiz
10631
                    if ($this->ref === 'chamilo_scorm_export') {
10632
                        $path_to_remove = 'scorm/'.$this->path.'/';
10633
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10634
                    }
10635
                }
10636
10637
                $my_sub_dir = dirname($my_file_path);
10638
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10639
                $my_xml_sub_dir = $my_sub_dir;
10640
                // Give a <resource> child to the <resources> element
10641
                $my_resource = $xmldoc->createElement('resource');
10642
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10643
                $my_resource->setAttribute('type', 'webcontent');
10644
                $my_resource->setAttribute('href', $my_xml_file_path);
10645
                // adlcp:scormtype can be either 'sco' or 'asset'.
10646
                if ($item->type === 'sco') {
10647
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10648
                } else {
10649
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10650
                }
10651
                // xml:base is the base directory to find the files declared in this resource.
10652
                $my_resource->setAttribute('xml:base', '');
10653
                // Give a <file> child to the <resource> element.
10654
                $my_file = $xmldoc->createElement('file');
10655
                $my_file->setAttribute('href', $my_xml_file_path);
10656
                $my_resource->appendChild($my_file);
10657
10658
                // Dependency to other files - not yet supported.
10659
                $i = 1;
10660
                if ($inc_docs) {
10661
                    foreach ($inc_docs as $doc_info) {
10662
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10663
                            continue;
10664
                        }
10665
                        $my_dep = $xmldoc->createElement('resource');
10666
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10667
                        $my_dep->setAttribute('identifier', $res_id);
10668
                        $my_dep->setAttribute('type', 'webcontent');
10669
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10670
                        $my_dep_file = $xmldoc->createElement('file');
10671
                        // Check type of URL.
10672
                        if ($doc_info[1] == 'remote') {
10673
                            // Remote file. Save url as is.
10674
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10675
                            $my_dep->setAttribute('xml:base', '');
10676
                        } elseif ($doc_info[1] === 'local') {
10677
                            switch ($doc_info[2]) {
10678
                                case 'url':
10679
                                    // Local URL - save path as url for now, don't zip file.
10680
                                    $abs_path = api_get_path(SYS_PATH).
10681
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10682
                                    $current_dir = dirname($abs_path);
10683
                                    $current_dir = str_replace('\\', '/', $current_dir);
10684
                                    $file_path = realpath($abs_path);
10685
                                    $file_path = str_replace('\\', '/', $file_path);
10686
                                    $my_dep_file->setAttribute('href', $file_path);
10687
                                    $my_dep->setAttribute('xml:base', '');
10688
                                    if (strstr($file_path, $main_path) !== false) {
10689
                                        // The calculated real path is really inside Chamilo's root path.
10690
                                        // Reduce file path to what's under the DocumentRoot.
10691
                                        $replace = $file_path;
10692
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10693
                                        $destinationFile = $file_path;
10694
10695
                                        if (strstr($file_path, 'upload/users') !== false) {
10696
                                            $pos = strpos($file_path, 'my_files/');
10697
                                            if ($pos !== false) {
10698
                                                $onlyDirectory = str_replace(
10699
                                                    'upload/users/',
10700
                                                    '',
10701
                                                    substr($file_path, $pos, strlen($file_path))
10702
                                                );
10703
                                            }
10704
                                            $replace = $onlyDirectory;
10705
                                            $destinationFile = $replace;
10706
                                        }
10707
                                        $zip_files_abs[] = $file_path;
10708
                                        $link_updates[$my_file_path][] = [
10709
                                            'orig' => $doc_info[0],
10710
                                            'dest' => $destinationFile,
10711
                                            'replace' => $replace,
10712
                                        ];
10713
                                        $my_dep_file->setAttribute('href', $file_path);
10714
                                        $my_dep->setAttribute('xml:base', '');
10715
                                    } elseif (empty($file_path)) {
10716
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10717
                                        $file_path = str_replace('//', '/', $file_path);
10718
                                        if (file_exists($file_path)) {
10719
                                            // We get the relative path.
10720
                                            $file_path = substr($file_path, strlen($current_dir));
10721
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10722
                                            $link_updates[$my_file_path][] = [
10723
                                                'orig' => $doc_info[0],
10724
                                                'dest' => $file_path,
10725
                                            ];
10726
                                            $my_dep_file->setAttribute('href', $file_path);
10727
                                            $my_dep->setAttribute('xml:base', '');
10728
                                        }
10729
                                    }
10730
                                    break;
10731
                                case 'abs':
10732
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10733
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10734
                                    $my_dep->setAttribute('xml:base', '');
10735
10736
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10737
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10738
                                    $abs_img_path_without_subdir = $doc_info[0];
10739
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10740
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10741
                                    if ($pos === 0) {
10742
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10743
                                    }
10744
10745
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10746
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10747
10748
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10749
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10750
                                    // Check if the current document is in that path.
10751
                                    if (strstr($file_path, $cur_path) !== false) {
10752
                                        $destinationFile = substr($file_path, strlen($cur_path));
10753
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10754
10755
                                        $fileToTest = $cur_path.$my_file_path;
10756
                                        if (!empty($path_to_remove)) {
10757
                                            $fileToTest = str_replace(
10758
                                                $path_to_remove.'/',
10759
                                                $path_to_replace,
10760
                                                $cur_path.$my_file_path
10761
                                            );
10762
                                        }
10763
10764
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10765
10766
                                        // Put the current document in the zip (this array is the array
10767
                                        // that will manage documents already in the course folder - relative).
10768
                                        $zip_files[] = $filePathNoCoursePart;
10769
                                        // Update the links to the current document in the
10770
                                        // containing document (make them relative).
10771
                                        $link_updates[$my_file_path][] = [
10772
                                            'orig' => $doc_info[0],
10773
                                            'dest' => $destinationFile,
10774
                                            'replace' => $relative_path,
10775
                                        ];
10776
10777
                                        $my_dep_file->setAttribute('href', $file_path);
10778
                                        $my_dep->setAttribute('xml:base', '');
10779
                                    } elseif (strstr($file_path, $main_path) !== false) {
10780
                                        // The calculated real path is really inside Chamilo's root path.
10781
                                        // Reduce file path to what's under the DocumentRoot.
10782
                                        $file_path = substr($file_path, strlen($root_path));
10783
                                        $zip_files_abs[] = $file_path;
10784
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10785
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10786
                                        $my_dep->setAttribute('xml:base', '');
10787
                                    } elseif (empty($file_path)) {
10788
                                        // Probably this is an image inside "/main" directory
10789
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10790
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10791
10792
                                        if (file_exists($file_path)) {
10793
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10794
                                                // We get the relative path.
10795
                                                $pos = strpos($file_path, 'main/default_course_document/');
10796
                                                if ($pos !== false) {
10797
                                                    $onlyDirectory = str_replace(
10798
                                                        'main/default_course_document/',
10799
                                                        '',
10800
                                                        substr($file_path, $pos, strlen($file_path))
10801
                                                    );
10802
                                                }
10803
10804
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10805
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10806
                                                $zip_files_abs[] = $fileAbs;
10807
                                                $link_updates[$my_file_path][] = [
10808
                                                    'orig' => $doc_info[0],
10809
                                                    'dest' => $destinationFile,
10810
                                                ];
10811
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10812
                                                $my_dep->setAttribute('xml:base', '');
10813
                                            }
10814
                                        }
10815
                                    }
10816
                                    break;
10817
                                case 'rel':
10818
                                    // Path relative to the current document.
10819
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10820
                                    if (substr($doc_info[0], 0, 2) === '..') {
10821
                                        // Relative path going up.
10822
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10823
                                        $current_dir = str_replace('\\', '/', $current_dir);
10824
                                        $file_path = realpath($current_dir.$doc_info[0]);
10825
                                        $file_path = str_replace('\\', '/', $file_path);
10826
                                        if (strstr($file_path, $main_path) !== false) {
10827
                                            // The calculated real path is really inside Chamilo's root path.
10828
                                            // Reduce file path to what's under the DocumentRoot.
10829
                                            $file_path = substr($file_path, strlen($root_path));
10830
                                            $zip_files_abs[] = $file_path;
10831
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10832
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10833
                                            $my_dep->setAttribute('xml:base', '');
10834
                                        }
10835
                                    } else {
10836
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10837
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10838
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10839
                                    }
10840
                                    break;
10841
                                default:
10842
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10843
                                    $my_dep->setAttribute('xml:base', '');
10844
                                    break;
10845
                            }
10846
                        }
10847
                        $my_dep->appendChild($my_dep_file);
10848
                        $resources->appendChild($my_dep);
10849
                        $dependency = $xmldoc->createElement('dependency');
10850
                        $dependency->setAttribute('identifierref', $res_id);
10851
                        $my_resource->appendChild($dependency);
10852
                        $i++;
10853
                    }
10854
                }
10855
                $resources->appendChild($my_resource);
10856
                $zip_files[] = $my_file_path;
10857
            } else {
10858
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10859
                switch ($item->type) {
10860
                    case TOOL_LINK:
10861
                        $my_item = $xmldoc->createElement('item');
10862
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10863
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10864
                        $my_item->setAttribute('isvisible', 'true');
10865
                        // Give a child element <title> to the <item> element.
10866
                        $my_title = $xmldoc->createElement(
10867
                            'title',
10868
                            htmlspecialchars(
10869
                                api_utf8_encode($item->get_title()),
10870
                                ENT_QUOTES,
10871
                                'UTF-8'
10872
                            )
10873
                        );
10874
                        $my_item->appendChild($my_title);
10875
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10876
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10877
                        $my_prereqs->setAttribute('type', 'aicc_script');
10878
                        $my_item->appendChild($my_prereqs);
10879
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10880
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10881
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10882
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10883
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10884
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10885
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10886
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10887
                        $my_item->appendChild($my_masteryscore);
10888
10889
                        // Attach this item to the organization element or its parent if there is one.
10890
                        if (!empty($item->parent) && $item->parent != 0) {
10891
                            $children = $organization->childNodes;
10892
                            for ($i = 0; $i < $children->length; $i++) {
10893
                                $item_temp = $children->item($i);
10894
                                if ($item_temp->nodeName == 'item') {
10895
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10896
                                        $item_temp->appendChild($my_item);
10897
                                    }
10898
                                }
10899
                            }
10900
                        } else {
10901
                            $organization->appendChild($my_item);
10902
                        }
10903
10904
                        $my_file_path = 'link_'.$item->get_id().'.html';
10905
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10906
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10907
                        $rs = Database::query($sql);
10908
                        if ($link = Database::fetch_array($rs)) {
10909
                            $url = $link['url'];
10910
                            $title = stripslashes($link['title']);
10911
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10912
                            $my_xml_file_path = $my_file_path;
10913
                            $my_sub_dir = dirname($my_file_path);
10914
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10915
                            $my_xml_sub_dir = $my_sub_dir;
10916
                            // Give a <resource> child to the <resources> element.
10917
                            $my_resource = $xmldoc->createElement('resource');
10918
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10919
                            $my_resource->setAttribute('type', 'webcontent');
10920
                            $my_resource->setAttribute('href', $my_xml_file_path);
10921
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10922
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10923
                            // xml:base is the base directory to find the files declared in this resource.
10924
                            $my_resource->setAttribute('xml:base', '');
10925
                            // give a <file> child to the <resource> element.
10926
                            $my_file = $xmldoc->createElement('file');
10927
                            $my_file->setAttribute('href', $my_xml_file_path);
10928
                            $my_resource->appendChild($my_file);
10929
                            $resources->appendChild($my_resource);
10930
                        }
10931
                        break;
10932
                    case TOOL_QUIZ:
10933
                        $exe_id = $item->path;
10934
                        // Should be using ref when everything will be cleaned up in this regard.
10935
                        $exe = new Exercise();
10936
                        $exe->read($exe_id);
10937
                        $my_item = $xmldoc->createElement('item');
10938
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10939
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10940
                        $my_item->setAttribute('isvisible', 'true');
10941
                        // Give a child element <title> to the <item> element.
10942
                        $my_title = $xmldoc->createElement(
10943
                            'title',
10944
                            htmlspecialchars(
10945
                                api_utf8_encode($item->get_title()),
10946
                                ENT_QUOTES,
10947
                                'UTF-8'
10948
                            )
10949
                        );
10950
                        $my_item->appendChild($my_title);
10951
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10952
                        $my_item->appendChild($my_max_score);
10953
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10954
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10955
                        $my_prereqs->setAttribute('type', 'aicc_script');
10956
                        $my_item->appendChild($my_prereqs);
10957
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10958
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10959
                        $my_item->appendChild($my_masteryscore);
10960
10961
                        // Attach this item to the organization element or hits parent if there is one.
10962
                        if (!empty($item->parent) && $item->parent != 0) {
10963
                            $children = $organization->childNodes;
10964
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10965
                            if ($possible_parent) {
10966
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10967
                                    $possible_parent->appendChild($my_item);
10968
                                }
10969
                            }
10970
                        } else {
10971
                            $organization->appendChild($my_item);
10972
                        }
10973
10974
                        // Get the path of the file(s) from the course directory root
10975
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10976
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10977
                        // Write the contents of the exported exercise into a (big) html file
10978
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10979
                        $scormExercise = new ScormExercise($exe, true);
10980
                        $contents = $scormExercise->export();
10981
10982
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10983
                        $res = file_put_contents($tmp_file_path, $contents);
10984
                        if ($res === false) {
10985
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10986
                        }
10987
                        $files_cleanup[] = $tmp_file_path;
10988
                        $my_xml_file_path = $my_file_path;
10989
                        $my_sub_dir = dirname($my_file_path);
10990
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10991
                        $my_xml_sub_dir = $my_sub_dir;
10992
                        // Give a <resource> child to the <resources> element.
10993
                        $my_resource = $xmldoc->createElement('resource');
10994
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10995
                        $my_resource->setAttribute('type', 'webcontent');
10996
                        $my_resource->setAttribute('href', $my_xml_file_path);
10997
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10998
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
10999
                        // xml:base is the base directory to find the files declared in this resource.
11000
                        $my_resource->setAttribute('xml:base', '');
11001
                        // Give a <file> child to the <resource> element.
11002
                        $my_file = $xmldoc->createElement('file');
11003
                        $my_file->setAttribute('href', $my_xml_file_path);
11004
                        $my_resource->appendChild($my_file);
11005
11006
                        // Get included docs.
11007
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11008
11009
                        // Dependency to other files - not yet supported.
11010
                        $i = 1;
11011
                        foreach ($inc_docs as $doc_info) {
11012
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11013
                                continue;
11014
                            }
11015
                            $my_dep = $xmldoc->createElement('resource');
11016
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11017
                            $my_dep->setAttribute('identifier', $res_id);
11018
                            $my_dep->setAttribute('type', 'webcontent');
11019
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11020
                            $my_dep_file = $xmldoc->createElement('file');
11021
                            // Check type of URL.
11022
                            if ($doc_info[1] == 'remote') {
11023
                                // Remote file. Save url as is.
11024
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11025
                                $my_dep->setAttribute('xml:base', '');
11026
                            } elseif ($doc_info[1] == 'local') {
11027
                                switch ($doc_info[2]) {
11028
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11029
                                        // Save file but as local file (retrieve from URL).
11030
                                        $abs_path = api_get_path(SYS_PATH).
11031
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11032
                                        $current_dir = dirname($abs_path);
11033
                                        $current_dir = str_replace('\\', '/', $current_dir);
11034
                                        $file_path = realpath($abs_path);
11035
                                        $file_path = str_replace('\\', '/', $file_path);
11036
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11037
                                        $my_dep->setAttribute('xml:base', '');
11038
                                        if (strstr($file_path, $main_path) !== false) {
11039
                                            // The calculated real path is really inside the chamilo root path.
11040
                                            // Reduce file path to what's under the DocumentRoot.
11041
                                            $file_path = substr($file_path, strlen($root_path));
11042
                                            $zip_files_abs[] = $file_path;
11043
                                            $link_updates[$my_file_path][] = [
11044
                                                'orig' => $doc_info[0],
11045
                                                'dest' => 'document/'.$file_path,
11046
                                            ];
11047
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11048
                                            $my_dep->setAttribute('xml:base', '');
11049
                                        } elseif (empty($file_path)) {
11050
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11051
                                            $file_path = str_replace('//', '/', $file_path);
11052
                                            if (file_exists($file_path)) {
11053
                                                $file_path = substr($file_path, strlen($current_dir));
11054
                                                // We get the relative path.
11055
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11056
                                                $link_updates[$my_file_path][] = [
11057
                                                    'orig' => $doc_info[0],
11058
                                                    'dest' => 'document/'.$file_path,
11059
                                                ];
11060
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11061
                                                $my_dep->setAttribute('xml:base', '');
11062
                                            }
11063
                                        }
11064
                                        break;
11065
                                    case 'abs':
11066
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11067
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11068
                                        $current_dir = str_replace('\\', '/', $current_dir);
11069
                                        $file_path = realpath($doc_info[0]);
11070
                                        $file_path = str_replace('\\', '/', $file_path);
11071
                                        $my_dep_file->setAttribute('href', $file_path);
11072
                                        $my_dep->setAttribute('xml:base', '');
11073
11074
                                        if (strstr($file_path, $main_path) !== false) {
11075
                                            // The calculated real path is really inside the chamilo root path.
11076
                                            // Reduce file path to what's under the DocumentRoot.
11077
                                            $file_path = substr($file_path, strlen($root_path));
11078
                                            $zip_files_abs[] = $file_path;
11079
                                            $link_updates[$my_file_path][] = [
11080
                                                'orig' => $doc_info[0],
11081
                                                'dest' => $file_path,
11082
                                            ];
11083
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11084
                                            $my_dep->setAttribute('xml:base', '');
11085
                                        } elseif (empty($file_path)) {
11086
                                            $docSysPartPath = str_replace(
11087
                                                api_get_path(REL_COURSE_PATH),
11088
                                                '',
11089
                                                $doc_info[0]
11090
                                            );
11091
11092
                                            $docSysPartPathNoCourseCode = str_replace(
11093
                                                $_course['directory'].'/',
11094
                                                '',
11095
                                                $docSysPartPath
11096
                                            );
11097
11098
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11099
                                            if (file_exists($docSysPath)) {
11100
                                                $file_path = $docSysPartPathNoCourseCode;
11101
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11102
                                                $link_updates[$my_file_path][] = [
11103
                                                    'orig' => $doc_info[0],
11104
                                                    'dest' => $file_path,
11105
                                                ];
11106
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11107
                                                $my_dep->setAttribute('xml:base', '');
11108
                                            }
11109
                                        }
11110
                                        break;
11111
                                    case 'rel':
11112
                                        // Path relative to the current document. Save xml:base as current document's
11113
                                        // directory and save file in zip as subdir.file_path
11114
                                        if (substr($doc_info[0], 0, 2) === '..') {
11115
                                            // Relative path going up.
11116
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11117
                                            $current_dir = str_replace('\\', '/', $current_dir);
11118
                                            $file_path = realpath($current_dir.$doc_info[0]);
11119
                                            $file_path = str_replace('\\', '/', $file_path);
11120
                                            if (strstr($file_path, $main_path) !== false) {
11121
                                                // The calculated real path is really inside Chamilo's root path.
11122
                                                // Reduce file path to what's under the DocumentRoot.
11123
11124
                                                $file_path = substr($file_path, strlen($root_path));
11125
                                                $file_path_dest = $file_path;
11126
11127
                                                // File path is courses/CHAMILO/document/....
11128
                                                $info_file_path = explode('/', $file_path);
11129
                                                if ($info_file_path[0] == 'courses') {
11130
                                                    // Add character "/" in file path.
11131
                                                    $file_path_dest = 'document/'.$file_path;
11132
                                                }
11133
                                                $zip_files_abs[] = $file_path;
11134
11135
                                                $link_updates[$my_file_path][] = [
11136
                                                    'orig' => $doc_info[0],
11137
                                                    'dest' => $file_path_dest,
11138
                                                ];
11139
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11140
                                                $my_dep->setAttribute('xml:base', '');
11141
                                            }
11142
                                        } else {
11143
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11144
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11145
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11146
                                        }
11147
                                        break;
11148
                                    default:
11149
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11150
                                        $my_dep->setAttribute('xml:base', '');
11151
                                        break;
11152
                                }
11153
                            }
11154
                            $my_dep->appendChild($my_dep_file);
11155
                            $resources->appendChild($my_dep);
11156
                            $dependency = $xmldoc->createElement('dependency');
11157
                            $dependency->setAttribute('identifierref', $res_id);
11158
                            $my_resource->appendChild($dependency);
11159
                            $i++;
11160
                        }
11161
                        $resources->appendChild($my_resource);
11162
                        $zip_files[] = $my_file_path;
11163
                        break;
11164
                    default:
11165
                        // Get the path of the file(s) from the course directory root
11166
                        $my_file_path = 'non_exportable.html';
11167
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11168
                        $my_xml_file_path = $my_file_path;
11169
                        $my_sub_dir = dirname($my_file_path);
11170
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11171
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11172
                        $my_xml_sub_dir = $my_sub_dir;
11173
                        // Give a <resource> child to the <resources> element.
11174
                        $my_resource = $xmldoc->createElement('resource');
11175
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11176
                        $my_resource->setAttribute('type', 'webcontent');
11177
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11178
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11179
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11180
                        // xml:base is the base directory to find the files declared in this resource.
11181
                        $my_resource->setAttribute('xml:base', '');
11182
                        // Give a <file> child to the <resource> element.
11183
                        $my_file = $xmldoc->createElement('file');
11184
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11185
                        $my_resource->appendChild($my_file);
11186
                        $resources->appendChild($my_resource);
11187
                        break;
11188
                }
11189
            }
11190
        }
11191
        $organizations->appendChild($organization);
11192
        $root->appendChild($organizations);
11193
        $root->appendChild($resources);
11194
        $xmldoc->appendChild($root);
11195
11196
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11197
11198
        // then add the file to the zip, then destroy the file (this is done automatically).
11199
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11200
        foreach ($zip_files as $file_path) {
11201
            if (empty($file_path)) {
11202
                continue;
11203
            }
11204
11205
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11206
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11207
11208
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11209
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11210
            }
11211
11212
            $this->create_path($dest_file);
11213
            @copy($filePath, $dest_file);
11214
11215
            // Check if the file needs a link update.
11216
            if (in_array($file_path, array_keys($link_updates))) {
11217
                $string = file_get_contents($dest_file);
11218
                unlink($dest_file);
11219
                foreach ($link_updates[$file_path] as $old_new) {
11220
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11221
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11222
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11223
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11224
                    if (substr($old_new['dest'], -3) === 'flv' &&
11225
                        substr($old_new['dest'], 0, 5) === 'main/'
11226
                    ) {
11227
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11228
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11229
                        substr($old_new['dest'], 0, 6) === 'video/'
11230
                    ) {
11231
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11232
                    }
11233
11234
                    // Fix to avoid problems with default_course_document
11235
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11236
                        $newDestination = $old_new['dest'];
11237
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11238
                            $newDestination = $old_new['replace'];
11239
                        }
11240
                    } else {
11241
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11242
                    }
11243
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11244
11245
                    // Add files inside the HTMLs
11246
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11247
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11248
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11249
                        copy(
11250
                            $sys_course_path.$new_path,
11251
                            $destinationFile
11252
                        );
11253
                    }
11254
                }
11255
                file_put_contents($dest_file, $string);
11256
            }
11257
11258
            if (file_exists($filePath) && $copyAll) {
11259
                $extension = $this->get_extension($filePath);
11260
                if (in_array($extension, ['html', 'html'])) {
11261
                    $containerOrigin = dirname($filePath);
11262
                    $containerDestination = dirname($dest_file);
11263
11264
                    $finder = new Finder();
11265
                    $finder->files()->in($containerOrigin)
11266
                        ->notName('*_DELETED_*')
11267
                        ->exclude('share_folder')
11268
                        ->exclude('chat_files')
11269
                        ->exclude('certificates')
11270
                    ;
11271
11272
                    if (is_dir($containerOrigin) &&
11273
                        is_dir($containerDestination)
11274
                    ) {
11275
                        $fs = new Filesystem();
11276
                        $fs->mirror(
11277
                            $containerOrigin,
11278
                            $containerDestination,
11279
                            $finder
11280
                        );
11281
                    }
11282
                }
11283
            }
11284
        }
11285
11286
        foreach ($zip_files_abs as $file_path) {
11287
            if (empty($file_path)) {
11288
                continue;
11289
            }
11290
11291
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11292
                continue;
11293
            }
11294
11295
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11296
            if (strstr($file_path, 'upload/users') !== false) {
11297
                $pos = strpos($file_path, 'my_files/');
11298
                if ($pos !== false) {
11299
                    $onlyDirectory = str_replace(
11300
                        'upload/users/',
11301
                        '',
11302
                        substr($file_path, $pos, strlen($file_path))
11303
                    );
11304
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11305
                }
11306
            }
11307
11308
            if (strstr($file_path, 'default_course_document/') !== false) {
11309
                $replace = str_replace('/main', '', $file_path);
11310
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11311
            }
11312
11313
            if (empty($dest_file)) {
11314
                continue;
11315
            }
11316
11317
            $this->create_path($dest_file);
11318
            copy($main_path.$file_path, $dest_file);
11319
            // Check if the file needs a link update.
11320
            if (in_array($file_path, array_keys($link_updates))) {
11321
                $string = file_get_contents($dest_file);
11322
                unlink($dest_file);
11323
                foreach ($link_updates[$file_path] as $old_new) {
11324
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11325
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11326
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11327
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11328
                    if (substr($old_new['dest'], -3) == 'flv' &&
11329
                        substr($old_new['dest'], 0, 5) == 'main/'
11330
                    ) {
11331
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11332
                    }
11333
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11334
                }
11335
                file_put_contents($dest_file, $string);
11336
            }
11337
        }
11338
11339
        if (is_array($links_to_create)) {
11340
            foreach ($links_to_create as $file => $link) {
11341
                $content = '<!DOCTYPE html><head>
11342
                            <meta charset="'.api_get_language_isocode().'" />
11343
                            <title>'.$link['title'].'</title>
11344
                            </head>
11345
                            <body dir="'.api_get_text_direction().'">
11346
                            <div style="text-align:center">
11347
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11348
                            </body>
11349
                            </html>';
11350
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11351
            }
11352
        }
11353
11354
        // Add non exportable message explanation.
11355
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
11356
        $file_content = '<!DOCTYPE html><head>
11357
                        <meta charset="'.api_get_language_isocode().'" />
11358
                        <title>'.$lang_not_exportable.'</title>
11359
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11360
                        </head>
11361
                        <body dir="'.api_get_text_direction().'">';
11362
        $file_content .=
11363
            <<<EOD
11364
                    <style>
11365
            .error-message {
11366
                font-family: arial, verdana, helvetica, sans-serif;
11367
                border-width: 1px;
11368
                border-style: solid;
11369
                left: 50%;
11370
                margin: 10px auto;
11371
                min-height: 30px;
11372
                padding: 5px;
11373
                right: 50%;
11374
                width: 500px;
11375
                background-color: #FFD1D1;
11376
                border-color: #FF0000;
11377
                color: #000;
11378
            }
11379
        </style>
11380
    <body>
11381
        <div class="error-message">
11382
            $lang_not_exportable
11383
        </div>
11384
    </body>
11385
</html>
11386
EOD;
11387
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11388
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11389
        }
11390
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11391
11392
        // Add the extra files that go along with a SCORM package.
11393
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11394
11395
        $fs = new Filesystem();
11396
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11397
11398
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11399
        $manifest = @$xmldoc->saveXML();
11400
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11401
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11402
        $zip_folder->add(
11403
            $archivePath.'/'.$temp_dir_short,
11404
            PCLZIP_OPT_REMOVE_PATH,
11405
            $archivePath.'/'.$temp_dir_short.'/'
11406
        );
11407
11408
        // Clean possible temporary files.
11409
        foreach ($files_cleanup as $file) {
11410
            $res = unlink($file);
11411
            if ($res === false) {
11412
                error_log(
11413
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11414
                    0
11415
                );
11416
            }
11417
        }
11418
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11419
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11420
    }
11421
11422
    /**
11423
     * @param int $lp_id
11424
     *
11425
     * @return bool
11426
     */
11427
    public function scorm_export_to_pdf($lp_id)
11428
    {
11429
        $lp_id = (int) $lp_id;
11430
        $files_to_export = [];
11431
11432
        $sessionId = api_get_session_id();
11433
        $course_data = api_get_course_info($this->cc);
11434
11435
        if (!empty($course_data)) {
11436
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11437
            $list = self::get_flat_ordered_items_list($lp_id);
11438
            if (!empty($list)) {
11439
                foreach ($list as $item_id) {
11440
                    $item = $this->items[$item_id];
11441
                    switch ($item->type) {
11442
                        case 'document':
11443
                            // Getting documents from a LP with chamilo documents
11444
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11445
                            // Try loading document from the base course.
11446
                            if (empty($file_data) && !empty($sessionId)) {
11447
                                $file_data = DocumentManager::get_document_data_by_id(
11448
                                    $item->path,
11449
                                    $this->cc,
11450
                                    false,
11451
                                    0
11452
                                );
11453
                            }
11454
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11455
                            if (file_exists($file_path)) {
11456
                                $files_to_export[] = [
11457
                                    'title' => $item->get_title(),
11458
                                    'path' => $file_path,
11459
                                ];
11460
                            }
11461
                            break;
11462
                        case 'asset': //commes from a scorm package generated by chamilo
11463
                        case 'sco':
11464
                            $file_path = $scorm_path.'/'.$item->path;
11465
                            if (file_exists($file_path)) {
11466
                                $files_to_export[] = [
11467
                                    'title' => $item->get_title(),
11468
                                    'path' => $file_path,
11469
                                ];
11470
                            }
11471
                            break;
11472
                        case 'dir':
11473
                            $files_to_export[] = [
11474
                                'title' => $item->get_title(),
11475
                                'path' => null,
11476
                            ];
11477
                            break;
11478
                    }
11479
                }
11480
            }
11481
11482
            $pdf = new PDF();
11483
            $result = $pdf->html_to_pdf(
11484
                $files_to_export,
11485
                $this->name,
11486
                $this->cc,
11487
                true,
11488
                true,
11489
                true,
11490
                $this->get_name()
11491
            );
11492
11493
            return $result;
11494
        }
11495
11496
        return false;
11497
    }
11498
11499
    /**
11500
     * Temp function to be moved in main_api or the best place around for this.
11501
     * Creates a file path if it doesn't exist.
11502
     *
11503
     * @param string $path
11504
     */
11505
    public function create_path($path)
11506
    {
11507
        $path_bits = explode('/', dirname($path));
11508
11509
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11510
        $path_built = IS_WINDOWS_OS ? '' : '/';
11511
        foreach ($path_bits as $bit) {
11512
            if (!empty($bit)) {
11513
                $new_path = $path_built.$bit;
11514
                if (is_dir($new_path)) {
11515
                    $path_built = $new_path.'/';
11516
                } else {
11517
                    mkdir($new_path, api_get_permissions_for_new_directories());
11518
                    $path_built = $new_path.'/';
11519
                }
11520
            }
11521
        }
11522
    }
11523
11524
    /**
11525
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11526
     *
11527
     * @return bool The results of the unlink function, or false if there was no image to start with
11528
     */
11529
    public function delete_lp_image()
11530
    {
11531
        $img = $this->get_preview_image();
11532
        if ($img != '') {
11533
            $del_file = $this->get_preview_image_path(null, 'sys');
11534
            if (isset($del_file) && file_exists($del_file)) {
11535
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11536
                if (file_exists($del_file_2)) {
11537
                    unlink($del_file_2);
11538
                }
11539
                $this->set_preview_image('');
11540
11541
                return @unlink($del_file);
11542
            }
11543
        }
11544
11545
        return false;
11546
    }
11547
11548
    /**
11549
     * Uploads an author image to the upload/learning_path/images directory.
11550
     *
11551
     * @param array    The image array, coming from the $_FILES superglobal
11552
     *
11553
     * @return bool True on success, false on error
11554
     */
11555
    public function upload_image($image_array)
11556
    {
11557
        if (!empty($image_array['name'])) {
11558
            $upload_ok = process_uploaded_file($image_array);
11559
            $has_attachment = true;
11560
        }
11561
11562
        if ($upload_ok && $has_attachment) {
11563
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11564
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11565
            $updir = $sys_course_path.$courseDir;
11566
            // Try to add an extension to the file if it hasn't one.
11567
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11568
11569
            if (filter_extension($new_file_name)) {
11570
                $file_extension = explode('.', $image_array['name']);
11571
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11572
                $filename = uniqid('');
11573
                $new_file_name = $filename.'.'.$file_extension;
11574
                $new_path = $updir.'/'.$new_file_name;
11575
11576
                // Resize the image.
11577
                $temp = new Image($image_array['tmp_name']);
11578
                $temp->resize(104);
11579
                $result = $temp->send_image($new_path);
11580
11581
                // Storing the image filename.
11582
                if ($result) {
11583
                    $this->set_preview_image($new_file_name);
11584
11585
                    //Resize to 64px to use on course homepage
11586
                    $temp->resize(64);
11587
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11588
11589
                    return true;
11590
                }
11591
            }
11592
        }
11593
11594
        return false;
11595
    }
11596
11597
    /**
11598
     * @param int    $lp_id
11599
     * @param string $status
11600
     */
11601
    public function set_autolaunch($lp_id, $status)
11602
    {
11603
        $course_id = api_get_course_int_id();
11604
        $lp_id = (int) $lp_id;
11605
        $status = (int) $status;
11606
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11607
11608
        // Setting everything to autolaunch = 0
11609
        $attributes['autolaunch'] = 0;
11610
        $where = [
11611
            'session_id = ? AND c_id = ? ' => [
11612
                api_get_session_id(),
11613
                $course_id,
11614
            ],
11615
        ];
11616
        Database::update($lp_table, $attributes, $where);
11617
        if ($status == 1) {
11618
            //Setting my lp_id to autolaunch = 1
11619
            $attributes['autolaunch'] = 1;
11620
            $where = [
11621
                'iid = ? AND session_id = ? AND c_id = ?' => [
11622
                    $lp_id,
11623
                    api_get_session_id(),
11624
                    $course_id,
11625
                ],
11626
            ];
11627
            Database::update($lp_table, $attributes, $where);
11628
        }
11629
    }
11630
11631
    /**
11632
     * Gets previous_item_id for the next element of the lp_item table.
11633
     *
11634
     * @author Isaac flores paz
11635
     *
11636
     * @return int Previous item ID
11637
     */
11638
    public function select_previous_item_id()
11639
    {
11640
        $course_id = api_get_course_int_id();
11641
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11642
11643
        // Get the max order of the items
11644
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11645
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11646
        $rs_max_order = Database::query($sql);
11647
        $row_max_order = Database::fetch_object($rs_max_order);
11648
        $max_order = $row_max_order->display_order;
11649
        // Get the previous item ID
11650
        $sql = "SELECT iid as previous FROM $table_lp_item
11651
                WHERE 
11652
                    c_id = $course_id AND 
11653
                    lp_id = ".$this->lp_id." AND 
11654
                    display_order = '$max_order' ";
11655
        $rs_max = Database::query($sql);
11656
        $row_max = Database::fetch_object($rs_max);
11657
11658
        // Return the previous item ID
11659
        return $row_max->previous;
11660
    }
11661
11662
    /**
11663
     * Copies an LP.
11664
     */
11665
    public function copy()
11666
    {
11667
        // Course builder
11668
        $cb = new CourseBuilder();
11669
11670
        //Setting tools that will be copied
11671
        $cb->set_tools_to_build(['learnpaths']);
11672
11673
        //Setting elements that will be copied
11674
        $cb->set_tools_specific_id_list(
11675
            ['learnpaths' => [$this->lp_id]]
11676
        );
11677
11678
        $course = $cb->build();
11679
11680
        //Course restorer
11681
        $course_restorer = new CourseRestorer($course);
11682
        $course_restorer->set_add_text_in_items(true);
11683
        $course_restorer->set_tool_copy_settings(
11684
            ['learnpaths' => ['reset_dates' => true]]
11685
        );
11686
        $course_restorer->restore(
11687
            api_get_course_id(),
11688
            api_get_session_id(),
11689
            false,
11690
            false
11691
        );
11692
    }
11693
11694
    /**
11695
     * Verify document size.
11696
     *
11697
     * @param string $s
11698
     *
11699
     * @return bool
11700
     */
11701
    public static function verify_document_size($s)
11702
    {
11703
        $post_max = ini_get('post_max_size');
11704
        if (substr($post_max, -1, 1) == 'M') {
11705
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11706
        } elseif (substr($post_max, -1, 1) == 'G') {
11707
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11708
        }
11709
        $upl_max = ini_get('upload_max_filesize');
11710
        if (substr($upl_max, -1, 1) == 'M') {
11711
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11712
        } elseif (substr($upl_max, -1, 1) == 'G') {
11713
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11714
        }
11715
11716
        $repo = Container::getDocumentRepository();
11717
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11718
11719
        $course_max_space = DocumentManager::get_course_quota();
11720
        $total_size = filesize($s) + $documents_total_space;
11721
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11722
            return true;
11723
        }
11724
11725
        return false;
11726
    }
11727
11728
    /**
11729
     * Clear LP prerequisites.
11730
     */
11731
    public function clear_prerequisites()
11732
    {
11733
        $course_id = $this->get_course_int_id();
11734
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11735
        $lp_id = $this->get_id();
11736
        // Cleaning prerequisites
11737
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11738
                WHERE c_id = $course_id AND lp_id = $lp_id";
11739
        Database::query($sql);
11740
11741
        // Cleaning mastery score for exercises
11742
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11743
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11744
        Database::query($sql);
11745
    }
11746
11747
    public function set_previous_step_as_prerequisite_for_all_items()
11748
    {
11749
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11750
        $course_id = $this->get_course_int_id();
11751
        $lp_id = $this->get_id();
11752
11753
        if (!empty($this->items)) {
11754
            $previous_item_id = null;
11755
            $previous_item_max = 0;
11756
            $previous_item_type = null;
11757
            $last_item_not_dir = null;
11758
            $last_item_not_dir_type = null;
11759
            $last_item_not_dir_max = null;
11760
11761
            foreach ($this->ordered_items as $itemId) {
11762
                $item = $this->getItem($itemId);
11763
                // if there was a previous item... (otherwise jump to set it)
11764
                if (!empty($previous_item_id)) {
11765
                    $current_item_id = $item->get_id(); //save current id
11766
                    if ($item->get_type() != 'dir') {
11767
                        // Current item is not a folder, so it qualifies to get a prerequisites
11768
                        if ($last_item_not_dir_type == 'quiz') {
11769
                            // if previous is quiz, mark its max score as default score to be achieved
11770
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11771
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11772
                            Database::query($sql);
11773
                        }
11774
                        // now simply update the prerequisite to set it to the last non-chapter item
11775
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11776
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11777
                        Database::query($sql);
11778
                        // record item as 'non-chapter' reference
11779
                        $last_item_not_dir = $item->get_id();
11780
                        $last_item_not_dir_type = $item->get_type();
11781
                        $last_item_not_dir_max = $item->get_max();
11782
                    }
11783
                } else {
11784
                    if ($item->get_type() != 'dir') {
11785
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11786
                        $last_item_not_dir = $item->get_id();
11787
                        $last_item_not_dir_type = $item->get_type();
11788
                        $last_item_not_dir_max = $item->get_max();
11789
                    }
11790
                }
11791
                // Saving the item as "previous item" for the next loop
11792
                $previous_item_id = $item->get_id();
11793
                $previous_item_max = $item->get_max();
11794
                $previous_item_type = $item->get_type();
11795
            }
11796
        }
11797
    }
11798
11799
    /**
11800
     * @param array $params
11801
     *
11802
     * @throws \Doctrine\ORM\OptimisticLockException
11803
     *
11804
     * @return int
11805
     */
11806
    public static function createCategory($params)
11807
    {
11808
        $em = Database::getManager();
11809
        $item = new CLpCategory();
11810
        $item->setName($params['name']);
11811
        $item->setCId($params['c_id']);
11812
        $em->persist($item);
11813
        $em->flush();
11814
11815
        api_item_property_update(
11816
            api_get_course_info(),
11817
            TOOL_LEARNPATH_CATEGORY,
11818
            $item->getId(),
11819
            'visible',
11820
            api_get_user_id()
11821
        );
11822
11823
        return $item->getId();
11824
    }
11825
11826
    /**
11827
     * @param array $params
11828
     *
11829
     * @throws \Doctrine\ORM\ORMException
11830
     * @throws \Doctrine\ORM\OptimisticLockException
11831
     * @throws \Doctrine\ORM\TransactionRequiredException
11832
     */
11833
    public static function updateCategory($params)
11834
    {
11835
        $em = Database::getManager();
11836
        /** @var CLpCategory $item */
11837
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11838
        if ($item) {
11839
            $item->setName($params['name']);
11840
            $em->merge($item);
11841
            $em->flush();
11842
        }
11843
    }
11844
11845
    /**
11846
     * @param int $id
11847
     *
11848
     * @throws \Doctrine\ORM\ORMException
11849
     * @throws \Doctrine\ORM\OptimisticLockException
11850
     * @throws \Doctrine\ORM\TransactionRequiredException
11851
     */
11852
    public static function moveUpCategory($id)
11853
    {
11854
        $id = (int) $id;
11855
        $em = Database::getManager();
11856
        /** @var CLpCategory $item */
11857
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11858
        if ($item) {
11859
            $position = $item->getPosition() - 1;
11860
            $item->setPosition($position);
11861
            $em->persist($item);
11862
            $em->flush();
11863
        }
11864
    }
11865
11866
    /**
11867
     * @param int $id
11868
     *
11869
     * @throws \Doctrine\ORM\ORMException
11870
     * @throws \Doctrine\ORM\OptimisticLockException
11871
     * @throws \Doctrine\ORM\TransactionRequiredException
11872
     */
11873
    public static function moveDownCategory($id)
11874
    {
11875
        $id = (int) $id;
11876
        $em = Database::getManager();
11877
        /** @var CLpCategory $item */
11878
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11879
        if ($item) {
11880
            $position = $item->getPosition() + 1;
11881
            $item->setPosition($position);
11882
            $em->persist($item);
11883
            $em->flush();
11884
        }
11885
    }
11886
11887
    /**
11888
     * @param int $courseId
11889
     *
11890
     * @throws \Doctrine\ORM\Query\QueryException
11891
     *
11892
     * @return int|mixed
11893
     */
11894
    public static function getCountCategories($courseId)
11895
    {
11896
        if (empty($courseId)) {
11897
            return 0;
11898
        }
11899
        $em = Database::getManager();
11900
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11901
        $query->setParameter('id', $courseId);
11902
11903
        return $query->getSingleScalarResult();
11904
    }
11905
11906
    /**
11907
     * @param int $courseId
11908
     *
11909
     * @return mixed
11910
     */
11911
    public static function getCategories($courseId)
11912
    {
11913
        $em = Database::getManager();
11914
11915
        // Using doctrine extensions
11916
        /** @var SortableRepository $repo */
11917
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11918
        $items = $repo
11919
            ->getBySortableGroupsQuery(['cId' => $courseId])
11920
            ->getResult();
11921
11922
        return $items;
11923
    }
11924
11925
    /**
11926
     * @param int $id
11927
     *
11928
     * @throws \Doctrine\ORM\ORMException
11929
     * @throws \Doctrine\ORM\OptimisticLockException
11930
     * @throws \Doctrine\ORM\TransactionRequiredException
11931
     *
11932
     * @return CLpCategory
11933
     */
11934
    public static function getCategory($id)
11935
    {
11936
        $id = (int) $id;
11937
        $em = Database::getManager();
11938
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11939
11940
        return $item;
11941
    }
11942
11943
    /**
11944
     * @param int $courseId
11945
     *
11946
     * @return array
11947
     */
11948
    public static function getCategoryByCourse($courseId)
11949
    {
11950
        $em = Database::getManager();
11951
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11952
            ['cId' => $courseId]
11953
        );
11954
11955
        return $items;
11956
    }
11957
11958
    /**
11959
     * @param int $id
11960
     *
11961
     * @throws \Doctrine\ORM\ORMException
11962
     * @throws \Doctrine\ORM\OptimisticLockException
11963
     * @throws \Doctrine\ORM\TransactionRequiredException
11964
     *
11965
     * @return mixed
11966
     */
11967
    public static function deleteCategory($id)
11968
    {
11969
        $em = Database::getManager();
11970
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11971
        if ($item) {
11972
            $courseId = $item->getCId();
11973
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11974
            $query->setParameter('id', $courseId);
11975
            $query->setParameter('catId', $item->getId());
11976
            $lps = $query->getResult();
11977
11978
            // Setting category = 0.
11979
            if ($lps) {
11980
                foreach ($lps as $lpItem) {
11981
                    $lpItem->setCategoryId(0);
11982
                }
11983
            }
11984
11985
            // Removing category.
11986
            $em->remove($item);
11987
            $em->flush();
11988
11989
            $courseInfo = api_get_course_info_by_id($courseId);
11990
            $sessionId = api_get_session_id();
11991
11992
            // Delete link tool
11993
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11994
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11995
            // Delete tools
11996
            $sql = "DELETE FROM $tbl_tool
11997
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11998
            Database::query($sql);
11999
12000
            return true;
12001
        }
12002
12003
        return false;
12004
    }
12005
12006
    /**
12007
     * @param int  $courseId
12008
     * @param bool $addSelectOption
12009
     *
12010
     * @return mixed
12011
     */
12012
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12013
    {
12014
        $items = self::getCategoryByCourse($courseId);
12015
        $cats = [];
12016
        if ($addSelectOption) {
12017
            $cats = [get_lang('Select a category')];
12018
        }
12019
12020
        if (!empty($items)) {
12021
            foreach ($items as $cat) {
12022
                $cats[$cat->getId()] = $cat->getName();
12023
            }
12024
        }
12025
12026
        return $cats;
12027
    }
12028
12029
    /**
12030
     * @param string $courseCode
12031
     * @param int    $lpId
12032
     * @param int    $user_id
12033
     *
12034
     * @return learnpath
12035
     */
12036
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12037
    {
12038
        $debug = 0;
12039
        $learnPath = null;
12040
        $lpObject = Session::read('lpobject');
12041
        if ($lpObject !== null) {
12042
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12043
            if ($debug) {
12044
                error_log('getLpFromSession: unserialize');
12045
                error_log('------getLpFromSession------');
12046
                error_log('------unserialize------');
12047
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12048
                error_log("api_get_sessionid: ".api_get_session_id());
12049
            }
12050
        }
12051
12052
        if (!is_object($learnPath)) {
12053
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12054
            if ($debug) {
12055
                error_log('------getLpFromSession------');
12056
                error_log('getLpFromSession: create new learnpath');
12057
                error_log("create new LP with $courseCode - $lpId - $user_id");
12058
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12059
                error_log("api_get_sessionid: ".api_get_session_id());
12060
            }
12061
        }
12062
12063
        return $learnPath;
12064
    }
12065
12066
    /**
12067
     * @param int $itemId
12068
     *
12069
     * @return learnpathItem|false
12070
     */
12071
    public function getItem($itemId)
12072
    {
12073
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12074
            return $this->items[$itemId];
12075
        }
12076
12077
        return false;
12078
    }
12079
12080
    /**
12081
     * @return int
12082
     */
12083
    public function getCurrentAttempt()
12084
    {
12085
        $attempt = $this->getItem($this->get_current_item_id());
12086
        if ($attempt) {
12087
            $attemptId = $attempt->get_attempt_id();
12088
12089
            return $attemptId;
12090
        }
12091
12092
        return 0;
12093
    }
12094
12095
    /**
12096
     * @return int
12097
     */
12098
    public function getCategoryId()
12099
    {
12100
        return (int) $this->categoryId;
12101
    }
12102
12103
    /**
12104
     * @param int $categoryId
12105
     *
12106
     * @return bool
12107
     */
12108
    public function setCategoryId($categoryId)
12109
    {
12110
        $this->categoryId = (int) $categoryId;
12111
        $table = Database::get_course_table(TABLE_LP_MAIN);
12112
        $lp_id = $this->get_id();
12113
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12114
                WHERE iid = $lp_id";
12115
        Database::query($sql);
12116
12117
        return true;
12118
    }
12119
12120
    /**
12121
     * Get whether this is a learning path with the possibility to subscribe
12122
     * users or not.
12123
     *
12124
     * @return int
12125
     */
12126
    public function getSubscribeUsers()
12127
    {
12128
        return $this->subscribeUsers;
12129
    }
12130
12131
    /**
12132
     * Set whether this is a learning path with the possibility to subscribe
12133
     * users or not.
12134
     *
12135
     * @param int $value (0 = false, 1 = true)
12136
     *
12137
     * @return bool
12138
     */
12139
    public function setSubscribeUsers($value)
12140
    {
12141
        $this->subscribeUsers = (int) $value;
12142
        $table = Database::get_course_table(TABLE_LP_MAIN);
12143
        $lp_id = $this->get_id();
12144
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12145
                WHERE iid = $lp_id";
12146
        Database::query($sql);
12147
12148
        return true;
12149
    }
12150
12151
    /**
12152
     * Calculate the count of stars for a user in this LP
12153
     * This calculation is based on the following rules:
12154
     * - the student gets one star when he gets to 50% of the learning path
12155
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12156
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12157
     * - the student gets the final star when the score for the *last* test is >= 80%.
12158
     *
12159
     * @param int $sessionId Optional. The session ID
12160
     *
12161
     * @return int The count of stars
12162
     */
12163
    public function getCalculateStars($sessionId = 0)
12164
    {
12165
        $stars = 0;
12166
        $progress = self::getProgress(
12167
            $this->lp_id,
12168
            $this->user_id,
12169
            $this->course_int_id,
12170
            $sessionId
12171
        );
12172
12173
        if ($progress >= 50) {
12174
            $stars++;
12175
        }
12176
12177
        // Calculate stars chapters evaluation
12178
        $exercisesItems = $this->getExercisesItems();
12179
12180
        if (!empty($exercisesItems)) {
12181
            $totalResult = 0;
12182
12183
            foreach ($exercisesItems as $exerciseItem) {
12184
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12185
                    $this->user_id,
12186
                    $exerciseItem->path,
12187
                    $this->course_int_id,
12188
                    $sessionId,
12189
                    $this->lp_id,
12190
                    $exerciseItem->db_id
12191
                );
12192
12193
                $exerciseResultInfo = end($exerciseResultInfo);
12194
12195
                if (!$exerciseResultInfo) {
12196
                    continue;
12197
                }
12198
12199
                if (!empty($exerciseResultInfo['max_score'])) {
12200
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12201
                } else {
12202
                    $exerciseResult = 0;
12203
                }
12204
                $totalResult += $exerciseResult;
12205
            }
12206
12207
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12208
12209
            if ($totalExerciseAverage >= 50) {
12210
                $stars++;
12211
            }
12212
12213
            if ($totalExerciseAverage >= 80) {
12214
                $stars++;
12215
            }
12216
        }
12217
12218
        // Calculate star for final evaluation
12219
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12220
12221
        if (!empty($finalEvaluationItem)) {
12222
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12223
                $this->user_id,
12224
                $finalEvaluationItem->path,
12225
                $this->course_int_id,
12226
                $sessionId,
12227
                $this->lp_id,
12228
                $finalEvaluationItem->db_id
12229
            );
12230
12231
            $evaluationResultInfo = end($evaluationResultInfo);
12232
12233
            if ($evaluationResultInfo) {
12234
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12235
12236
                if ($evaluationResult >= 80) {
12237
                    $stars++;
12238
                }
12239
            }
12240
        }
12241
12242
        return $stars;
12243
    }
12244
12245
    /**
12246
     * Get the items of exercise type.
12247
     *
12248
     * @return array The items. Otherwise return false
12249
     */
12250
    public function getExercisesItems()
12251
    {
12252
        $exercises = [];
12253
        foreach ($this->items as $item) {
12254
            if ($item->type != 'quiz') {
12255
                continue;
12256
            }
12257
            $exercises[] = $item;
12258
        }
12259
12260
        array_pop($exercises);
12261
12262
        return $exercises;
12263
    }
12264
12265
    /**
12266
     * Get the item of exercise type (evaluation type).
12267
     *
12268
     * @return array The final evaluation. Otherwise return false
12269
     */
12270
    public function getFinalEvaluationItem()
12271
    {
12272
        $exercises = [];
12273
        foreach ($this->items as $item) {
12274
            if ($item->type != 'quiz') {
12275
                continue;
12276
            }
12277
12278
            $exercises[] = $item;
12279
        }
12280
12281
        return array_pop($exercises);
12282
    }
12283
12284
    /**
12285
     * Calculate the total points achieved for the current user in this learning path.
12286
     *
12287
     * @param int $sessionId Optional. The session Id
12288
     *
12289
     * @return int
12290
     */
12291
    public function getCalculateScore($sessionId = 0)
12292
    {
12293
        // Calculate stars chapters evaluation
12294
        $exercisesItems = $this->getExercisesItems();
12295
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12296
        $totalExercisesResult = 0;
12297
        $totalEvaluationResult = 0;
12298
12299
        if ($exercisesItems !== false) {
12300
            foreach ($exercisesItems as $exerciseItem) {
12301
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12302
                    $this->user_id,
12303
                    $exerciseItem->path,
12304
                    $this->course_int_id,
12305
                    $sessionId,
12306
                    $this->lp_id,
12307
                    $exerciseItem->db_id
12308
                );
12309
12310
                $exerciseResultInfo = end($exerciseResultInfo);
12311
12312
                if (!$exerciseResultInfo) {
12313
                    continue;
12314
                }
12315
12316
                $totalExercisesResult += $exerciseResultInfo['score'];
12317
            }
12318
        }
12319
12320
        if (!empty($finalEvaluationItem)) {
12321
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12322
                $this->user_id,
12323
                $finalEvaluationItem->path,
12324
                $this->course_int_id,
12325
                $sessionId,
12326
                $this->lp_id,
12327
                $finalEvaluationItem->db_id
12328
            );
12329
12330
            $evaluationResultInfo = end($evaluationResultInfo);
12331
12332
            if ($evaluationResultInfo) {
12333
                $totalEvaluationResult += $evaluationResultInfo['score'];
12334
            }
12335
        }
12336
12337
        return $totalExercisesResult + $totalEvaluationResult;
12338
    }
12339
12340
    /**
12341
     * Check if URL is not allowed to be show in a iframe.
12342
     *
12343
     * @param string $src
12344
     *
12345
     * @return string
12346
     */
12347
    public function fixBlockedLinks($src)
12348
    {
12349
        $urlInfo = parse_url($src);
12350
12351
        $platformProtocol = 'https';
12352
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12353
            $platformProtocol = 'http';
12354
        }
12355
12356
        $protocolFixApplied = false;
12357
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12358
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12359
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12360
12361
        if ($platformProtocol != $scheme) {
12362
            Session::write('x_frame_source', $src);
12363
            $src = 'blank.php?error=x_frames_options';
12364
            $protocolFixApplied = true;
12365
        }
12366
12367
        if ($protocolFixApplied == false) {
12368
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12369
                // Check X-Frame-Options
12370
                $ch = curl_init();
12371
                $options = [
12372
                    CURLOPT_URL => $src,
12373
                    CURLOPT_RETURNTRANSFER => true,
12374
                    CURLOPT_HEADER => true,
12375
                    CURLOPT_FOLLOWLOCATION => true,
12376
                    CURLOPT_ENCODING => "",
12377
                    CURLOPT_AUTOREFERER => true,
12378
                    CURLOPT_CONNECTTIMEOUT => 120,
12379
                    CURLOPT_TIMEOUT => 120,
12380
                    CURLOPT_MAXREDIRS => 10,
12381
                ];
12382
12383
                $proxySettings = api_get_configuration_value('proxy_settings');
12384
                if (!empty($proxySettings) &&
12385
                    isset($proxySettings['curl_setopt_array'])
12386
                ) {
12387
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12388
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12389
                }
12390
12391
                curl_setopt_array($ch, $options);
12392
                $response = curl_exec($ch);
12393
                $httpCode = curl_getinfo($ch);
12394
                $headers = substr($response, 0, $httpCode['header_size']);
12395
12396
                $error = false;
12397
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12398
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12399
                ) {
12400
                    $error = true;
12401
                }
12402
12403
                if ($error) {
12404
                    Session::write('x_frame_source', $src);
12405
                    $src = 'blank.php?error=x_frames_options';
12406
                }
12407
            }
12408
        }
12409
12410
        return $src;
12411
    }
12412
12413
    /**
12414
     * Check if this LP has a created forum in the basis course.
12415
     *
12416
     * @return bool
12417
     */
12418
    public function lpHasForum()
12419
    {
12420
        $forumTable = Database::get_course_table(TABLE_FORUM);
12421
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12422
12423
        $fakeFrom = "
12424
            $forumTable f
12425
            INNER JOIN $itemProperty ip
12426
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12427
        ";
12428
12429
        $resultData = Database::select(
12430
            'COUNT(f.iid) AS qty',
12431
            $fakeFrom,
12432
            [
12433
                'where' => [
12434
                    'ip.visibility != ? AND ' => 2,
12435
                    'ip.tool = ? AND ' => TOOL_FORUM,
12436
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12437
                    'f.lp_id = ?' => intval($this->lp_id),
12438
                ],
12439
            ],
12440
            'first'
12441
        );
12442
12443
        return $resultData['qty'] > 0;
12444
    }
12445
12446
    /**
12447
     * Get the forum for this learning path.
12448
     *
12449
     * @param int $sessionId
12450
     *
12451
     * @return bool
12452
     */
12453
    public function getForum($sessionId = 0)
12454
    {
12455
        $forumTable = Database::get_course_table(TABLE_FORUM);
12456
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12457
12458
        $fakeFrom = "$forumTable f
12459
            INNER JOIN $itemProperty ip ";
12460
12461
        if ($this->lp_session_id == 0) {
12462
            $fakeFrom .= "
12463
                ON (
12464
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12465
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12466
                    )
12467
                )
12468
            ";
12469
        } else {
12470
            $fakeFrom .= "
12471
                ON (
12472
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12473
                )
12474
            ";
12475
        }
12476
12477
        $resultData = Database::select(
12478
            'f.*',
12479
            $fakeFrom,
12480
            [
12481
                'where' => [
12482
                    'ip.visibility != ? AND ' => 2,
12483
                    'ip.tool = ? AND ' => TOOL_FORUM,
12484
                    'f.session_id = ? AND ' => $sessionId,
12485
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12486
                    'f.lp_id = ?' => intval($this->lp_id),
12487
                ],
12488
            ],
12489
            'first'
12490
        );
12491
12492
        if (empty($resultData)) {
12493
            return false;
12494
        }
12495
12496
        return $resultData;
12497
    }
12498
12499
    /**
12500
     * Create a forum for this learning path.
12501
     *
12502
     * @param int $forumCategoryId
12503
     *
12504
     * @return int The forum ID if was created. Otherwise return false
12505
     */
12506
    public function createForum($forumCategoryId)
12507
    {
12508
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12509
12510
        $forumId = store_forum(
12511
            [
12512
                'lp_id' => $this->lp_id,
12513
                'forum_title' => $this->name,
12514
                'forum_comment' => null,
12515
                'forum_category' => (int) $forumCategoryId,
12516
                'students_can_edit_group' => ['students_can_edit' => 0],
12517
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12518
                'default_view_type_group' => ['default_view_type' => 'flat'],
12519
                'group_forum' => 0,
12520
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12521
            ],
12522
            [],
12523
            true
12524
        );
12525
12526
        return $forumId;
12527
    }
12528
12529
    /**
12530
     * Get the LP Final Item form.
12531
     *
12532
     * @throws Exception
12533
     * @throws HTML_QuickForm_Error
12534
     *
12535
     * @return string
12536
     */
12537
    public function getFinalItemForm()
12538
    {
12539
        $finalItem = $this->getFinalItem();
12540
        $title = '';
12541
12542
        if ($finalItem) {
12543
            $title = $finalItem->get_title();
12544
            $buttonText = get_lang('Save');
12545
            $content = $this->getSavedFinalItem();
12546
        } else {
12547
            $buttonText = get_lang('Add this document to the course');
12548
            $content = $this->getFinalItemTemplate();
12549
        }
12550
12551
        $courseInfo = api_get_course_info();
12552
        $result = $this->generate_lp_folder($courseInfo);
12553
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12554
        $relative_prefix = '../../';
12555
12556
        $editorConfig = [
12557
            'ToolbarSet' => 'LearningPathDocuments',
12558
            'Width' => '100%',
12559
            'Height' => '500',
12560
            'FullPage' => true,
12561
            'CreateDocumentDir' => $relative_prefix,
12562
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12563
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12564
        ];
12565
12566
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12567
            'type' => 'document',
12568
            'lp_id' => $this->lp_id,
12569
        ]);
12570
12571
        $form = new FormValidator('final_item', 'POST', $url);
12572
        $form->addText('title', get_lang('Title'));
12573
        $form->addButtonSave($buttonText);
12574
        $form->addHtml(
12575
            Display::return_message(
12576
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12577
                'normal',
12578
                false
12579
            )
12580
        );
12581
12582
        $renderer = $form->defaultRenderer();
12583
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12584
12585
        $form->addHtmlEditor(
12586
            'content_lp_certificate',
12587
            null,
12588
            true,
12589
            false,
12590
            $editorConfig,
12591
            true
12592
        );
12593
        $form->addHidden('action', 'add_final_item');
12594
        $form->addHidden('path', Session::read('pathItem'));
12595
        $form->addHidden('previous', $this->get_last());
12596
        $form->setDefaults(
12597
            ['title' => $title, 'content_lp_certificate' => $content]
12598
        );
12599
12600
        if ($form->validate()) {
12601
            $values = $form->exportValues();
12602
            $lastItemId = $this->getLastInFirstLevel();
12603
12604
            if (!$finalItem) {
12605
                $documentId = $this->create_document(
12606
                    $this->course_info,
12607
                    $values['content_lp_certificate'],
12608
                    $values['title']
12609
                );
12610
                $this->add_item(
12611
                    0,
12612
                    $lastItemId,
12613
                    'final_item',
12614
                    $documentId,
12615
                    $values['title'],
12616
                    ''
12617
                );
12618
12619
                Display::addFlash(
12620
                    Display::return_message(get_lang('Added'))
12621
                );
12622
            } else {
12623
                $this->edit_document($this->course_info);
12624
            }
12625
        }
12626
12627
        return $form->returnForm();
12628
    }
12629
12630
    /**
12631
     * Check if the current lp item is first, both, last or none from lp list.
12632
     *
12633
     * @param int $currentItemId
12634
     *
12635
     * @return string
12636
     */
12637
    public function isFirstOrLastItem($currentItemId)
12638
    {
12639
        $lpItemId = [];
12640
        $typeListNotToVerify = self::getChapterTypes();
12641
12642
        // Using get_toc() function instead $this->items because returns the correct order of the items
12643
        foreach ($this->get_toc() as $item) {
12644
            if (!in_array($item['type'], $typeListNotToVerify)) {
12645
                $lpItemId[] = $item['id'];
12646
            }
12647
        }
12648
12649
        $lastLpItemIndex = count($lpItemId) - 1;
12650
        $position = array_search($currentItemId, $lpItemId);
12651
12652
        switch ($position) {
12653
            case 0:
12654
                if (!$lastLpItemIndex) {
12655
                    $answer = 'both';
12656
                    break;
12657
                }
12658
12659
                $answer = 'first';
12660
                break;
12661
            case $lastLpItemIndex:
12662
                $answer = 'last';
12663
                break;
12664
            default:
12665
                $answer = 'none';
12666
        }
12667
12668
        return $answer;
12669
    }
12670
12671
    /**
12672
     * Get whether this is a learning path with the accumulated SCORM time or not.
12673
     *
12674
     * @return int
12675
     */
12676
    public function getAccumulateScormTime()
12677
    {
12678
        return $this->accumulateScormTime;
12679
    }
12680
12681
    /**
12682
     * Set whether this is a learning path with the accumulated SCORM time or not.
12683
     *
12684
     * @param int $value (0 = false, 1 = true)
12685
     *
12686
     * @return bool Always returns true
12687
     */
12688
    public function setAccumulateScormTime($value)
12689
    {
12690
        $this->accumulateScormTime = (int) $value;
12691
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12692
        $lp_id = $this->get_id();
12693
        $sql = "UPDATE $lp_table 
12694
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12695
                WHERE iid = $lp_id";
12696
        Database::query($sql);
12697
12698
        return true;
12699
    }
12700
12701
    /**
12702
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12703
     * the new learning path tool.
12704
     *
12705
     * The function is a big switch on tool type.
12706
     * In each case, we query the corresponding table for information and build the link
12707
     * with that information.
12708
     *
12709
     * @author Yannick Warnier <[email protected]> - rebranding based on
12710
     * previous work (display_addedresource_link_in_learnpath())
12711
     *
12712
     * @param int $course_id      Course code
12713
     * @param int $learningPathId The learning path ID (in lp table)
12714
     * @param int $id_in_path     the unique index in the items table
12715
     * @param int $lpViewId
12716
     *
12717
     * @return string
12718
     */
12719
    public static function rl_get_resource_link_for_learnpath(
12720
        $course_id,
12721
        $learningPathId,
12722
        $id_in_path,
12723
        $lpViewId
12724
    ) {
12725
        $session_id = api_get_session_id();
12726
        $course_info = api_get_course_info_by_id($course_id);
12727
12728
        $learningPathId = (int) $learningPathId;
12729
        $id_in_path = (int) $id_in_path;
12730
        $lpViewId = (int) $lpViewId;
12731
12732
        $em = Database::getManager();
12733
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12734
12735
        /** @var CLpItem $rowItem */
12736
        $rowItem = $lpItemRepo->findOneBy([
12737
            'cId' => $course_id,
12738
            'lpId' => $learningPathId,
12739
            'iid' => $id_in_path,
12740
        ]);
12741
12742
        if (!$rowItem) {
12743
            // Try one more time with "id"
12744
            /** @var CLpItem $rowItem */
12745
            $rowItem = $lpItemRepo->findOneBy([
12746
                'cId' => $course_id,
12747
                'lpId' => $learningPathId,
12748
                'id' => $id_in_path,
12749
            ]);
12750
12751
            if (!$rowItem) {
12752
                return -1;
12753
            }
12754
        }
12755
12756
        $course_code = $course_info['code'];
12757
        $type = $rowItem->getItemType();
12758
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12759
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12760
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12761
        $link = '';
12762
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12763
12764
        switch ($type) {
12765
            case 'dir':
12766
                return $main_dir_path.'lp/blank.php';
12767
            case TOOL_CALENDAR_EVENT:
12768
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12769
            case TOOL_ANNOUNCEMENT:
12770
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12771
            case TOOL_LINK:
12772
                $linkInfo = Link::getLinkInfo($id);
12773
                if (isset($linkInfo['url'])) {
12774
                    return $linkInfo['url'];
12775
                }
12776
12777
                return '';
12778
            case TOOL_QUIZ:
12779
                if (empty($id)) {
12780
                    return '';
12781
                }
12782
12783
                // Get the lp_item_view with the highest view_count.
12784
                $learnpathItemViewResult = $em
12785
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12786
                    ->findBy(
12787
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12788
                        ['viewCount' => 'DESC'],
12789
                        1
12790
                    );
12791
                /** @var CLpItemView $learnpathItemViewData */
12792
                $learnpathItemViewData = current($learnpathItemViewResult);
12793
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12794
12795
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12796
                    .http_build_query([
12797
                        'lp_init' => 1,
12798
                        'learnpath_item_view_id' => $learnpathItemViewId,
12799
                        'learnpath_id' => $learningPathId,
12800
                        'learnpath_item_id' => $id_in_path,
12801
                        'exerciseId' => $id,
12802
                    ]);
12803
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12804
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12805
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12806
                $myrow = Database::fetch_array($result);
12807
                $path = $myrow['path'];
12808
12809
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12810
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12811
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
12812
            case TOOL_FORUM:
12813
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12814
            case TOOL_THREAD:
12815
                // forum post
12816
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12817
                if (empty($id)) {
12818
                    return '';
12819
                }
12820
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12821
                $result = Database::query($sql);
12822
                $myrow = Database::fetch_array($result);
12823
12824
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12825
                    .$extraParams;
12826
            case TOOL_POST:
12827
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12828
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12829
                $myrow = Database::fetch_array($result);
12830
12831
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12832
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12833
            case TOOL_READOUT_TEXT:
12834
                return api_get_path(WEB_CODE_PATH).
12835
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12836
            case TOOL_DOCUMENT:
12837
                $document = $em
12838
                    ->getRepository('ChamiloCourseBundle:CDocument')
12839
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
12840
12841
                if (empty($document)) {
12842
                    // Try with normal id
12843
                    $document = $em
12844
                        ->getRepository('ChamiloCourseBundle:CDocument')
12845
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
12846
12847
                    if (empty($document)) {
12848
                        return '';
12849
                    }
12850
                }
12851
12852
                $documentPathInfo = pathinfo($document->getPath());
12853
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
12854
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12855
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12856
12857
                $openmethod = 2;
12858
                $officedoc = false;
12859
                Session::write('openmethod', $openmethod);
12860
                Session::write('officedoc', $officedoc);
12861
12862
                if ($showDirectUrl) {
12863
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12864
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12865
                        if (Link::isPdfLink($file)) {
12866
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12867
12868
                            return $pdfUrl;
12869
                        }
12870
                    }
12871
12872
                    return $file;
12873
                }
12874
12875
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12876
            case TOOL_LP_FINAL_ITEM:
12877
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12878
                    .$extraParams;
12879
            case 'assignments':
12880
                return $main_dir_path.'work/work.php?'.$extraParams;
12881
            case TOOL_DROPBOX:
12882
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12883
            case 'introduction_text': //DEPRECATED
12884
                return '';
12885
            case TOOL_COURSE_DESCRIPTION:
12886
                return $main_dir_path.'course_description?'.$extraParams;
12887
            case TOOL_GROUP:
12888
                return $main_dir_path.'group/group.php?'.$extraParams;
12889
            case TOOL_USER:
12890
                return $main_dir_path.'user/user.php?'.$extraParams;
12891
            case TOOL_STUDENTPUBLICATION:
12892
                if (!empty($rowItem->getPath())) {
12893
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12894
                }
12895
12896
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12897
        }
12898
12899
        return $link;
12900
    }
12901
12902
    /**
12903
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12904
     *
12905
     * @author Yannick Warnier <[email protected]>
12906
     *
12907
     * @param string $course_code    Course code
12908
     * @param int    $learningPathId
12909
     * @param int    $id_in_path     The resource ID
12910
     *
12911
     * @return string
12912
     */
12913
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12914
    {
12915
        $_course = api_get_course_info($course_code);
12916
        if (empty($_course)) {
12917
            return '';
12918
        }
12919
        $course_id = $_course['real_id'];
12920
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12921
        $learningPathId = (int) $learningPathId;
12922
        $id_in_path = (int) $id_in_path;
12923
12924
        $sql = "SELECT item_type, title, ref 
12925
                FROM $tbl_lp_item
12926
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12927
        $res_item = Database::query($sql);
12928
12929
        if (Database::num_rows($res_item) < 1) {
12930
            return '';
12931
        }
12932
        $row_item = Database::fetch_array($res_item);
12933
        $type = strtolower($row_item['item_type']);
12934
        $id = $row_item['ref'];
12935
        $output = '';
12936
12937
        switch ($type) {
12938
            case TOOL_CALENDAR_EVENT:
12939
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12940
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12941
                $myrow = Database::fetch_array($result);
12942
                $output = $myrow['title'];
12943
                break;
12944
            case TOOL_ANNOUNCEMENT:
12945
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12946
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12947
                $myrow = Database::fetch_array($result);
12948
                $output = $myrow['title'];
12949
                break;
12950
            case TOOL_LINK:
12951
                // Doesn't take $target into account.
12952
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12953
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12954
                $myrow = Database::fetch_array($result);
12955
                $output = $myrow['title'];
12956
                break;
12957
            case TOOL_QUIZ:
12958
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12959
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12960
                $myrow = Database::fetch_array($result);
12961
                $output = $myrow['title'];
12962
                break;
12963
            case TOOL_FORUM:
12964
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12965
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12966
                $myrow = Database::fetch_array($result);
12967
                $output = $myrow['forum_name'];
12968
                break;
12969
            case TOOL_THREAD:
12970
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12971
                // Grabbing the title of the post.
12972
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12973
                $result_title = Database::query($sql_title);
12974
                $myrow_title = Database::fetch_array($result_title);
12975
                $output = $myrow_title['post_title'];
12976
                break;
12977
            case TOOL_POST:
12978
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12979
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12980
                $result = Database::query($sql);
12981
                $post = Database::fetch_array($result);
12982
                $output = $post['post_title'];
12983
                break;
12984
            case 'dir':
12985
            case TOOL_DOCUMENT:
12986
                $title = $row_item['title'];
12987
                $output = '-';
12988
                if (!empty($title)) {
12989
                    $output = $title;
12990
                }
12991
                break;
12992
            case 'hotpotatoes':
12993
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12994
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12995
                $myrow = Database::fetch_array($result);
12996
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12997
                $last = count($pathname) - 1; // Making a correct name for the link.
12998
                $filename = $pathname[$last]; // Making a correct name for the link.
12999
                $myrow['path'] = rawurlencode($myrow['path']);
13000
                $output = $filename;
13001
                break;
13002
        }
13003
13004
        return stripslashes($output);
13005
    }
13006
13007
    /**
13008
     * Get the parent names for the current item.
13009
     *
13010
     * @param int $newItemId Optional. The item ID
13011
     *
13012
     * @return array
13013
     */
13014
    public function getCurrentItemParentNames($newItemId = 0)
13015
    {
13016
        $newItemId = $newItemId ?: $this->get_current_item_id();
13017
        $return = [];
13018
        $item = $this->getItem($newItemId);
13019
        $parent = $this->getItem($item->get_parent());
13020
13021
        while ($parent) {
13022
            $return[] = $parent->get_title();
13023
            $parent = $this->getItem($parent->get_parent());
13024
        }
13025
13026
        return array_reverse($return);
13027
    }
13028
13029
    /**
13030
     * Reads and process "lp_subscription_settings" setting.
13031
     *
13032
     * @return array
13033
     */
13034
    public static function getSubscriptionSettings()
13035
    {
13036
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13037
        if (empty($subscriptionSettings)) {
13038
            // By default allow both settings
13039
            $subscriptionSettings = [
13040
                'allow_add_users_to_lp' => true,
13041
                'allow_add_users_to_lp_category' => true,
13042
            ];
13043
        } else {
13044
            $subscriptionSettings = $subscriptionSettings['options'];
13045
        }
13046
13047
        return $subscriptionSettings;
13048
    }
13049
13050
    /**
13051
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13052
     */
13053
    public function exportToCourseBuildFormat()
13054
    {
13055
        if (!api_is_allowed_to_edit()) {
13056
            return false;
13057
        }
13058
13059
        $courseBuilder = new CourseBuilder();
13060
        $itemList = [];
13061
        /** @var learnpathItem $item */
13062
        foreach ($this->items as $item) {
13063
            $itemList[$item->get_type()][] = $item->get_path();
13064
        }
13065
13066
        if (empty($itemList)) {
13067
            return false;
13068
        }
13069
13070
        if (isset($itemList['document'])) {
13071
            // Get parents
13072
            foreach ($itemList['document'] as $documentId) {
13073
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13074
                if (!empty($documentInfo['parents'])) {
13075
                    foreach ($documentInfo['parents'] as $parentInfo) {
13076
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13077
                            continue;
13078
                        }
13079
                        $itemList['document'][] = $parentInfo['iid'];
13080
                    }
13081
                }
13082
            }
13083
13084
            $courseInfo = api_get_course_info();
13085
            foreach ($itemList['document'] as $documentId) {
13086
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13087
                $items = DocumentManager::get_resources_from_source_html(
13088
                    $documentInfo['absolute_path'],
13089
                    true,
13090
                    TOOL_DOCUMENT
13091
                );
13092
13093
                if (!empty($items)) {
13094
                    foreach ($items as $item) {
13095
                        // Get information about source url
13096
                        $url = $item[0]; // url
13097
                        $scope = $item[1]; // scope (local, remote)
13098
                        $type = $item[2]; // type (rel, abs, url)
13099
13100
                        $origParseUrl = parse_url($url);
13101
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13102
13103
                        if ($scope == 'local') {
13104
                            if ($type == 'abs' || $type == 'rel') {
13105
                                $documentFile = strstr($realOrigPath, 'document');
13106
                                if (strpos($realOrigPath, $documentFile) !== false) {
13107
                                    $documentFile = str_replace('document', '', $documentFile);
13108
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13109
                                    // Document found! Add it to the list
13110
                                    if ($itemDocumentId) {
13111
                                        $itemList['document'][] = $itemDocumentId;
13112
                                    }
13113
                                }
13114
                            }
13115
                        }
13116
                    }
13117
                }
13118
            }
13119
13120
            $courseBuilder->build_documents(
13121
                api_get_session_id(),
13122
                $this->get_course_int_id(),
13123
                true,
13124
                $itemList['document']
13125
            );
13126
        }
13127
13128
        if (isset($itemList['quiz'])) {
13129
            $courseBuilder->build_quizzes(
13130
                api_get_session_id(),
13131
                $this->get_course_int_id(),
13132
                true,
13133
                $itemList['quiz']
13134
            );
13135
        }
13136
13137
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13138
13139
        /*if (!empty($itemList['thread'])) {
13140
            $postList = [];
13141
            foreach ($itemList['thread'] as $postId) {
13142
                $post = get_post_information($postId);
13143
                if ($post) {
13144
                    if (!isset($itemList['forum'])) {
13145
                        $itemList['forum'] = [];
13146
                    }
13147
                    $itemList['forum'][] = $post['forum_id'];
13148
                    $postList[] = $postId;
13149
                }
13150
            }
13151
13152
            if (!empty($postList)) {
13153
                $courseBuilder->build_forum_posts(
13154
                    $this->get_course_int_id(),
13155
                    null,
13156
                    null,
13157
                    $postList
13158
                );
13159
            }
13160
        }*/
13161
13162
        if (!empty($itemList['thread'])) {
13163
            $threadList = [];
13164
            $em = Database::getManager();
13165
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13166
            foreach ($itemList['thread'] as $threadId) {
13167
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13168
                $thread = $repo->find($threadId);
13169
                if ($thread) {
13170
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13171
                    $threadList[] = $thread->getIid();
13172
                }
13173
            }
13174
13175
            if (!empty($threadList)) {
13176
                $courseBuilder->build_forum_topics(
13177
                    api_get_session_id(),
13178
                    $this->get_course_int_id(),
13179
                    null,
13180
                    $threadList
13181
                );
13182
            }
13183
        }
13184
13185
        $forumCategoryList = [];
13186
        if (isset($itemList['forum'])) {
13187
            foreach ($itemList['forum'] as $forumId) {
13188
                $forumInfo = get_forums($forumId);
13189
                $forumCategoryList[] = $forumInfo['forum_category'];
13190
            }
13191
        }
13192
13193
        if (!empty($forumCategoryList)) {
13194
            $courseBuilder->build_forum_category(
13195
                api_get_session_id(),
13196
                $this->get_course_int_id(),
13197
                true,
13198
                $forumCategoryList
13199
            );
13200
        }
13201
13202
        if (!empty($itemList['forum'])) {
13203
            $courseBuilder->build_forums(
13204
                api_get_session_id(),
13205
                $this->get_course_int_id(),
13206
                true,
13207
                $itemList['forum']
13208
            );
13209
        }
13210
13211
        if (isset($itemList['link'])) {
13212
            $courseBuilder->build_links(
13213
                api_get_session_id(),
13214
                $this->get_course_int_id(),
13215
                true,
13216
                $itemList['link']
13217
            );
13218
        }
13219
13220
        if (!empty($itemList['student_publication'])) {
13221
            $courseBuilder->build_works(
13222
                api_get_session_id(),
13223
                $this->get_course_int_id(),
13224
                true,
13225
                $itemList['student_publication']
13226
            );
13227
        }
13228
13229
        $courseBuilder->build_learnpaths(
13230
            api_get_session_id(),
13231
            $this->get_course_int_id(),
13232
            true,
13233
            [$this->get_id()],
13234
            false
13235
        );
13236
13237
        $courseBuilder->restoreDocumentsFromList();
13238
13239
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13240
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13241
        $result = DocumentManager::file_send_for_download(
13242
            $zipPath,
13243
            true,
13244
            $this->get_name().'.zip'
13245
        );
13246
13247
        if ($result) {
13248
            api_not_allowed();
13249
        }
13250
13251
        return true;
13252
    }
13253
13254
    /**
13255
     * Get whether this is a learning path with the accumulated work time or not.
13256
     *
13257
     * @return int
13258
     */
13259
    public function getAccumulateWorkTime()
13260
    {
13261
        return (int) $this->accumulateWorkTime;
13262
    }
13263
13264
    /**
13265
     * Get whether this is a learning path with the accumulated work time or not.
13266
     *
13267
     * @return int
13268
     */
13269
    public function getAccumulateWorkTimeTotalCourse()
13270
    {
13271
        $table = Database::get_course_table(TABLE_LP_MAIN);
13272
        $sql = "SELECT SUM(accumulate_work_time) AS total 
13273
                FROM $table 
13274
                WHERE c_id = ".$this->course_int_id;
13275
        $result = Database::query($sql);
13276
        $row = Database::fetch_array($result);
13277
13278
        return (int) $row['total'];
13279
    }
13280
13281
    /**
13282
     * Set whether this is a learning path with the accumulated work time or not.
13283
     *
13284
     * @param int $value (0 = false, 1 = true)
13285
     *
13286
     * @return bool
13287
     */
13288
    public function setAccumulateWorkTime($value)
13289
    {
13290
        if (!api_get_configuration_value('lp_minimum_time')) {
13291
            return false;
13292
        }
13293
13294
        $this->accumulateWorkTime = (int) $value;
13295
        $table = Database::get_course_table(TABLE_LP_MAIN);
13296
        $lp_id = $this->get_id();
13297
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13298
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13299
        Database::query($sql);
13300
13301
        return true;
13302
    }
13303
13304
    /**
13305
     * @param int $lpId
13306
     * @param int $courseId
13307
     *
13308
     * @return mixed
13309
     */
13310
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13311
    {
13312
        $lpId = (int) $lpId;
13313
        $courseId = (int) $courseId;
13314
13315
        $table = Database::get_course_table(TABLE_LP_MAIN);
13316
        $sql = "SELECT accumulate_work_time 
13317
                FROM $table 
13318
                WHERE c_id = $courseId AND id = $lpId";
13319
        $result = Database::query($sql);
13320
        $row = Database::fetch_array($result);
13321
13322
        return $row['accumulate_work_time'];
13323
    }
13324
13325
    /**
13326
     * @param int $courseId
13327
     *
13328
     * @return int
13329
     */
13330
    public static function getAccumulateWorkTimeTotal($courseId)
13331
    {
13332
        $table = Database::get_course_table(TABLE_LP_MAIN);
13333
        $courseId = (int) $courseId;
13334
        $sql = "SELECT SUM(accumulate_work_time) AS total
13335
                FROM $table
13336
                WHERE c_id = $courseId";
13337
        $result = Database::query($sql);
13338
        $row = Database::fetch_array($result);
13339
13340
        return (int) $row['total'];
13341
    }
13342
13343
    /**
13344
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13345
     * and put the images in.
13346
     *
13347
     * @return array
13348
     */
13349
    public static function getIconSelect()
13350
    {
13351
        $theme = api_get_visual_theme();
13352
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13353
        $icons = ['' => get_lang('Please select an option')];
13354
13355
        if (is_dir($path)) {
13356
            $finder = new Finder();
13357
            $finder->files()->in($path);
13358
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13359
            /** @var SplFileInfo $file */
13360
            foreach ($finder as $file) {
13361
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13362
                    $icons[$file->getFilename()] = $file->getFilename();
13363
                }
13364
            }
13365
        }
13366
13367
        return $icons;
13368
    }
13369
13370
    /**
13371
     * @param int $lpId
13372
     *
13373
     * @return string
13374
     */
13375
    public static function getSelectedIcon($lpId)
13376
    {
13377
        $extraFieldValue = new ExtraFieldValue('lp');
13378
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13379
        $icon = '';
13380
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13381
            $icon = $lpIcon['value'];
13382
        }
13383
13384
        return $icon;
13385
    }
13386
13387
    /**
13388
     * @param int $lpId
13389
     *
13390
     * @return string
13391
     */
13392
    public static function getSelectedIconHtml($lpId)
13393
    {
13394
        $icon = self::getSelectedIcon($lpId);
13395
13396
        if (empty($icon)) {
13397
            return '';
13398
        }
13399
13400
        $theme = api_get_visual_theme();
13401
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13402
13403
        return Display::img($path);
13404
    }
13405
13406
    /**
13407
     * @param string $value
13408
     *
13409
     * @return string
13410
     */
13411
    public function cleanItemTitle($value)
13412
    {
13413
        $value = Security::remove_XSS(strip_tags($value));
13414
13415
        return $value;
13416
    }
13417
13418
    public function setItemTitle(FormValidator $form)
13419
    {
13420
        if (api_get_configuration_value('save_titles_as_html')) {
13421
            $form->addHtmlEditor(
13422
                'title',
13423
                get_lang('Title'),
13424
                true,
13425
                false,
13426
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13427
            );
13428
        } else {
13429
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13430
            $form->applyFilter('title', 'trim');
13431
            $form->applyFilter('title', 'html_filter');
13432
        }
13433
    }
13434
13435
    /**
13436
     * @return array
13437
     */
13438
    public function getItemsForForm($addParentCondition = false)
13439
    {
13440
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13441
        $course_id = api_get_course_int_id();
13442
13443
        $sql = "SELECT * FROM $tbl_lp_item 
13444
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13445
13446
        if ($addParentCondition) {
13447
            $sql .= ' AND parent_item_id = 0 ';
13448
        }
13449
        $sql .= ' ORDER BY display_order ASC';
13450
13451
        $result = Database::query($sql);
13452
        $arrLP = [];
13453
        while ($row = Database::fetch_array($result)) {
13454
            $arrLP[] = [
13455
                'iid' => $row['iid'],
13456
                'id' => $row['iid'],
13457
                'item_type' => $row['item_type'],
13458
                'title' => $this->cleanItemTitle($row['title']),
13459
                'title_raw' => $row['title'],
13460
                'path' => $row['path'],
13461
                'description' => Security::remove_XSS($row['description']),
13462
                'parent_item_id' => $row['parent_item_id'],
13463
                'previous_item_id' => $row['previous_item_id'],
13464
                'next_item_id' => $row['next_item_id'],
13465
                'display_order' => $row['display_order'],
13466
                'max_score' => $row['max_score'],
13467
                'min_score' => $row['min_score'],
13468
                'mastery_score' => $row['mastery_score'],
13469
                'prerequisite' => $row['prerequisite'],
13470
                'max_time_allowed' => $row['max_time_allowed'],
13471
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13472
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13473
            ];
13474
        }
13475
13476
        return $arrLP;
13477
    }
13478
13479
    /**
13480
     * Get the depth level of LP item.
13481
     *
13482
     * @param array $items
13483
     * @param int   $currentItemId
13484
     *
13485
     * @return int
13486
     */
13487
    private static function get_level_for_item($items, $currentItemId)
13488
    {
13489
        $parentItemId = 0;
13490
        if (isset($items[$currentItemId])) {
13491
            $parentItemId = $items[$currentItemId]->parent;
13492
        }
13493
13494
        if ($parentItemId == 0) {
13495
            return 0;
13496
        } else {
13497
            return self::get_level_for_item($items, $parentItemId) + 1;
13498
        }
13499
    }
13500
13501
    /**
13502
     * Generate the link for a learnpath category as course tool.
13503
     *
13504
     * @param int $categoryId
13505
     *
13506
     * @return string
13507
     */
13508
    private static function getCategoryLinkForTool($categoryId)
13509
    {
13510
        $categoryId = (int) $categoryId;
13511
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13512
            .http_build_query(
13513
                [
13514
                    'action' => 'view_category',
13515
                    'id' => $categoryId,
13516
                ]
13517
            );
13518
13519
        return $link;
13520
    }
13521
13522
    /**
13523
     * Return the scorm item type object with spaces replaced with _
13524
     * The return result is use to build a css classname like scorm_type_$return.
13525
     *
13526
     * @param $in_type
13527
     *
13528
     * @return mixed
13529
     */
13530
    private static function format_scorm_type_item($in_type)
13531
    {
13532
        return str_replace(' ', '_', $in_type);
13533
    }
13534
13535
    /**
13536
     * Check and obtain the lp final item if exist.
13537
     *
13538
     * @return learnpathItem
13539
     */
13540
    private function getFinalItem()
13541
    {
13542
        if (empty($this->items)) {
13543
            return null;
13544
        }
13545
13546
        foreach ($this->items as $item) {
13547
            if ($item->type !== 'final_item') {
13548
                continue;
13549
            }
13550
13551
            return $item;
13552
        }
13553
    }
13554
13555
    /**
13556
     * Get the LP Final Item Template.
13557
     *
13558
     * @return string
13559
     */
13560
    private function getFinalItemTemplate()
13561
    {
13562
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13563
    }
13564
13565
    /**
13566
     * Get the LP Final Item Url.
13567
     *
13568
     * @return string
13569
     */
13570
    private function getSavedFinalItem()
13571
    {
13572
        $finalItem = $this->getFinalItem();
13573
        $doc = DocumentManager::get_document_data_by_id(
13574
            $finalItem->path,
13575
            $this->cc
13576
        );
13577
        if ($doc && file_exists($doc['absolute_path'])) {
13578
            return file_get_contents($doc['absolute_path']);
13579
        }
13580
13581
        return '';
13582
    }
13583
}
13584