learnpath::display_edit_item()   F
last analyzed

Complexity

Conditions 22
Paths 28

Size

Total Lines 137
Code Lines 112

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 112
c 0
b 0
f 0
nc 28
nop 2
dl 0
loc 137
rs 3.3333

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
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
6
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CItemProperty;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CTool;
17
use Chamilo\PluginBundle\Entity\H5pImport\H5pImport;
18
use Chamilo\PluginBundle\Entity\XApi\ToolLaunch;
19
use Chamilo\UserBundle\Entity\User;
20
use ChamiloSession as Session;
21
use Gedmo\Sortable\Entity\Repository\SortableRepository;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
25
/**
26
 * Class learnpath
27
 * This class defines the parent attributes and methods for Chamilo learnpaths
28
 * and SCORM learnpaths. It is used by the scorm class.
29
 *
30
 * @todo decouple class
31
 *
32
 * @author  Yannick Warnier <[email protected]>
33
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
34
 */
35
class learnpath
36
{
37
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
38
    public const STATUS_CSS_CLASS_NAME = [
39
        'not attempted' => 'scorm_not_attempted',
40
        'incomplete' => 'scorm_not_attempted',
41
        'failed' => 'scorm_failed',
42
        'completed' => 'scorm_completed',
43
        'passed' => 'scorm_completed',
44
        'succeeded' => 'scorm_completed',
45
        'browsed' => 'scorm_completed',
46
    ];
47
48
    public $attempt = 0; // The number for the current ID view.
49
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
50
    public $current; // Id of the current item the user is viewing.
51
    public $current_score; // The score of the current item.
52
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
53
    public $current_time_stop; // The time the user closed this resource.
54
    public $default_status = 'not attempted';
55
    public $encoding = 'UTF-8';
56
    public $error = '';
57
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
58
    public $index; // The index of the active learnpath_item in $ordered_items array.
59
    /** @var learnpathItem[] */
60
    public $items = [];
61
    public $last; // item_id of last item viewed in the learning path.
62
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
63
    public $license; // Which license this course has been given - not used yet on 20060522.
64
    public $lp_id; // DB iid for this learnpath.
65
    public $lp_view_id; // DB ID for lp_view
66
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
67
    public $message = '';
68
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
69
    public $name; // Learnpath name (they generally have one).
70
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
71
    public $path = ''; // Path inside the scorm directory (if scorm).
72
    public $theme; // The current theme of the learning path.
73
    public $preview_image; // The current image of the learning path.
74
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
75
    public $accumulateWorkTime; // The min time of learnpath
76
77
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
78
    public $prevent_reinit = 1;
79
80
    // Describes the mode of progress bar display.
81
    public $seriousgame_mode = 0;
82
    public $progress_bar_mode = '%';
83
84
    // Percentage progress as saved in the db.
85
    public $progress_db = 0;
86
    public $proximity; // Wether the content is distant or local or unknown.
87
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
88
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
89
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
90
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
91
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
92
    public $user_id; //ID of the user that is viewing/using the course
93
    public $update_queue = [];
94
    public $scorm_debug = 0;
95
    public $arrMenu = []; // Array for the menu items.
96
    public $debug = 0; // Logging level.
97
    public $lp_session_id = 0;
98
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
99
    public $prerequisite = 0;
100
    public $use_max_score = 1; // 1 or 0
101
    public $subscribeUsers = 0; // Subscribe users or not
102
    public $created_on = '';
103
    public $modified_on = '';
104
    public $publicated_on = '';
105
    public $expired_on = '';
106
    public $ref = null;
107
    public $course_int_id;
108
    public $course_info = [];
109
    public $categoryId;
110
111
    /**
112
     * Constructor.
113
     * Needs a database handler, a course code and a learnpath id from the database.
114
     * Also builds the list of items into $this->items.
115
     *
116
     * @param string $course  Course code
117
     * @param int    $lp_id   c_lp.iid
118
     * @param int    $user_id
119
     */
120
    public function __construct($course, $lp_id, $user_id)
121
    {
122
        $debug = $this->debug;
123
        $this->encoding = api_get_system_encoding();
124
        if (empty($course)) {
125
            $course = api_get_course_id();
126
        }
127
        $course_info = api_get_course_info($course);
128
        if (!empty($course_info)) {
129
            $this->cc = $course_info['code'];
130
            $this->course_info = $course_info;
131
            $course_id = $course_info['real_id'];
132
        } else {
133
            $this->error = 'Course code does not exist in database.';
134
        }
135
136
        $lp_id = (int) $lp_id;
137
        $course_id = (int) $course_id;
138
        $this->set_course_int_id($course_id);
139
        // Check learnpath ID.
140
        if (empty($lp_id) || empty($course_id)) {
141
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
142
        } else {
143
            // TODO: Make it flexible to use any course_code (still using env course code here).
144
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
145
            $sql = "SELECT * FROM $lp_table
146
                    WHERE iid = $lp_id";
147
            if ($debug) {
148
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
149
            }
150
            $res = Database::query($sql);
151
            if (Database::num_rows($res) > 0) {
152
                $this->lp_id = $lp_id;
153
                $row = Database::fetch_array($res);
154
                $this->type = $row['lp_type'];
155
                $this->name = stripslashes($row['name']);
156
                $this->proximity = $row['content_local'];
157
                $this->theme = $row['theme'];
158
                $this->maker = $row['content_maker'];
159
                $this->prevent_reinit = $row['prevent_reinit'];
160
                $this->seriousgame_mode = $row['seriousgame_mode'];
161
                $this->license = $row['content_license'];
162
                $this->scorm_debug = $row['debug'];
163
                $this->js_lib = $row['js_lib'];
164
                $this->path = $row['path'];
165
                $this->preview_image = $row['preview_image'];
166
                $this->author = $row['author'];
167
                $this->hide_toc_frame = $row['hide_toc_frame'];
168
                $this->lp_session_id = $row['session_id'];
169
                $this->use_max_score = $row['use_max_score'];
170
                $this->subscribeUsers = $row['subscribe_users'];
171
                $this->created_on = $row['created_on'];
172
                $this->modified_on = $row['modified_on'];
173
                $this->ref = $row['ref'];
174
                $this->categoryId = $row['category_id'];
175
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
176
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
177
178
                if (!empty($row['publicated_on'])) {
179
                    $this->publicated_on = $row['publicated_on'];
180
                }
181
182
                if (!empty($row['expired_on'])) {
183
                    $this->expired_on = $row['expired_on'];
184
                }
185
                if ($this->type == 2) {
186
                    if ($row['force_commit'] == 1) {
187
                        $this->force_commit = true;
188
                    }
189
                }
190
                $this->mode = $row['default_view_mod'];
191
192
                // Check user ID.
193
                if (empty($user_id)) {
194
                    $this->error = 'User ID is empty';
195
                } else {
196
                    $userInfo = api_get_user_info($user_id);
197
                    if (!empty($userInfo)) {
198
                        $this->user_id = $userInfo['user_id'];
199
                    } else {
200
                        $this->error = 'User ID does not exist in database #'.$user_id;
201
                    }
202
                }
203
204
                // End of variables checking.
205
                $session_id = api_get_session_id();
206
                //  Get the session condition for learning paths of the base + session.
207
                $session = api_get_session_condition($session_id);
208
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
209
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
210
211
                // Selecting by view_count descending allows to get the highest view_count first.
212
                $sql = "SELECT * FROM $lp_table
213
                        WHERE
214
                            c_id = $course_id AND
215
                            lp_id = $lp_id AND
216
                            user_id = $user_id
217
                            $session
218
                        ORDER BY view_count DESC";
219
                $res = Database::query($sql);
220
                if ($debug) {
221
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
222
                }
223
224
                if (Database::num_rows($res) > 0) {
225
                    if ($debug) {
226
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
227
                    }
228
                    $row = Database::fetch_array($res);
229
                    $this->attempt = $row['view_count'];
230
                    $this->lp_view_id = $row['id'];
231
                    $this->last_item_seen = $row['last_item'];
232
                    $this->progress_db = $row['progress'];
233
                    $this->lp_view_session_id = $row['session_id'];
234
                } elseif (!api_is_invitee()) {
235
                    if ($debug) {
236
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
237
                    }
238
                    $this->attempt = 1;
239
                    $params = [
240
                        'c_id' => $course_id,
241
                        'lp_id' => $lp_id,
242
                        'user_id' => $user_id,
243
                        'view_count' => 1,
244
                        'session_id' => $session_id,
245
                        'last_item' => 0,
246
                    ];
247
                    $this->last_item_seen = 0;
248
                    $this->lp_view_session_id = $session_id;
249
                    $this->lp_view_id = Database::insert($lp_table, $params);
250
                    if (!empty($this->lp_view_id)) {
251
                        $sql = "UPDATE $lp_table SET id = iid
252
                                WHERE iid = ".$this->lp_view_id;
253
                        Database::query($sql);
254
                    }
255
                }
256
257
                // Initialise items.
258
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
259
                $sql = "SELECT * FROM $lp_item_table
260
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
261
                        ORDER BY parent_item_id, display_order";
262
                $res = Database::query($sql);
263
264
                $lp_item_id_list = [];
265
                while ($row = Database::fetch_array($res)) {
266
                    $lp_item_id_list[] = $row['iid'];
267
                    switch ($this->type) {
268
                        case 3: //aicc
269
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
270
                            if (is_object($oItem)) {
271
                                $my_item_id = $oItem->get_id();
272
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
273
                                $oItem->set_prevent_reinit($this->prevent_reinit);
274
                                // Don't use reference here as the next loop will make the pointed object change.
275
                                $this->items[$my_item_id] = $oItem;
276
                                $this->refs_list[$oItem->ref] = $my_item_id;
277
                                if ($debug) {
278
                                    error_log(
279
                                        'learnpath::__construct() - '.
280
                                        'aicc object with id '.$my_item_id.
281
                                        ' set in items[]',
282
                                        0
283
                                    );
284
                                }
285
                            }
286
                            break;
287
                        case 2:
288
                            $oItem = new scormItem('db', $row['iid'], $course_id);
289
                            if (is_object($oItem)) {
290
                                $my_item_id = $oItem->get_id();
291
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
292
                                $oItem->set_prevent_reinit($this->prevent_reinit);
293
                                // Don't use reference here as the next loop will make the pointed object change.
294
                                $this->items[$my_item_id] = $oItem;
295
                                $this->refs_list[$oItem->ref] = $my_item_id;
296
                                if ($debug) {
297
                                    error_log('object with id '.$my_item_id.' set in items[]');
298
                                }
299
                            }
300
                            break;
301
                        case 1:
302
                        default:
303
                            if ($debug) {
304
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
305
                            }
306
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
307
308
                            if ($debug) {
309
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
310
                            }
311
                            if (is_object($oItem)) {
312
                                $my_item_id = $oItem->get_id();
313
                                // Moved down to when we are sure the item_view exists.
314
                                //$oItem->set_lp_view($this->lp_view_id);
315
                                $oItem->set_prevent_reinit($this->prevent_reinit);
316
                                // Don't use reference here as the next loop will make the pointed object change.
317
                                $this->items[$my_item_id] = $oItem;
318
                                $this->refs_list[$my_item_id] = $my_item_id;
319
                                if ($debug) {
320
                                    error_log(
321
                                        'learnpath::__construct() '.__LINE__.
322
                                        ' - object with id '.$my_item_id.' set in items[]'
323
                                    );
324
                                }
325
                            }
326
                            break;
327
                    }
328
329
                    // Setting the object level with variable $this->items[$i][parent]
330
                    foreach ($this->items as $itemLPObject) {
331
                        $level = self::get_level_for_item(
332
                            $this->items,
333
                            $itemLPObject->db_id
334
                        );
335
                        $itemLPObject->level = $level;
336
                    }
337
338
                    // Setting the view in the item object.
339
                    if (is_object($this->items[$row['iid']])) {
340
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
341
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
342
                            $this->items[$row['iid']]->current_start_time = 0;
343
                            $this->items[$row['iid']]->current_stop_time = 0;
344
                        }
345
                    }
346
                }
347
348
                if (!empty($lp_item_id_list)) {
349
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
350
                    if (!empty($lp_item_id_list_to_string)) {
351
                        // Get last viewing vars.
352
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
353
                        // This query should only return one or zero result.
354
                        $sql = "SELECT lp_item_id, status
355
                                FROM $itemViewTable
356
                                WHERE
357
                                    c_id = $course_id AND
358
                                    lp_view_id = ".$this->get_view_id()." AND
359
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
360
                                ORDER BY view_count DESC ";
361
                        $status_list = [];
362
                        $res = Database::query($sql);
363
                        while ($row = Database::fetch_array($res)) {
364
                            $status_list[$row['lp_item_id']] = $row['status'];
365
                        }
366
367
                        foreach ($lp_item_id_list as $item_id) {
368
                            if (isset($status_list[$item_id])) {
369
                                $status = $status_list[$item_id];
370
                                if (is_object($this->items[$item_id])) {
371
                                    $this->items[$item_id]->set_status($status);
372
                                    if (empty($status)) {
373
                                        $this->items[$item_id]->set_status(
374
                                            $this->default_status
375
                                        );
376
                                    }
377
                                }
378
                            } else {
379
                                if (!api_is_invitee()) {
380
                                    if (is_object($this->items[$item_id])) {
381
                                        $this->items[$item_id]->set_status(
382
                                            $this->default_status
383
                                        );
384
                                    }
385
386
                                    if (!empty($this->lp_view_id)) {
387
                                        // Add that row to the lp_item_view table so that
388
                                        // we have something to show in the stats page.
389
                                        $params = [
390
                                            'c_id' => $course_id,
391
                                            'lp_item_id' => $item_id,
392
                                            'lp_view_id' => $this->lp_view_id,
393
                                            'view_count' => 1,
394
                                            'status' => 'not attempted',
395
                                            'start_time' => time(),
396
                                            'total_time' => 0,
397
                                            'score' => 0,
398
                                        ];
399
                                        $insertId = Database::insert($itemViewTable, $params);
400
401
                                        if ($insertId) {
402
                                            $sql = "UPDATE $itemViewTable SET id = iid
403
                                                    WHERE iid = $insertId";
404
                                            Database::query($sql);
405
                                        }
406
407
                                        $this->items[$item_id]->set_lp_view(
408
                                            $this->lp_view_id,
409
                                            $course_id
410
                                        );
411
                                    }
412
                                }
413
                            }
414
                        }
415
                    }
416
                }
417
418
                $this->ordered_items = self::get_flat_ordered_items_list(
419
                    $this->get_id(),
420
                    0,
421
                    $course_id
422
                );
423
                $this->max_ordered_items = 0;
424
                foreach ($this->ordered_items as $index => $dummy) {
425
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
426
                        $this->max_ordered_items = $index;
427
                    }
428
                }
429
                // TODO: Define the current item better.
430
                $this->first();
431
                if ($debug) {
432
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
433
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
434
                }
435
            } else {
436
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
437
            }
438
        }
439
    }
440
441
    /**
442
     * @return string
443
     */
444
    public function getCourseCode()
445
    {
446
        return $this->cc;
447
    }
448
449
    /**
450
     * @return int
451
     */
452
    public function get_course_int_id()
453
    {
454
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
455
    }
456
457
    /**
458
     * @param $course_id
459
     *
460
     * @return int
461
     */
462
    public function set_course_int_id($course_id)
463
    {
464
        return $this->course_int_id = (int) $course_id;
465
    }
466
467
    /**
468
     * Function rewritten based on old_add_item() from Yannick Warnier.
469
     * Due the fact that users can decide where the item should come, I had to overlook this function and
470
     * I found it better to rewrite it. Old function is still available.
471
     * Added also the possibility to add a description.
472
     *
473
     * @param int    $parent
474
     * @param int    $previous
475
     * @param string $type
476
     * @param int    $id               resource ID (ref)
477
     * @param string $title
478
     * @param string $description
479
     * @param int    $prerequisites
480
     * @param int    $max_time_allowed
481
     * @param int    $userId
482
     *
483
     * @return int
484
     */
485
    public function add_item(
486
        $parent,
487
        $previous,
488
        $type,
489
        $id,
490
        $title,
491
        $description,
492
        $prerequisites = 0,
493
        $max_time_allowed = 0,
494
        $userId = 0,
495
        $dspOrder = 0
496
    ) {
497
        $course_id = $this->course_info['real_id'];
498
        if (empty($course_id)) {
499
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
500
            $this->course_info = api_get_course_info($this->cc);
501
            $course_id = $this->course_info['real_id'];
502
        }
503
        $userId = empty($userId) ? api_get_user_id() : $userId;
504
        $sessionId = api_get_session_id();
505
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
506
        $_course = $this->course_info;
507
        $parent = (int) $parent;
508
        $previous = (int) $previous;
509
        $id = (int) $id;
510
        $max_time_allowed = htmlentities($max_time_allowed);
511
        if (empty($max_time_allowed)) {
512
            $max_time_allowed = 0;
513
        }
514
        $sql = "SELECT COUNT(iid) AS num
515
                FROM $tbl_lp_item
516
                WHERE
517
                    c_id = $course_id AND
518
                    lp_id = ".$this->get_id()." AND
519
                    parent_item_id = $parent ";
520
521
        $res_count = Database::query($sql);
522
        $row = Database::fetch_array($res_count);
523
        $num = $row['num'];
524
525
        $tmp_previous = 0;
526
        $display_order = 0;
527
        $next = 0;
528
        if ($num > 0) {
529
            if (empty($previous)) {
530
                $sql = "SELECT iid, next_item_id, display_order
531
                        FROM $tbl_lp_item
532
                        WHERE
533
                            c_id = $course_id AND
534
                            lp_id = ".$this->get_id()." AND
535
                            parent_item_id = $parent AND
536
                            previous_item_id = 0 OR
537
                            previous_item_id = $parent";
538
                $result = Database::query($sql);
539
                $row = Database::fetch_array($result);
540
                if ($row) {
541
                    $next = $row['iid'];
542
                }
543
            } else {
544
                $previous = (int) $previous;
545
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
546
						FROM $tbl_lp_item
547
                        WHERE
548
                            c_id = $course_id AND
549
                            lp_id = ".$this->get_id()." AND
550
                            id = $previous";
551
                $result = Database::query($sql);
552
                $row = Database::fetch_array($result);
553
                if ($row) {
554
                    $tmp_previous = $row['iid'];
555
                    $next = $row['next_item_id'];
556
                    $display_order = $row['display_order'];
557
                }
558
            }
559
        }
560
561
        $id = (int) $id;
562
        $typeCleaned = Database::escape_string($type);
563
        $max_score = 100;
564
        if ($type === 'quiz' && $id) {
565
            $sql = 'SELECT SUM(ponderation)
566
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
567
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
568
                    ON
569
                        quiz_question.iid = quiz_rel_question.question_id
570
                    WHERE
571
                        quiz_rel_question.exercice_id = '.$id." AND
572
                        quiz_rel_question.c_id = $course_id ";
573
            $rsQuiz = Database::query($sql);
574
            $max_score = Database::result($rsQuiz, 0, 0);
575
576
            // Disabling the exercise if we add it inside a LP
577
            $exercise = new Exercise($course_id);
578
            $exercise->read($id);
579
            $exercise->disable();
580
            $exercise->save();
581
        }
582
583
        $newDisplayOrder = $display_order + 1;
584
        if (!empty($dspOrder)) {
585
            $newDisplayOrder = (int) $dspOrder;
586
        }
587
588
        $params = [
589
            'c_id' => $course_id,
590
            'lp_id' => $this->get_id(),
591
            'item_type' => $typeCleaned,
592
            'ref' => '',
593
            'title' => $title,
594
            'description' => $description,
595
            'path' => $id,
596
            'max_score' => $max_score,
597
            'parent_item_id' => $parent,
598
            'previous_item_id' => $previous,
599
            'next_item_id' => (int) $next,
600
            'display_order' => $newDisplayOrder,
601
            'prerequisite' => $prerequisites,
602
            'max_time_allowed' => $max_time_allowed,
603
            'min_score' => 0,
604
            'launch_data' => '',
605
        ];
606
607
        if ($prerequisites != 0) {
608
            $params['prerequisite'] = $prerequisites;
609
        }
610
611
        $new_item_id = Database::insert($tbl_lp_item, $params);
612
        if ($new_item_id) {
613
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
614
            Database::query($sql);
615
616
            if (!empty($next)) {
617
                $sql = "UPDATE $tbl_lp_item
618
                        SET previous_item_id = $new_item_id
619
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
620
                Database::query($sql);
621
            }
622
623
            // Update the item that should be before the new item.
624
            if (!empty($tmp_previous)) {
625
                $sql = "UPDATE $tbl_lp_item
626
                        SET next_item_id = $new_item_id
627
                        WHERE c_id = $course_id AND id = $tmp_previous";
628
                Database::query($sql);
629
            }
630
631
            // Update all the items after the new item.
632
            $sql = "UPDATE $tbl_lp_item
633
                        SET display_order = display_order + 1
634
                    WHERE
635
                        c_id = $course_id AND
636
                        lp_id = ".$this->get_id()." AND
637
                        iid <> $new_item_id AND
638
                        parent_item_id = $parent AND
639
                        display_order > $display_order";
640
            Database::query($sql);
641
642
            // Update the item that should come after the new item.
643
            $sql = "UPDATE $tbl_lp_item
644
                    SET ref = $new_item_id
645
                    WHERE c_id = $course_id AND iid = $new_item_id";
646
            Database::query($sql);
647
648
            $sql = "UPDATE $tbl_lp_item
649
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
650
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
651
            Database::query($sql);
652
653
            // Upload audio.
654
            if (!empty($_FILES['mp3']['name'])) {
655
                // Create the audio folder if it does not exist yet.
656
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
657
                if (!is_dir($filepath.'audio')) {
658
                    mkdir(
659
                        $filepath.'audio',
660
                        api_get_permissions_for_new_directories()
661
                    );
662
                    $audio_id = add_document(
663
                        $_course,
664
                        '/audio',
665
                        'folder',
666
                        0,
667
                        'audio',
668
                        '',
669
                        0,
670
                        true,
671
                        null,
672
                        $sessionId,
673
                        $userId
674
                    );
675
                    api_item_property_update(
676
                        $_course,
677
                        TOOL_DOCUMENT,
678
                        $audio_id,
679
                        'FolderCreated',
680
                        $userId,
681
                        null,
682
                        null,
683
                        null,
684
                        null,
685
                        $sessionId
686
                    );
687
                    api_item_property_update(
688
                        $_course,
689
                        TOOL_DOCUMENT,
690
                        $audio_id,
691
                        'invisible',
692
                        $userId,
693
                        null,
694
                        null,
695
                        null,
696
                        null,
697
                        $sessionId
698
                    );
699
                }
700
701
                $file_path = handle_uploaded_document(
702
                    $_course,
703
                    $_FILES['mp3'],
704
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
705
                    '/audio',
706
                    $userId,
707
                    '',
708
                    '',
709
                    '',
710
                    '',
711
                    false
712
                );
713
714
                // Store the mp3 file in the lp_item table.
715
                $sql = "UPDATE $tbl_lp_item SET
716
                          audio = '".Database::escape_string($file_path)."'
717
                        WHERE iid = '".intval($new_item_id)."'";
718
                Database::query($sql);
719
            }
720
        }
721
722
        return $new_item_id;
723
    }
724
725
    /**
726
     * Static admin function allowing addition of a learnpath to a course.
727
     *
728
     * @param string $courseCode
729
     * @param string $name
730
     * @param string $description
731
     * @param string $learnpath
732
     * @param string $origin
733
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
734
     * @param string $publicated_on
735
     * @param string $expired_on
736
     * @param int    $categoryId
737
     * @param int    $userId
738
     *
739
     * @return int The new learnpath ID on success, 0 on failure
740
     */
741
    public static function add_lp(
742
        $courseCode,
743
        $name,
744
        $description = '',
745
        $learnpath = 'guess',
746
        $origin = 'zip',
747
        $zipname = '',
748
        $publicated_on = '',
749
        $expired_on = '',
750
        $categoryId = 0,
751
        $userId = 0
752
    ) {
753
        global $charset;
754
755
        if (!empty($courseCode)) {
756
            $courseInfo = api_get_course_info($courseCode);
757
            $course_id = $courseInfo['real_id'];
758
        } else {
759
            $course_id = api_get_course_int_id();
760
            $courseInfo = api_get_course_info();
761
        }
762
763
        $em = Database::getManager();
764
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
765
        // Check course code exists.
766
        // Check lp_name doesn't exist, otherwise append something.
767
        $i = 0;
768
        $categoryId = (int) $categoryId;
769
        // Session id.
770
        $session_id = api_get_session_id();
771
        $userId = empty($userId) ? api_get_user_id() : $userId;
772
773
        if (empty($publicated_on)) {
774
            $publicated_on = null;
775
        } else {
776
            $publicated_on = api_get_utc_datetime($publicated_on, false, true);
777
        }
778
779
        if (empty($expired_on)) {
780
            $expired_on = null;
781
        } else {
782
            $expired_on = api_get_utc_datetime($expired_on, false, true);
783
        }
784
785
        $check_name = "SELECT * FROM $tbl_lp
786
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
787
        $res_name = Database::query($check_name);
788
789
        while (Database::num_rows($res_name)) {
790
            // There is already one such name, update the current one a bit.
791
            $i++;
792
            $name = $name.' - '.$i;
793
            $check_name = "SELECT * FROM $tbl_lp
794
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
795
            $res_name = Database::query($check_name);
796
        }
797
        // New name does not exist yet; keep it.
798
        // Escape description.
799
        // Kevin: added htmlentities().
800
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
801
        $type = 1;
802
        switch ($learnpath) {
803
            case 'guess':
804
                break;
805
            case 'dokeos':
806
            case 'chamilo':
807
                $type = 1;
808
                break;
809
            case 'aicc':
810
                break;
811
        }
812
813
        switch ($origin) {
814
            case 'zip':
815
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
816
                break;
817
            case 'manual':
818
            default:
819
                $get_max = "SELECT MAX(display_order)
820
                            FROM $tbl_lp WHERE c_id = $course_id";
821
                $res_max = Database::query($get_max);
822
                if (Database::num_rows($res_max) < 1) {
823
                    $dsp = 1;
824
                } else {
825
                    $row = Database::fetch_array($res_max);
826
                    $dsp = $row[0] + 1;
827
                }
828
829
                $newLp = (new CLp())
830
                    ->setCId($course_id)
831
                    ->setLpType($type)
832
                    ->setName($name)
833
                    ->setDescription($description)
834
                    ->setPath('')
835
                    ->setDefaultViewMod('embedded')
836
                    ->setDefaultEncoding('UTF-8')
837
                    ->setDisplayOrder($dsp)
838
                    ->setContentMaker('Chamilo')
839
                    ->setContentLocal('local')
840
                    ->setJsLib('')
841
                    ->setSessionId($session_id)
842
                    ->setCreatedOn(api_get_utc_datetime(null, false, true))
843
                    ->setModifiedOn(api_get_utc_datetime(null, false, true))
844
                    ->setPublicatedOn($publicated_on)
845
                    ->setExpiredOn($expired_on)
846
                    ->setCategoryId($categoryId)
847
                    ->setForceCommit(false)
848
                    ->setContentLicense('')
849
                    ->setDebug(false)
850
                    ->setTheme('')
851
                    ->setPreviewImage('')
852
                    ->setAuthor('')
853
                    ->setPrerequisite(0)
854
                    ->setHideTocFrame(false)
855
                    ->setSeriousgameMode(false)
856
                    ->setAutolaunch(0)
857
                    ->setMaxAttempts(0)
858
                    ->setSubscribeUsers(0)
859
                    ->setAccumulateScormTime(1)
860
                ;
861
862
                $em->persist($newLp);
863
                $em->flush();
864
865
                HookLearningPathCreated::create()
866
                    ->setEventData(['lp' => $newLp])
867
                    ->notifyCreated()
868
                ;
869
870
                $newLp->setId($newLp->getIid());
871
872
                $em->flush();
873
874
                // Insert into item_property.
875
                api_item_property_update(
876
                    $courseInfo,
877
                    TOOL_LEARNPATH,
878
                    $newLp->getIid(),
879
                    'LearnpathAdded',
880
                    $userId
881
                );
882
                api_set_default_visibility(
883
                    $newLp->getIid(),
884
                    TOOL_LEARNPATH,
885
                    0,
886
                    $courseInfo,
887
                    $session_id,
888
                    $userId
889
                );
890
                Event::addEvent(
891
                    LOG_LP_CREATE,
892
                    LOG_LP_ID,
893
                    $newLp->getIid().' - '.$name
894
                );
895
896
                return $newLp->getIid();
897
        }
898
    }
899
900
    /**
901
     * Auto completes the parents of an item in case it's been completed or passed.
902
     *
903
     * @param int $item Optional ID of the item from which to look for parents
904
     */
905
    public function autocomplete_parents($item)
906
    {
907
        $debug = $this->debug;
908
909
        if (empty($item)) {
910
            $item = $this->current;
911
        }
912
913
        $currentItem = $this->getItem($item);
914
        if ($currentItem) {
915
            $parent_id = $currentItem->get_parent();
916
            $parent = $this->getItem($parent_id);
917
            if ($parent) {
918
                // if $item points to an object and there is a parent.
919
                if ($debug) {
920
                    error_log(
921
                        'Autocompleting parent of item '.$item.' '.
922
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
923
                        0
924
                    );
925
                }
926
927
                // New experiment including failed and browsed in completed status.
928
                //$current_status = $currentItem->get_status();
929
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
930
                // Fixes chapter auto complete
931
                if (true) {
932
                    // If the current item is completed or passes or succeeded.
933
                    $updateParentStatus = true;
934
                    if ($debug) {
935
                        error_log('Status of current item is alright');
936
                    }
937
938
                    foreach ($parent->get_children() as $childItemId) {
939
                        $childItem = $this->getItem($childItemId);
940
941
                        // If children was not set try to get the info
942
                        if (empty($childItem->db_item_view_id)) {
943
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
944
                        }
945
946
                        // Check all his brothers (parent's children) for completion status.
947
                        if ($childItemId != $item) {
948
                            if ($debug) {
949
                                error_log(
950
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
951
                                    0
952
                                );
953
                            }
954
                            // Trying completing parents of failed and browsed items as well.
955
                            if ($childItem->status_is(
956
                                [
957
                                    'completed',
958
                                    'passed',
959
                                    'succeeded',
960
                                    'browsed',
961
                                    'failed',
962
                                ]
963
                            )
964
                            ) {
965
                                // Keep completion status to true.
966
                                continue;
967
                            } else {
968
                                if ($debug > 2) {
969
                                    error_log(
970
                                        '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,
971
                                        0
972
                                    );
973
                                }
974
                                $updateParentStatus = false;
975
                                break;
976
                            }
977
                        }
978
                    }
979
980
                    if ($updateParentStatus) {
981
                        // If all the children were completed:
982
                        $parent->set_status('completed');
983
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
984
                        // Force the status to "completed"
985
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
986
                        $this->update_queue[$parent->get_id()] = 'completed';
987
                        if ($debug) {
988
                            error_log(
989
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
990
                                print_r($this->update_queue, 1),
991
                                0
992
                            );
993
                        }
994
                        // Recursive call.
995
                        $this->autocomplete_parents($parent->get_id());
996
                    }
997
                }
998
            } else {
999
                if ($debug) {
1000
                    error_log("Parent #$parent_id does not exists");
1001
                }
1002
            }
1003
        } else {
1004
            if ($debug) {
1005
                error_log("#$item is an item that doesn't have parents");
1006
            }
1007
        }
1008
    }
1009
1010
    /**
1011
     * Closes the current resource.
1012
     *
1013
     * Stops the timer
1014
     * Saves into the database if required
1015
     * Clears the current resource data from this object
1016
     *
1017
     * @return bool True on success, false on failure
1018
     */
1019
    public function close()
1020
    {
1021
        if (empty($this->lp_id)) {
1022
            $this->error = 'Trying to close this learnpath but no ID is set';
1023
1024
            return false;
1025
        }
1026
        $this->current_time_stop = time();
1027
        $this->ordered_items = [];
1028
        $this->index = 0;
1029
        unset($this->lp_id);
1030
        //unset other stuff
1031
        return true;
1032
    }
1033
1034
    /**
1035
     * Static admin function allowing removal of a learnpath.
1036
     *
1037
     * @param array  $courseInfo
1038
     * @param int    $id         Learnpath ID
1039
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1040
     *
1041
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1042
     */
1043
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1044
    {
1045
        $course_id = api_get_course_int_id();
1046
        if (!empty($courseInfo)) {
1047
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1048
        }
1049
1050
        // TODO: Implement a way of getting this to work when the current object is not set.
1051
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1052
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1053
        if (!empty($id) && ($id != $this->lp_id)) {
1054
            return false;
1055
        }
1056
1057
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1058
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1059
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1060
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1061
1062
        // Delete lp item id.
1063
        foreach ($this->items as $lpItemId => $dummy) {
1064
            $sql = "DELETE FROM $lp_item_view
1065
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1066
            Database::query($sql);
1067
        }
1068
1069
        // Proposed by Christophe (nickname: clefevre)
1070
        $sql = "DELETE FROM $lp_item
1071
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1072
        Database::query($sql);
1073
1074
        $sql = "DELETE FROM $lp_view
1075
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1076
        Database::query($sql);
1077
1078
        self::toggle_publish($this->lp_id, 'i');
1079
1080
        if ($this->type == 2 || $this->type == 3) {
1081
            // This is a scorm learning path, delete the files as well.
1082
            $sql = "SELECT path FROM $lp
1083
                    WHERE iid = ".$this->lp_id;
1084
            $res = Database::query($sql);
1085
            if (Database::num_rows($res) > 0) {
1086
                $row = Database::fetch_array($res);
1087
                $path = $row['path'];
1088
                $sql = "SELECT id FROM $lp
1089
                        WHERE
1090
                            c_id = $course_id AND
1091
                            path = '$path' AND
1092
                            iid != ".$this->lp_id;
1093
                $res = Database::query($sql);
1094
                if (Database::num_rows($res) > 0) {
1095
                    // Another learning path uses this directory, so don't delete it.
1096
                    if ($this->debug > 2) {
1097
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1098
                    }
1099
                } else {
1100
                    // No other LP uses that directory, delete it.
1101
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1102
                    // The absolute system path for this course.
1103
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1104
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1105
                        if ($this->debug > 2) {
1106
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1107
                        }
1108
                        // Proposed by Christophe (clefevre).
1109
                        if (strcmp(substr($path, -2), "/.") == 0) {
1110
                            $path = substr($path, 0, -1); // Remove "." at the end.
1111
                        }
1112
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1113
                        rmdirr($course_scorm_dir.$path);
1114
                    }
1115
                }
1116
            }
1117
        }
1118
1119
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1120
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1121
        // Delete tools
1122
        $sql = "DELETE FROM $tbl_tool
1123
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1124
        Database::query($sql);
1125
1126
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1127
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1128
            $sql = "DELETE FROM $table
1129
                    WHERE
1130
                        lp_id = {$this->lp_id} AND
1131
                        c_id = $course_id ";
1132
            Database::query($sql);
1133
        }
1134
1135
        $sql = "DELETE FROM $lp
1136
                WHERE iid = ".$this->lp_id;
1137
        Database::query($sql);
1138
        // Updates the display order of all lps.
1139
        $this->update_display_order();
1140
1141
        // Remove prerequisite based on the deleted LP
1142
        $sql = "UPDATE $lp
1143
                SET prerequisite = 0 where c_id = $course_id AND prerequisite = ".$this->lp_id;
1144
        Database::query($sql);
1145
1146
        api_item_property_update(
1147
            api_get_course_info(),
1148
            TOOL_LEARNPATH,
1149
            $this->lp_id,
1150
            'delete',
1151
            api_get_user_id()
1152
        );
1153
        Event::addEvent(
1154
            LOG_LP_DELETE,
1155
            LOG_LP_ID,
1156
            $this->lp_id.' - '.$this->get_name()
1157
        );
1158
1159
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1160
            api_get_course_id(),
1161
            4,
1162
            $id,
1163
            api_get_session_id()
1164
        );
1165
1166
        if ($link_info !== false) {
1167
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1168
        }
1169
1170
        if (api_get_setting('search_enabled') == 'true') {
1171
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1172
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1173
        }
1174
    }
1175
1176
    /**
1177
     * Removes all the children of one item - dangerous!
1178
     *
1179
     * @param int $id Element ID of which children have to be removed
1180
     *
1181
     * @return int Total number of children removed
1182
     */
1183
    public function delete_children_items($id)
1184
    {
1185
        $course_id = $this->course_info['real_id'];
1186
1187
        $num = 0;
1188
        $id = (int) $id;
1189
        if (empty($id) || empty($course_id)) {
1190
            return false;
1191
        }
1192
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1193
        $sql = "SELECT * FROM $lp_item
1194
                WHERE c_id = $course_id AND parent_item_id = $id";
1195
        $res = Database::query($sql);
1196
        while ($row = Database::fetch_array($res)) {
1197
            $num += $this->delete_children_items($row['iid']);
1198
            $sql = "DELETE FROM $lp_item
1199
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1200
            Database::query($sql);
1201
            $num++;
1202
        }
1203
1204
        return $num;
1205
    }
1206
1207
    /**
1208
     * Removes an item from the current learnpath.
1209
     *
1210
     * @param int $id Elem ID (0 if first)
1211
     *
1212
     * @return int Number of elements moved
1213
     *
1214
     * @todo implement resource removal
1215
     */
1216
    public function delete_item($id)
1217
    {
1218
        $course_id = api_get_course_int_id();
1219
        $id = (int) $id;
1220
        // TODO: Implement the resource removal.
1221
        if (empty($id) || empty($course_id)) {
1222
            return false;
1223
        }
1224
        // First select item to get previous, next, and display order.
1225
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1226
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1227
        $res_sel = Database::query($sql_sel);
1228
        if (Database::num_rows($res_sel) < 1) {
1229
            return false;
1230
        }
1231
        $row = Database::fetch_array($res_sel);
1232
        $previous = $row['previous_item_id'];
1233
        $next = $row['next_item_id'];
1234
        $display = $row['display_order'];
1235
        $parent = $row['parent_item_id'];
1236
        $lp = $row['lp_id'];
1237
        // Delete children items.
1238
        $this->delete_children_items($id);
1239
        // Now delete the item.
1240
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1241
        Database::query($sql_del);
1242
        // Now update surrounding items.
1243
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1244
                    WHERE iid = $previous";
1245
        Database::query($sql_upd);
1246
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1247
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1248
        Database::query($sql_upd);
1249
        // Now update all following items with new display order.
1250
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1251
                    WHERE
1252
                        c_id = $course_id AND
1253
                        lp_id = $lp AND
1254
                        parent_item_id = $parent AND
1255
                        display_order > $display";
1256
        Database::query($sql_all);
1257
1258
        //Removing prerequisites since the item will not longer exist
1259
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1260
                    WHERE c_id = $course_id AND prerequisite = $id";
1261
        Database::query($sql_all);
1262
1263
        $sql = "UPDATE $lp_item
1264
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1265
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1266
        Database::query($sql);
1267
1268
        // Remove from search engine if enabled.
1269
        if (api_get_setting('search_enabled') === 'true') {
1270
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1271
            $sql = 'SELECT * FROM %s
1272
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1273
                    LIMIT 1';
1274
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1275
            $res = Database::query($sql);
1276
            if (Database::num_rows($res) > 0) {
1277
                $row2 = Database::fetch_array($res);
1278
                $di = new ChamiloIndexer();
1279
                $di->remove_document($row2['search_did']);
1280
            }
1281
            $sql = 'DELETE FROM %s
1282
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1283
                    LIMIT 1';
1284
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1285
            Database::query($sql);
1286
        }
1287
    }
1288
1289
    /**
1290
     * Updates an item's content in place.
1291
     *
1292
     * @param int    $id               Element ID
1293
     * @param int    $parent           Parent item ID
1294
     * @param int    $previous         Previous item ID
1295
     * @param string $title            Item title
1296
     * @param string $description      Item description
1297
     * @param string $prerequisites    Prerequisites (optional)
1298
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1299
     * @param int    $max_time_allowed
1300
     * @param string $url
1301
     *
1302
     * @return bool True on success, false on error
1303
     */
1304
    public function edit_item(
1305
        $id,
1306
        $parent,
1307
        $previous,
1308
        $title,
1309
        $description,
1310
        $prerequisites = '0',
1311
        $audio = [],
1312
        $max_time_allowed = 0,
1313
        $url = ''
1314
    ) {
1315
        $course_id = api_get_course_int_id();
1316
        $_course = api_get_course_info();
1317
        $id = (int) $id;
1318
1319
        if (empty($max_time_allowed)) {
1320
            $max_time_allowed = 0;
1321
        }
1322
1323
        if (empty($id) || empty($_course)) {
1324
            return false;
1325
        }
1326
1327
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1328
        $sql = "SELECT * FROM $tbl_lp_item
1329
                WHERE iid = $id";
1330
        $res_select = Database::query($sql);
1331
        $row_select = Database::fetch_array($res_select);
1332
        $audio_update_sql = '';
1333
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1334
            // Create the audio folder if it does not exist yet.
1335
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1336
            if (!is_dir($filepath.'audio')) {
1337
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1338
                $audio_id = add_document(
1339
                    $_course,
1340
                    '/audio',
1341
                    'folder',
1342
                    0,
1343
                    'audio'
1344
                );
1345
                api_item_property_update(
1346
                    $_course,
1347
                    TOOL_DOCUMENT,
1348
                    $audio_id,
1349
                    'FolderCreated',
1350
                    api_get_user_id(),
1351
                    null,
1352
                    null,
1353
                    null,
1354
                    null,
1355
                    api_get_session_id()
1356
                );
1357
                api_item_property_update(
1358
                    $_course,
1359
                    TOOL_DOCUMENT,
1360
                    $audio_id,
1361
                    'invisible',
1362
                    api_get_user_id(),
1363
                    null,
1364
                    null,
1365
                    null,
1366
                    null,
1367
                    api_get_session_id()
1368
                );
1369
            }
1370
1371
            // Upload file in documents.
1372
            $pi = pathinfo($audio['name']);
1373
            if ($pi['extension'] === 'mp3') {
1374
                $c_det = api_get_course_info($this->cc);
1375
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1376
                $path = handle_uploaded_document(
1377
                    $c_det,
1378
                    $audio,
1379
                    $bp,
1380
                    '/audio',
1381
                    api_get_user_id(),
1382
                    0,
1383
                    null,
1384
                    0,
1385
                    'rename',
1386
                    false,
1387
                    0
1388
                );
1389
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1390
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1391
            }
1392
        }
1393
1394
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1395
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1396
1397
        // TODO: htmlspecialchars to be checked for encoding related problems.
1398
        if ($same_parent && $same_previous) {
1399
            // Only update title and description.
1400
            $sql = "UPDATE $tbl_lp_item
1401
                    SET title = '".Database::escape_string($title)."',
1402
                        prerequisite = '".$prerequisites."',
1403
                        description = '".Database::escape_string($description)."'
1404
                        ".$audio_update_sql.",
1405
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1406
                    WHERE iid = $id";
1407
            Database::query($sql);
1408
        } else {
1409
            $old_parent = $row_select['parent_item_id'];
1410
            $old_previous = $row_select['previous_item_id'];
1411
            $old_next = $row_select['next_item_id'];
1412
            $old_order = $row_select['display_order'];
1413
            $old_prerequisite = $row_select['prerequisite'];
1414
            $old_max_time_allowed = $row_select['max_time_allowed'];
1415
1416
            /* BEGIN -- virtually remove the current item id */
1417
            /* for the next and previous item it is like the current item doesn't exist anymore */
1418
            if ($old_previous != 0) {
1419
                // Next
1420
                $sql = "UPDATE $tbl_lp_item
1421
                        SET next_item_id = $old_next
1422
                        WHERE iid = $old_previous";
1423
                Database::query($sql);
1424
            }
1425
1426
            if (!empty($old_next)) {
1427
                // Previous
1428
                $sql = "UPDATE $tbl_lp_item
1429
                        SET previous_item_id = $old_previous
1430
                        WHERE iid = $old_next";
1431
                Database::query($sql);
1432
            }
1433
1434
            // display_order - 1 for every item with a display_order
1435
            // bigger then the display_order of the current item.
1436
            $sql = "UPDATE $tbl_lp_item
1437
                    SET display_order = display_order - 1
1438
                    WHERE
1439
                        c_id = $course_id AND
1440
                        display_order > $old_order AND
1441
                        lp_id = ".$this->lp_id." AND
1442
                        parent_item_id = $old_parent";
1443
            Database::query($sql);
1444
            /* END -- virtually remove the current item id */
1445
1446
            /* BEGIN -- update the current item id to his new location */
1447
            if ($previous == 0) {
1448
                // Select the data of the item that should come after the current item.
1449
                $sql = "SELECT id, display_order
1450
                        FROM $tbl_lp_item
1451
                        WHERE
1452
                            c_id = $course_id AND
1453
                            lp_id = ".$this->lp_id." AND
1454
                            parent_item_id = $parent AND
1455
                            previous_item_id = $previous";
1456
                $res_select_old = Database::query($sql);
1457
                $row_select_old = Database::fetch_array($res_select_old);
1458
1459
                // If the new parent didn't have children before.
1460
                if (Database::num_rows($res_select_old) == 0) {
1461
                    $new_next = 0;
1462
                    $new_order = 1;
1463
                } else {
1464
                    $new_next = $row_select_old['id'];
1465
                    $new_order = $row_select_old['display_order'];
1466
                }
1467
            } else {
1468
                // Select the data of the item that should come before the current item.
1469
                $sql = "SELECT next_item_id, display_order
1470
                        FROM $tbl_lp_item
1471
                        WHERE iid = $previous";
1472
                $res_select_old = Database::query($sql);
1473
                $row_select_old = Database::fetch_array($res_select_old);
1474
                $new_next = $row_select_old['next_item_id'];
1475
                $new_order = $row_select_old['display_order'] + 1;
1476
            }
1477
1478
            // TODO: htmlspecialchars to be checked for encoding related problems.
1479
            // Update the current item with the new data.
1480
            $sql = "UPDATE $tbl_lp_item
1481
                    SET
1482
                        title = '".Database::escape_string($title)."',
1483
                        description = '".Database::escape_string($description)."',
1484
                        parent_item_id = $parent,
1485
                        previous_item_id = $previous,
1486
                        next_item_id = $new_next,
1487
                        display_order = $new_order
1488
                        $audio_update_sql
1489
                    WHERE iid = $id";
1490
            Database::query($sql);
1491
1492
            if ($previous != 0) {
1493
                // Update the previous item's next_item_id.
1494
                $sql = "UPDATE $tbl_lp_item
1495
                        SET next_item_id = $id
1496
                        WHERE iid = $previous";
1497
                Database::query($sql);
1498
            }
1499
1500
            if (!empty($new_next)) {
1501
                // Update the next item's previous_item_id.
1502
                $sql = "UPDATE $tbl_lp_item
1503
                        SET previous_item_id = $id
1504
                        WHERE iid = $new_next";
1505
                Database::query($sql);
1506
            }
1507
1508
            if ($old_prerequisite != $prerequisites) {
1509
                $sql = "UPDATE $tbl_lp_item
1510
                        SET prerequisite = '$prerequisites'
1511
                        WHERE iid = $id";
1512
                Database::query($sql);
1513
            }
1514
1515
            if ($old_max_time_allowed != $max_time_allowed) {
1516
                // update max time allowed
1517
                $sql = "UPDATE $tbl_lp_item
1518
                        SET max_time_allowed = $max_time_allowed
1519
                        WHERE iid = $id";
1520
                Database::query($sql);
1521
            }
1522
1523
            // Update all the items with the same or a bigger display_order than the current item.
1524
            $sql = "UPDATE $tbl_lp_item
1525
                    SET display_order = display_order + 1
1526
                    WHERE
1527
                       c_id = $course_id AND
1528
                       lp_id = ".$this->get_id()." AND
1529
                       iid <> $id AND
1530
                       parent_item_id = $parent AND
1531
                       display_order >= $new_order";
1532
            Database::query($sql);
1533
        }
1534
1535
        if ($row_select['item_type'] == 'link') {
1536
            $link = new Link();
1537
            $linkId = $row_select['path'];
1538
            $link->updateLink($linkId, $url);
1539
        }
1540
    }
1541
1542
    /**
1543
     * Updates an item's prereq in place.
1544
     *
1545
     * @param int    $id              Element ID
1546
     * @param string $prerequisite_id Prerequisite Element ID
1547
     * @param int    $minScore        Prerequisite min score
1548
     * @param int    $maxScore        Prerequisite max score
1549
     *
1550
     * @return bool True on success, false on error
1551
     */
1552
    public function edit_item_prereq(
1553
        $id,
1554
        $prerequisite_id,
1555
        $minScore = 0,
1556
        $maxScore = 100
1557
    ) {
1558
        $id = (int) $id;
1559
1560
        if (empty($id)) {
1561
            return false;
1562
        }
1563
1564
        $prerequisite_id = (int) $prerequisite_id;
1565
1566
        if (empty($minScore) || $minScore < 0) {
1567
            $minScore = 0;
1568
        }
1569
1570
        if (empty($maxScore) || $maxScore < 0) {
1571
            $maxScore = 100;
1572
        }
1573
1574
        $minScore = (float) $minScore;
1575
        $maxScore = (float) $maxScore;
1576
1577
        if (empty($prerequisite_id)) {
1578
            $prerequisite_id = 'NULL';
1579
            $minScore = 0;
1580
            $maxScore = 100;
1581
        }
1582
1583
        $table = Database::get_course_table(TABLE_LP_ITEM);
1584
        $sql = " UPDATE $table
1585
                 SET
1586
                    prerequisite = $prerequisite_id ,
1587
                    prerequisite_min_score = $minScore ,
1588
                    prerequisite_max_score = $maxScore
1589
                 WHERE iid = $id";
1590
        Database::query($sql);
1591
1592
        return true;
1593
    }
1594
1595
    /**
1596
     * Get the specific prefix index terms of this learning path.
1597
     *
1598
     * @param string $prefix
1599
     *
1600
     * @return array Array of terms
1601
     */
1602
    public function get_common_index_terms_by_prefix($prefix)
1603
    {
1604
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1605
        $terms = get_specific_field_values_list_by_prefix(
1606
            $prefix,
1607
            $this->cc,
1608
            TOOL_LEARNPATH,
1609
            $this->lp_id
1610
        );
1611
        $prefix_terms = [];
1612
        if (!empty($terms)) {
1613
            foreach ($terms as $term) {
1614
                $prefix_terms[] = $term['value'];
1615
            }
1616
        }
1617
1618
        return $prefix_terms;
1619
    }
1620
1621
    /**
1622
     * Gets the number of items currently completed.
1623
     *
1624
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1625
     *
1626
     * @return int The number of items currently completed
1627
     */
1628
    public function get_complete_items_count($failedStatusException = false, $typeListNotToCount = [])
1629
    {
1630
        $i = 0;
1631
        $completedStatusList = [
1632
            'completed',
1633
            'passed',
1634
            'succeeded',
1635
            'browsed',
1636
        ];
1637
1638
        if (!$failedStatusException) {
1639
            $completedStatusList[] = 'failed';
1640
        }
1641
1642
        if ($this->debug) {
1643
            error_log('START - get_complete_items_count');
1644
            error_log('Counting steps with status in: '.print_r($completedStatusList, 1));
1645
        }
1646
1647
        $chapters = self::getChapterTypes();
1648
        if (!empty($typeListNotToCount)) {
1649
            $typeListNotToCount = array_merge($typeListNotToCount, $chapters);
1650
        } else {
1651
            $typeListNotToCount = $chapters;
1652
        }
1653
1654
        foreach ($this->items as $id => $dummy) {
1655
            // Trying failed and browsed considered "progressed" as well.
1656
            if ($this->items[$id]->status_is($completedStatusList) &&
1657
                !in_array($this->items[$id]->get_type(), $typeListNotToCount)
1658
            ) {
1659
                $i++;
1660
            }
1661
        }
1662
1663
        if ($this->debug) {
1664
            error_log('END - Count: '.$i);
1665
        }
1666
1667
        return $i;
1668
    }
1669
1670
    /**
1671
     * Gets the current item ID.
1672
     *
1673
     * @return int The current learnpath item id
1674
     */
1675
    public function get_current_item_id()
1676
    {
1677
        $current = 0;
1678
        if (!empty($this->current)) {
1679
            $current = (int) $this->current;
1680
        }
1681
1682
        return $current;
1683
    }
1684
1685
    /**
1686
     * Force to get the first learnpath item id.
1687
     *
1688
     * @return int The current learnpath item id
1689
     */
1690
    public function get_first_item_id()
1691
    {
1692
        $current = 0;
1693
        if (is_array($this->ordered_items)) {
1694
            $current = $this->ordered_items[0];
1695
        }
1696
1697
        return $current;
1698
    }
1699
1700
    /**
1701
     * Gets the total number of items available for viewing in this SCORM.
1702
     *
1703
     * @return int The total number of items
1704
     */
1705
    public function get_total_items_count()
1706
    {
1707
        return count($this->items);
1708
    }
1709
1710
    /**
1711
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1712
     *
1713
     * @return int The total no-chapters number of items
1714
     */
1715
    public function getTotalItemsCountWithoutDirs($typeListNotToCount = [])
1716
    {
1717
        $total = 0;
1718
        $chapters = self::getChapterTypes();
1719
        if (!empty($typeListNotToCount)) {
1720
            $typeListNotToCount = array_merge($typeListNotToCount, $chapters);
1721
        } else {
1722
            $typeListNotToCount = $chapters;
1723
        }
1724
        foreach ($this->items as $temp2) {
1725
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1726
                $total++;
1727
            }
1728
        }
1729
1730
        return $total;
1731
    }
1732
1733
    /**
1734
     *  Sets the first element URL.
1735
     */
1736
    public function first()
1737
    {
1738
        if ($this->debug > 0) {
1739
            error_log('In learnpath::first()', 0);
1740
            error_log('$this->last_item_seen '.$this->last_item_seen);
1741
        }
1742
1743
        // Test if the last_item_seen exists and is not a dir.
1744
        if (count($this->ordered_items) == 0) {
1745
            $this->index = 0;
1746
        }
1747
1748
        if (!empty($this->last_item_seen) &&
1749
            !empty($this->items[$this->last_item_seen]) &&
1750
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1751
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1752
            //&& !$this->items[$this->last_item_seen]->is_done()
1753
        ) {
1754
            if ($this->debug > 2) {
1755
                error_log(
1756
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1757
                    $this->items[$this->last_item_seen]->get_type()
1758
                );
1759
            }
1760
            $index = -1;
1761
            foreach ($this->ordered_items as $myindex => $item_id) {
1762
                if ($item_id == $this->last_item_seen) {
1763
                    $index = $myindex;
1764
                    break;
1765
                }
1766
            }
1767
            if ($index == -1) {
1768
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1769
                if ($this->debug > 2) {
1770
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1771
                }
1772
1773
                return false;
1774
            } else {
1775
                $this->last = $this->last_item_seen;
1776
                $this->current = $this->last_item_seen;
1777
                $this->index = $index;
1778
            }
1779
        } else {
1780
            if ($this->debug > 2) {
1781
                error_log('In learnpath::first() - No last item seen', 0);
1782
            }
1783
            $index = 0;
1784
            // Loop through all ordered items and stop at the first item that is
1785
            // not a directory *and* that has not been completed yet.
1786
            while (!empty($this->ordered_items[$index]) &&
1787
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1788
                (
1789
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1790
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1791
                ) && $index < $this->max_ordered_items) {
1792
                $index++;
1793
            }
1794
1795
            $this->last = $this->current;
1796
            // current is
1797
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1798
            $this->index = $index;
1799
            if ($this->debug > 2) {
1800
                error_log('$index '.$index);
1801
                error_log('In learnpath::first() - No last item seen');
1802
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1803
            }
1804
        }
1805
        if ($this->debug > 2) {
1806
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1807
        }
1808
    }
1809
1810
    /**
1811
     * Gets the js library from the database.
1812
     *
1813
     * @return string The name of the javascript library to be used
1814
     */
1815
    public function get_js_lib()
1816
    {
1817
        $lib = '';
1818
        if (!empty($this->js_lib)) {
1819
            $lib = $this->js_lib;
1820
        }
1821
1822
        return $lib;
1823
    }
1824
1825
    /**
1826
     * Gets the learnpath database ID.
1827
     *
1828
     * @return int Learnpath ID in the lp table
1829
     */
1830
    public function get_id()
1831
    {
1832
        if (!empty($this->lp_id)) {
1833
            return (int) $this->lp_id;
1834
        }
1835
1836
        return 0;
1837
    }
1838
1839
    /**
1840
     * Gets the last element URL.
1841
     *
1842
     * @return string URL to load into the viewer
1843
     */
1844
    public function get_last()
1845
    {
1846
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1847
        if (count($this->ordered_items) > 0) {
1848
            $this->index = count($this->ordered_items) - 1;
1849
1850
            return $this->ordered_items[$this->index];
1851
        }
1852
1853
        return false;
1854
    }
1855
1856
    /**
1857
     * Get the last element in the first level.
1858
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1859
     *
1860
     * @return mixed
1861
     */
1862
    public function getLastInFirstLevel()
1863
    {
1864
        try {
1865
            $lastId = Database::getManager()
1866
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1867
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1868
                ->setMaxResults(1)
1869
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1870
                ->getSingleScalarResult();
1871
1872
            return $lastId;
1873
        } catch (Exception $exception) {
1874
            return 0;
1875
        }
1876
    }
1877
1878
    /**
1879
     * Get the learning path name by id.
1880
     *
1881
     * @param int $lpId
1882
     *
1883
     * @return mixed
1884
     */
1885
    public static function getLpNameById($lpId)
1886
    {
1887
        $em = Database::getManager();
1888
1889
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1890
            WHERE clp.iid = :iid')
1891
            ->setParameter('iid', $lpId)
1892
            ->getSingleScalarResult();
1893
    }
1894
1895
    /**
1896
     * Gets the navigation bar for the learnpath display screen.
1897
     *
1898
     * @param string $barId
1899
     *
1900
     * @return string The HTML string to use as a navigation bar
1901
     */
1902
    public function get_navigation_bar($barId = '')
1903
    {
1904
        if (empty($barId)) {
1905
            $barId = 'control-top';
1906
        }
1907
        $lpId = $this->lp_id;
1908
        $mycurrentitemid = $this->get_current_item_id();
1909
1910
        $reportingText = get_lang('Reporting');
1911
        $previousText = get_lang('ScormPrevious');
1912
        $nextText = get_lang('ScormNext');
1913
        $fullScreenText = get_lang('ScormExitFullScreen');
1914
        $lessonsText = get_lang('LearningPathList');
1915
1916
        $settings = api_get_configuration_value('lp_view_settings');
1917
        $display = isset($settings['display']) ? $settings['display'] : false;
1918
        $reportingIcon = '
1919
        <a class="icon-toolbar"
1920
            id="stats_link"
1921
            href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1922
            onclick="window.parent.API.save_asset(); return true;"
1923
            target="content_name" title="'.$reportingText.'">
1924
            <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1925
        </a>';
1926
1927
        if (!empty($display)) {
1928
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1929
            if ($showReporting === false) {
1930
                $reportingIcon = '';
1931
            }
1932
        }
1933
1934
        $hideArrows = false;
1935
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1936
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1937
        }
1938
1939
        $previousIcon = '';
1940
        $nextIcon = '';
1941
        if ($hideArrows === false) {
1942
            $previousIcon = '
1943
            <a class="icon-toolbar" id="scorm-previous" href="#"
1944
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1945
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1946
            </a>';
1947
1948
            $nextIcon = '
1949
            <a class="icon-toolbar" id="scorm-next" href="#"
1950
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1951
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1952
            </a>';
1953
        }
1954
1955
        $lessonsIcon = '
1956
        <a class="icon-toolbar" id="lessons-link" href="lp_controller.php?'.api_get_cidreq(true).'&reduced=true&isStudentView=true&hide_course_breadcrumb=true"
1957
            onclick="window.parent.API.save_asset(); return true;"
1958
            target="content_name" title="'.$lessonsText.'">
1959
            <span class="fa fa-star"></span><span class="sr-only">'.$lessonsText.'</span>
1960
        </a>';
1961
1962
        if ($this->mode === 'fullscreen') {
1963
            $navbar = '
1964
              <span id="'.$barId.'" class="buttons">
1965
                '.$reportingIcon.'
1966
                '.$previousIcon.'
1967
                '.$nextIcon.'
1968
                '.$lessonsIcon.'
1969
                <a class="icon-toolbar" id="view-embedded"
1970
                    href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1971
                    <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1972
                </a>
1973
              </span>';
1974
        } else {
1975
            $navbar = '
1976
             <span id="'.$barId.'" class="buttons text-right">
1977
                '.$reportingIcon.'
1978
                '.$previousIcon.'
1979
                '.$nextIcon.'
1980
                '.$lessonsIcon.'
1981
            </span>';
1982
        }
1983
1984
        return $navbar;
1985
    }
1986
1987
    /**
1988
     * Gets the next resource in queue (url).
1989
     *
1990
     * @return string URL to load into the viewer
1991
     */
1992
    public function get_next_index()
1993
    {
1994
        // TODO
1995
        $index = $this->index;
1996
        $index++;
1997
        while (
1998
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1999
            $index < $this->max_ordered_items
2000
        ) {
2001
            $index++;
2002
            if ($index == $this->max_ordered_items) {
2003
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2004
                    return $this->index;
2005
                }
2006
2007
                return $index;
2008
            }
2009
        }
2010
        if (empty($this->ordered_items[$index])) {
2011
            return $this->index;
2012
        }
2013
2014
        return $index;
2015
    }
2016
2017
    /**
2018
     * Gets item_id for the next element.
2019
     *
2020
     * @return int Next item (DB) ID
2021
     */
2022
    public function get_next_item_id()
2023
    {
2024
        $new_index = $this->get_next_index();
2025
        if (!empty($new_index)) {
2026
            if (isset($this->ordered_items[$new_index])) {
2027
                return $this->ordered_items[$new_index];
2028
            }
2029
        }
2030
2031
        return 0;
2032
    }
2033
2034
    /**
2035
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2036
     *
2037
     * Generally, the package provided is in the form of a zip file, so the function
2038
     * has been written to test a zip file. If not a zip, the function will return the
2039
     * default return value: ''
2040
     *
2041
     * @param string $file_path the path to the file
2042
     * @param string $file_name the original name of the file
2043
     *
2044
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
2045
     */
2046
    public static function getPackageType($file_path, $file_name)
2047
    {
2048
        // Get name of the zip file without the extension.
2049
        $file_info = pathinfo($file_name);
2050
        $extension = $file_info['extension']; // Extension only.
2051
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2052
                'dll',
2053
                'exe',
2054
            ])) {
2055
            return 'oogie';
2056
        }
2057
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2058
                'dll',
2059
                'exe',
2060
            ])) {
2061
            return 'woogie';
2062
        }
2063
2064
        $zipFile = new PclZip($file_path);
2065
        // Check the zip content (real size and file extension).
2066
        $zipContentArray = $zipFile->listContent();
2067
        $package_type = '';
2068
        $manifest = '';
2069
        $aicc_match_crs = 0;
2070
        $aicc_match_au = 0;
2071
        $aicc_match_des = 0;
2072
        $aicc_match_cst = 0;
2073
        $countItems = 0;
2074
2075
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2076
        if (is_array($zipContentArray)) {
2077
            $countItems = count($zipContentArray);
2078
            if ($countItems > 0) {
2079
                foreach ($zipContentArray as $thisContent) {
2080
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2081
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2082
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2083
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2084
                        $package_type = 'scorm';
2085
                        break; // Exit the foreach loop.
2086
                    } elseif (
2087
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2088
                        in_array(
2089
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2090
                            ['crs', 'au', 'des', 'cst']
2091
                        )
2092
                    ) {
2093
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2094
                        switch ($ext) {
2095
                            case 'crs':
2096
                                $aicc_match_crs = 1;
2097
                                break;
2098
                            case 'au':
2099
                                $aicc_match_au = 1;
2100
                                break;
2101
                            case 'des':
2102
                                $aicc_match_des = 1;
2103
                                break;
2104
                            case 'cst':
2105
                                $aicc_match_cst = 1;
2106
                                break;
2107
                            default:
2108
                                break;
2109
                        }
2110
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2111
                    } else {
2112
                        $package_type = '';
2113
                    }
2114
                }
2115
            }
2116
        }
2117
2118
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2119
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2120
            $package_type = 'aicc';
2121
        }
2122
2123
        // Try with chamilo course builder
2124
        if (empty($package_type)) {
2125
            // Sometimes users will try to upload an empty zip, or a zip with
2126
            // only a folder. Catch that and make the calling function aware.
2127
            // If the single file was the imsmanifest.xml, then $package_type
2128
            // would be 'scorm' and we wouldn't be here.
2129
            if ($countItems < 2) {
2130
                return 'error-empty-package';
2131
            }
2132
            $package_type = 'chamilo';
2133
        }
2134
2135
        return $package_type;
2136
    }
2137
2138
    /**
2139
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2140
     *
2141
     * @return string URL to load into the viewer
2142
     */
2143
    public function get_previous_index()
2144
    {
2145
        $index = $this->index;
2146
        if (isset($this->ordered_items[$index - 1])) {
2147
            $index--;
2148
            while (isset($this->ordered_items[$index]) &&
2149
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2150
            ) {
2151
                $index--;
2152
                if ($index < 0) {
2153
                    return $this->index;
2154
                }
2155
            }
2156
        }
2157
2158
        return $index;
2159
    }
2160
2161
    /**
2162
     * Gets item_id for the next element.
2163
     *
2164
     * @return int Previous item (DB) ID
2165
     */
2166
    public function get_previous_item_id()
2167
    {
2168
        $index = $this->get_previous_index();
2169
2170
        return $this->ordered_items[$index];
2171
    }
2172
2173
    /**
2174
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2175
     *
2176
     * @param int    $lpItemId
2177
     * @param string $autostart
2178
     *
2179
     * @return string The mediaplayer HTML
2180
     */
2181
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2182
    {
2183
        $course_id = api_get_course_int_id();
2184
        $courseInfo = api_get_course_info();
2185
        $lpItemId = (int) $lpItemId;
2186
2187
        if (empty($courseInfo) || empty($lpItemId)) {
2188
            return '';
2189
        }
2190
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2191
2192
        if (empty($item)) {
2193
            return '';
2194
        }
2195
2196
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2197
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2198
        $itemViewId = (int) $item->db_item_view_id;
2199
2200
        // Getting all the information about the item.
2201
        $sql = "SELECT lp_view.status
2202
                FROM $tbl_lp_item as lpi
2203
                INNER JOIN $tbl_lp_item_view as lp_view
2204
                ON (lpi.iid = lp_view.lp_item_id)
2205
                WHERE
2206
                    lp_view.iid = $itemViewId AND
2207
                    lpi.iid = $lpItemId AND
2208
                    lp_view.c_id = $course_id";
2209
        $result = Database::query($sql);
2210
        $row = Database::fetch_assoc($result);
2211
        $output = '';
2212
        $audio = $item->audio;
2213
2214
        if (!empty($audio)) {
2215
            $list = $_SESSION['oLP']->get_toc();
2216
2217
            switch ($item->get_type()) {
2218
                case 'quiz':
2219
                    $type_quiz = false;
2220
                    foreach ($list as $toc) {
2221
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2222
                            $type_quiz = true;
2223
                        }
2224
                    }
2225
2226
                    if ($type_quiz) {
2227
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2228
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2229
                        } else {
2230
                            $autostart_audio = $autostart;
2231
                        }
2232
                    }
2233
                    break;
2234
                case TOOL_READOUT_TEXT:
2235
                    $autostart_audio = 'false';
2236
                    break;
2237
                default:
2238
                    $autostart_audio = 'true';
2239
            }
2240
2241
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2242
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2243
2244
            $player = Display::getMediaPlayer(
2245
                $file,
2246
                [
2247
                    'id' => 'lp_audio_media_player',
2248
                    'url' => $url,
2249
                    'autoplay' => $autostart_audio,
2250
                    'width' => '100%',
2251
                ]
2252
            );
2253
2254
            // The mp3 player.
2255
            $output = '<div id="container">';
2256
            $output .= $player;
2257
            $output .= '</div>';
2258
        }
2259
2260
        return $output;
2261
    }
2262
2263
    /**
2264
     * @param int   $studentId
2265
     * @param int   $prerequisite
2266
     * @param array $courseInfo
2267
     * @param int   $sessionId
2268
     *
2269
     * @return bool
2270
     */
2271
    public static function isBlockedByPrerequisite(
2272
        $studentId,
2273
        $prerequisite,
2274
        $courseInfo,
2275
        $sessionId
2276
    ) {
2277
        if (empty($courseInfo)) {
2278
            return false;
2279
        }
2280
2281
        $courseId = $courseInfo['real_id'];
2282
2283
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2284
        if ($allow) {
2285
            if (api_is_allowed_to_edit() ||
2286
                api_is_platform_admin(true) ||
2287
                api_is_drh() ||
2288
                api_is_coach($sessionId, $courseId, false)
2289
            ) {
2290
                return false;
2291
            }
2292
        }
2293
2294
        $isBlocked = false;
2295
        if (!empty($prerequisite)) {
2296
            $progress = self::getProgress(
2297
                $prerequisite,
2298
                $studentId,
2299
                $courseId,
2300
                $sessionId
2301
            );
2302
            if ($progress < 100) {
2303
                $isBlocked = true;
2304
            }
2305
2306
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2307
                // Block if it does not exceed minimum time
2308
                // Minimum time (in minutes) to pass the learning path
2309
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2310
2311
                if ($accumulateWorkTime > 0) {
2312
                    // Total time in course (sum of times in learning paths from course)
2313
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2314
2315
                    // Connect with the plugin_licences_course_session table
2316
                    // which indicates what percentage of the time applies
2317
                    // Minimum connection percentage
2318
                    $perc = 100;
2319
                    // Time from the course
2320
                    $tc = $accumulateWorkTimeTotal;
2321
2322
                    // Percentage of the learning paths
2323
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2324
                    // Minimum time for each learning path
2325
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2326
2327
                    // Spent time (in seconds) so far in the learning path
2328
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2329
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2330
2331
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2332
                        $isBlocked = true;
2333
                    }
2334
                }
2335
            }
2336
        }
2337
2338
        return $isBlocked;
2339
    }
2340
2341
    /**
2342
     * Checks if the learning path is visible for student after the progress
2343
     * of its prerequisite is completed, considering the time availability and
2344
     * the LP visibility.
2345
     *
2346
     * @param int   $lp_id
2347
     * @param int   $student_id
2348
     * @param array $courseInfo
2349
     * @param int   $sessionId
2350
     * @param bool  $checkSubscription Optional. Allow don't check if user is subscribed to the LP.
2351
     *
2352
     * @return bool
2353
     */
2354
    public static function is_lp_visible_for_student(
2355
        $lp_id,
2356
        $student_id,
2357
        $courseInfo = [],
2358
        $sessionId = 0,
2359
        bool $checkSubscription = true
2360
    ) {
2361
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2362
        $lp_id = (int) $lp_id;
2363
        $sessionId = (int) $sessionId;
2364
2365
        if (empty($courseInfo)) {
2366
            return false;
2367
        }
2368
2369
        if (empty($sessionId)) {
2370
            $sessionId = api_get_session_id();
2371
        }
2372
2373
        $courseId = $courseInfo['real_id'];
2374
2375
        $itemInfo = api_get_item_property_info(
2376
            $courseId,
2377
            TOOL_LEARNPATH,
2378
            $lp_id,
2379
            $sessionId
2380
        );
2381
        // If there is no registry for the session verify the registry in the base course
2382
        if (empty($itemInfo)) {
2383
            $itemInfo = api_get_item_property_info(
2384
                $courseId,
2385
                TOOL_LEARNPATH,
2386
                $lp_id,
2387
                0
2388
            );
2389
        }
2390
2391
        // If the item was deleted or is invisible.
2392
        if (isset($itemInfo['visibility']) && ($itemInfo['visibility'] == 2 || $itemInfo['visibility'] == 0)) {
2393
            return false;
2394
        }
2395
2396
        // @todo remove this query and load the row info as a parameter
2397
        $table = Database::get_course_table(TABLE_LP_MAIN);
2398
        // Get current prerequisite
2399
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2400
                FROM $table
2401
                WHERE iid = $lp_id";
2402
        $rs = Database::query($sql);
2403
        $now = time();
2404
        if (Database::num_rows($rs) > 0) {
2405
            $row = Database::fetch_array($rs, 'ASSOC');
2406
            if (!empty($row['category_id'])) {
2407
                $category = self::getCategory($row['category_id']);
2408
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2409
                    return false;
2410
                }
2411
            }
2412
2413
            $prerequisite = $row['prerequisite'];
2414
            $is_visible = true;
2415
2416
            $isBlocked = self::isBlockedByPrerequisite(
2417
                $student_id,
2418
                $prerequisite,
2419
                $courseInfo,
2420
                $sessionId
2421
            );
2422
2423
            if ($isBlocked) {
2424
                $is_visible = false;
2425
            }
2426
2427
            // Also check the time availability of the LP
2428
            if ($is_visible) {
2429
                // Adding visibility restrictions
2430
                if (!empty($row['publicated_on'])) {
2431
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2432
                        $is_visible = false;
2433
                    }
2434
                }
2435
                // Blocking empty start times see BT#2800
2436
                global $_custom;
2437
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2438
                    $_custom['lps_hidden_when_no_start_date']
2439
                ) {
2440
                    if (empty($row['publicated_on'])) {
2441
                        $is_visible = false;
2442
                    }
2443
                }
2444
2445
                if (!empty($row['expired_on'])) {
2446
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2447
                        $is_visible = false;
2448
                    }
2449
                }
2450
            }
2451
2452
            if ($is_visible && $checkSubscription) {
2453
                $is_visible = self::isUserSubscribedToLp(
2454
                    $row,
2455
                    (int) $student_id,
2456
                    $courseInfo,
2457
                    (int) $sessionId
2458
                );
2459
            }
2460
2461
            return $is_visible;
2462
        }
2463
2464
        return false;
2465
    }
2466
2467
    public static function isUserSubscribedToLp(
2468
        array $lpInfo,
2469
        int $studentId,
2470
        array $courseInfo,
2471
        int $sessionId = 0
2472
    ): bool {
2473
        $subscriptionSettings = self::getSubscriptionSettings();
2474
2475
        // Check if the subscription users/group to a LP is ON
2476
        if (isset($lpInfo['subscribe_users']) && $lpInfo['subscribe_users'] == 1 &&
2477
            $subscriptionSettings['allow_add_users_to_lp'] === true
2478
        ) {
2479
            // Checking only the user visibility
2480
            $userVisibility = api_get_item_visibility(
2481
                $courseInfo,
2482
                'learnpath',
2483
                $lpInfo['id'],
2484
                $sessionId,
2485
                $studentId,
2486
                'LearnpathSubscription'
2487
            );
2488
2489
            if (1 == $userVisibility) {
2490
                return true;
2491
            }
2492
2493
            // Try group
2494
            $userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $courseInfo['real_id']);
2495
2496
            if (!empty($userGroups)) {
2497
                foreach ($userGroups as $groupInfo) {
2498
                    $userVisibility = api_get_item_visibility(
2499
                        $courseInfo,
2500
                        'learnpath',
2501
                        $lpInfo['id'],
2502
                        $sessionId,
2503
                        null,
2504
                        'LearnpathSubscription',
2505
                        $groupInfo['iid']
2506
                    );
2507
2508
                    if (1 == $userVisibility) {
2509
                        return true;
2510
                    }
2511
                }
2512
            }
2513
2514
            return false;
2515
        }
2516
2517
        return true;
2518
    }
2519
2520
    /**
2521
     * @param int $lpId
2522
     * @param int $userId
2523
     * @param int $courseId
2524
     * @param int $sessionId
2525
     *
2526
     * @return int
2527
     */
2528
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2529
    {
2530
        $lpId = (int) $lpId;
2531
        $userId = (int) $userId;
2532
        $courseId = (int) $courseId;
2533
        $sessionId = (int) $sessionId;
2534
2535
        $sessionCondition = api_get_session_condition($sessionId);
2536
        $table = Database::get_course_table(TABLE_LP_VIEW);
2537
        $sql = "SELECT progress FROM $table
2538
                WHERE
2539
                    c_id = $courseId AND
2540
                    lp_id = $lpId AND
2541
                    user_id = $userId $sessionCondition ";
2542
        $res = Database::query($sql);
2543
2544
        $progress = 0;
2545
        if (Database::num_rows($res) > 0) {
2546
            $row = Database::fetch_array($res);
2547
            $progress = (int) $row['progress'];
2548
        }
2549
2550
        return $progress;
2551
    }
2552
2553
    /**
2554
     * @param array $lpList
2555
     * @param int   $userId
2556
     * @param int   $courseId
2557
     * @param int   $sessionId
2558
     *
2559
     * @return array
2560
     */
2561
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2562
    {
2563
        $lpList = array_map('intval', $lpList);
2564
        if (empty($lpList)) {
2565
            return [];
2566
        }
2567
2568
        $lpList = implode("','", $lpList);
2569
2570
        $userId = (int) $userId;
2571
        $courseId = (int) $courseId;
2572
        $sessionId = (int) $sessionId;
2573
2574
        $sessionCondition = api_get_session_condition($sessionId);
2575
        $table = Database::get_course_table(TABLE_LP_VIEW);
2576
        $sql = "SELECT lp_id, progress FROM $table
2577
                WHERE
2578
                    c_id = $courseId AND
2579
                    lp_id IN ('".$lpList."') AND
2580
                    user_id = $userId $sessionCondition ";
2581
        $res = Database::query($sql);
2582
2583
        if (Database::num_rows($res) > 0) {
2584
            $list = [];
2585
            while ($row = Database::fetch_array($res)) {
2586
                $list[$row['lp_id']] = $row['progress'];
2587
            }
2588
2589
            return $list;
2590
        }
2591
2592
        return [];
2593
    }
2594
2595
    /**
2596
     * Displays a progress bar
2597
     * completed so far.
2598
     *
2599
     * @param int    $percentage Progress value to display
2600
     * @param string $text_add   Text to display near the progress value
2601
     *
2602
     * @return string HTML string containing the progress bar
2603
     */
2604
    public static function get_progress_bar($percentage = -1, $text_add = '')
2605
    {
2606
        $text = $percentage.$text_add;
2607
        $output = '<div class="progress">
2608
            <div id="progress_bar_value"
2609
                class="progress-bar progress-bar-success" role="progressbar"
2610
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2611
            '.$text.'
2612
            </div>
2613
        </div>';
2614
2615
        return $output;
2616
    }
2617
2618
    /**
2619
     * @param string $mode can be '%' or 'abs'
2620
     *                     otherwise this value will be used $this->progress_bar_mode
2621
     *
2622
     * @return string
2623
     */
2624
    public function getProgressBar($mode = null)
2625
    {
2626
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2627
2628
        return self::get_progress_bar($percentage, $text_add);
2629
    }
2630
2631
    /**
2632
     * Gets the progress bar info to display inside the progress bar.
2633
     * Also used by scorm_api.php.
2634
     *
2635
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2636
     *                     we display a number of completed elements per total elements
2637
     * @param int    $add  Additional steps to fake as completed
2638
     *
2639
     * @return array Percentage or number and symbol (% or /xx)
2640
     */
2641
    public function get_progress_bar_text($mode = '', $add = 0)
2642
    {
2643
        if (empty($mode)) {
2644
            $mode = $this->progress_bar_mode;
2645
        }
2646
        $text = '';
2647
        $percentage = 0;
2648
        // If the option to use the score as progress is set for this learning
2649
        // path, then the rules are completely different: we assume only one
2650
        // item exists and the progress of the LP depends on the score
2651
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2652
2653
        if ($scoreAsProgressSetting === true) {
2654
            $scoreAsProgress = $this->getUseScoreAsProgress();
2655
            if ($scoreAsProgress) {
2656
                // Get single item's score
2657
                $itemId = $this->get_current_item_id();
2658
                $item = $this->getItem($itemId);
2659
                $score = $item->get_score();
2660
                $maxScore = $item->get_max();
2661
                if ($mode = '%') {
2662
                    if (!empty($maxScore)) {
2663
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2664
                    }
2665
                    $percentage = number_format($percentage, 0);
2666
                    $text = '%';
2667
                } else {
2668
                    $percentage = $score;
2669
                    $text = '/'.$maxScore;
2670
                }
2671
2672
                return [$percentage, $text];
2673
            }
2674
        }
2675
        // otherwise just continue the normal processing of progress
2676
        $total_items = $this->getTotalItemsCountWithoutDirs();
2677
        $completeItems = $this->get_complete_items_count();
2678
        if ($add != 0) {
2679
            $completeItems += $add;
2680
        }
2681
        if ($completeItems > $total_items) {
2682
            $completeItems = $total_items;
2683
        }
2684
2685
        if ($mode == '%') {
2686
            if ($total_items > 0) {
2687
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2688
            }
2689
            $percentage = number_format($percentage, 0);
2690
            $text = '%';
2691
        } elseif ($mode === 'abs') {
2692
            $percentage = $completeItems;
2693
            $text = '/'.$total_items;
2694
        }
2695
2696
        return [
2697
            $percentage,
2698
            $text,
2699
        ];
2700
    }
2701
2702
    /**
2703
     * Gets the progress bar mode.
2704
     *
2705
     * @return string The progress bar mode attribute
2706
     */
2707
    public function get_progress_bar_mode()
2708
    {
2709
        if (!empty($this->progress_bar_mode)) {
2710
            return $this->progress_bar_mode;
2711
        }
2712
2713
        return '%';
2714
    }
2715
2716
    /**
2717
     * Gets the learnpath theme (remote or local).
2718
     *
2719
     * @return string Learnpath theme
2720
     */
2721
    public function get_theme()
2722
    {
2723
        if (!empty($this->theme)) {
2724
            return $this->theme;
2725
        }
2726
2727
        return '';
2728
    }
2729
2730
    /**
2731
     * Gets the learnpath session id.
2732
     *
2733
     * @return int
2734
     */
2735
    public function get_lp_session_id()
2736
    {
2737
        if (!empty($this->lp_session_id)) {
2738
            return (int) $this->lp_session_id;
2739
        }
2740
2741
        return 0;
2742
    }
2743
2744
    /**
2745
     * Gets the learnpath image.
2746
     *
2747
     * @return string Web URL of the LP image
2748
     */
2749
    public function get_preview_image()
2750
    {
2751
        if (!empty($this->preview_image)) {
2752
            return $this->preview_image;
2753
        }
2754
2755
        return '';
2756
    }
2757
2758
    /**
2759
     * @param string $size
2760
     * @param string $path_type
2761
     *
2762
     * @return bool|string
2763
     */
2764
    public function get_preview_image_path($size = null, $path_type = 'web')
2765
    {
2766
        $preview_image = $this->get_preview_image();
2767
        if (isset($preview_image) && !empty($preview_image)) {
2768
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2769
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2770
2771
            if (isset($size)) {
2772
                $info = pathinfo($preview_image);
2773
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2774
2775
                if (file_exists($image_sys_path.$image_custom_size)) {
2776
                    if ($path_type == 'web') {
2777
                        return $image_path.$image_custom_size;
2778
                    } else {
2779
                        return $image_sys_path.$image_custom_size;
2780
                    }
2781
                }
2782
            } else {
2783
                if ($path_type == 'web') {
2784
                    return $image_path.$preview_image;
2785
                } else {
2786
                    return $image_sys_path.$preview_image;
2787
                }
2788
            }
2789
        }
2790
2791
        return false;
2792
    }
2793
2794
    /**
2795
     * Gets the learnpath author.
2796
     *
2797
     * @return string LP's author
2798
     */
2799
    public function get_author()
2800
    {
2801
        if (!empty($this->author)) {
2802
            return $this->author;
2803
        }
2804
2805
        return '';
2806
    }
2807
2808
    /**
2809
     * Gets hide table of contents.
2810
     *
2811
     * @return int
2812
     */
2813
    public function getHideTableOfContents()
2814
    {
2815
        return (int) $this->hide_toc_frame;
2816
    }
2817
2818
    /**
2819
     * Generate a new prerequisites string for a given item. If this item was a sco and
2820
     * its prerequisites were strings (instead of IDs), then transform those strings into
2821
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2822
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2823
     * same rule as the scormExport() method.
2824
     *
2825
     * @param int $item_id Item ID
2826
     *
2827
     * @return string Prerequisites string ready for the export as SCORM
2828
     */
2829
    public function get_scorm_prereq_string($item_id)
2830
    {
2831
        if ($this->debug > 0) {
2832
            error_log('In learnpath::get_scorm_prereq_string()');
2833
        }
2834
        if (!is_object($this->items[$item_id])) {
2835
            return false;
2836
        }
2837
        /** @var learnpathItem $oItem */
2838
        $oItem = $this->items[$item_id];
2839
        $prereq = $oItem->get_prereq_string();
2840
2841
        if (empty($prereq)) {
2842
            return '';
2843
        }
2844
        if (preg_match('/^\d+$/', $prereq) &&
2845
            isset($this->items[$prereq]) &&
2846
            is_object($this->items[$prereq])
2847
        ) {
2848
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2849
            // then simply return it (with the ITEM_ prefix).
2850
            //return 'ITEM_' . $prereq;
2851
            return $this->items[$prereq]->ref;
2852
        } else {
2853
            if (isset($this->refs_list[$prereq])) {
2854
                // It's a simple string item from which the ID can be found in the refs list,
2855
                // so we can transform it directly to an ID for export.
2856
                return $this->items[$this->refs_list[$prereq]]->ref;
2857
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2858
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2859
            } else {
2860
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2861
                // and replace them, one by one, by the internal IDs (chamilo db)
2862
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2863
                // by a space as well.
2864
                $find = [
2865
                    '&',
2866
                    '|',
2867
                    '~',
2868
                    '=',
2869
                    '<>',
2870
                    '{',
2871
                    '}',
2872
                    '*',
2873
                    '(',
2874
                    ')',
2875
                ];
2876
                $replace = [
2877
                    ' ',
2878
                    ' ',
2879
                    ' ',
2880
                    ' ',
2881
                    ' ',
2882
                    ' ',
2883
                    ' ',
2884
                    ' ',
2885
                    ' ',
2886
                    ' ',
2887
                ];
2888
                $prereq_mod = str_replace($find, $replace, $prereq);
2889
                $ids = explode(' ', $prereq_mod);
2890
                foreach ($ids as $id) {
2891
                    $id = trim($id);
2892
                    if (isset($this->refs_list[$id])) {
2893
                        $prereq = preg_replace(
2894
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2895
                            'ITEM_'.$this->refs_list[$id],
2896
                            $prereq
2897
                        );
2898
                    }
2899
                }
2900
2901
                return $prereq;
2902
            }
2903
        }
2904
    }
2905
2906
    /**
2907
     * Returns the XML DOM document's node.
2908
     *
2909
     * @param DOMNodeList $children Reference to a list of objects to search for the given ITEM_*
2910
     * @param string      $id       The identifier to look for
2911
     *
2912
     * @return mixed The reference to the element found with that identifier. False if not found
2913
     */
2914
    public function get_scorm_xml_node(DOMNodeList &$children, string $id, $nodeName = 'item', $attributeName = 'identifier')
2915
    {
2916
        for ($i = 0; $i < $children->length; $i++) {
2917
            $item_temp = $children->item($i);
2918
            if ($item_temp->nodeName == $nodeName) {
2919
                if ($item_temp instanceof DOMElement && $item_temp->getAttribute($attributeName) == $id) {
2920
                    return $item_temp;
2921
                }
2922
            }
2923
            $subchildren = $item_temp->childNodes;
2924
            if ($subchildren && $subchildren->length > 0) {
2925
                $val = $this->get_scorm_xml_node($subchildren, $id, $nodeName, $attributeName);
2926
                if (is_object($val)) {
2927
                    return $val;
2928
                }
2929
            }
2930
        }
2931
2932
        return false;
2933
    }
2934
2935
    /**
2936
     * Gets the status list for all LP's items.
2937
     *
2938
     * @return array Array of [index] => [item ID => current status]
2939
     */
2940
    public function get_items_status_list()
2941
    {
2942
        $list = [];
2943
        foreach ($this->ordered_items as $item_id) {
2944
            $list[] = [
2945
                $item_id => $this->items[$item_id]->get_status(),
2946
            ];
2947
        }
2948
2949
        return $list;
2950
    }
2951
2952
    /**
2953
     * Return the number of interactions for the given learnpath Item View ID.
2954
     * This method can be used as static.
2955
     *
2956
     * @param int $lp_iv_id  Item View ID
2957
     * @param int $course_id course id
2958
     *
2959
     * @return int
2960
     */
2961
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2962
    {
2963
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2964
        $lp_iv_id = (int) $lp_iv_id;
2965
        $course_id = (int) $course_id;
2966
2967
        $sql = "SELECT count(*) FROM $table
2968
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2969
        $res = Database::query($sql);
2970
        $num = 0;
2971
        if (Database::num_rows($res)) {
2972
            $row = Database::fetch_array($res);
2973
            $num = $row[0];
2974
        }
2975
2976
        return $num;
2977
    }
2978
2979
    /**
2980
     * Return the interactions as an array for the given lp_iv_id.
2981
     * This method can be used as static.
2982
     *
2983
     * @param int $lp_iv_id Learnpath Item View ID
2984
     *
2985
     * @return array
2986
     *
2987
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2988
     */
2989
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2990
    {
2991
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2992
        $list = [];
2993
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2994
        $lp_iv_id = (int) $lp_iv_id;
2995
2996
        if (empty($lp_iv_id) || empty($course_id)) {
2997
            return [];
2998
        }
2999
3000
        $sql = "SELECT * FROM $table
3001
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
3002
                ORDER BY order_id ASC";
3003
        $res = Database::query($sql);
3004
        $num = Database::num_rows($res);
3005
        if ($num > 0) {
3006
            $list[] = [
3007
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3008
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
3009
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
3010
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
3011
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
3012
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
3013
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
3014
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
3015
                'student_response_formatted' => '',
3016
            ];
3017
            while ($row = Database::fetch_array($res)) {
3018
                $studentResponseFormatted = urldecode($row['student_response']);
3019
                $content_student_response = explode('__|', $studentResponseFormatted);
3020
                if (count($content_student_response) > 0) {
3021
                    if (count($content_student_response) >= 3) {
3022
                        // Pop the element off the end of array.
3023
                        array_pop($content_student_response);
3024
                    }
3025
                    $studentResponseFormatted = implode(',', $content_student_response);
3026
                }
3027
3028
                $list[] = [
3029
                    'order_id' => $row['order_id'] + 1,
3030
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
3031
                    'type' => $row['interaction_type'],
3032
                    'time' => $row['completion_time'],
3033
                    'correct_responses' => '', // Hide correct responses from students.
3034
                    'student_response' => $row['student_response'],
3035
                    'result' => $row['result'],
3036
                    'latency' => $row['latency'],
3037
                    'student_response_formatted' => $studentResponseFormatted,
3038
                ];
3039
            }
3040
        }
3041
3042
        return $list;
3043
    }
3044
3045
    /**
3046
     * Return the number of objectives for the given learnpath Item View ID.
3047
     * This method can be used as static.
3048
     *
3049
     * @param int $lp_iv_id  Item View ID
3050
     * @param int $course_id Course ID
3051
     *
3052
     * @return int Number of objectives
3053
     */
3054
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3055
    {
3056
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3057
        $course_id = (int) $course_id;
3058
        $lp_iv_id = (int) $lp_iv_id;
3059
        $sql = "SELECT count(*) FROM $table
3060
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3061
        //@todo seems that this always returns 0
3062
        $res = Database::query($sql);
3063
        $num = 0;
3064
        if (Database::num_rows($res)) {
3065
            $row = Database::fetch_array($res);
3066
            $num = $row[0];
3067
        }
3068
3069
        return $num;
3070
    }
3071
3072
    /**
3073
     * Return the objectives as an array for the given lp_iv_id.
3074
     * This method can be used as static.
3075
     *
3076
     * @param int $lpItemViewId Learnpath Item View ID
3077
     * @param int $course_id
3078
     *
3079
     * @return array
3080
     *
3081
     * @todo    Translate labels
3082
     */
3083
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3084
    {
3085
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3086
        $lpItemViewId = (int) $lpItemViewId;
3087
3088
        if (empty($course_id) || empty($lpItemViewId)) {
3089
            return [];
3090
        }
3091
3092
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3093
        $sql = "SELECT * FROM $table
3094
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3095
                ORDER BY order_id ASC";
3096
        $res = Database::query($sql);
3097
        $num = Database::num_rows($res);
3098
        $list = [];
3099
        if ($num > 0) {
3100
            $list[] = [
3101
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3102
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3103
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3104
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3105
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3106
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3107
            ];
3108
            while ($row = Database::fetch_array($res)) {
3109
                $list[] = [
3110
                    'order_id' => $row['order_id'] + 1,
3111
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3112
                    'score_raw' => $row['score_raw'],
3113
                    'score_max' => $row['score_max'],
3114
                    'score_min' => $row['score_min'],
3115
                    'status' => $row['status'],
3116
                ];
3117
            }
3118
        }
3119
3120
        return $list;
3121
    }
3122
3123
    /**
3124
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3125
     * used by get_html_toc() to be ready to display.
3126
     *
3127
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3128
     */
3129
    public function get_toc()
3130
    {
3131
        $toc = [];
3132
        foreach ($this->ordered_items as $item_id) {
3133
            // TODO: Change this link generation and use new function instead.
3134
            $toc[] = [
3135
                'id' => $item_id,
3136
                'title' => $this->items[$item_id]->get_title(),
3137
                'status' => $this->items[$item_id]->get_status(),
3138
                'level' => $this->items[$item_id]->get_level(),
3139
                'type' => $this->items[$item_id]->get_type(),
3140
                'description' => $this->items[$item_id]->get_description(),
3141
                'path' => $this->items[$item_id]->get_path(),
3142
                'parent' => $this->items[$item_id]->get_parent(),
3143
            ];
3144
        }
3145
3146
        return $toc;
3147
    }
3148
3149
    /**
3150
     * Returns the CSS class name associated with a given item status.
3151
     *
3152
     * @param $status string an item status
3153
     *
3154
     * @return string CSS class name
3155
     */
3156
    public static function getStatusCSSClassName($status)
3157
    {
3158
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3159
            return self::STATUS_CSS_CLASS_NAME[$status];
3160
        }
3161
3162
        return '';
3163
    }
3164
3165
    /**
3166
     * Generate the tree of contents for this learnpath as an associative array tree
3167
     * with keys id, title, status, type, description, path, parent_id, children
3168
     * (title and descriptions as secured)
3169
     * and clues for CSS class composition:
3170
     *  - booleans is_current, is_parent_of_current, is_chapter
3171
     *  - string status_css_class_name.
3172
     *
3173
     * @param $parentId int restrict returned list to children of this parent
3174
     *
3175
     * @return array TOC as a table
3176
     */
3177
    public function getTOCTree($parentId = 0)
3178
    {
3179
        $toc = [];
3180
        $currentItemId = $this->get_current_item_id();
3181
3182
        foreach ($this->ordered_items as $itemId) {
3183
            $item = $this->items[$itemId];
3184
            if ($item->get_parent() == $parentId) {
3185
                $title = $item->get_title();
3186
                if (empty($title)) {
3187
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3188
                }
3189
3190
                $itemData = [
3191
                    'id' => $itemId,
3192
                    'title' => Security::remove_XSS($title),
3193
                    'status' => $item->get_status(),
3194
                    'level' => $item->get_level(), // FIXME should not be needed
3195
                    'type' => $item->get_type(),
3196
                    'description' => Security::remove_XSS($item->get_description()),
3197
                    'path' => $item->get_path(),
3198
                    'parent_id' => $item->get_parent(),
3199
                    'children' => $this->getTOCTree($itemId),
3200
                    'is_current' => ($itemId == $currentItemId),
3201
                    'is_parent_of_current' => false,
3202
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3203
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3204
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3205
                ];
3206
3207
                if (!empty($itemData['children'])) {
3208
                    foreach ($itemData['children'] as $child) {
3209
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3210
                            $itemData['is_parent_of_current'] = true;
3211
                            break;
3212
                        }
3213
                    }
3214
                }
3215
3216
                $toc[] = $itemData;
3217
            }
3218
        }
3219
3220
        return $toc;
3221
    }
3222
3223
    /**
3224
     * Generate and return the table of contents for this learnpath. The JS
3225
     * table returned is used inside of scorm_api.php.
3226
     *
3227
     * @param string $varname
3228
     *
3229
     * @return string A JS array variable construction
3230
     */
3231
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3232
    {
3233
        $toc = $varname.' = new Array();';
3234
        foreach ($this->ordered_items as $item_id) {
3235
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3236
        }
3237
3238
        return $toc;
3239
    }
3240
3241
    /**
3242
     * Gets the learning path type.
3243
     *
3244
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3245
     *
3246
     * @return mixed Type ID or name, depending on the parameter
3247
     */
3248
    public function get_type($get_name = false)
3249
    {
3250
        $res = false;
3251
        if (!empty($this->type) && (!$get_name)) {
3252
            $res = $this->type;
3253
        }
3254
3255
        return $res;
3256
    }
3257
3258
    /**
3259
     * Gets the learning path type as static method.
3260
     *
3261
     * @param int $lp_id
3262
     *
3263
     * @return mixed Returns the lp_type: 1 = Chamilo lms / 2 = SCORM
3264
     */
3265
    public static function get_type_static($lp_id = 0)
3266
    {
3267
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3268
        $lp_id = (int) $lp_id;
3269
        $sql = "SELECT lp_type FROM $tbl_lp
3270
                WHERE iid = $lp_id";
3271
        $res = Database::query($sql);
3272
        if ($res === false) {
3273
            return null;
3274
        }
3275
        if (Database::num_rows($res) <= 0) {
3276
            return null;
3277
        }
3278
        $row = Database::fetch_array($res);
3279
3280
        return $row['lp_type'];
3281
    }
3282
3283
    /**
3284
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3285
     * This method can be used as abstract and is recursive.
3286
     *
3287
     * @param int $lp        Learnpath ID
3288
     * @param int $parent    Parent ID of the items to look for
3289
     * @param int $course_id
3290
     *
3291
     * @return array Ordered list of item IDs (empty array on error)
3292
     */
3293
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3294
    {
3295
        if (empty($course_id)) {
3296
            $course_id = api_get_course_int_id();
3297
        } else {
3298
            $course_id = (int) $course_id;
3299
        }
3300
        $list = [];
3301
3302
        if (empty($lp)) {
3303
            return $list;
3304
        }
3305
3306
        $lp = (int) $lp;
3307
        $parent = (int) $parent;
3308
3309
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3310
        $sql = "SELECT iid FROM $tbl_lp_item
3311
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3312
                ORDER BY display_order";
3313
3314
        $res = Database::query($sql);
3315
        while ($row = Database::fetch_array($res)) {
3316
            $sublist = self::get_flat_ordered_items_list(
3317
                $lp,
3318
                $row['iid'],
3319
                $course_id
3320
            );
3321
            $list[] = $row['iid'];
3322
            foreach ($sublist as $item) {
3323
                $list[] = $item;
3324
            }
3325
        }
3326
3327
        return $list;
3328
    }
3329
3330
    /**
3331
     * @return array
3332
     */
3333
    public static function getChapterTypes()
3334
    {
3335
        return [
3336
            'dir',
3337
        ];
3338
    }
3339
3340
    /**
3341
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3342
     *
3343
     * @param $tree
3344
     *
3345
     * @return array HTML TOC ready to display
3346
     */
3347
    public function getParentToc($tree)
3348
    {
3349
        if (empty($tree)) {
3350
            $tree = $this->get_toc();
3351
        }
3352
        $dirTypes = self::getChapterTypes();
3353
        $myCurrentId = $this->get_current_item_id();
3354
        $listParent = [];
3355
        $listChildren = [];
3356
        $listNotParent = [];
3357
        $list = [];
3358
        foreach ($tree as $subtree) {
3359
            if (in_array($subtree['type'], $dirTypes)) {
3360
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3361
                $subtree['children'] = $listChildren;
3362
                if (!empty($subtree['children'])) {
3363
                    foreach ($subtree['children'] as $subItem) {
3364
                        if ($subItem['id'] == $this->current) {
3365
                            $subtree['parent_current'] = 'in';
3366
                            $subtree['current'] = 'on';
3367
                        }
3368
                    }
3369
                }
3370
                $listParent[] = $subtree;
3371
            }
3372
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3373
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3374
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3375
                }
3376
3377
                $title = Security::remove_XSS($subtree['title']);
3378
                unset($subtree['title']);
3379
3380
                if (empty($title)) {
3381
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3382
                }
3383
                $classStyle = null;
3384
                if ($subtree['id'] == $this->current) {
3385
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3386
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3387
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3388
                }
3389
                $subtree['title'] = $title;
3390
                $subtree['class'] = $classStyle.' '.$cssStatus;
3391
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3392
                $subtree['current_id'] = $myCurrentId;
3393
                $listNotParent[] = $subtree;
3394
            }
3395
        }
3396
3397
        $list['are_parents'] = $listParent;
3398
        $list['not_parents'] = $listNotParent;
3399
3400
        return $list;
3401
    }
3402
3403
    /**
3404
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3405
     *
3406
     * @param array $tree
3407
     * @param int   $id
3408
     * @param bool  $parent
3409
     *
3410
     * @return array HTML TOC ready to display
3411
     */
3412
    public function getChildrenToc($tree, $id, $parent = true)
3413
    {
3414
        if (empty($tree)) {
3415
            $tree = $this->get_toc();
3416
        }
3417
3418
        $dirTypes = self::getChapterTypes();
3419
        $currentItemId = $this->get_current_item_id();
3420
        $list = [];
3421
3422
        foreach ($tree as $subtree) {
3423
            $subtree['tree'] = null;
3424
3425
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3426
                if ($subtree['id'] == $this->current) {
3427
                    $subtree['current'] = 'active';
3428
                } else {
3429
                    $subtree['current'] = null;
3430
                }
3431
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3432
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3433
                }
3434
3435
                $title = Security::remove_XSS($subtree['title']);
3436
                unset($subtree['title']);
3437
                if (empty($title)) {
3438
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3439
                }
3440
3441
                $classStyle = null;
3442
                if ($subtree['id'] == $this->current) {
3443
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3444
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3445
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3446
                }
3447
3448
                if (in_array($subtree['type'], $dirTypes)) {
3449
                    $subtree['title'] = stripslashes($title);
3450
                } else {
3451
                    $subtree['title'] = $title;
3452
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3453
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3454
                    $subtree['current_id'] = $currentItemId;
3455
                }
3456
                $list[] = $subtree;
3457
            }
3458
        }
3459
3460
        return $list;
3461
    }
3462
3463
    /**
3464
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3465
     *
3466
     * @param array $toc_list
3467
     *
3468
     * @return array HTML TOC ready to display
3469
     */
3470
    public function getListArrayToc($toc_list = [])
3471
    {
3472
        if (empty($toc_list)) {
3473
            $toc_list = $this->get_toc();
3474
        }
3475
        // Temporary variables.
3476
        $currentItemId = $this->get_current_item_id();
3477
        $list = [];
3478
        $arrayList = [];
3479
3480
        foreach ($toc_list as $item) {
3481
            $list['id'] = $item['id'];
3482
            $list['status'] = $item['status'];
3483
            $cssStatus = null;
3484
3485
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3486
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3487
            }
3488
3489
            $classStyle = ' ';
3490
            $dirTypes = self::getChapterTypes();
3491
3492
            if (in_array($item['type'], $dirTypes)) {
3493
                $classStyle = 'scorm_item_section ';
3494
            }
3495
            if ($item['id'] == $this->current) {
3496
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3497
            } elseif (!in_array($item['type'], $dirTypes)) {
3498
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3499
            }
3500
            $title = $item['title'];
3501
            if (empty($title)) {
3502
                $title = self::rl_get_resource_name(
3503
                    api_get_course_id(),
3504
                    $this->get_id(),
3505
                    $item['id']
3506
                );
3507
            }
3508
            $title = Security::remove_XSS($item['title']);
3509
3510
            if (empty($item['description'])) {
3511
                $list['description'] = $title;
3512
            } else {
3513
                $list['description'] = $item['description'];
3514
            }
3515
3516
            $list['class'] = $classStyle.' '.$cssStatus;
3517
            $list['level'] = $item['level'];
3518
            $list['type'] = $item['type'];
3519
3520
            if (in_array($item['type'], $dirTypes)) {
3521
                $list['css_level'] = 'level_'.$item['level'];
3522
            } else {
3523
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3524
            }
3525
3526
            if (in_array($item['type'], $dirTypes)) {
3527
                $list['title'] = stripslashes($title);
3528
            } else {
3529
                $list['title'] = stripslashes($title);
3530
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3531
                $list['current_id'] = $currentItemId;
3532
            }
3533
            $arrayList[] = $list;
3534
        }
3535
3536
        return $arrayList;
3537
    }
3538
3539
    /**
3540
     * Returns an HTML-formatted string ready to display with teacher buttons
3541
     * in LP view menu.
3542
     *
3543
     * @return string HTML TOC ready to display
3544
     */
3545
    public function get_teacher_toc_buttons()
3546
    {
3547
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3548
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3549
        $html = '';
3550
        if ($isAllow && $hideIcons == false) {
3551
            if ($this->get_lp_session_id() == api_get_session_id()) {
3552
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3553
                $html .= '<div class="btn-group">';
3554
                $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'>".
3555
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3556
                $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'>".
3557
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3558
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3559
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3560
                $html .= '</div>';
3561
                $html .= '</div>';
3562
            }
3563
        }
3564
3565
        return $html;
3566
    }
3567
3568
    /**
3569
     * Returns an HTML-formatted string ready to display flow buttons
3570
     * in LP view menu.
3571
     *
3572
     * @return string HTML TOC ready to display
3573
     */
3574
    public function getFlowLpbuttons()
3575
    {
3576
        $allowFlowButtons = api_get_configuration_value('lp_enable_flow');
3577
        $html = '';
3578
        if ($allowFlowButtons) {
3579
            $nextLpId = self::getFlowNextLpId($this->lp_id, api_get_course_int_id());
3580
            $prevLpId = self::getFlowPrevLpId($this->lp_id, api_get_course_int_id());
3581
            if (!empty($nextLpId) || !empty($prevLpId)) {
3582
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3583
                $html .= '<div class="btn-group">';
3584
                if ($prevLpId > 0) {
3585
                    $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=view&lp_id=".$prevLpId."' target='_parent'>".
3586
                        Display::returnFontAwesomeIcon('arrow-left').'&nbsp;'.get_lang('Previous')."</a>";
3587
                }
3588
                if ($nextLpId > 0) {
3589
                    $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=view&lp_id=".$nextLpId."' target='_parent'>".
3590
                        get_lang('Next').'&nbsp;'.Display::returnFontAwesomeIcon('arrow-right')."</a>";
3591
                }
3592
                $html .= '</div>';
3593
                $html .= '</div>';
3594
            }
3595
        }
3596
3597
        return $html;
3598
    }
3599
3600
    /**
3601
     * Gets the learnpath maker name - generally the editor's name.
3602
     *
3603
     * @return string Learnpath maker name
3604
     */
3605
    public function get_maker()
3606
    {
3607
        if (!empty($this->maker)) {
3608
            return $this->maker;
3609
        }
3610
3611
        return '';
3612
    }
3613
3614
    /**
3615
     * Gets the learnpath name/title.
3616
     *
3617
     * @return string Learnpath name/title
3618
     */
3619
    public function get_name()
3620
    {
3621
        if (!empty($this->name)) {
3622
            return $this->name;
3623
        }
3624
3625
        return 'N/A';
3626
    }
3627
3628
    /**
3629
     * @return string
3630
     */
3631
    public function getNameNoTags()
3632
    {
3633
        return Security::remove_XSS(strip_tags($this->get_name()));
3634
    }
3635
3636
    /**
3637
     * Gets a link to the resource from the present location, depending on item ID.
3638
     *
3639
     * @param string $type         Type of link expected
3640
     * @param int    $item_id      Learnpath item ID
3641
     * @param bool   $provided_toc
3642
     *
3643
     * @return string $provided_toc Link to the lp_item resource
3644
     */
3645
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3646
    {
3647
        $course_id = $this->get_course_int_id();
3648
        $item_id = (int) $item_id;
3649
3650
        if (empty($item_id)) {
3651
            $item_id = $this->get_current_item_id();
3652
3653
            if (empty($item_id)) {
3654
                //still empty, this means there was no item_id given and we are not in an object context or
3655
                //the object property is empty, return empty link
3656
                $this->first();
3657
3658
                return '';
3659
            }
3660
        }
3661
3662
        $file = '';
3663
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3664
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3665
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3666
3667
        $sql = "SELECT
3668
                    l.lp_type as ltype,
3669
                    l.path as lpath,
3670
                    li.item_type as litype,
3671
                    li.path as lipath,
3672
                    li.parameters as liparams
3673
        		FROM $lp_table l
3674
                INNER JOIN $lp_item_table li
3675
                ON (li.lp_id = l.iid)
3676
        		WHERE
3677
        		    li.iid = $item_id
3678
        		";
3679
        $res = Database::query($sql);
3680
        if (Database::num_rows($res) > 0) {
3681
            $row = Database::fetch_array($res);
3682
            $lp_type = $row['ltype'];
3683
            $lp_path = $row['lpath'];
3684
            $lp_item_type = $row['litype'];
3685
            $lp_item_path = $row['lipath'];
3686
            $lp_item_params = $row['liparams'];
3687
3688
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3689
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3690
            }
3691
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3692
            if ($type === 'http') {
3693
                //web path
3694
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3695
            } else {
3696
                $course_path = $sys_course_path; //system path
3697
            }
3698
3699
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3700
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3701
            if (in_array(
3702
                $lp_item_type,
3703
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication', 'xapi', 'h5p', 'survey']
3704
            )
3705
            ) {
3706
                $lp_type = 1;
3707
            }
3708
3709
            // Now go through the specific cases to get the end of the path
3710
            // @todo Use constants instead of int values.
3711
            switch ($lp_type) {
3712
                case 1:
3713
                    $file = self::rl_get_resource_link_for_learnpath(
3714
                        $course_id,
3715
                        $this->get_id(),
3716
                        $item_id,
3717
                        $this->get_view_id(),
3718
                        $this->get_lp_session_id()
3719
                    );
3720
                    switch ($lp_item_type) {
3721
                        case 'document':
3722
                            // Shows a button to download the file instead of just downloading the file directly.
3723
                            $documentPathInfo = pathinfo($file);
3724
                            if (isset($documentPathInfo['extension'])) {
3725
                                $parsed = parse_url($documentPathInfo['extension']);
3726
                                if (isset($parsed['path'])) {
3727
                                    $extension = $parsed['path'];
3728
                                    $extensionsToDownload = [
3729
                                        'zip',
3730
                                        'ppt',
3731
                                        'pptx',
3732
                                        'odp',
3733
                                        'xls',
3734
                                        'xlsx',
3735
                                        'ods',
3736
                                        'csv',
3737
                                        'doc',
3738
                                        'docx',
3739
                                        'odt',
3740
                                        'dot',
3741
                                    ];
3742
3743
                                    $onlyofficeEditable = false;
3744
3745
                                    if (OnlyofficePlugin::create()->isEnabled()) {
3746
                                        $lpItem = $this->getItem($item_id);
3747
3748
                                        if ($lpItem->get_type() == 'document'
3749
                                            && OnlyofficePlugin::isExtensionAllowed($extension)
3750
                                        ) {
3751
                                            $docId = $lpItem->get_path();
3752
3753
                                            if (method_exists('OnlyofficeTools', 'getPathToView')) {
3754
                                                $pathToView = OnlyofficeTools::getPathToView($docId, false);
3755
                                                // getPathView returns empty on error, so if this is the case,
3756
                                                // fallback to normal viewer/downloader
3757
                                                if (!empty($pathToView)) {
3758
                                                    $file = $pathToView;
3759
                                                    $onlyofficeEditable = true;
3760
                                                }
3761
                                            }
3762
                                        }
3763
                                    }
3764
3765
                                    if (in_array($extension, $extensionsToDownload) && false === $onlyofficeEditable) {
3766
                                        $file = api_get_path(WEB_CODE_PATH)
3767
                                            .'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'
3768
                                            .api_get_cidreq();
3769
                                    }
3770
                                }
3771
                            }
3772
                            break;
3773
                        case 'dir':
3774
                            $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3775
                            break;
3776
                        case 'link':
3777
                            if (!empty($file)) {
3778
                                if (Link::is_youtube_link($file)) {
3779
                                    $src = Link::get_youtube_video_id($file);
3780
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3781
                                } elseif (Link::isVimeoLink($file)) {
3782
                                    $src = Link::getVimeoLinkId($file);
3783
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3784
                                } else {
3785
                                    // If the current site is HTTPS and the link is
3786
                                    // HTTP, browsers will refuse opening the link
3787
                                    $urlId = api_get_current_access_url_id();
3788
                                    $url = api_get_access_url($urlId, false);
3789
                                    $protocol = substr($url['url'], 0, 5);
3790
                                    if ($protocol === 'https') {
3791
                                        $linkProtocol = substr($file, 0, 5);
3792
                                        if ($linkProtocol === 'http:') {
3793
                                            //this is the special intervention case
3794
                                            $file = api_get_path(
3795
                                                    WEB_CODE_PATH
3796
                                                ).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3797
                                        }
3798
                                    }
3799
                                }
3800
                            }
3801
                            break;
3802
                        case 'quiz':
3803
                            // Check how much attempts of a exercise exits in lp
3804
                            $lp_item_id = $this->get_current_item_id();
3805
                            $lp_view_id = $this->get_view_id();
3806
3807
                            $prevent_reinit = null;
3808
                            if (isset($this->items[$this->current])) {
3809
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3810
                            }
3811
3812
                            if (empty($provided_toc)) {
3813
                                if ($this->debug > 0) {
3814
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3815
                                }
3816
                                $list = $this->get_toc();
3817
                            } else {
3818
                                if ($this->debug > 0) {
3819
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3820
                                }
3821
                                $list = $provided_toc;
3822
                            }
3823
3824
                            $type_quiz = false;
3825
                            foreach ($list as $toc) {
3826
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3827
                                    $type_quiz = true;
3828
                                }
3829
                            }
3830
3831
                            if ($type_quiz) {
3832
                                $lp_item_id = (int) $lp_item_id;
3833
                                $lp_view_id = (int) $lp_view_id;
3834
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3835
                                        WHERE
3836
                                            c_id = $course_id AND
3837
                                            lp_item_id='".$lp_item_id."' AND
3838
                                            lp_view_id ='".$lp_view_id."' AND
3839
                                            status='completed'";
3840
                                $result = Database::query($sql);
3841
                                $row_count = Database::fetch_row($result);
3842
                                $count_item_view = (int) $row_count[0];
3843
                                $not_multiple_attempt = 0;
3844
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3845
                                    $not_multiple_attempt = 1;
3846
                                }
3847
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3848
                            }
3849
                            break;
3850
                    }
3851
3852
                    $tmp_array = explode('/', $file);
3853
                    $document_name = $tmp_array[count($tmp_array) - 1];
3854
                    if (strpos($document_name, '_DELETED_')) {
3855
                        $file = 'blank.php?error=document_deleted';
3856
                    }
3857
                    break;
3858
                case 2:
3859
                    if ($this->debug > 2) {
3860
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3861
                    }
3862
3863
                    if ($lp_item_type != 'dir') {
3864
                        // Quite complex here:
3865
                        // We want to make sure 'http://' (and similar) links can
3866
                        // be loaded as is (withouth the Chamilo path in front) but
3867
                        // some contents use this form: resource.htm?resource=http://blablabla
3868
                        // which means we have to find a protocol at the path's start, otherwise
3869
                        // it should not be considered as an external URL.
3870
                        // if ($this->prerequisites_match($item_id)) {
3871
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3872
                            if ($this->debug > 2) {
3873
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3874
                            }
3875
                            // Distant url, return as is.
3876
                            $file = $lp_item_path;
3877
                        } else {
3878
                            if ($this->debug > 2) {
3879
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3880
                            }
3881
                            // Prevent getting untranslatable urls.
3882
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3883
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3884
                            // Prepare the path.
3885
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3886
                            // TODO: Fix this for urls with protocol header.
3887
                            $file = str_replace('//', '/', $file);
3888
                            $file = str_replace(':/', '://', $file);
3889
                            if (substr($lp_path, -1) == '/') {
3890
                                $lp_path = substr($lp_path, 0, -1);
3891
                            }
3892
3893
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3894
                                // if file not found.
3895
                                $decoded = html_entity_decode($lp_item_path);
3896
                                [$decoded] = explode('?', $decoded);
3897
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3898
                                    $file = self::rl_get_resource_link_for_learnpath(
3899
                                        $course_id,
3900
                                        $this->get_id(),
3901
                                        $item_id,
3902
                                        $this->get_view_id()
3903
                                    );
3904
                                    if (empty($file)) {
3905
                                        $file = 'blank.php?error=document_not_found';
3906
                                    } else {
3907
                                        $tmp_array = explode('/', $file);
3908
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3909
                                        if (strpos($document_name, '_DELETED_')) {
3910
                                            $file = 'blank.php?error=document_deleted';
3911
                                        } else {
3912
                                            $file = 'blank.php?error=document_not_found';
3913
                                        }
3914
                                    }
3915
                                } else {
3916
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3917
                                }
3918
                            }
3919
                        }
3920
3921
                        // We want to use parameters if they were defined in the imsmanifest
3922
                        if (strpos($file, 'blank.php') === false) {
3923
                            $lp_item_params = ltrim($lp_item_params, '?');
3924
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3925
                        }
3926
                    } else {
3927
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3928
                    }
3929
                    break;
3930
                case 3:
3931
                    if ($this->debug > 2) {
3932
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3933
                    }
3934
                    // Formatting AICC HACP append URL.
3935
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3936
                    if (!empty($lp_item_params)) {
3937
                        $aicc_append .= $lp_item_params.'&';
3938
                    }
3939
                    if ($lp_item_type != 'dir') {
3940
                        // Quite complex here:
3941
                        // We want to make sure 'http://' (and similar) links can
3942
                        // be loaded as is (withouth the Chamilo path in front) but
3943
                        // some contents use this form: resource.htm?resource=http://blablabla
3944
                        // which means we have to find a protocol at the path's start, otherwise
3945
                        // it should not be considered as an external URL.
3946
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3947
                            if ($this->debug > 2) {
3948
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3949
                            }
3950
                            // Distant url, return as is.
3951
                            $file = $lp_item_path;
3952
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3953
                            /*
3954
                            if (stristr($file,'<servername>') !== false) {
3955
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3956
                            }
3957
                            */
3958
                            if (stripos($file, '<servername>') !== false) {
3959
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3960
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3961
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3962
                            }
3963
3964
                            $file .= $aicc_append;
3965
                        } else {
3966
                            if ($this->debug > 2) {
3967
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3968
                            }
3969
                            // Prevent getting untranslatable urls.
3970
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3971
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3972
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3973
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3974
                            // TODO: Fix this for urls with protocol header.
3975
                            $file = str_replace('//', '/', $file);
3976
                            $file = str_replace(':/', '://', $file);
3977
                            $file .= $aicc_append;
3978
                        }
3979
                    } else {
3980
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3981
                    }
3982
                    break;
3983
                case 4:
3984
                    break;
3985
                default:
3986
                    break;
3987
            }
3988
            // Replace &amp; by & because &amp; will break URL with params
3989
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3990
        }
3991
        if ($this->debug > 2) {
3992
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3993
        }
3994
3995
        return $file;
3996
    }
3997
3998
    /**
3999
     * Gets the latest usable view or generate a new one.
4000
     *
4001
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
4002
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
4003
     *
4004
     * @return int DB lp_view id
4005
     */
4006
    public function get_view($attempt_num = 0, $userId = null)
4007
    {
4008
        $search = '';
4009
        // Use $attempt_num to enable multi-views management (disabled so far).
4010
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
4011
            $search = 'AND view_count = '.$attempt_num;
4012
        }
4013
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
4014
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4015
4016
        $course_id = api_get_course_int_id();
4017
        $sessionId = api_get_session_id();
4018
4019
        // Check user ID.
4020
        if (empty($userId)) {
4021
            if (empty($this->get_user_id())) {
4022
                $this->error = 'User ID is empty in learnpath::get_view()';
4023
4024
                return null;
4025
            } else {
4026
                $userId = $this->get_user_id();
4027
            }
4028
        }
4029
4030
        $sql = "SELECT iid, view_count FROM $lp_view_table
4031
        		WHERE
4032
        		    c_id = $course_id AND
4033
        		    lp_id = ".$this->get_id()." AND
4034
        		    user_id = ".$userId." AND
4035
        		    session_id = $sessionId
4036
        		    $search
4037
                ORDER BY view_count DESC";
4038
        $res = Database::query($sql);
4039
        if (Database::num_rows($res) > 0) {
4040
            $row = Database::fetch_array($res);
4041
            $this->lp_view_id = $row['iid'];
4042
        } elseif (!api_is_invitee()) {
4043
            // There is no database record, create one.
4044
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
4045
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
4046
            Database::query($sql);
4047
            $id = Database::insert_id();
4048
            $this->lp_view_id = $id;
4049
4050
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
4051
            Database::query($sql);
4052
        }
4053
4054
        return $this->lp_view_id;
4055
    }
4056
4057
    /**
4058
     * Gets the current view id.
4059
     *
4060
     * @return int View ID (from lp_view)
4061
     */
4062
    public function get_view_id()
4063
    {
4064
        if (!empty($this->lp_view_id)) {
4065
            return (int) $this->lp_view_id;
4066
        }
4067
4068
        return 0;
4069
    }
4070
4071
    /**
4072
     * Gets the update queue.
4073
     *
4074
     * @return array Array containing IDs of items to be updated by JavaScript
4075
     */
4076
    public function get_update_queue()
4077
    {
4078
        return $this->update_queue;
4079
    }
4080
4081
    /**
4082
     * Gets the user ID.
4083
     *
4084
     * @return int User ID
4085
     */
4086
    public function get_user_id()
4087
    {
4088
        if (!empty($this->user_id)) {
4089
            return (int) $this->user_id;
4090
        }
4091
4092
        return false;
4093
    }
4094
4095
    /**
4096
     * Checks if any of the items has an audio element attached.
4097
     *
4098
     * @return bool True or false
4099
     */
4100
    public function has_audio()
4101
    {
4102
        $has = false;
4103
        foreach ($this->items as $i => $item) {
4104
            if (!empty($this->items[$i]->audio)) {
4105
                $has = true;
4106
                break;
4107
            }
4108
        }
4109
4110
        return $has;
4111
    }
4112
4113
    /**
4114
     * Moves an item up and down at its level.
4115
     *
4116
     * @param int    $id        Item to move up and down
4117
     * @param string $direction Direction 'up' or 'down'
4118
     *
4119
     * @return bool|int
4120
     */
4121
    public function move_item($id, $direction)
4122
    {
4123
        $course_id = api_get_course_int_id();
4124
        if (empty($id) || empty($direction)) {
4125
            return false;
4126
        }
4127
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4128
        $sql_sel = "SELECT *
4129
                    FROM $tbl_lp_item
4130
                    WHERE
4131
                        iid = $id
4132
                    ";
4133
        $res_sel = Database::query($sql_sel);
4134
        // Check if elem exists.
4135
        if (Database::num_rows($res_sel) < 1) {
4136
            return false;
4137
        }
4138
        // Gather data.
4139
        $row = Database::fetch_array($res_sel);
4140
        $previous = $row['previous_item_id'];
4141
        $next = $row['next_item_id'];
4142
        $display = $row['display_order'];
4143
        $parent = $row['parent_item_id'];
4144
        $lp = $row['lp_id'];
4145
        // Update the item (switch with previous/next one).
4146
        switch ($direction) {
4147
            case 'up':
4148
                if ($display > 1) {
4149
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4150
                                 WHERE iid = $previous";
4151
                    $res_sel2 = Database::query($sql_sel2);
4152
                    if (Database::num_rows($res_sel2) < 1) {
4153
                        $previous_previous = 0;
4154
                    }
4155
                    // Gather data.
4156
                    $row2 = Database::fetch_array($res_sel2);
4157
                    $previous_previous = $row2['previous_item_id'];
4158
                    // Update previous_previous item (switch "next" with current).
4159
                    if ($previous_previous != 0) {
4160
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4161
                                        next_item_id = $id
4162
                                    WHERE iid = $previous_previous";
4163
                        Database::query($sql_upd2);
4164
                    }
4165
                    // Update previous item (switch with current).
4166
                    if ($previous != 0) {
4167
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4168
                                    next_item_id = $next,
4169
                                    previous_item_id = $id,
4170
                                    display_order = display_order +1
4171
                                    WHERE iid = $previous";
4172
                        Database::query($sql_upd2);
4173
                    }
4174
4175
                    // Update current item (switch with previous).
4176
                    if ($id != 0) {
4177
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4178
                                        next_item_id = $previous,
4179
                                        previous_item_id = $previous_previous,
4180
                                        display_order = display_order-1
4181
                                    WHERE c_id = ".$course_id." AND id = $id";
4182
                        Database::query($sql_upd2);
4183
                    }
4184
                    // Update next item (new previous item).
4185
                    if (!empty($next)) {
4186
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4187
                                     WHERE iid = $next";
4188
                        Database::query($sql_upd2);
4189
                    }
4190
                    $display = $display - 1;
4191
                }
4192
                break;
4193
            case 'down':
4194
                if ($next != 0) {
4195
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4196
                                 WHERE iid = $next";
4197
                    $res_sel2 = Database::query($sql_sel2);
4198
                    if (Database::num_rows($res_sel2) < 1) {
4199
                        $next_next = 0;
4200
                    }
4201
                    // Gather data.
4202
                    $row2 = Database::fetch_array($res_sel2);
4203
                    $next_next = $row2['next_item_id'];
4204
                    // Update previous item (switch with current).
4205
                    if ($previous != 0) {
4206
                        $sql_upd2 = "UPDATE $tbl_lp_item
4207
                                     SET next_item_id = $next
4208
                                     WHERE iid = $previous";
4209
                        Database::query($sql_upd2);
4210
                    }
4211
                    // Update current item (switch with previous).
4212
                    if ($id != 0) {
4213
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4214
                                     previous_item_id = $next,
4215
                                     next_item_id = $next_next,
4216
                                     display_order = display_order + 1
4217
                                     WHERE iid = $id";
4218
                        Database::query($sql_upd2);
4219
                    }
4220
4221
                    // Update next item (new previous item).
4222
                    if ($next != 0) {
4223
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4224
                                     previous_item_id = $previous,
4225
                                     next_item_id = $id,
4226
                                     display_order = display_order-1
4227
                                     WHERE iid = $next";
4228
                        Database::query($sql_upd2);
4229
                    }
4230
4231
                    // Update next_next item (switch "previous" with current).
4232
                    if ($next_next != 0) {
4233
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4234
                                     previous_item_id = $id
4235
                                     WHERE iid = $next_next";
4236
                        Database::query($sql_upd2);
4237
                    }
4238
                    $display = $display + 1;
4239
                }
4240
                break;
4241
            default:
4242
                return false;
4243
        }
4244
4245
        return $display;
4246
    }
4247
4248
    /**
4249
     * Move a LP up (display_order).
4250
     *
4251
     * @param int $lp_id      Learnpath ID
4252
     * @param int $categoryId Category ID
4253
     *
4254
     * @return bool
4255
     */
4256
    public static function move_up($lp_id, $categoryId = 0)
4257
    {
4258
        $courseId = api_get_course_int_id();
4259
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4260
4261
        $categoryCondition = '';
4262
        if (!empty($categoryId)) {
4263
            $categoryId = (int) $categoryId;
4264
            $categoryCondition = " AND category_id = $categoryId";
4265
        }
4266
        $sql = "SELECT * FROM $lp_table
4267
                WHERE c_id = $courseId
4268
                $categoryCondition
4269
                ORDER BY display_order";
4270
        $res = Database::query($sql);
4271
        if ($res === false) {
4272
            return false;
4273
        }
4274
4275
        $lps = [];
4276
        $lp_order = [];
4277
        $num = Database::num_rows($res);
4278
        // First check the order is correct, globally (might be wrong because
4279
        // of versions < 1.8.4)
4280
        if ($num > 0) {
4281
            $i = 1;
4282
            while ($row = Database::fetch_array($res)) {
4283
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4284
                    $sql = "UPDATE $lp_table SET display_order = $i
4285
                            WHERE iid = ".$row['iid'];
4286
                    Database::query($sql);
4287
                }
4288
                $row['display_order'] = $i;
4289
                $lps[$row['iid']] = $row;
4290
                $lp_order[$i] = $row['iid'];
4291
                $i++;
4292
            }
4293
        }
4294
        if ($num > 1) { // If there's only one element, no need to sort.
4295
            $order = $lps[$lp_id]['display_order'];
4296
            if ($order > 1) { // If it's the first element, no need to move up.
4297
                $sql = "UPDATE $lp_table SET display_order = $order
4298
                        WHERE iid = ".$lp_order[$order - 1];
4299
                Database::query($sql);
4300
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4301
                        WHERE iid = $lp_id";
4302
                Database::query($sql);
4303
            }
4304
        }
4305
4306
        return true;
4307
    }
4308
4309
    /**
4310
     * Move a learnpath down (display_order).
4311
     *
4312
     * @param int $lp_id      Learnpath ID
4313
     * @param int $categoryId Category ID
4314
     *
4315
     * @return bool
4316
     */
4317
    public static function move_down($lp_id, $categoryId = 0)
4318
    {
4319
        $courseId = api_get_course_int_id();
4320
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4321
4322
        $categoryCondition = '';
4323
        if (!empty($categoryId)) {
4324
            $categoryId = (int) $categoryId;
4325
            $categoryCondition = " AND category_id = $categoryId";
4326
        }
4327
4328
        $sql = "SELECT * FROM $lp_table
4329
                WHERE c_id = $courseId
4330
                $categoryCondition
4331
                ORDER BY display_order";
4332
        $res = Database::query($sql);
4333
        if ($res === false) {
4334
            return false;
4335
        }
4336
        $lps = [];
4337
        $lp_order = [];
4338
        $num = Database::num_rows($res);
4339
        $max = 0;
4340
        // First check the order is correct, globally (might be wrong because
4341
        // of versions < 1.8.4).
4342
        if ($num > 0) {
4343
            $i = 1;
4344
            while ($row = Database::fetch_array($res)) {
4345
                $max = $i;
4346
                if ($row['display_order'] != $i) {
4347
                    // If we find a gap in the order, we need to fix it.
4348
                    $sql = "UPDATE $lp_table SET display_order = $i
4349
                              WHERE iid = ".$row['iid'];
4350
                    Database::query($sql);
4351
                }
4352
                $row['display_order'] = $i;
4353
                $lps[$row['iid']] = $row;
4354
                $lp_order[$i] = $row['iid'];
4355
                $i++;
4356
            }
4357
        }
4358
        if ($num > 1) { // If there's only one element, no need to sort.
4359
            $order = $lps[$lp_id]['display_order'];
4360
            if ($order < $max) { // If it's the first element, no need to move up.
4361
                $sql = "UPDATE $lp_table SET display_order = $order
4362
                        WHERE iid = ".$lp_order[$order + 1];
4363
                Database::query($sql);
4364
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4365
                        WHERE iid = $lp_id";
4366
                Database::query($sql);
4367
            }
4368
        }
4369
4370
        return true;
4371
    }
4372
4373
    /**
4374
     * Updates learnpath attributes to point to the next element
4375
     * The last part is similar to set_current_item but processing the other way around.
4376
     */
4377
    public function next()
4378
    {
4379
        if ($this->debug > 0) {
4380
            error_log('In learnpath::next()', 0);
4381
        }
4382
        $this->last = $this->get_current_item_id();
4383
        $this->items[$this->last]->save(
4384
            false,
4385
            $this->prerequisites_match($this->last)
4386
        );
4387
        $this->autocomplete_parents($this->last);
4388
        $new_index = $this->get_next_index();
4389
        if ($this->debug > 2) {
4390
            error_log('New index: '.$new_index, 0);
4391
        }
4392
        $this->index = $new_index;
4393
        if ($this->debug > 2) {
4394
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4395
        }
4396
        $this->current = $this->ordered_items[$new_index];
4397
        if ($this->debug > 2) {
4398
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4399
        }
4400
    }
4401
4402
    /**
4403
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4404
     * class, this might be redefined to allow several behaviours depending on the document type.
4405
     *
4406
     * @param int $id Resource ID
4407
     */
4408
    public function open($id)
4409
    {
4410
        // TODO:
4411
        // set the current resource attribute to this resource
4412
        // switch on element type (redefine in child class?)
4413
        // set status for this item to "opened"
4414
        // start timer
4415
        // initialise score
4416
        $this->index = 0; //or = the last item seen (see $this->last)
4417
    }
4418
4419
    /**
4420
     * Check that all prerequisites are fulfilled. Returns true and an
4421
     * empty string on success, returns false
4422
     * and the prerequisite string on error.
4423
     * This function is based on the rules for aicc_script language as
4424
     * described in the SCORM 1.2 CAM documentation page 108.
4425
     *
4426
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4427
     *
4428
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4429
     *              string otherwise
4430
     */
4431
    public function prerequisites_match($itemId = null)
4432
    {
4433
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4434
        if ($allow) {
4435
            if (api_is_allowed_to_edit() ||
4436
                api_is_platform_admin(true) ||
4437
                api_is_drh() ||
4438
                api_is_coach(api_get_session_id(), api_get_course_int_id(), false)
4439
            ) {
4440
                return true;
4441
            }
4442
        }
4443
4444
        $debug = $this->debug;
4445
        if ($debug > 0) {
4446
            error_log('In learnpath::prerequisites_match()');
4447
        }
4448
4449
        if (empty($itemId)) {
4450
            $itemId = $this->current;
4451
        }
4452
4453
        $currentItem = $this->getItem($itemId);
4454
        if ($debug > 0) {
4455
            error_log("Checking item id $itemId");
4456
        }
4457
4458
        if ($currentItem) {
4459
            if ($this->type == 2) {
4460
                // Getting prereq from scorm
4461
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4462
            } else {
4463
                $prereq_string = $currentItem->get_prereq_string();
4464
            }
4465
4466
            if (empty($prereq_string)) {
4467
                if ($debug > 0) {
4468
                    error_log('Found prereq_string is empty return true');
4469
                }
4470
                // checks the dates values as prerequisites
4471
                $result = $this->prerequistesDatesMatch($itemId);
4472
4473
                return $result;
4474
            }
4475
4476
            // Clean spaces.
4477
            $prereq_string = str_replace(' ', '', $prereq_string);
4478
            if ($debug > 0) {
4479
                error_log('Found prereq_string: '.$prereq_string, 0);
4480
            }
4481
4482
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4483
            $result = $currentItem->parse_prereq(
4484
                $prereq_string,
4485
                $this->items,
4486
                $this->refs_list,
4487
                $this->get_user_id()
4488
            );
4489
4490
            if ($result === false) {
4491
                $this->set_error_msg($currentItem->prereq_alert);
4492
            }
4493
        } else {
4494
            $result = true;
4495
            if ($debug > 1) {
4496
                error_log('$this->items['.$itemId.'] was not an object');
4497
            }
4498
        }
4499
4500
        if ($debug > 1) {
4501
            error_log('Result: '.$result);
4502
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4503
        }
4504
4505
        if (true === $result && $itemId) {
4506
            // checks the dates values as prerequisites
4507
            $result = $this->prerequistesDatesMatch($itemId);
4508
        }
4509
4510
        return $result;
4511
    }
4512
4513
    public function prerequistesDatesMatch(int $itemId)
4514
    {
4515
        if (true === api_get_configuration_value('lp_item_prerequisite_dates')) {
4516
            $extraFieldValue = new ExtraFieldValue('lp_item');
4517
            $startDate = $extraFieldValue->get_values_by_handler_and_field_variable(
4518
                $itemId,
4519
                'start_date'
4520
            );
4521
            $endDate = $extraFieldValue->get_values_by_handler_and_field_variable(
4522
                $itemId,
4523
                'end_date'
4524
            );
4525
4526
            $now = time();
4527
            $start = !empty($startDate['value']) ? api_strtotime($startDate['value']) : 0;
4528
            $end = !empty($endDate['value']) ? api_strtotime($endDate['value']) : 0;
4529
            $result = false;
4530
4531
            if (($start == 0 && $end == 0) ||
4532
                (($start > 0 && $end == 0) && $now > $start) ||
4533
                (($start == 0 && $end > 0) && $now < $end) ||
4534
                (($start > 0 && $end > 0) && ($now > $start && $now < $end))
4535
            ) {
4536
                $result = true;
4537
            }
4538
4539
            if (!$result) {
4540
                $errMsg = get_lang('ItemCanNotBeAccessedPrerequisiteDates');
4541
                if ($start > 0 && $start > $now) {
4542
                    $errMsg = get_lang('AccessibleFrom').' '.api_format_date($start, DATE_TIME_FORMAT_LONG);
4543
                }
4544
                if ($end > 0 && $end < $now) {
4545
                    $errMsg = get_lang('NoMoreAccessible');
4546
                }
4547
                $this->set_error_msg($errMsg);
4548
                $currentItem = $this->getItem($itemId);
4549
                $currentItem->prereq_alert = $errMsg;
4550
            }
4551
4552
            return $result;
4553
        }
4554
4555
        return true;
4556
    }
4557
4558
    /**
4559
     * Updates learnpath attributes to point to the previous element
4560
     * The last part is similar to set_current_item but processing the other way around.
4561
     */
4562
    public function previous()
4563
    {
4564
        $this->last = $this->get_current_item_id();
4565
        $this->items[$this->last]->save(
4566
            false,
4567
            $this->prerequisites_match($this->last)
4568
        );
4569
        $this->autocomplete_parents($this->last);
4570
        $new_index = $this->get_previous_index();
4571
        $this->index = $new_index;
4572
        $this->current = $this->ordered_items[$new_index];
4573
    }
4574
4575
    /**
4576
     * Publishes a learnpath. This basically means show or hide the learnpath
4577
     * to normal users.
4578
     * Can be used as abstract.
4579
     *
4580
     * @param int $lp_id          Learnpath ID
4581
     * @param int $set_visibility New visibility
4582
     *
4583
     * @return bool
4584
     */
4585
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4586
    {
4587
        if (empty($lp_id)) {
4588
            return false;
4589
        }
4590
4591
        $action = 'visible';
4592
        if ($set_visibility != 1) {
4593
            $action = 'invisible';
4594
            self::toggle_publish($lp_id, 'i');
4595
        }
4596
4597
        return api_item_property_update(
4598
            api_get_course_info(),
4599
            TOOL_LEARNPATH,
4600
            $lp_id,
4601
            $action,
4602
            api_get_user_id()
4603
        );
4604
    }
4605
4606
    /**
4607
     * Publishes a learnpath category.
4608
     * This basically means show or hide the learnpath category to normal users.
4609
     *
4610
     * @param int $id
4611
     * @param int $visibility
4612
     *
4613
     * @return bool
4614
     */
4615
    public static function toggleCategoryVisibility($id, $visibility = 1)
4616
    {
4617
        $action = 'visible';
4618
        if ($visibility != 1) {
4619
            self::toggleCategoryPublish($id, 0);
4620
            $action = 'invisible';
4621
        }
4622
4623
        return api_item_property_update(
4624
            api_get_course_info(),
4625
            TOOL_LEARNPATH_CATEGORY,
4626
            $id,
4627
            $action,
4628
            api_get_user_id()
4629
        );
4630
    }
4631
4632
    /**
4633
     * Publishes a learnpath. This basically means show or hide the learnpath
4634
     * on the course homepage
4635
     * Can be used as abstract.
4636
     *
4637
     * @param int    $lp_id          Learnpath id
4638
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4639
     *
4640
     * @return bool
4641
     */
4642
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4643
    {
4644
        if (empty($lp_id)) {
4645
            return false;
4646
        }
4647
        $course_id = api_get_course_int_id();
4648
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4649
        $lp_id = (int) $lp_id;
4650
        $sql = "SELECT * FROM $tbl_lp
4651
                WHERE iid = $lp_id";
4652
        $result = Database::query($sql);
4653
        if (Database::num_rows($result)) {
4654
            $row = Database::fetch_array($result);
4655
            $name = Database::escape_string($row['name']);
4656
            if ($set_visibility === 'i') {
4657
                $v = 0;
4658
            }
4659
            if ($set_visibility === 'v') {
4660
                $v = 1;
4661
            }
4662
4663
            $session_id = api_get_session_id();
4664
            $session_condition = api_get_session_condition($session_id);
4665
4666
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4667
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4668
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4669
4670
            $extraLpCondition = '';
4671
            $extraLink = '';
4672
            if (!empty($session_id)) {
4673
                $extraLink = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session=0';
4674
                $extraLpCondition = " OR (link = '$link' AND session_id = $session_id )  ";
4675
            }
4676
4677
            $sql = "SELECT * FROM $tbl_tool
4678
                    WHERE
4679
                        c_id = $course_id AND
4680
                        (link = '$link' OR link = '$oldLink' $extraLpCondition ) AND
4681
                        image = 'scormbuilder.gif' AND
4682
                        (
4683
                            link LIKE '$link%' OR
4684
                            link LIKE '$oldLink%'
4685
                            $extraLpCondition
4686
                        )
4687
                        $session_condition
4688
                    ";
4689
4690
            $result = Database::query($sql);
4691
            $num = Database::num_rows($result);
4692
            $resultTool = Database::fetch_array($result, 'ASSOC');
4693
4694
            if ($set_visibility === 'i') {
4695
                if ($num > 0) {
4696
                    $sql = "DELETE FROM $tbl_tool
4697
                            WHERE
4698
                                c_id = $course_id AND
4699
                                (link = '$link' OR link = '$oldLink') AND
4700
                                image='scormbuilder.gif'
4701
                                $session_condition";
4702
                    Database::query($sql);
4703
                }
4704
4705
                // Disables the base course link inside a session.
4706
                if (!empty($session_id) && 0 === (int) $row['session_id']) {
4707
                    $sql = "SELECT iid FROM $tbl_tool
4708
                            WHERE
4709
                                c_id = $course_id AND
4710
                                (link = '$extraLink') AND
4711
                                image = 'scormbuilder.gif' AND
4712
                                session_id = $session_id
4713
                    ";
4714
                    $resultBaseLp = Database::query($sql);
4715
                    if (Database::num_rows($resultBaseLp)) {
4716
                        $resultBaseLpRow = Database::fetch_array($resultBaseLp);
4717
                        $id = $resultBaseLpRow['iid'];
4718
                        /*$sql = "UPDATE $tbl_tool
4719
                                SET visibility = 0
4720
                                WHERE iid = $id ";
4721
                        Database::query($sql);*/
4722
                        $sql = "DELETE FROM $tbl_tool
4723
                                WHERE iid = $id";
4724
                        Database::query($sql);
4725
                    } else {
4726
                        /*$params = [
4727
                            'category' => 'authoring',
4728
                            'c_id' => $course_id,
4729
                            'name' => $name,
4730
                            'link' => $link,
4731
                            'image' => 'scormbuilder.gif',
4732
                            'visibility' => '0',
4733
                            'admin' => '0',
4734
                            'address' => 'pastillegris.gif',
4735
                            'added_tool' => '0',
4736
                            'session_id' => $session_id,
4737
                        ];
4738
                        $insertId = Database::insert($tbl_tool, $params);
4739
                        if ($insertId) {
4740
                            $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4741
                            Database::query($sql);
4742
                        }*/
4743
                    }
4744
                }
4745
            }
4746
4747
            if ($set_visibility === 'v') {
4748
                if ($num == 0) {
4749
                    $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id)
4750
                            VALUES ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4751
                    Database::query($sql);
4752
                    $insertId = Database::insert_id();
4753
                    if ($insertId) {
4754
                        $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4755
                        Database::query($sql);
4756
                    }
4757
                }
4758
                if ($num > 0) {
4759
                    $id = $resultTool['iid'];
4760
                    $sql = "UPDATE $tbl_tool SET
4761
                        c_id = $course_id,
4762
                        name = '$name',
4763
                        link = '$link',
4764
                        image = 'scormbuilder.gif',
4765
                        visibility = '$v',
4766
                        admin = '0',
4767
                        address = 'pastillegris.gif',
4768
                        added_tool = 0,
4769
                        session_id = $session_id
4770
                    WHERE
4771
                        c_id = ".$course_id." AND
4772
                        iid = $id
4773
                    ";
4774
                    Database::query($sql);
4775
                }
4776
            }
4777
        }
4778
4779
        return false;
4780
    }
4781
4782
    /**
4783
     * Publishes a learnpath.
4784
     * Show or hide the learnpath category on the course homepage.
4785
     *
4786
     * @param int $id
4787
     * @param int $setVisibility
4788
     *
4789
     * @return bool
4790
     */
4791
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4792
    {
4793
        $courseId = api_get_course_int_id();
4794
        $sessionId = api_get_session_id();
4795
        $sessionCondition = api_get_session_condition(
4796
            $sessionId,
4797
            true,
4798
            false,
4799
            't.sessionId'
4800
        );
4801
4802
        $em = Database::getManager();
4803
        $category = self::getCategory($id);
4804
4805
        if (!$category) {
4806
            return false;
4807
        }
4808
4809
        if (empty($courseId)) {
4810
            return false;
4811
        }
4812
4813
        $link = self::getCategoryLinkForTool($id);
4814
4815
        /** @var CTool $tool */
4816
        $tool = $em->createQuery("
4817
                SELECT t FROM ChamiloCourseBundle:CTool t
4818
                WHERE
4819
                    t.cId = :course AND
4820
                    t.link = :link1 AND
4821
                    t.image = 'lp_category.gif' AND
4822
                    t.link LIKE :link2
4823
                    $sessionCondition
4824
            ")
4825
            ->setParameters([
4826
                'course' => $courseId,
4827
                'link1' => $link,
4828
                'link2' => "$link%",
4829
            ])
4830
            ->getOneOrNullResult();
4831
4832
        if ($setVisibility == 0 && $tool) {
4833
            $em->remove($tool);
4834
            $em->flush();
4835
4836
            return true;
4837
        }
4838
4839
        if ($setVisibility == 1 && !$tool) {
4840
            $tool = new CTool();
4841
            $tool
4842
                ->setCategory('authoring')
4843
                ->setCId($courseId)
4844
                ->setName(strip_tags($category->getName()))
4845
                ->setLink($link)
4846
                ->setImage('lp_category.gif')
4847
                ->setVisibility(1)
4848
                ->setAdmin(0)
4849
                ->setAddress('pastillegris.gif')
4850
                ->setAddedTool(0)
4851
                ->setSessionId($sessionId)
4852
                ->setTarget('_self');
4853
4854
            $em->persist($tool);
4855
            $em->flush();
4856
4857
            $tool->setId($tool->getIid());
4858
4859
            $em->persist($tool);
4860
            $em->flush();
4861
4862
            return true;
4863
        }
4864
4865
        if ($setVisibility == 1 && $tool) {
4866
            $tool
4867
                ->setName(strip_tags($category->getName()))
4868
                ->setVisibility(1);
4869
4870
            $em->persist($tool);
4871
            $em->flush();
4872
4873
            return true;
4874
        }
4875
4876
        return false;
4877
    }
4878
4879
    /**
4880
     * Check if the learnpath category is visible for a user.
4881
     *
4882
     * @param int $courseId
4883
     * @param int $sessionId
4884
     */
4885
    public static function categoryIsVisibleForStudent(
4886
        ?CLpCategory $category,
4887
        User $user,
4888
        $courseId = 0,
4889
        $sessionId = 0
4890
    ): bool {
4891
        if (empty($category)) {
4892
            return false;
4893
        }
4894
4895
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4896
4897
        if ($isAllowedToEdit) {
4898
            return true;
4899
        }
4900
4901
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4902
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4903
4904
        $courseInfo = api_get_course_info_by_id($courseId);
4905
4906
        $categoryVisibility = api_get_item_visibility(
4907
            $courseInfo,
4908
            TOOL_LEARNPATH_CATEGORY,
4909
            $category->getId(),
4910
            $sessionId
4911
        );
4912
4913
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4914
            return false;
4915
        }
4916
4917
        $subscriptionSettings = self::getSubscriptionSettings();
4918
4919
        if (!$subscriptionSettings['allow_add_users_to_lp_category']) {
4920
            return true;
4921
        }
4922
4923
        $noUserSubscribed = false;
4924
        $noGroupSubscribed = true;
4925
        $users = $category->getUsers();
4926
        if (empty($users) || !$users->count()) {
4927
            $noUserSubscribed = true;
4928
        } elseif ($category->hasUserAdded($user)) {
4929
            return true;
4930
        }
4931
4932
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4933
        $em = Database::getManager();
4934
4935
        /** @var ItemPropertyRepository $itemRepo */
4936
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4937
4938
        /** @var CourseRepository $courseRepo */
4939
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4940
        $session = null;
4941
        if (!empty($sessionId)) {
4942
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4943
        }
4944
4945
        $course = $courseRepo->find($courseId);
4946
4947
        if ($courseId != 0) {
4948
            // Subscribed groups to a LP
4949
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4950
                TOOL_LEARNPATH_CATEGORY,
4951
                $category->getId(),
4952
                $course,
4953
                $session
4954
            );
4955
        }
4956
4957
        if (!empty($subscribedGroupsInLp)) {
4958
            $noGroupSubscribed = false;
4959
            if (!empty($groups)) {
4960
                $groups = array_column($groups, 'iid');
4961
                /** @var CItemProperty $item */
4962
                foreach ($subscribedGroupsInLp as $item) {
4963
                    if ($item->getGroup() &&
4964
                        in_array($item->getGroup()->getId(), $groups)
4965
                    ) {
4966
                        return true;
4967
                    }
4968
                }
4969
            }
4970
        }
4971
4972
        return $noGroupSubscribed && $noUserSubscribed;
4973
    }
4974
4975
    /**
4976
     * Check if a learnpath category is published as course tool.
4977
     *
4978
     * @param int $courseId
4979
     *
4980
     * @return bool
4981
     */
4982
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4983
    {
4984
        $link = self::getCategoryLinkForTool($category->getId());
4985
        $em = Database::getManager();
4986
4987
        $tools = $em
4988
            ->createQuery("
4989
                SELECT t FROM ChamiloCourseBundle:CTool t
4990
                WHERE t.cId = :course AND
4991
                    t.name = :name AND
4992
                    t.image = 'lp_category.gif' AND
4993
                    t.link LIKE :link
4994
            ")
4995
            ->setParameters([
4996
                'course' => $courseId,
4997
                'name' => strip_tags($category->getName()),
4998
                'link' => "$link%",
4999
            ])
5000
            ->getResult();
5001
5002
        /** @var CTool $tool */
5003
        $tool = current($tools);
5004
5005
        return $tool ? $tool->getVisibility() : false;
5006
    }
5007
5008
    /**
5009
     * Restart the whole learnpath. Return the URL of the first element.
5010
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
5011
     * To use a similar method  statically, use the create_new_attempt() method.
5012
     *
5013
     * @return bool
5014
     */
5015
    public function restart()
5016
    {
5017
        if ($this->debug > 0) {
5018
            error_log('In learnpath::restart()', 0);
5019
        }
5020
        // TODO
5021
        // Call autosave method to save the current progress.
5022
        //$this->index = 0;
5023
        if (api_is_invitee()) {
5024
            return false;
5025
        }
5026
        $session_id = api_get_session_id();
5027
        $course_id = api_get_course_int_id();
5028
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
5029
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
5030
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
5031
        if ($this->debug > 2) {
5032
            error_log('Inserting new lp_view for restart: '.$sql, 0);
5033
        }
5034
        Database::query($sql);
5035
        $view_id = Database::insert_id();
5036
5037
        if ($view_id) {
5038
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
5039
            Database::query($sql);
5040
            $this->lp_view_id = $view_id;
5041
            $this->attempt = $this->attempt + 1;
5042
        } else {
5043
            $this->error = 'Could not insert into item_view table...';
5044
5045
            return false;
5046
        }
5047
        $this->autocomplete_parents($this->current);
5048
        foreach ($this->items as $index => $dummy) {
5049
            $this->items[$index]->restart();
5050
            $this->items[$index]->set_lp_view($this->lp_view_id);
5051
        }
5052
        $this->first();
5053
5054
        return true;
5055
    }
5056
5057
    /**
5058
     * Saves the current item.
5059
     *
5060
     * @return bool
5061
     */
5062
    public function save_current()
5063
    {
5064
        $debug = $this->debug;
5065
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5066
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5067
        if ($debug) {
5068
            error_log('save_current() saving item '.$this->current, 0);
5069
            error_log(''.print_r($this->items, true), 0);
5070
        }
5071
        if (isset($this->items[$this->current]) &&
5072
            is_object($this->items[$this->current])
5073
        ) {
5074
            if ($debug) {
5075
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
5076
            }
5077
5078
            $res = $this->items[$this->current]->save(
5079
                false,
5080
                $this->prerequisites_match($this->current)
5081
            );
5082
            $this->autocomplete_parents($this->current);
5083
            $status = $this->items[$this->current]->get_status();
5084
            $this->update_queue[$this->current] = $status;
5085
5086
            if ($debug) {
5087
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
5088
            }
5089
5090
            return $res;
5091
        }
5092
5093
        return false;
5094
    }
5095
5096
    /**
5097
     * Saves the given item.
5098
     *
5099
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
5100
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
5101
     *
5102
     * @return bool
5103
     */
5104
    public function save_item($item_id = null, $from_outside = true)
5105
    {
5106
        $debug = $this->debug;
5107
        if ($debug) {
5108
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
5109
        }
5110
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5111
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5112
        if (empty($item_id)) {
5113
            $item_id = (int) $_REQUEST['id'];
5114
        }
5115
5116
        if (empty($item_id)) {
5117
            $item_id = $this->get_current_item_id();
5118
        }
5119
        if (isset($this->items[$item_id]) &&
5120
            is_object($this->items[$item_id])
5121
        ) {
5122
            // Saving the item.
5123
            $res = $this->items[$item_id]->save(
5124
                $from_outside,
5125
                $this->prerequisites_match($item_id)
5126
            );
5127
5128
            if ($debug) {
5129
                error_log('update_queue before:');
5130
                error_log(print_r($this->update_queue, 1));
5131
            }
5132
            $this->autocomplete_parents($item_id);
5133
5134
            $status = $this->items[$item_id]->get_status();
5135
            $this->update_queue[$item_id] = $status;
5136
5137
            if ($debug) {
5138
                error_log('get_status(): '.$status);
5139
                error_log('update_queue after:');
5140
                error_log(print_r($this->update_queue, 1));
5141
            }
5142
5143
            return $res;
5144
        }
5145
5146
        return false;
5147
    }
5148
5149
    /**
5150
     * Update the last progress only in case.
5151
     */
5152
    public function updateLpProgress()
5153
    {
5154
        $debug = $this->debug;
5155
        if ($debug) {
5156
            error_log('In learnpath::updateLpProgress()', 0);
5157
        }
5158
        $sessionCondition = api_get_session_condition(
5159
            api_get_session_id(),
5160
            true,
5161
            false
5162
        );
5163
        $courseId = api_get_course_int_id();
5164
        $userId = $this->get_user_id();
5165
        $table = Database::get_course_table(TABLE_LP_VIEW);
5166
5167
        [$progress] = $this->get_progress_bar_text('%');
5168
        if ($progress >= 0 && $progress <= 100) {
5169
            // Check database.
5170
            $progress = (int) $progress;
5171
            $sql = "UPDATE $table SET
5172
                            progress = $progress
5173
                        WHERE
5174
                            c_id = $courseId AND
5175
                            lp_id = ".$this->get_id()." AND
5176
                            progress < $progress AND
5177
                            user_id = ".$userId." ".$sessionCondition;
5178
            // Ignore errors as some tables might not have the progress field just yet.
5179
            Database::query($sql);
5180
            if ($debug) {
5181
                error_log($sql);
5182
            }
5183
            $this->progress_db = $progress;
5184
5185
            if (100 == $progress) {
5186
                HookLearningPathEnd::create()
5187
                    ->setEventData(['lp_view_id' => $this->lp_view_id])
5188
                    ->hookLearningPathEnd();
5189
            }
5190
        }
5191
    }
5192
5193
    /**
5194
     * Saves the last item seen's ID only in case.
5195
     */
5196
    public function save_last($score = null)
5197
    {
5198
        $course_id = api_get_course_int_id();
5199
        $debug = $this->debug;
5200
        if ($debug) {
5201
            error_log('In learnpath::save_last()', 0);
5202
        }
5203
        $session_condition = api_get_session_condition(
5204
            api_get_session_id(),
5205
            true,
5206
            false
5207
        );
5208
        $table = Database::get_course_table(TABLE_LP_VIEW);
5209
5210
        $userId = $this->get_user_id();
5211
        if (empty($userId)) {
5212
            $userId = api_get_user_id();
5213
            if ($debug) {
5214
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
5215
            }
5216
        }
5217
        if (isset($this->current) && !api_is_invitee()) {
5218
            if ($debug) {
5219
                error_log('Saving current item ('.$this->current.') for later review', 0);
5220
            }
5221
            $sql = "UPDATE $table SET
5222
                        last_item = ".$this->get_current_item_id()."
5223
                    WHERE
5224
                        c_id = $course_id AND
5225
                        lp_id = ".$this->get_id()." AND
5226
                        user_id = ".$userId." ".$session_condition;
5227
            if ($debug) {
5228
                error_log('Saving last item seen : '.$sql, 0);
5229
            }
5230
            Database::query($sql);
5231
        }
5232
5233
        if (!api_is_invitee()) {
5234
            // Save progress.
5235
            [$progress] = $this->get_progress_bar_text('%');
5236
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
5237
            $scoreAsProgress = $this->getUseScoreAsProgress();
5238
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
5239
                if ($debug) {
5240
                    error_log("Return false: Dont save score: $score");
5241
                    error_log("progress: $progress");
5242
                }
5243
5244
                return false;
5245
            }
5246
5247
            if ($scoreAsProgress && $scoreAsProgressSetting) {
5248
                $storedProgress = self::getProgress(
5249
                    $this->get_id(),
5250
                    $userId,
5251
                    $course_id,
5252
                    $this->get_lp_session_id()
5253
                );
5254
5255
                // Check if the stored progress is higher than the new value
5256
                if ($storedProgress >= $progress) {
5257
                    if ($debug) {
5258
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
5259
                    }
5260
5261
                    return false;
5262
                }
5263
            }
5264
5265
            if ($progress >= 0 && $progress <= 100) {
5266
                // Check database.
5267
                $progress = (int) $progress;
5268
                $sql = "UPDATE $table SET
5269
                            progress = $progress
5270
                        WHERE
5271
                            c_id = $course_id AND
5272
                            lp_id = ".$this->get_id()." AND
5273
                            user_id = ".$userId." ".$session_condition;
5274
                // Ignore errors as some tables might not have the progress field just yet.
5275
                Database::query($sql);
5276
                if ($debug) {
5277
                    error_log($sql);
5278
                }
5279
                $this->progress_db = $progress;
5280
5281
                if (100 == $progress) {
5282
                    HookLearningPathEnd::create()
5283
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
5284
                        ->hookLearningPathEnd();
5285
                }
5286
            }
5287
        }
5288
    }
5289
5290
    /**
5291
     * Sets the current item ID (checks if valid and authorized first).
5292
     *
5293
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5294
     */
5295
    public function set_current_item($item_id = null)
5296
    {
5297
        $debug = $this->debug;
5298
        if ($debug) {
5299
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5300
        }
5301
        if (empty($item_id)) {
5302
            if ($debug) {
5303
                error_log('No new current item given, ignore...', 0);
5304
            }
5305
            // Do nothing.
5306
        } else {
5307
            if ($debug) {
5308
                error_log('New current item given is '.$item_id.'...', 0);
5309
            }
5310
            if (is_numeric($item_id)) {
5311
                $item_id = (int) $item_id;
5312
                // TODO: Check in database here.
5313
                $this->last = $this->current;
5314
                $this->current = $item_id;
5315
                // TODO: Update $this->index as well.
5316
                foreach ($this->ordered_items as $index => $item) {
5317
                    if ($item == $this->current) {
5318
                        $this->index = $index;
5319
                        break;
5320
                    }
5321
                }
5322
                if ($debug) {
5323
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5324
                }
5325
            } else {
5326
                if ($debug) {
5327
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5328
                }
5329
            }
5330
        }
5331
    }
5332
5333
    /**
5334
     * Sets the encoding.
5335
     *
5336
     * @param string $enc New encoding
5337
     *
5338
     * @return bool
5339
     *
5340
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5341
     */
5342
    public function set_encoding($enc = 'UTF-8')
5343
    {
5344
        $enc = api_refine_encoding_id($enc);
5345
        if (empty($enc)) {
5346
            $enc = api_get_system_encoding();
5347
        }
5348
        if (api_is_encoding_supported($enc)) {
5349
            $lp = $this->get_id();
5350
            if ($lp != 0) {
5351
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5352
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5353
                        WHERE iid = ".$lp;
5354
                $res = Database::query($sql);
5355
5356
                return $res;
5357
            }
5358
        }
5359
5360
        return false;
5361
    }
5362
5363
    /**
5364
     * Sets the JS lib setting in the database directly.
5365
     * This is the JavaScript library file this lp needs to load on startup.
5366
     *
5367
     * @param string $lib Proximity setting
5368
     *
5369
     * @return bool True on update success. False otherwise.
5370
     */
5371
    public function set_jslib($lib = '')
5372
    {
5373
        $lp = $this->get_id();
5374
5375
        if ($lp != 0) {
5376
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5377
            $lib = Database::escape_string($lib);
5378
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5379
                    WHERE iid = $lp";
5380
            $res = Database::query($sql);
5381
5382
            return $res;
5383
        }
5384
5385
        return false;
5386
    }
5387
5388
    /**
5389
     * Sets the name of the LP maker (publisher) (and save).
5390
     *
5391
     * @param string $name Optional string giving the new content_maker of this learnpath
5392
     *
5393
     * @return bool True
5394
     */
5395
    public function set_maker($name = '')
5396
    {
5397
        if (empty($name)) {
5398
            return false;
5399
        }
5400
        $this->maker = $name;
5401
        $table = Database::get_course_table(TABLE_LP_MAIN);
5402
        $lp_id = $this->get_id();
5403
        $sql = "UPDATE $table SET
5404
                content_maker = '".Database::escape_string($this->maker)."'
5405
                WHERE iid = $lp_id";
5406
        Database::query($sql);
5407
5408
        return true;
5409
    }
5410
5411
    /**
5412
     * Sets the name of the current learnpath (and save).
5413
     *
5414
     * @param string $name Optional string giving the new name of this learnpath
5415
     *
5416
     * @return bool True/False
5417
     */
5418
    public function set_name($name = null)
5419
    {
5420
        if (empty($name)) {
5421
            return false;
5422
        }
5423
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5424
        $name = Database::escape_string($name);
5425
5426
        $this->name = $name;
5427
5428
        $lp_id = $this->get_id();
5429
        $course_id = $this->course_info['real_id'];
5430
        $sql = "UPDATE $lp_table SET
5431
                name = '$name'
5432
                WHERE iid = $lp_id";
5433
        $result = Database::query($sql);
5434
        // If the lp is visible on the homepage, change his name there.
5435
        if (Database::affected_rows($result)) {
5436
            $session_id = api_get_session_id();
5437
            $session_condition = api_get_session_condition($session_id);
5438
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5439
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5440
            $sql = "UPDATE $tbl_tool SET name = '$name'
5441
            	    WHERE
5442
            	        c_id = $course_id AND
5443
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5444
            Database::query($sql);
5445
5446
            return true;
5447
        }
5448
5449
        return false;
5450
    }
5451
5452
    /**
5453
     * Set index specified prefix terms for all items in this path.
5454
     *
5455
     * @param string $terms_string Comma-separated list of terms
5456
     * @param string $prefix       Xapian term prefix
5457
     *
5458
     * @return bool False on error, true otherwise
5459
     */
5460
    public function set_terms_by_prefix($terms_string, $prefix)
5461
    {
5462
        $course_id = api_get_course_int_id();
5463
        if (api_get_setting('search_enabled') !== 'true') {
5464
            return false;
5465
        }
5466
5467
        if (!extension_loaded('xapian')) {
5468
            return false;
5469
        }
5470
5471
        $terms_string = trim($terms_string);
5472
        $terms = explode(',', $terms_string);
5473
        array_walk($terms, 'trim_value');
5474
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5475
5476
        // Don't do anything if no change, verify only at DB, not the search engine.
5477
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5478
            return false;
5479
        }
5480
5481
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5482
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5483
5484
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5485
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5486
        $lp_id = (int) $_POST['lp_id'];
5487
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5488
        $result = Database::query($sql);
5489
        $di = new ChamiloIndexer();
5490
5491
        while ($lp_item = Database::fetch_array($result)) {
5492
            // Get search_did.
5493
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5494
            $sql = 'SELECT * FROM %s
5495
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5496
                    LIMIT 1';
5497
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5498
5499
            //echo $sql; echo '<br>';
5500
            $res = Database::query($sql);
5501
            if (Database::num_rows($res) > 0) {
5502
                $se_ref = Database::fetch_array($res);
5503
                // Compare terms.
5504
                $doc = $di->get_document($se_ref['search_did']);
5505
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5506
                $xterms = [];
5507
                foreach ($xapian_terms as $xapian_term) {
5508
                    $xterms[] = substr($xapian_term['name'], 1);
5509
                }
5510
5511
                $dterms = $terms;
5512
                $missing_terms = array_diff($dterms, $xterms);
5513
                $deprecated_terms = array_diff($xterms, $dterms);
5514
5515
                // Save it to search engine.
5516
                foreach ($missing_terms as $term) {
5517
                    $doc->add_term($prefix.$term, 1);
5518
                }
5519
                foreach ($deprecated_terms as $term) {
5520
                    $doc->remove_term($prefix.$term);
5521
                }
5522
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5523
                $di->getDb()->flush();
5524
            }
5525
        }
5526
5527
        return true;
5528
    }
5529
5530
    /**
5531
     * Sets the theme of the LP (local/remote) (and save).
5532
     *
5533
     * @param string $name Optional string giving the new theme of this learnpath
5534
     *
5535
     * @return bool Returns true if theme name is not empty
5536
     */
5537
    public function set_theme($name = '')
5538
    {
5539
        $this->theme = $name;
5540
        $table = Database::get_course_table(TABLE_LP_MAIN);
5541
        $lp_id = $this->get_id();
5542
        $sql = "UPDATE $table
5543
                SET theme = '".Database::escape_string($this->theme)."'
5544
                WHERE iid = $lp_id";
5545
        Database::query($sql);
5546
5547
        return true;
5548
    }
5549
5550
    /**
5551
     * Sets the image of an LP (and save).
5552
     *
5553
     * @param string $name Optional string giving the new image of this learnpath
5554
     *
5555
     * @return bool Returns true if theme name is not empty
5556
     */
5557
    public function set_preview_image($name = '')
5558
    {
5559
        $this->preview_image = $name;
5560
        $table = Database::get_course_table(TABLE_LP_MAIN);
5561
        $lp_id = $this->get_id();
5562
        $sql = "UPDATE $table SET
5563
                preview_image = '".Database::escape_string($this->preview_image)."'
5564
                WHERE iid = $lp_id";
5565
        Database::query($sql);
5566
5567
        return true;
5568
    }
5569
5570
    /**
5571
     * Sets the author of a LP (and save).
5572
     *
5573
     * @param string $name Optional string giving the new author of this learnpath
5574
     *
5575
     * @return bool Returns true if author's name is not empty
5576
     */
5577
    public function set_author($name = '')
5578
    {
5579
        $this->author = $name;
5580
        $table = Database::get_course_table(TABLE_LP_MAIN);
5581
        $lp_id = $this->get_id();
5582
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5583
                WHERE iid = $lp_id";
5584
        Database::query($sql);
5585
5586
        return true;
5587
    }
5588
5589
    /**
5590
     * Sets the hide_toc_frame parameter of a LP (and save).
5591
     *
5592
     * @param int $hide 1 if frame is hidden 0 then else
5593
     *
5594
     * @return bool Returns true if author's name is not empty
5595
     */
5596
    public function set_hide_toc_frame($hide)
5597
    {
5598
        if (intval($hide) == $hide) {
5599
            $this->hide_toc_frame = $hide;
5600
            $table = Database::get_course_table(TABLE_LP_MAIN);
5601
            $lp_id = $this->get_id();
5602
            $sql = "UPDATE $table SET
5603
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5604
                    WHERE iid = $lp_id";
5605
            Database::query($sql);
5606
5607
            return true;
5608
        }
5609
5610
        return false;
5611
    }
5612
5613
    /**
5614
     * Sets the prerequisite of a LP (and save).
5615
     *
5616
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5617
     *
5618
     * @return bool returns true if prerequisite is not empty
5619
     */
5620
    public function set_prerequisite($prerequisite)
5621
    {
5622
        $this->prerequisite = (int) $prerequisite;
5623
        $table = Database::get_course_table(TABLE_LP_MAIN);
5624
        $lp_id = $this->get_id();
5625
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5626
                WHERE iid = $lp_id";
5627
        Database::query($sql);
5628
5629
        return true;
5630
    }
5631
5632
    /**
5633
     * Sets the location/proximity of the LP (local/remote) (and save).
5634
     *
5635
     * @param string $name Optional string giving the new location of this learnpath
5636
     *
5637
     * @return bool True on success / False on error
5638
     */
5639
    public function set_proximity($name = '')
5640
    {
5641
        if (empty($name)) {
5642
            return false;
5643
        }
5644
5645
        $this->proximity = $name;
5646
        $table = Database::get_course_table(TABLE_LP_MAIN);
5647
        $lp_id = $this->get_id();
5648
        $sql = "UPDATE $table SET
5649
                    content_local = '".Database::escape_string($name)."'
5650
                WHERE iid = $lp_id";
5651
        Database::query($sql);
5652
5653
        return true;
5654
    }
5655
5656
    /**
5657
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5658
     *
5659
     * @param int $id DB ID of the item
5660
     */
5661
    public function set_previous_item($id)
5662
    {
5663
        if ($this->debug > 0) {
5664
            error_log('In learnpath::set_previous_item()', 0);
5665
        }
5666
        $this->last = $id;
5667
    }
5668
5669
    /**
5670
     * Sets use_max_score.
5671
     *
5672
     * @param int $use_max_score Optional string giving the new location of this learnpath
5673
     *
5674
     * @return bool True on success / False on error
5675
     */
5676
    public function set_use_max_score($use_max_score = 1)
5677
    {
5678
        $use_max_score = (int) $use_max_score;
5679
        $this->use_max_score = $use_max_score;
5680
        $table = Database::get_course_table(TABLE_LP_MAIN);
5681
        $lp_id = $this->get_id();
5682
        $sql = "UPDATE $table SET
5683
                    use_max_score = '".$this->use_max_score."'
5684
                WHERE iid = $lp_id";
5685
        Database::query($sql);
5686
5687
        return true;
5688
    }
5689
5690
    /**
5691
     * Sets and saves the expired_on date.
5692
     *
5693
     * @param string $expired_on Optional string giving the new author of this learnpath
5694
     *
5695
     * @throws \Doctrine\ORM\OptimisticLockException
5696
     *
5697
     * @return bool Returns true if author's name is not empty
5698
     */
5699
    public function set_expired_on($expired_on)
5700
    {
5701
        $em = Database::getManager();
5702
        /** @var CLp $lp */
5703
        $lp = $em
5704
            ->getRepository('ChamiloCourseBundle:CLp')
5705
            ->findOneBy(
5706
                [
5707
                    'iid' => $this->get_id(),
5708
                ]
5709
            );
5710
5711
        if (!$lp) {
5712
            return false;
5713
        }
5714
5715
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5716
5717
        $lp->setExpiredOn($this->expired_on);
5718
        $em->persist($lp);
5719
        $em->flush();
5720
5721
        return true;
5722
    }
5723
5724
    /**
5725
     * Sets and saves the publicated_on date.
5726
     *
5727
     * @param string $publicated_on Optional string giving the new author of this learnpath
5728
     *
5729
     * @throws \Doctrine\ORM\OptimisticLockException
5730
     *
5731
     * @return bool Returns true if author's name is not empty
5732
     */
5733
    public function set_publicated_on($publicated_on)
5734
    {
5735
        $em = Database::getManager();
5736
        /** @var CLp $lp */
5737
        $lp = $em
5738
            ->getRepository('ChamiloCourseBundle:CLp')
5739
            ->findOneBy(
5740
                [
5741
                    'iid' => $this->get_id(),
5742
                ]
5743
            );
5744
5745
        if (!$lp) {
5746
            return false;
5747
        }
5748
5749
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5750
        $lp->setPublicatedOn($this->publicated_on);
5751
        $em->persist($lp);
5752
        $em->flush();
5753
5754
        return true;
5755
    }
5756
5757
    /**
5758
     * Sets and saves the expired_on date.
5759
     *
5760
     * @return bool Returns true if author's name is not empty
5761
     */
5762
    public function set_modified_on()
5763
    {
5764
        $this->modified_on = api_get_utc_datetime();
5765
        $table = Database::get_course_table(TABLE_LP_MAIN);
5766
        $lp_id = $this->get_id();
5767
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5768
                WHERE iid = $lp_id";
5769
        Database::query($sql);
5770
5771
        return true;
5772
    }
5773
5774
    /**
5775
     * Sets the object's error message.
5776
     *
5777
     * @param string $error Error message. If empty, reinits the error string
5778
     */
5779
    public function set_error_msg($error = '')
5780
    {
5781
        if ($this->debug > 0) {
5782
            error_log('In learnpath::set_error_msg()', 0);
5783
        }
5784
        if (empty($error)) {
5785
            $this->error = '';
5786
        } else {
5787
            $this->error .= $error;
5788
        }
5789
    }
5790
5791
    /**
5792
     * Launches the current item if not 'sco'
5793
     * (starts timer and make sure there is a record ready in the DB).
5794
     *
5795
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5796
     *
5797
     * @return bool
5798
     */
5799
    public function start_current_item($allow_new_attempt = false)
5800
    {
5801
        $debug = $this->debug;
5802
        if ($debug) {
5803
            error_log('In learnpath::start_current_item()');
5804
            error_log('current: '.$this->current);
5805
        }
5806
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5807
            $type = $this->get_type();
5808
            $item_type = $this->items[$this->current]->get_type();
5809
            if (($type == 2 && $item_type != 'sco') ||
5810
                ($type == 3 && $item_type != 'au') ||
5811
                (
5812
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5813
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5814
                )
5815
            ) {
5816
                if ($debug) {
5817
                    error_log('item type: '.$item_type);
5818
                    error_log('lp type: '.$type);
5819
                }
5820
                $this->items[$this->current]->open($allow_new_attempt);
5821
                $this->autocomplete_parents($this->current);
5822
                $prereq_check = $this->prerequisites_match($this->current);
5823
                if ($debug) {
5824
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5825
                }
5826
5827
                $saveStatus = learnpathItem::isLpItemAutoComplete($this->current);
5828
                if ($saveStatus) {
5829
                    $this->items[$this->current]->save(false, $prereq_check);
5830
                }
5831
            }
5832
            // If sco, then it is supposed to have been updated by some other call.
5833
            if ($item_type === 'sco') {
5834
                $this->items[$this->current]->restart();
5835
            }
5836
        }
5837
        if ($debug) {
5838
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5839
            error_log('api_get_session_id: '.api_get_session_id());
5840
            error_log('End of learnpath::start_current_item()');
5841
        }
5842
5843
        return true;
5844
    }
5845
5846
    /**
5847
     * Stops the processing and counters for the old item (as held in $this->last).
5848
     *
5849
     * @return bool True/False
5850
     */
5851
    public function stop_previous_item()
5852
    {
5853
        $debug = $this->debug;
5854
        if ($debug) {
5855
            error_log('In learnpath::stop_previous_item()');
5856
        }
5857
5858
        if ($this->last != 0 && $this->last != $this->current &&
5859
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5860
        ) {
5861
            if ($debug) {
5862
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5863
            }
5864
            switch ($this->get_type()) {
5865
                case '3':
5866
                    if ($this->items[$this->last]->get_type() != 'au') {
5867
                        if ($debug) {
5868
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5869
                        }
5870
                        $this->items[$this->last]->close();
5871
                    } else {
5872
                        if ($debug) {
5873
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5874
                        }
5875
                    }
5876
                    break;
5877
                case '2':
5878
                    if ($this->items[$this->last]->get_type() != 'sco') {
5879
                        if ($debug) {
5880
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5881
                        }
5882
                        $this->items[$this->last]->close();
5883
                    } else {
5884
                        if ($debug) {
5885
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5886
                        }
5887
                    }
5888
                    break;
5889
                case '1':
5890
                default:
5891
                    if ($debug) {
5892
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5893
                    }
5894
                    $this->items[$this->last]->close();
5895
                    break;
5896
            }
5897
        } else {
5898
            if ($debug) {
5899
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5900
            }
5901
5902
            return false;
5903
        }
5904
5905
        return true;
5906
    }
5907
5908
    /**
5909
     * Updates the default view mode from fullscreen to embedded and inversely.
5910
     *
5911
     * @return string The current default view mode ('fullscreen' or 'embedded')
5912
     */
5913
    public function update_default_view_mode()
5914
    {
5915
        $table = Database::get_course_table(TABLE_LP_MAIN);
5916
        $sql = "SELECT * FROM $table
5917
                WHERE iid = ".$this->get_id();
5918
        $res = Database::query($sql);
5919
        if (Database::num_rows($res) > 0) {
5920
            $row = Database::fetch_array($res);
5921
            $default_view_mode = $row['default_view_mod'];
5922
            $view_mode = $default_view_mode;
5923
            switch ($default_view_mode) {
5924
                case 'fullscreen': // default with popup
5925
                    $view_mode = 'embedded';
5926
                    break;
5927
                case 'embedded': // default view with left menu
5928
                    $view_mode = 'embedframe';
5929
                    break;
5930
                case 'embedframe': //folded menu
5931
                    $view_mode = 'impress';
5932
                    break;
5933
                case 'impress':
5934
                    $view_mode = 'fullscreen';
5935
                    break;
5936
            }
5937
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5938
                    WHERE iid = ".$this->get_id();
5939
            Database::query($sql);
5940
            $this->mode = $view_mode;
5941
5942
            return $view_mode;
5943
        }
5944
5945
        return -1;
5946
    }
5947
5948
    /**
5949
     * Updates the default behaviour about auto-commiting SCORM updates.
5950
     *
5951
     * @return bool True if auto-commit has been set to 'on', false otherwise
5952
     */
5953
    public function update_default_scorm_commit()
5954
    {
5955
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5956
        $sql = "SELECT * FROM $lp_table
5957
                WHERE iid = ".$this->get_id();
5958
        $res = Database::query($sql);
5959
        if (Database::num_rows($res) > 0) {
5960
            $row = Database::fetch_array($res);
5961
            $force = $row['force_commit'];
5962
            if ($force == 1) {
5963
                $force = 0;
5964
                $force_return = false;
5965
            } elseif ($force == 0) {
5966
                $force = 1;
5967
                $force_return = true;
5968
            }
5969
            $sql = "UPDATE $lp_table SET force_commit = $force
5970
                    WHERE iid = ".$this->get_id();
5971
            Database::query($sql);
5972
            $this->force_commit = $force_return;
5973
5974
            return $force_return;
5975
        }
5976
5977
        return -1;
5978
    }
5979
5980
    /**
5981
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5982
     *
5983
     * @return bool True on success, false on failure
5984
     */
5985
    public function update_display_order()
5986
    {
5987
        $course_id = api_get_course_int_id();
5988
        $table = Database::get_course_table(TABLE_LP_MAIN);
5989
        $sql = "SELECT * FROM $table
5990
                WHERE c_id = $course_id
5991
                ORDER BY display_order";
5992
        $res = Database::query($sql);
5993
        if ($res === false) {
5994
            return false;
5995
        }
5996
5997
        $num = Database::num_rows($res);
5998
        // First check the order is correct, globally (might be wrong because
5999
        // of versions < 1.8.4).
6000
        if ($num > 0) {
6001
            $i = 1;
6002
            while ($row = Database::fetch_array($res)) {
6003
                if ($row['display_order'] != $i) {
6004
                    // If we find a gap in the order, we need to fix it.
6005
                    $sql = "UPDATE $table SET display_order = $i
6006
                            WHERE iid = ".$row['iid'];
6007
                    Database::query($sql);
6008
                }
6009
                $i++;
6010
            }
6011
        }
6012
6013
        return true;
6014
    }
6015
6016
    /**
6017
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
6018
     *
6019
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
6020
     */
6021
    public function update_reinit()
6022
    {
6023
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6024
        $sql = "SELECT * FROM $lp_table
6025
                WHERE iid = ".$this->get_id();
6026
        $res = Database::query($sql);
6027
        if (Database::num_rows($res) > 0) {
6028
            $row = Database::fetch_array($res);
6029
            $force = $row['prevent_reinit'];
6030
            if ($force == 1) {
6031
                $force = 0;
6032
            } elseif ($force == 0) {
6033
                $force = 1;
6034
            }
6035
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
6036
                    WHERE iid = ".$this->get_id();
6037
            Database::query($sql);
6038
            $this->prevent_reinit = $force;
6039
6040
            return $force;
6041
        }
6042
6043
        return -1;
6044
    }
6045
6046
    /**
6047
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
6048
     *
6049
     * @return string 'single', 'multi' or 'seriousgame'
6050
     *
6051
     * @author ndiechburg <[email protected]>
6052
     */
6053
    public function get_attempt_mode()
6054
    {
6055
        //Set default value for seriousgame_mode
6056
        if (!isset($this->seriousgame_mode)) {
6057
            $this->seriousgame_mode = 0;
6058
        }
6059
        // Set default value for prevent_reinit
6060
        if (!isset($this->prevent_reinit)) {
6061
            $this->prevent_reinit = 1;
6062
        }
6063
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
6064
            return 'seriousgame';
6065
        }
6066
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
6067
            return 'single';
6068
        }
6069
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
6070
            return 'multiple';
6071
        }
6072
6073
        return 'single';
6074
    }
6075
6076
    /**
6077
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
6078
     *
6079
     * @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...
6080
     *
6081
     * @return bool
6082
     *
6083
     * @author ndiechburg <[email protected]>
6084
     */
6085
    public function set_attempt_mode($mode)
6086
    {
6087
        switch ($mode) {
6088
            case 'seriousgame':
6089
                $sg_mode = 1;
6090
                $prevent_reinit = 1;
6091
                break;
6092
            case 'single':
6093
                $sg_mode = 0;
6094
                $prevent_reinit = 1;
6095
                break;
6096
            case 'multiple':
6097
                $sg_mode = 0;
6098
                $prevent_reinit = 0;
6099
                break;
6100
            default:
6101
                $sg_mode = 0;
6102
                $prevent_reinit = 0;
6103
                break;
6104
        }
6105
        $this->prevent_reinit = $prevent_reinit;
6106
        $this->seriousgame_mode = $sg_mode;
6107
        $table = Database::get_course_table(TABLE_LP_MAIN);
6108
        $sql = "UPDATE $table SET
6109
                prevent_reinit = $prevent_reinit ,
6110
                seriousgame_mode = $sg_mode
6111
                WHERE iid = ".$this->get_id();
6112
        $res = Database::query($sql);
6113
        if ($res) {
6114
            return true;
6115
        } else {
6116
            return false;
6117
        }
6118
    }
6119
6120
    /**
6121
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
6122
     *
6123
     * @author ndiechburg <[email protected]>
6124
     */
6125
    public function switch_attempt_mode()
6126
    {
6127
        $mode = $this->get_attempt_mode();
6128
        switch ($mode) {
6129
            case 'single':
6130
                $next_mode = 'multiple';
6131
                break;
6132
            case 'multiple':
6133
                $next_mode = 'seriousgame';
6134
                break;
6135
            case 'seriousgame':
6136
            default:
6137
                $next_mode = 'single';
6138
                break;
6139
        }
6140
        $this->set_attempt_mode($next_mode);
6141
    }
6142
6143
    /**
6144
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
6145
     * but possibility to do again a completed item.
6146
     *
6147
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
6148
     *
6149
     * @author ndiechburg <[email protected]>
6150
     */
6151
    public function set_seriousgame_mode()
6152
    {
6153
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6154
        $sql = "SELECT * FROM $lp_table
6155
                WHERE iid = ".$this->get_id();
6156
        $res = Database::query($sql);
6157
        if (Database::num_rows($res) > 0) {
6158
            $row = Database::fetch_array($res);
6159
            $force = $row['seriousgame_mode'];
6160
            if ($force == 1) {
6161
                $force = 0;
6162
            } elseif ($force == 0) {
6163
                $force = 1;
6164
            }
6165
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
6166
			        WHERE iid = ".$this->get_id();
6167
            Database::query($sql);
6168
            $this->seriousgame_mode = $force;
6169
6170
            return $force;
6171
        }
6172
6173
        return -1;
6174
    }
6175
6176
    /**
6177
     * Updates the "scorm_debug" value that shows or hide the debug window.
6178
     *
6179
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6180
     */
6181
    public function update_scorm_debug()
6182
    {
6183
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6184
        $sql = "SELECT * FROM $lp_table
6185
                WHERE iid = ".$this->get_id();
6186
        $res = Database::query($sql);
6187
        if (Database::num_rows($res) > 0) {
6188
            $row = Database::fetch_array($res);
6189
            $force = $row['debug'];
6190
            if ($force == 1) {
6191
                $force = 0;
6192
            } elseif ($force == 0) {
6193
                $force = 1;
6194
            }
6195
            $sql = "UPDATE $lp_table SET debug = $force
6196
                    WHERE iid = ".$this->get_id();
6197
            Database::query($sql);
6198
            $this->scorm_debug = $force;
6199
6200
            return $force;
6201
        }
6202
6203
        return -1;
6204
    }
6205
6206
    /**
6207
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6208
     *
6209
     * @author Kevin Van Den Haute
6210
     *
6211
     * @param  array
6212
     */
6213
    public function tree_array($array)
6214
    {
6215
        $array = $this->sort_tree_array($array);
6216
        $this->create_tree_array($array);
6217
    }
6218
6219
    /**
6220
     * Creates an array with the elements of the learning path tree in it.
6221
     *
6222
     * @author Kevin Van Den Haute
6223
     *
6224
     * @param array $array
6225
     * @param int   $parent
6226
     * @param int   $depth
6227
     * @param array $tmp
6228
     */
6229
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6230
    {
6231
        if (is_array($array)) {
6232
            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...
6233
                if ($array[$i]['parent_item_id'] == $parent) {
6234
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6235
                        $tmp[] = $array[$i]['parent_item_id'];
6236
                        $depth++;
6237
                    }
6238
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6239
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6240
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6241
6242
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6243
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6244
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6245
                    $this->arrMenu[] = [
6246
                        'id' => $array[$i]['id'],
6247
                        'ref' => $ref,
6248
                        'item_type' => $array[$i]['item_type'],
6249
                        'title' => $array[$i]['title'],
6250
                        'title_raw' => $array[$i]['title_raw'],
6251
                        'path' => $path,
6252
                        'description' => $array[$i]['description'],
6253
                        'parent_item_id' => $array[$i]['parent_item_id'],
6254
                        'previous_item_id' => $array[$i]['previous_item_id'],
6255
                        'next_item_id' => $array[$i]['next_item_id'],
6256
                        'min_score' => $array[$i]['min_score'],
6257
                        'max_score' => $array[$i]['max_score'],
6258
                        'mastery_score' => $array[$i]['mastery_score'],
6259
                        'display_order' => $array[$i]['display_order'],
6260
                        'prerequisite' => $preq,
6261
                        'depth' => $depth,
6262
                        'audio' => $audio,
6263
                        'prerequisite_min_score' => $prerequisiteMinScore,
6264
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6265
                    ];
6266
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6267
                }
6268
            }
6269
        }
6270
    }
6271
6272
    /**
6273
     * Sorts a multi dimensional array by parent id and display order.
6274
     *
6275
     * @author Kevin Van Den Haute
6276
     *
6277
     * @param array $array (array with al the learning path items in it)
6278
     *
6279
     * @return array
6280
     */
6281
    public function sort_tree_array($array)
6282
    {
6283
        foreach ($array as $key => $row) {
6284
            $parent[$key] = $row['parent_item_id'];
6285
            $position[$key] = $row['display_order'];
6286
        }
6287
6288
        if (count($array) > 0) {
6289
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6290
        }
6291
6292
        return $array;
6293
    }
6294
6295
    /**
6296
     * Function that creates a html list of learning path items so that we can add audio files to them.
6297
     *
6298
     * @author Kevin Van Den Haute
6299
     *
6300
     * @return string
6301
     */
6302
    public function overview()
6303
    {
6304
        $return = '';
6305
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6306
6307
        // we need to start a form when we want to update all the mp3 files
6308
        if ($update_audio == 'true') {
6309
            $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">';
6310
        }
6311
        $return .= '<div id="message"></div>';
6312
        if (count($this->items) == 0) {
6313
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6314
        } else {
6315
            $return_audio = '<table class="table table-hover table-striped data_table">';
6316
            $return_audio .= '<tr>';
6317
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6318
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6319
            $return_audio .= '</tr>';
6320
6321
            if ($update_audio != 'true') {
6322
                $return .= '<div class="col-md-12">';
6323
                $return .= self::return_new_tree($update_audio);
6324
                $return .= '</div>';
6325
                $return .= Display::div(
6326
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6327
                    ['style' => 'float:left; margin-top:15px;width:100%']
6328
                );
6329
            } else {
6330
                $return_audio .= self::return_new_tree($update_audio);
6331
                $return .= $return_audio.'</table>';
6332
            }
6333
6334
            // We need to close the form when we are updating the mp3 files.
6335
            if ($update_audio == 'true') {
6336
                $return .= '<div class="footer-audio">';
6337
                $return .= Display::button(
6338
                    'save_audio',
6339
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6340
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6341
                );
6342
                $return .= '</div>';
6343
            }
6344
        }
6345
6346
        // We need to close the form when we are updating the mp3 files.
6347
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6348
            $return .= '</form>';
6349
        }
6350
6351
        return $return;
6352
    }
6353
6354
    /**
6355
     * @param string $update_audio
6356
     *
6357
     * @return array
6358
     */
6359
    public function processBuildMenuElements($update_audio = 'false')
6360
    {
6361
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6362
        $arrLP = $this->getItemsForForm();
6363
6364
        $this->tree_array($arrLP);
6365
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6366
        unset($this->arrMenu);
6367
        $default_data = null;
6368
        $default_content = null;
6369
        $elements = [];
6370
        $return_audio = null;
6371
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6372
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6373
        $countItems = count($arrLP);
6374
6375
        $upIcon = Display::return_icon(
6376
            'up.png',
6377
            get_lang('Up'),
6378
            [],
6379
            ICON_SIZE_TINY
6380
        );
6381
6382
        $disableUpIcon = Display::return_icon(
6383
            'up_na.png',
6384
            get_lang('Up'),
6385
            [],
6386
            ICON_SIZE_TINY
6387
        );
6388
6389
        $downIcon = Display::return_icon(
6390
            'down.png',
6391
            get_lang('Down'),
6392
            [],
6393
            ICON_SIZE_TINY
6394
        );
6395
6396
        $disableDownIcon = Display::return_icon(
6397
            'down_na.png',
6398
            get_lang('Down'),
6399
            [],
6400
            ICON_SIZE_TINY
6401
        );
6402
6403
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6404
6405
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6406
        $plugin = null;
6407
        if ($pluginCalendar) {
6408
            $plugin = LearningCalendarPlugin::create();
6409
        }
6410
6411
        for ($i = 0; $i < $countItems; $i++) {
6412
            $parent_id = $arrLP[$i]['parent_item_id'];
6413
            $title = $arrLP[$i]['title'];
6414
            $title_cut = $arrLP[$i]['title_raw'];
6415
            if ($show === false) {
6416
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6417
            }
6418
            // Link for the documents
6419
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6420
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6421
                $title_cut = Display::url(
6422
                    $title_cut,
6423
                    $url,
6424
                    [
6425
                        'class' => 'ajax moved',
6426
                        'data-title' => $title,
6427
                        'title' => $title,
6428
                    ]
6429
                );
6430
            }
6431
6432
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6433
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6434
                Session::write('pathItem', $arrLP[$i]['path']);
6435
            }
6436
6437
            $oddClass = 'row_even';
6438
            if (($i % 2) == 0) {
6439
                $oddClass = 'row_odd';
6440
            }
6441
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6442
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6443
6444
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6445
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6446
            } else {
6447
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6448
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6449
                } else {
6450
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6451
                        $icon = Display::return_icon('flag_checkered.png');
6452
                    } elseif (TOOL_XAPI === $arrLP[$i]['item_type']) {
6453
                        $icon = Display::return_icon('import_scorm.png');
6454
                    } elseif (TOOL_H5P) {
6455
                        $icon = Display::return_icon('plugin_h5p_import.png');
6456
                    } elseif (TOOL_SURVEY === $arrLP[$i]['item_type']) {
6457
                        $icon = Display::return_icon('survey.gif');
6458
                    } else {
6459
                        $icon = Display::return_icon('folder_document.gif');
6460
                    }
6461
                }
6462
            }
6463
6464
            // The audio column.
6465
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6466
            $audio = '';
6467
            if (!$update_audio || $update_audio != 'true') {
6468
                if (empty($arrLP[$i]['audio'])) {
6469
                    $audio .= '';
6470
                }
6471
            } else {
6472
                $types = self::getChapterTypes();
6473
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6474
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6475
                    if (!empty($arrLP[$i]['audio'])) {
6476
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6477
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6478
                    }
6479
                }
6480
            }
6481
6482
            $return_audio .= Display::span($icon.' '.$title).
6483
                Display::tag(
6484
                    'td',
6485
                    $audio,
6486
                    ['style' => '']
6487
                );
6488
            $return_audio .= '</td>';
6489
            $move_icon = '';
6490
            $move_item_icon = '';
6491
            $edit_icon = '';
6492
            $delete_icon = '';
6493
            $audio_icon = '';
6494
            $prerequisities_icon = '';
6495
            $forumIcon = '';
6496
            $previewIcon = '';
6497
            $pluginCalendarIcon = '';
6498
            $orderIcons = '';
6499
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6500
6501
            if ($is_allowed_to_edit) {
6502
                if (!$update_audio || $update_audio != 'true') {
6503
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6504
                        $move_icon .= '<a class="moved" href="#">';
6505
                        $move_icon .= Display::return_icon(
6506
                            'move_everywhere.png',
6507
                            get_lang('Move'),
6508
                            [],
6509
                            ICON_SIZE_TINY
6510
                        );
6511
                        $move_icon .= '</a>';
6512
                    }
6513
                }
6514
6515
                // No edit for this item types
6516
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6517
                    if ($arrLP[$i]['item_type'] != 'dir') {
6518
                        $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">';
6519
                        $edit_icon .= Display::return_icon(
6520
                            'edit.png',
6521
                            get_lang('LearnpathEditModule'),
6522
                            [],
6523
                            ICON_SIZE_TINY
6524
                        );
6525
                        $edit_icon .= '</a>';
6526
6527
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6528
                            $forumThread = null;
6529
                            if (isset($this->items[$arrLP[$i]['id']])) {
6530
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6531
                                    $this->course_int_id,
6532
                                    $this->lp_session_id
6533
                                );
6534
                            }
6535
                            if ($forumThread) {
6536
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6537
                                        'action' => 'dissociate_forum',
6538
                                        'id' => $arrLP[$i]['id'],
6539
                                        'lp_id' => $this->lp_id,
6540
                                    ]);
6541
                                $forumIcon = Display::url(
6542
                                    Display::return_icon(
6543
                                        'forum.png',
6544
                                        get_lang('DissociateForumToLPItem'),
6545
                                        [],
6546
                                        ICON_SIZE_TINY
6547
                                    ),
6548
                                    $forumIconUrl,
6549
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6550
                                );
6551
                            } else {
6552
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6553
                                        'action' => 'create_forum',
6554
                                        'id' => $arrLP[$i]['id'],
6555
                                        'lp_id' => $this->lp_id,
6556
                                    ]);
6557
                                $forumIcon = Display::url(
6558
                                    Display::return_icon(
6559
                                        'forum.png',
6560
                                        get_lang('AssociateForumToLPItem'),
6561
                                        [],
6562
                                        ICON_SIZE_TINY
6563
                                    ),
6564
                                    $forumIconUrl,
6565
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6566
                                );
6567
                            }
6568
                        }
6569
                    } else {
6570
                        $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">';
6571
                        $edit_icon .= Display::return_icon(
6572
                            'edit.png',
6573
                            get_lang('LearnpathEditModule'),
6574
                            [],
6575
                            ICON_SIZE_TINY
6576
                        );
6577
                        $edit_icon .= '</a>';
6578
                    }
6579
                } else {
6580
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6581
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6582
                        $edit_icon .= Display::return_icon(
6583
                            'edit.png',
6584
                            get_lang('Edit'),
6585
                            [],
6586
                            ICON_SIZE_TINY
6587
                        );
6588
                        $edit_icon .= '</a>';
6589
                    }
6590
                }
6591
6592
                if ($pluginCalendar) {
6593
                    $pluginLink = $pluginUrl.
6594
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6595
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6596
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6597
                    if ($itemInfo && $itemInfo['value'] == 1) {
6598
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6599
                    }
6600
                    $pluginCalendarIcon = Display::url(
6601
                        $iconCalendar,
6602
                        $pluginLink,
6603
                        ['class' => 'btn btn-default']
6604
                    );
6605
                }
6606
6607
                if ($arrLP[$i]['item_type'] != 'final_item') {
6608
                    $orderIcons = Display::url(
6609
                        $upIcon,
6610
                        'javascript:void(0)',
6611
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6612
                    );
6613
                    $orderIcons .= Display::url(
6614
                        $downIcon,
6615
                        'javascript:void(0)',
6616
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6617
                    );
6618
                }
6619
6620
                $delete_icon .= ' <a
6621
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6622
                    onclick="return confirmation(\''.addslashes($title).'\');"
6623
                    class="btn btn-default">';
6624
                $delete_icon .= Display::return_icon(
6625
                    'delete.png',
6626
                    get_lang('LearnpathDeleteModule'),
6627
                    [],
6628
                    ICON_SIZE_TINY
6629
                );
6630
                $delete_icon .= '</a>';
6631
6632
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6633
                $previewImage = Display::return_icon(
6634
                    'preview_view.png',
6635
                    get_lang('Preview'),
6636
                    [],
6637
                    ICON_SIZE_TINY
6638
                );
6639
6640
                switch ($arrLP[$i]['item_type']) {
6641
                    case TOOL_DOCUMENT:
6642
                    case TOOL_LP_FINAL_ITEM:
6643
                    case TOOL_READOUT_TEXT:
6644
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6645
                        $previewIcon = Display::url(
6646
                            $previewImage,
6647
                            $urlPreviewLink,
6648
                            [
6649
                                'target' => '_blank',
6650
                                'class' => 'btn btn-default',
6651
                                'data-title' => $arrLP[$i]['title'],
6652
                                'title' => $arrLP[$i]['title'],
6653
                            ]
6654
                        );
6655
                        break;
6656
                    case TOOL_THREAD:
6657
                    case TOOL_FORUM:
6658
                    case TOOL_QUIZ:
6659
                    case TOOL_STUDENTPUBLICATION:
6660
                    case TOOL_LP_FINAL_ITEM:
6661
                    case TOOL_LINK:
6662
                        $class = 'btn btn-default';
6663
                        $target = '_blank';
6664
                        $link = self::rl_get_resource_link_for_learnpath(
6665
                            $this->course_int_id,
6666
                            $this->lp_id,
6667
                            $arrLP[$i]['id'],
6668
                            0
6669
                        );
6670
                        $previewIcon = Display::url(
6671
                            $previewImage,
6672
                            $link,
6673
                            [
6674
                                'class' => $class,
6675
                                'data-title' => $arrLP[$i]['title'],
6676
                                'title' => $arrLP[$i]['title'],
6677
                                'target' => $target,
6678
                            ]
6679
                        );
6680
                        break;
6681
                    default:
6682
                        $previewIcon = Display::url(
6683
                            $previewImage,
6684
                            $url.'&action=view_item',
6685
                            ['class' => 'btn btn-default', 'target' => '_blank']
6686
                        );
6687
                        break;
6688
                }
6689
6690
                if ($arrLP[$i]['item_type'] != 'dir') {
6691
                    $prerequisities_icon = Display::url(
6692
                        Display::return_icon(
6693
                            'accept.png',
6694
                            get_lang('LearnpathPrerequisites'),
6695
                            [],
6696
                            ICON_SIZE_TINY
6697
                        ),
6698
                        $url.'&action=edit_item_prereq',
6699
                        ['class' => 'btn btn-default']
6700
                    );
6701
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6702
                        $move_item_icon = Display::url(
6703
                            Display::return_icon(
6704
                                'move.png',
6705
                                get_lang('Move'),
6706
                                [],
6707
                                ICON_SIZE_TINY
6708
                            ),
6709
                            $url.'&action=move_item',
6710
                            ['class' => 'btn btn-default']
6711
                        );
6712
                    }
6713
                    $audio_icon = Display::url(
6714
                        Display::return_icon(
6715
                            'audio.png',
6716
                            get_lang('UplUpload'),
6717
                            [],
6718
                            ICON_SIZE_TINY
6719
                        ),
6720
                        $url.'&action=add_audio',
6721
                        ['class' => 'btn btn-default']
6722
                    );
6723
                }
6724
            }
6725
            if ($update_audio != 'true') {
6726
                $row = $move_icon.' '.$icon.
6727
                    Display::span($title_cut).
6728
                    Display::tag(
6729
                        'div',
6730
                        "<div class=\"btn-group btn-group-xs\">
6731
                                    $previewIcon
6732
                                    $audio
6733
                                    $edit_icon
6734
                                    $pluginCalendarIcon
6735
                                    $forumIcon
6736
                                    $prerequisities_icon
6737
                                    $move_item_icon
6738
                                    $audio_icon
6739
                                    $orderIcons
6740
                                    $delete_icon
6741
                                </div>",
6742
                        ['class' => 'btn-toolbar button_actions']
6743
                    );
6744
            } else {
6745
                $row =
6746
                    Display::span($title.$icon).
6747
                    Display::span($audio, ['class' => 'button_actions']);
6748
            }
6749
6750
            $default_data[$arrLP[$i]['id']] = $row;
6751
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6752
6753
            if (empty($parent_id)) {
6754
                $elements[$arrLP[$i]['id']]['data'] = $row;
6755
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6756
            } else {
6757
                $parent_arrays = [];
6758
                if ($arrLP[$i]['depth'] > 1) {
6759
                    // Getting list of parents
6760
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6761
                        foreach ($arrLP as $item) {
6762
                            if ($item['id'] == $parent_id) {
6763
                                if ($item['parent_item_id'] == 0) {
6764
                                    $parent_id = $item['id'];
6765
                                    break;
6766
                                } else {
6767
                                    $parent_id = $item['parent_item_id'];
6768
                                    if (empty($parent_arrays)) {
6769
                                        $parent_arrays[] = intval($item['id']);
6770
                                    }
6771
                                    $parent_arrays[] = $parent_id;
6772
                                    break;
6773
                                }
6774
                            }
6775
                        }
6776
                    }
6777
                }
6778
6779
                if (!empty($parent_arrays)) {
6780
                    $parent_arrays = array_reverse($parent_arrays);
6781
                    $val = '$elements';
6782
                    $x = 0;
6783
                    foreach ($parent_arrays as $item) {
6784
                        if ($x != count($parent_arrays) - 1) {
6785
                            $val .= '["'.$item.'"]["children"]';
6786
                        } else {
6787
                            $val .= '["'.$item.'"]["children"]';
6788
                        }
6789
                        $x++;
6790
                    }
6791
                    $val .= "";
6792
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6793
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6794
                } else {
6795
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6796
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6797
                }
6798
            }
6799
        }
6800
6801
        return [
6802
            'elements' => $elements,
6803
            'default_data' => $default_data,
6804
            'default_content' => $default_content,
6805
            'return_audio' => $return_audio,
6806
        ];
6807
    }
6808
6809
    /**
6810
     * @param string $updateAudio true/false strings
6811
     *
6812
     * @return string
6813
     */
6814
    public function returnLpItemList($updateAudio)
6815
    {
6816
        $result = $this->processBuildMenuElements($updateAudio);
6817
6818
        $html = self::print_recursive(
6819
            $result['elements'],
6820
            $result['default_data'],
6821
            $result['default_content']
6822
        );
6823
6824
        if (!empty($html)) {
6825
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6826
        }
6827
6828
        return $html;
6829
    }
6830
6831
    /**
6832
     * @param string $update_audio
6833
     * @param bool   $drop_element_here
6834
     *
6835
     * @return string
6836
     */
6837
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6838
    {
6839
        $result = $this->processBuildMenuElements($update_audio);
6840
6841
        $list = '<ul id="lp_item_list">';
6842
        $tree = $this->print_recursive(
6843
            $result['elements'],
6844
            $result['default_data'],
6845
            $result['default_content']
6846
        );
6847
6848
        if (!empty($tree)) {
6849
            $list .= $tree;
6850
        } else {
6851
            if ($drop_element_here) {
6852
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6853
            }
6854
        }
6855
        $list .= '</ul>';
6856
6857
        $return = Display::panelCollapse(
6858
            $this->getNameNoTags(),
6859
            $list,
6860
            'scorm-list',
6861
            null,
6862
            'scorm-list-accordion',
6863
            'scorm-list-collapse'
6864
        );
6865
6866
        if ($update_audio === 'true') {
6867
            $return = $result['return_audio'];
6868
        }
6869
6870
        return $return;
6871
    }
6872
6873
    /**
6874
     * @param array $elements
6875
     * @param array $default_data
6876
     * @param array $default_content
6877
     *
6878
     * @return string
6879
     */
6880
    public function print_recursive($elements, $default_data, $default_content)
6881
    {
6882
        $return = '';
6883
        foreach ($elements as $key => $item) {
6884
            if (TOOL_LP_FINAL_ITEM === $item['type']) {
6885
                $key = 'final_item';
6886
            }
6887
            if (isset($item['load_data']) || empty($item['data'])) {
6888
                $item['data'] = $default_data[$item['load_data']];
6889
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6890
            }
6891
            $sub_list = '';
6892
            if (isset($item['type']) && $item['type'] === 'dir') {
6893
                // empty value
6894
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6895
            }
6896
            if (empty($item['children'])) {
6897
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6898
                $active = null;
6899
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6900
                    $active = 'active';
6901
                }
6902
                $return .= Display::tag(
6903
                    'li',
6904
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6905
                    ['id' => $key, 'class' => 'record li_container']
6906
                );
6907
            } else {
6908
                // Sections
6909
                $data = '';
6910
                if (isset($item['children'])) {
6911
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6912
                }
6913
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6914
                $return .= Display::tag(
6915
                    'li',
6916
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6917
                    ['id' => $key, 'class' => 'record li_container']
6918
                );
6919
            }
6920
        }
6921
6922
        return $return;
6923
    }
6924
6925
    /**
6926
     * This function builds the action menu.
6927
     *
6928
     * @param bool   $returnString           Optional
6929
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6930
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6931
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6932
     * @param string $action
6933
     * @param array  $extraField
6934
     *
6935
     * @return string
6936
     */
6937
    public function build_action_menu(
6938
        $returnString = false,
6939
        $showRequirementButtons = true,
6940
        $isConfigPage = false,
6941
        $allowExpand = true,
6942
        $action = '',
6943
        $extraField = []
6944
    ) {
6945
        $actionsRight = '';
6946
        $lpId = $this->lp_id;
6947
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6948
            $back = Display::url(
6949
                Display::return_icon(
6950
                    'back.png',
6951
                    get_lang('ReturnToLearningPaths'),
6952
                    '',
6953
                    ICON_SIZE_MEDIUM
6954
                ),
6955
                'lp_controller.php?'.api_get_cidreq()
6956
            );
6957
        } else {
6958
            $back = Display::url(
6959
                Display::return_icon(
6960
                    'back.png',
6961
                    get_lang('Back'),
6962
                    '',
6963
                    ICON_SIZE_MEDIUM
6964
                ),
6965
                $extraField['backTo']
6966
            );
6967
        }
6968
6969
        $actionsLeft = $back;
6970
        $actionsLeft .= Display::url(
6971
            Display::return_icon(
6972
                'preview_view.png',
6973
                get_lang('Preview'),
6974
                '',
6975
                ICON_SIZE_MEDIUM
6976
            ),
6977
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6978
                'action' => 'view',
6979
                'lp_id' => $lpId,
6980
                'isStudentView' => 'true',
6981
            ])
6982
        );
6983
6984
        $actionsLeft .= Display::url(
6985
            Display::return_icon(
6986
                'upload_audio.png',
6987
                get_lang('UpdateAllAudioFragments'),
6988
                '',
6989
                ICON_SIZE_MEDIUM
6990
            ),
6991
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6992
                'action' => 'admin_view',
6993
                'lp_id' => $lpId,
6994
                'updateaudio' => 'true',
6995
            ])
6996
        );
6997
6998
        $subscriptionSettings = self::getSubscriptionSettings();
6999
        $request = api_request_uri();
7000
        if (strpos($request, 'edit') === false) {
7001
            $actionsLeft .= Display::url(
7002
                Display::return_icon(
7003
                    'settings.png',
7004
                    get_lang('CourseSettings'),
7005
                    '',
7006
                    ICON_SIZE_MEDIUM
7007
                ),
7008
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7009
                    'action' => 'edit',
7010
                    'lp_id' => $lpId,
7011
                ])
7012
            );
7013
        }
7014
7015
        if ((strpos($request, 'build') === false &&
7016
            strpos($request, 'add_item') === false) ||
7017
            in_array($action, ['add_audio'])
7018
        ) {
7019
            $actionsLeft .= Display::url(
7020
                Display::return_icon(
7021
                    'edit.png',
7022
                    get_lang('Edit'),
7023
                    '',
7024
                    ICON_SIZE_MEDIUM
7025
                ),
7026
                'lp_controller.php?'.http_build_query([
7027
                    'action' => 'build',
7028
                    'lp_id' => $lpId,
7029
                ]).'&'.api_get_cidreq()
7030
            );
7031
        }
7032
7033
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
7034
            if ($this->subscribeUsers == 1 &&
7035
                $subscriptionSettings['allow_add_users_to_lp']) {
7036
                $actionsLeft .= Display::url(
7037
                    Display::return_icon(
7038
                        'user.png',
7039
                        get_lang('SubscribeUsersToLp'),
7040
                        '',
7041
                        ICON_SIZE_MEDIUM
7042
                    ),
7043
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
7044
                );
7045
            }
7046
        }
7047
7048
        if ($allowExpand) {
7049
            $actionsLeft .= Display::url(
7050
                Display::return_icon(
7051
                    'expand.png',
7052
                    get_lang('Expand'),
7053
                    ['id' => 'expand'],
7054
                    ICON_SIZE_MEDIUM
7055
                ).
7056
                Display::return_icon(
7057
                    'contract.png',
7058
                    get_lang('Collapse'),
7059
                    ['id' => 'contract', 'class' => 'hide'],
7060
                    ICON_SIZE_MEDIUM
7061
                ),
7062
                '#',
7063
                ['role' => 'button', 'id' => 'hide_bar_template']
7064
            );
7065
        }
7066
7067
        if ($showRequirementButtons) {
7068
            $buttons = [
7069
                [
7070
                    'title' => get_lang('SetPrerequisiteForEachItem'),
7071
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7072
                        'action' => 'set_previous_step_as_prerequisite',
7073
                        'lp_id' => $lpId,
7074
                    ]),
7075
                ],
7076
                [
7077
                    'title' => get_lang('ClearAllPrerequisites'),
7078
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7079
                        'action' => 'clear_prerequisites',
7080
                        'lp_id' => $lpId,
7081
                    ]),
7082
                ],
7083
            ];
7084
            $actionsRight = Display::groupButtonWithDropDown(
7085
                get_lang('PrerequisitesOptions'),
7086
                $buttons,
7087
                true
7088
            );
7089
        }
7090
7091
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
7092
            $actionsLeft .= Display::url(
7093
                Display::return_icon(
7094
                    'add-groups.png',
7095
                    get_lang('Author'),
7096
                    '',
7097
                    ICON_SIZE_MEDIUM
7098
                ),
7099
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7100
                    'action' => 'author_view',
7101
                    'lp_id' => $lpId,
7102
                ])
7103
            );
7104
        }
7105
7106
        $toolbar = Display::toolbarAction(
7107
            'actions-lp-controller',
7108
            [$actionsLeft, $actionsRight]
7109
        );
7110
7111
        if ($returnString) {
7112
            return $toolbar;
7113
        }
7114
7115
        echo $toolbar;
7116
    }
7117
7118
    /**
7119
     * Creates the default learning path folder.
7120
     *
7121
     * @param array $course
7122
     * @param int   $creatorId
7123
     *
7124
     * @return bool
7125
     */
7126
    public static function generate_learning_path_folder($course, $creatorId = 0)
7127
    {
7128
        // Creating learning_path folder
7129
        $dir = '/learning_path';
7130
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7131
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7132
7133
        $folder = false;
7134
        if (!is_dir($filepath.'/'.$dir)) {
7135
            $folderData = create_unexisting_directory(
7136
                $course,
7137
                $creatorId,
7138
                0,
7139
                null,
7140
                0,
7141
                $filepath,
7142
                $dir,
7143
                get_lang('LearningPaths'),
7144
                0
7145
            );
7146
            if (!empty($folderData)) {
7147
                $folder = true;
7148
            }
7149
        } else {
7150
            $folder = true;
7151
        }
7152
7153
        return $folder;
7154
    }
7155
7156
    /**
7157
     * @param array  $course
7158
     * @param string $lp_name
7159
     * @param int    $creatorId
7160
     *
7161
     * @return array
7162
     */
7163
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
7164
    {
7165
        $filepath = '';
7166
        $dir = '/learning_path/';
7167
7168
        if (empty($lp_name)) {
7169
            $lp_name = $this->name;
7170
        }
7171
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7172
        $folder = self::generate_learning_path_folder($course, $creatorId);
7173
7174
        // Limits title size
7175
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
7176
        $dir = $dir.$title;
7177
7178
        // Creating LP folder
7179
        $documentId = null;
7180
        if ($folder) {
7181
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7182
            if (!is_dir($filepath.'/'.$dir)) {
7183
                $folderData = create_unexisting_directory(
7184
                    $course,
7185
                    $creatorId,
7186
                    0,
7187
                    0,
7188
                    0,
7189
                    $filepath,
7190
                    $dir,
7191
                    $lp_name
7192
                );
7193
                if (!empty($folderData)) {
7194
                    $folder = true;
7195
                }
7196
7197
                $documentId = $folderData['id'];
7198
            } else {
7199
                $folder = true;
7200
            }
7201
            $dir = $dir.'/';
7202
            if ($folder) {
7203
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
7204
            }
7205
        }
7206
7207
        if (empty($documentId)) {
7208
            $dir = api_remove_trailing_slash($dir);
7209
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
7210
        }
7211
7212
        $array = [
7213
            'dir' => $dir,
7214
            'filepath' => $filepath,
7215
            'folder' => $folder,
7216
            'id' => $documentId,
7217
        ];
7218
7219
        return $array;
7220
    }
7221
7222
    /**
7223
     * Create a new document //still needs some finetuning.
7224
     *
7225
     * @param array  $courseInfo
7226
     * @param string $content
7227
     * @param string $title
7228
     * @param string $extension
7229
     * @param int    $parentId
7230
     * @param int    $creatorId  creator id
7231
     *
7232
     * @return int
7233
     */
7234
    public function create_document(
7235
        $courseInfo,
7236
        $content = '',
7237
        $title = '',
7238
        $extension = 'html',
7239
        $parentId = 0,
7240
        $creatorId = 0
7241
    ) {
7242
        if (!empty($courseInfo)) {
7243
            $course_id = $courseInfo['real_id'];
7244
        } else {
7245
            $course_id = api_get_course_int_id();
7246
        }
7247
7248
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7249
        $sessionId = api_get_session_id();
7250
7251
        // Generates folder
7252
        $result = $this->generate_lp_folder($courseInfo);
7253
        $dir = $result['dir'];
7254
7255
        if (empty($parentId) || $parentId == '/') {
7256
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7257
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7258
7259
            if ($parentId === '/') {
7260
                $dir = '/';
7261
            }
7262
7263
            // Please, do not modify this dirname formatting.
7264
            if (strstr($dir, '..')) {
7265
                $dir = '/';
7266
            }
7267
7268
            if (!empty($dir[0]) && $dir[0] == '.') {
7269
                $dir = substr($dir, 1);
7270
            }
7271
            if (!empty($dir[0]) && $dir[0] != '/') {
7272
                $dir = '/'.$dir;
7273
            }
7274
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7275
                $dir .= '/';
7276
            }
7277
        } else {
7278
            $parentInfo = DocumentManager::get_document_data_by_id(
7279
                $parentId,
7280
                $courseInfo['code']
7281
            );
7282
            if (!empty($parentInfo)) {
7283
                $dir = $parentInfo['path'].'/';
7284
            }
7285
        }
7286
7287
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7288
        if (!is_dir($filepath)) {
7289
            $dir = '/';
7290
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7291
        }
7292
7293
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7294
        // is already escaped twice when it gets here.
7295
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7296
        if (!empty($title)) {
7297
            $title = api_replace_dangerous_char(stripslashes($title));
7298
        } else {
7299
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7300
        }
7301
7302
        $title = disable_dangerous_file($title);
7303
        $filename = $title;
7304
        $content = !empty($content) ? $content : $_POST['content_lp'];
7305
        $tmp_filename = $filename;
7306
7307
        $i = 0;
7308
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7309
            $tmp_filename = $filename.'_'.++$i;
7310
        }
7311
7312
        $filename = $tmp_filename.'.'.$extension;
7313
        if ($extension == 'html') {
7314
            $content = stripslashes($content);
7315
            $content = str_replace(
7316
                api_get_path(WEB_COURSE_PATH),
7317
                api_get_path(REL_PATH).'courses/',
7318
                $content
7319
            );
7320
7321
            // Change the path of mp3 to absolute.
7322
            // The first regexp deals with :// urls.
7323
            $content = preg_replace(
7324
                "|(flashvars=\"file=)([^:/]+)/|",
7325
                "$1".api_get_path(
7326
                    REL_COURSE_PATH
7327
                ).$courseInfo['path'].'/document/',
7328
                $content
7329
            );
7330
            // The second regexp deals with audio/ urls.
7331
            $content = preg_replace(
7332
                "|(flashvars=\"file=)([^/]+)/|",
7333
                "$1".api_get_path(
7334
                    REL_COURSE_PATH
7335
                ).$courseInfo['path'].'/document/$2/',
7336
                $content
7337
            );
7338
            // For flv player: To prevent edition problem with firefox,
7339
            // we have to use a strange tip (don't blame me please).
7340
            $content = str_replace(
7341
                '</body>',
7342
                '<style type="text/css">body{}</style></body>',
7343
                $content
7344
            );
7345
        }
7346
7347
        if (!file_exists($filepath.$filename)) {
7348
            if ($fp = @fopen($filepath.$filename, 'w')) {
7349
                fputs($fp, $content);
7350
                fclose($fp);
7351
7352
                $file_size = filesize($filepath.$filename);
7353
                $save_file_path = $dir.$filename;
7354
7355
                $document_id = add_document(
7356
                    $courseInfo,
7357
                    $save_file_path,
7358
                    'file',
7359
                    $file_size,
7360
                    $tmp_filename,
7361
                    '',
7362
                    0, //readonly
7363
                    true,
7364
                    null,
7365
                    $sessionId,
7366
                    $creatorId
7367
                );
7368
7369
                if ($document_id) {
7370
                    api_item_property_update(
7371
                        $courseInfo,
7372
                        TOOL_DOCUMENT,
7373
                        $document_id,
7374
                        'DocumentAdded',
7375
                        $creatorId,
7376
                        null,
7377
                        null,
7378
                        null,
7379
                        null,
7380
                        $sessionId
7381
                    );
7382
7383
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7384
                    $new_title = $originalTitle;
7385
7386
                    if ($new_comment || $new_title) {
7387
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7388
                        $ct = '';
7389
                        if ($new_comment) {
7390
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7391
                        }
7392
                        if ($new_title) {
7393
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7394
                        }
7395
7396
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7397
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7398
                        Database::query($sql);
7399
                    }
7400
                }
7401
7402
                return $document_id;
7403
            }
7404
        }
7405
    }
7406
7407
    /**
7408
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7409
     *
7410
     * @param array $_course array
7411
     */
7412
    public function edit_document($_course)
7413
    {
7414
        $course_id = api_get_course_int_id();
7415
        $urlAppend = api_get_configuration_value('url_append');
7416
        // Please, do not modify this dirname formatting.
7417
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7418
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7419
7420
        if (strstr($dir, '..')) {
7421
            $dir = '/';
7422
        }
7423
7424
        if (isset($dir[0]) && $dir[0] == '.') {
7425
            $dir = substr($dir, 1);
7426
        }
7427
7428
        if (isset($dir[0]) && $dir[0] != '/') {
7429
            $dir = '/'.$dir;
7430
        }
7431
7432
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7433
            $dir .= '/';
7434
        }
7435
7436
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7437
        if (!is_dir($filepath)) {
7438
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7439
        }
7440
7441
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7442
7443
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7444
            $document_id = (int) $_POST['path'];
7445
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7446
            if (empty($documentInfo)) {
7447
                // Try with iid
7448
                $table = Database::get_course_table(TABLE_DOCUMENT);
7449
                $sql = "SELECT id, path FROM $table
7450
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7451
                $res_doc = Database::query($sql);
7452
                $row = Database::fetch_array($res_doc);
7453
                if ($row) {
7454
                    $document_id = $row['id'];
7455
                    $documentPath = $row['path'];
7456
                }
7457
            } else {
7458
                $documentPath = $documentInfo['path'];
7459
            }
7460
7461
            $content = stripslashes($_POST['content_lp']);
7462
            $file = $filepath.$documentPath;
7463
7464
            if (!file_exists($file)) {
7465
                return false;
7466
            }
7467
7468
            if ($fp = @fopen($file, 'w')) {
7469
                $content = str_replace(
7470
                    api_get_path(WEB_COURSE_PATH),
7471
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7472
                    $content
7473
                );
7474
                // Change the path of mp3 to absolute.
7475
                // The first regexp deals with :// urls.
7476
                $content = preg_replace(
7477
                    "|(flashvars=\"file=)([^:/]+)/|",
7478
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7479
                    $content
7480
                );
7481
                // The second regexp deals with audio/ urls.
7482
                $content = preg_replace(
7483
                    "|(flashvars=\"file=)([^:/]+)/|",
7484
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7485
                    $content
7486
                );
7487
                fputs($fp, $content);
7488
                fclose($fp);
7489
7490
                $sql = "UPDATE $table_doc SET
7491
                            title='".Database::escape_string($_POST['title'])."'
7492
                        WHERE c_id = $course_id AND id = ".$document_id;
7493
                Database::query($sql);
7494
            }
7495
        }
7496
    }
7497
7498
    /**
7499
     * Displays the selected item, with a panel for manipulating the item.
7500
     *
7501
     * @param int    $item_id
7502
     * @param string $msg
7503
     * @param bool   $show_actions
7504
     *
7505
     * @return string
7506
     */
7507
    public function display_item($item_id, $msg = null, $show_actions = true)
7508
    {
7509
        $course_id = api_get_course_int_id();
7510
        $return = '';
7511
        if (is_numeric($item_id)) {
7512
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7513
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7514
                    WHERE lp.iid = ".intval($item_id);
7515
            $result = Database::query($sql);
7516
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7517
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7518
7519
                // Prevents wrong parent selection for document, see Bug#1251.
7520
                if ($row['item_type'] != 'dir') {
7521
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7522
                }
7523
7524
                if ($show_actions) {
7525
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7526
                }
7527
                $return .= '<div style="padding:10px;">';
7528
7529
                if ($msg != '') {
7530
                    $return .= $msg;
7531
                }
7532
7533
                $return .= '<h3>'.$row['title'].'</h3>';
7534
7535
                switch ($row['item_type']) {
7536
                    case TOOL_THREAD:
7537
                        $link = $this->rl_get_resource_link_for_learnpath(
7538
                            $course_id,
7539
                            $row['lp_id'],
7540
                            $item_id,
7541
                            0
7542
                        );
7543
                        $return .= Display::url(
7544
                            get_lang('GoToThread'),
7545
                            $link,
7546
                            ['class' => 'btn btn-primary']
7547
                        );
7548
                        break;
7549
                    case TOOL_FORUM:
7550
                        $return .= Display::url(
7551
                            get_lang('GoToForum'),
7552
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7553
                            ['class' => 'btn btn-primary']
7554
                        );
7555
                        break;
7556
                    case TOOL_QUIZ:
7557
                        if (!empty($row['path'])) {
7558
                            $exercise = new Exercise();
7559
                            $exercise->read($row['path']);
7560
                            $return .= $exercise->description.'<br />';
7561
                            $return .= Display::url(
7562
                                get_lang('GoToExercise'),
7563
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7564
                                ['class' => 'btn btn-primary']
7565
                            );
7566
                        }
7567
                        break;
7568
                    case TOOL_LP_FINAL_ITEM:
7569
                        $return .= $this->getSavedFinalItem();
7570
                        break;
7571
                    case TOOL_DOCUMENT:
7572
                    case TOOL_READOUT_TEXT:
7573
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7574
                        $sql_doc = "SELECT path FROM $tbl_doc
7575
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7576
                        $result = Database::query($sql_doc);
7577
                        $path_file = Database::result($result, 0, 0);
7578
                        $path_parts = pathinfo($path_file);
7579
                        // TODO: Correct the following naive comparisons.
7580
                        if (in_array($path_parts['extension'], [
7581
                            'html',
7582
                            'txt',
7583
                            'png',
7584
                            'jpg',
7585
                            'JPG',
7586
                            'jpeg',
7587
                            'JPEG',
7588
                            'gif',
7589
                            'swf',
7590
                            'pdf',
7591
                            'htm',
7592
                        ])) {
7593
                            $return .= $this->display_document($row['path'], true, true);
7594
                        }
7595
                        break;
7596
                    case TOOL_HOTPOTATOES:
7597
                        $return .= $this->display_document($row['path'], false, true);
7598
                        break;
7599
                }
7600
                $return .= '</div>';
7601
            }
7602
        }
7603
7604
        return $return;
7605
    }
7606
7607
    /**
7608
     * Shows the needed forms for editing a specific item.
7609
     *
7610
     * @param int $item_id
7611
     *
7612
     * @throws Exception
7613
     * @throws HTML_QuickForm_Error
7614
     *
7615
     * @return string
7616
     */
7617
    public function display_edit_item($item_id, $excludeExtraFields = [])
7618
    {
7619
        $course_id = api_get_course_int_id();
7620
        $return = '';
7621
        $item_id = (int) $item_id;
7622
7623
        if (empty($item_id)) {
7624
            return '';
7625
        }
7626
7627
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7628
        $sql = "SELECT * FROM $tbl_lp_item
7629
                WHERE iid = ".$item_id;
7630
        $res = Database::query($sql);
7631
        $row = Database::fetch_array($res);
7632
        switch ($row['item_type']) {
7633
            case 'dir':
7634
            case 'asset':
7635
            case 'sco':
7636
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7637
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7638
                $return .= $this->display_item_form(
7639
                        $row['item_type'],
7640
                        get_lang('EditCurrentChapter').' :',
7641
                        'edit',
7642
                        $item_id,
7643
                        $row
7644
                    );
7645
            } else {
7646
                $return .= $this->display_item_form(
7647
                        $row['item_type'],
7648
                        get_lang('EditCurrentChapter').' :',
7649
                        'edit_item',
7650
                        $item_id,
7651
                        $row
7652
                    );
7653
            }
7654
                break;
7655
            case TOOL_DOCUMENT:
7656
            case TOOL_READOUT_TEXT:
7657
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7658
                $sql = "SELECT lp.*, doc.path as dir
7659
                        FROM $tbl_lp_item as lp
7660
                        LEFT JOIN $tbl_doc as doc
7661
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7662
                        WHERE
7663
                            doc.c_id = $course_id AND
7664
                            lp.iid = ".$item_id;
7665
                $res_step = Database::query($sql);
7666
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7667
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7668
7669
                if ($row['item_type'] === TOOL_DOCUMENT) {
7670
                    $return .= $this->display_document_form(
7671
                        'edit',
7672
                        $item_id,
7673
                        $row_step,
7674
                        null,
7675
                        $excludeExtraFields
7676
                    );
7677
                }
7678
7679
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7680
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7681
                }
7682
                break;
7683
            case TOOL_LINK:
7684
                $linkId = (int) $row['path'];
7685
                if (!empty($linkId)) {
7686
                    $table = Database::get_course_table(TABLE_LINK);
7687
                    $sql = 'SELECT url FROM '.$table.'
7688
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7689
                    $res_link = Database::query($sql);
7690
                    $row_link = Database::fetch_array($res_link);
7691
                    if (empty($row_link)) {
7692
                        // Try with id
7693
                        $sql = 'SELECT url FROM '.$table.'
7694
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7695
                        $res_link = Database::query($sql);
7696
                        $row_link = Database::fetch_array($res_link);
7697
                    }
7698
7699
                    if (is_array($row_link)) {
7700
                        $row['url'] = $row_link['url'];
7701
                    }
7702
                }
7703
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7704
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7705
                break;
7706
            case TOOL_LP_FINAL_ITEM:
7707
                Session::write('finalItem', true);
7708
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7709
                $sql = "SELECT lp.*, doc.path as dir
7710
                        FROM $tbl_lp_item as lp
7711
                        LEFT JOIN $tbl_doc as doc
7712
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7713
                        WHERE
7714
                            doc.c_id = $course_id AND
7715
                            lp.iid = ".$item_id;
7716
                $res_step = Database::query($sql);
7717
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7718
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7719
                $return .= $this->display_document_form(
7720
                    'edit',
7721
                    $item_id,
7722
                    $row_step,
7723
                    null,
7724
                    $excludeExtraFields
7725
                );
7726
                break;
7727
            case TOOL_QUIZ:
7728
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7729
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7730
                break;
7731
            case TOOL_HOTPOTATOES:
7732
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7733
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7734
                break;
7735
            case TOOL_STUDENTPUBLICATION:
7736
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7737
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7738
                break;
7739
            case TOOL_FORUM:
7740
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7741
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7742
                break;
7743
            case TOOL_THREAD:
7744
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7745
                $return .= $this->display_thread_form('edit', $item_id, $row);
7746
                break;
7747
            case TOOL_SURVEY:
7748
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7749
                $return .= $this->displaySurveyForm('edit', $item_id, $row);
7750
                break;
7751
        }
7752
7753
        return $return;
7754
    }
7755
7756
    /**
7757
     * Function that displays a list with al the resources that
7758
     * could be added to the learning path.
7759
     *
7760
     * @throws Exception
7761
     * @throws HTML_QuickForm_Error
7762
     *
7763
     * @return bool
7764
     */
7765
    public function display_resources()
7766
    {
7767
        $course_code = api_get_course_id();
7768
7769
        // Get all the docs.
7770
        $documents = $this->get_documents(true);
7771
7772
        // Get all the exercises.
7773
        $exercises = $this->get_exercises();
7774
7775
        // Get all the links.
7776
        $links = $this->get_links();
7777
7778
        // Get all the student publications.
7779
        $works = $this->get_student_publications();
7780
7781
        // Get all the forums.
7782
        $forums = $this->get_forums(null, $course_code);
7783
7784
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7785
7786
        // Get the final item form (see BT#11048) .
7787
        $finish = $this->getFinalItemForm();
7788
7789
        $headers = [
7790
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7791
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7792
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7793
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7794
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7795
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7796
        ];
7797
7798
        $items = [
7799
            $documents,
7800
            $exercises,
7801
            $links,
7802
            $works,
7803
            $forums,
7804
            $dir,
7805
        ];
7806
7807
        $allowSurveyTool = api_get_configuration_value('allow_survey_tool_in_lp');
7808
        if ($allowSurveyTool) {
7809
            // Get all the surveys
7810
            $surveys = $this->getSurveys();
7811
            $items[] = $surveys;
7812
            $headers[] = Display::return_icon('survey.png', get_lang('Surveys'), [], ICON_SIZE_BIG);
7813
        }
7814
7815
        $xApiPlugin = XApiPlugin::create();
7816
        if ($xApiPlugin->isEnabled()) {
7817
            $headers[] = Display::return_icon(
7818
                'import_scorm.png',
7819
                get_lang($xApiPlugin->get_lang('ToolTinCan')),
7820
                [],
7821
                ICON_SIZE_BIG
7822
            );
7823
            $items[] = $xApiPlugin->getLpResourceBlock($this->lp_id);
7824
        }
7825
7826
        $h5pImportPlugin = H5pImportPlugin::create();
7827
        if ($h5pImportPlugin->isEnabled()) {
7828
            $headers[] = Display::return_icon(
7829
                'plugin_h5p_import_upload.png',
7830
                $h5pImportPlugin->get_lang('plugin_title'),
7831
                [],
7832
                ICON_SIZE_BIG
7833
            );
7834
            $items[] = $h5pImportPlugin->getLpResourceBlock($this->lp_id);
7835
        }
7836
7837
        $headers[] = Display::return_icon('flag_checkered.png', get_lang('Certificate'), [], ICON_SIZE_BIG);
7838
        $items[] = $finish;
7839
7840
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7841
7842
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7843
7844
        echo Display::tabs(
7845
            $headers,
7846
            $items,
7847
            'resource_tab',
7848
            [],
7849
            [],
7850
            $selected
7851
        );
7852
7853
        return true;
7854
    }
7855
7856
    /**
7857
     * Returns the extension of a document.
7858
     *
7859
     * @param string $filename
7860
     *
7861
     * @return string Extension (part after the last dot)
7862
     */
7863
    public function get_extension($filename)
7864
    {
7865
        $explode = explode('.', $filename);
7866
7867
        return $explode[count($explode) - 1];
7868
    }
7869
7870
    /**
7871
     * Displays a document by id.
7872
     *
7873
     * @param int  $id
7874
     * @param bool $show_title
7875
     * @param bool $iframe
7876
     * @param bool $edit_link
7877
     *
7878
     * @return string
7879
     */
7880
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7881
    {
7882
        $_course = api_get_course_info();
7883
        $course_id = api_get_course_int_id();
7884
        $id = (int) $id;
7885
        $return = '';
7886
        $table = Database::get_course_table(TABLE_DOCUMENT);
7887
        $sql_doc = "SELECT * FROM $table
7888
                    WHERE c_id = $course_id AND iid = $id";
7889
        $res_doc = Database::query($sql_doc);
7890
        $row_doc = Database::fetch_array($res_doc);
7891
7892
        // TODO: Add a path filter.
7893
        if ($iframe) {
7894
            $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>';
7895
        } else {
7896
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7897
        }
7898
7899
        return $return;
7900
    }
7901
7902
    /**
7903
     * Return HTML form to add/edit a quiz.
7904
     *
7905
     * @param string $action     Action (add/edit)
7906
     * @param int    $id         Item ID if already exists
7907
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7908
     *
7909
     * @throws Exception
7910
     *
7911
     * @return string HTML form
7912
     */
7913
    public function display_quiz_form(
7914
        $action = 'add',
7915
        $id = 0,
7916
        $extra_info = '',
7917
        $excludeExtraFields = []
7918
    ) {
7919
        $course_id = api_get_course_int_id();
7920
        $id = (int) $id;
7921
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7922
7923
        if ($id != 0 && is_array($extra_info)) {
7924
            $item_title = $extra_info['title'];
7925
            $item_description = $extra_info['description'];
7926
        } elseif (is_numeric($extra_info)) {
7927
            $sql = "SELECT title, description
7928
                    FROM $tbl_quiz
7929
                    WHERE iid = $extra_info";
7930
7931
            $result = Database::query($sql);
7932
            $row = Database::fetch_array($result);
7933
            $item_title = $row['title'];
7934
            $item_description = $row['description'];
7935
        } else {
7936
            $item_title = '';
7937
            $item_description = '';
7938
        }
7939
        $item_title = Security::remove_XSS($item_title);
7940
        $item_description = Security::remove_XSS($item_description);
7941
7942
        $parent = 0;
7943
        if ($id != 0 && is_array($extra_info)) {
7944
            $parent = $extra_info['parent_item_id'];
7945
        }
7946
7947
        $arrLP = $this->getItemsForForm();
7948
        $this->tree_array($arrLP);
7949
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7950
        unset($this->arrMenu);
7951
7952
        $form = new FormValidator(
7953
            'quiz_form',
7954
            'POST',
7955
            $this->getCurrentBuildingModeURL()
7956
        );
7957
        $defaults = [];
7958
7959
        if ($action === 'add') {
7960
            $legend = get_lang('CreateTheExercise');
7961
        } elseif ($action === 'move') {
7962
            $legend = get_lang('MoveTheCurrentExercise');
7963
        } else {
7964
            $legend = get_lang('EditCurrentExecice');
7965
        }
7966
7967
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7968
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7969
        }
7970
7971
        $form->addHeader($legend);
7972
7973
        if ($action != 'move') {
7974
            $this->setItemTitle($form);
7975
            $defaults['title'] = $item_title;
7976
        }
7977
7978
        // Select for Parent item, root or chapter
7979
        $selectParent = $form->addSelect(
7980
            'parent',
7981
            get_lang('Parent'),
7982
            [],
7983
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7984
        );
7985
        $selectParent->addOption($this->name, 0);
7986
7987
        $arrHide = [
7988
            $id,
7989
        ];
7990
        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...
7991
            if ($action != 'add') {
7992
                if (
7993
                    ($arrLP[$i]['item_type'] == 'dir') &&
7994
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7995
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7996
                ) {
7997
                    $selectParent->addOption(
7998
                        $arrLP[$i]['title'],
7999
                        $arrLP[$i]['id'],
8000
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8001
                    );
8002
8003
                    if ($parent == $arrLP[$i]['id']) {
8004
                        $selectParent->setSelected($arrLP[$i]['id']);
8005
                    }
8006
                } else {
8007
                    $arrHide[] = $arrLP[$i]['id'];
8008
                }
8009
            } else {
8010
                if ($arrLP[$i]['item_type'] == 'dir') {
8011
                    $selectParent->addOption(
8012
                        $arrLP[$i]['title'],
8013
                        $arrLP[$i]['id'],
8014
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8015
                    );
8016
8017
                    if ($parent == $arrLP[$i]['id']) {
8018
                        $selectParent->setSelected($arrLP[$i]['id']);
8019
                    }
8020
                }
8021
            }
8022
        }
8023
8024
        if (is_array($arrLP)) {
8025
            reset($arrLP);
8026
        }
8027
8028
        $selectPrevious = $form->addSelect(
8029
            'previous',
8030
            get_lang('Position'),
8031
            [],
8032
            ['id' => 'previous']
8033
        );
8034
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8035
8036
        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...
8037
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8038
                $arrLP[$i]['id'] != $id
8039
            ) {
8040
                $selectPrevious->addOption(
8041
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8042
                    $arrLP[$i]['id']
8043
                );
8044
8045
                if (is_array($extra_info)) {
8046
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8047
                        $selectPrevious->setSelected($arrLP[$i]['id']);
8048
                    }
8049
                } elseif ($action == 'add') {
8050
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8051
                }
8052
            }
8053
        }
8054
8055
        if ($action != 'move') {
8056
            $arrHide = [];
8057
            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...
8058
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8059
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8060
                }
8061
            }
8062
        }
8063
8064
        if ('edit' === $action) {
8065
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8066
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8067
            }
8068
            $extraField = new ExtraField('lp_item');
8069
            $extraField->addElements($form, $id, $excludeExtraFields);
8070
        }
8071
8072
        if ($action === 'add') {
8073
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
8074
        } else {
8075
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
8076
        }
8077
8078
        if ($action === 'move') {
8079
            $form->addHidden('title', $item_title);
8080
            $form->addHidden('description', $item_description);
8081
        }
8082
8083
        if (is_numeric($extra_info)) {
8084
            $form->addHidden('path', $extra_info);
8085
        } elseif (is_array($extra_info)) {
8086
            $form->addHidden('path', $extra_info['path']);
8087
        }
8088
8089
        $form->addHidden('type', TOOL_QUIZ);
8090
        $form->addHidden('post_time', time());
8091
        $this->setAuthorLpItem($form);
8092
        $form->setDefaults($defaults);
8093
8094
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8095
    }
8096
8097
    /**
8098
     * Addition of Hotpotatoes tests.
8099
     *
8100
     * @param string $action
8101
     * @param int    $id         Internal ID of the item
8102
     * @param string $extra_info
8103
     *
8104
     * @return string HTML structure to display the hotpotatoes addition formular
8105
     */
8106
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
8107
    {
8108
        $course_id = api_get_course_int_id();
8109
        $uploadPath = DIR_HOTPOTATOES;
8110
8111
        if ($id != 0 && is_array($extra_info)) {
8112
            $item_title = stripslashes($extra_info['title']);
8113
            $item_description = stripslashes($extra_info['description']);
8114
        } elseif (is_numeric($extra_info)) {
8115
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8116
8117
            $sql = "SELECT * FROM $TBL_DOCUMENT
8118
                    WHERE
8119
                        c_id = $course_id AND
8120
                        path LIKE '".$uploadPath."/%/%htm%' AND
8121
                        iid = ".(int) $extra_info."
8122
                    ORDER BY iid ASC";
8123
8124
            $res_hot = Database::query($sql);
8125
            $row = Database::fetch_array($res_hot);
8126
8127
            $item_title = $row['title'];
8128
            $item_description = $row['description'];
8129
8130
            if (!empty($row['comment'])) {
8131
                $item_title = $row['comment'];
8132
            }
8133
        } else {
8134
            $item_title = '';
8135
            $item_description = '';
8136
        }
8137
8138
        $parent = 0;
8139
        if ($id != 0 && is_array($extra_info)) {
8140
            $parent = $extra_info['parent_item_id'];
8141
        }
8142
8143
        $arrLP = $this->getItemsForForm();
8144
        $legend = '<legend>';
8145
        if ($action == 'add') {
8146
            $legend .= get_lang('CreateTheExercise');
8147
        } elseif ($action == 'move') {
8148
            $legend .= get_lang('MoveTheCurrentExercise');
8149
        } else {
8150
            $legend .= get_lang('EditCurrentExecice');
8151
        }
8152
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8153
            $legend .= Display::return_message(
8154
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
8155
            );
8156
        }
8157
        $legend .= '</legend>';
8158
8159
        $return = '<form method="POST">';
8160
        $return .= $legend;
8161
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
8162
        $return .= '<tr>';
8163
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
8164
        $return .= '<td class="input">';
8165
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
8166
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
8167
        $arrHide = [$id];
8168
8169
        if (count($arrLP) > 0) {
8170
            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...
8171
                if ($action != 'add') {
8172
                    if ($arrLP[$i]['item_type'] == 'dir' &&
8173
                        !in_array($arrLP[$i]['id'], $arrHide) &&
8174
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8175
                    ) {
8176
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8177
                    } else {
8178
                        $arrHide[] = $arrLP[$i]['id'];
8179
                    }
8180
                } else {
8181
                    if ($arrLP[$i]['item_type'] == 'dir') {
8182
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8183
                    }
8184
                }
8185
            }
8186
            reset($arrLP);
8187
        }
8188
8189
        $return .= '</select>';
8190
        $return .= '</td>';
8191
        $return .= '</tr>';
8192
        $return .= '<tr>';
8193
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
8194
        $return .= '<td class="input">';
8195
        $return .= '<select id="previous" name="previous" size="1">';
8196
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
8197
8198
        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...
8199
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8200
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8201
                    $selected = 'selected="selected" ';
8202
                } elseif ($action == 'add') {
8203
                    $selected = 'selected="selected" ';
8204
                } else {
8205
                    $selected = '';
8206
                }
8207
8208
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
8209
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
8210
            }
8211
        }
8212
8213
        $return .= '</select>';
8214
        $return .= '</td>';
8215
        $return .= '</tr>';
8216
8217
        if ($action != 'move') {
8218
            $return .= '<tr>';
8219
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
8220
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
8221
            $return .= '</tr>';
8222
            $id_prerequisite = 0;
8223
            if (is_array($arrLP) && count($arrLP) > 0) {
8224
                foreach ($arrLP as $key => $value) {
8225
                    if ($value['id'] == $id) {
8226
                        $id_prerequisite = $value['prerequisite'];
8227
                        break;
8228
                    }
8229
                }
8230
8231
                $arrHide = [];
8232
                for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8233
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8234
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8235
                    }
8236
                }
8237
            }
8238
        }
8239
8240
        $return .= '<tr>';
8241
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8242
            get_lang('SaveHotpotatoes').'</button></td>';
8243
        $return .= '</tr>';
8244
        $return .= '</table>';
8245
8246
        if ($action == 'move') {
8247
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8248
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8249
        }
8250
8251
        if (is_numeric($extra_info)) {
8252
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8253
        } elseif (is_array($extra_info)) {
8254
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8255
        }
8256
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8257
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8258
        $return .= '</form>';
8259
8260
        return $return;
8261
    }
8262
8263
    public function displaySurveyForm(
8264
      $action = 'add',
8265
      $id = 0,
8266
      $extraInfo = '',
8267
      $excludeExtraFields = []
8268
    ) {
8269
        $courseId = api_get_course_int_id();
8270
        $tblSurvey = Database::get_course_table(TABLE_SURVEY);
8271
8272
        $itemTitle = '';
8273
        $itemDescription = '';
8274
8275
        if ($id != 0 && is_array($extraInfo)) {
8276
            $itemTitle = stripslashes($extraInfo['title']);
8277
        } elseif (is_numeric($extraInfo)) {
8278
            $sql = "SELECT title, intro as comment
8279
                    FROM $tblSurvey
8280
                    WHERE c_id = $courseId AND survey_id = ".(int) $extraInfo;
8281
8282
            $result = Database::query($sql);
8283
            $row = Database::fetch_array($result);
8284
8285
            $itemTitle = strip_tags($row['title']);
8286
            $itemDescription = $row['comment'];
8287
        }
8288
        $parent = 0;
8289
        if ($id != 0 && is_array($extraInfo)) {
8290
            $parent = $extraInfo['parent_item_id'];
8291
        }
8292
        $arrLP = $this->getItemsForForm();
8293
        $this->tree_array($arrLP);
8294
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8295
        unset($this->arrMenu);
8296
8297
        if ($action == 'add') {
8298
            $legend = get_lang('CreateSurvey');
8299
        } elseif ($action == 'move') {
8300
            $legend = get_lang('MoveTheCurrentSurvey');
8301
        } else {
8302
            $legend = get_lang('ModifySurveyInformation');
8303
        }
8304
8305
        $form = new FormValidator(
8306
            'survey_form',
8307
            'POST',
8308
            $this->getCurrentBuildingModeURL()
8309
        );
8310
        $defaults = [];
8311
8312
        $form->addHeader($legend);
8313
8314
        if ($action != 'move') {
8315
            $this->setItemTitle($form);
8316
            $defaults['title'] = $itemTitle;
8317
        }
8318
8319
        $selectParent = $form->addSelect(
8320
            'parent',
8321
            get_lang('Parent'),
8322
            [],
8323
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8324
        );
8325
        $selectParent->addOption($this->name, 0);
8326
        $arrHide = [
8327
            $id,
8328
        ];
8329
        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...
8330
            if ($action != 'add') {
8331
                if ($arrLP[$i]['item_type'] == 'dir' &&
8332
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8333
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8334
                ) {
8335
                    $selectParent->addOption(
8336
                        $arrLP[$i]['title'],
8337
                        $arrLP[$i]['id'],
8338
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8339
                    );
8340
8341
                    if ($parent == $arrLP[$i]['id']) {
8342
                        $selectParent->setSelected($arrLP[$i]['id']);
8343
                    }
8344
                } else {
8345
                    $arrHide[] = $arrLP[$i]['id'];
8346
                }
8347
            } else {
8348
                if ($arrLP[$i]['item_type'] == 'dir') {
8349
                    $selectParent->addOption(
8350
                        $arrLP[$i]['title'],
8351
                        $arrLP[$i]['id'],
8352
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8353
                    );
8354
8355
                    if ($parent == $arrLP[$i]['id']) {
8356
                        $selectParent->setSelected($arrLP[$i]['id']);
8357
                    }
8358
                }
8359
            }
8360
        }
8361
8362
        if (is_array($arrLP)) {
8363
            reset($arrLP);
8364
        }
8365
8366
        $selectPrevious = $form->addSelect(
8367
            'previous',
8368
            get_lang('Position'),
8369
            [],
8370
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8371
        );
8372
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8373
8374
        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...
8375
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8376
                $arrLP[$i]['id'] != $id
8377
            ) {
8378
                $selectPrevious->addOption(
8379
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8380
                    $arrLP[$i]['id']
8381
                );
8382
8383
                if (isset($extra_info['previous_item_id']) &&
8384
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8385
                ) {
8386
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8387
                } elseif ($action == 'add') {
8388
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8389
                }
8390
            }
8391
        }
8392
8393
        if ($action != 'move') {
8394
            $idPrerequisite = 0;
8395
            if (is_array($arrLP)) {
8396
                foreach ($arrLP as $key => $value) {
8397
                    if ($value['id'] == $id) {
8398
                        $idPrerequisite = $value['prerequisite'];
8399
                        break;
8400
                    }
8401
                }
8402
            }
8403
8404
            $arrHide = [];
8405
            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...
8406
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8407
                    if (isset($extra_info['previous_item_id']) &&
8408
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8409
                    ) {
8410
                        $sSelectedPosition = $arrLP[$i]['id'];
8411
                    } elseif ($action == 'add') {
8412
                        $sSelectedPosition = 0;
8413
                    }
8414
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8415
                }
8416
            }
8417
        }
8418
8419
        if ('edit' === $action) {
8420
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8421
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8422
            }
8423
            $extraField = new ExtraField('lp_item');
8424
            $extraField->addElements($form, $id, $excludeExtraFields);
8425
        }
8426
8427
        if ($action == 'add') {
8428
            $form->addButtonSave(get_lang('AddSurveyToCourse'), 'submit_button');
8429
        } else {
8430
            $form->addButtonSave(get_lang('EditCurrentSurvey'), 'submit_button');
8431
        }
8432
8433
        if ($action == 'move') {
8434
            $form->addHidden('title', $itemTitle);
8435
            $form->addHidden('description', $itemDescription);
8436
        }
8437
8438
        if (is_numeric($extraInfo)) {
8439
            $form->addHidden('path', $extraInfo);
8440
        } elseif (is_array($extraInfo)) {
8441
            $form->addHidden('path', $extraInfo['path']);
8442
        }
8443
        $form->addHidden('type', TOOL_SURVEY);
8444
        $form->addHidden('post_time', time());
8445
        $this->setAuthorLpItem($form);
8446
        $form->setDefaults($defaults);
8447
8448
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8449
    }
8450
8451
    /**
8452
     * Return the form to display the forum edit/add option.
8453
     *
8454
     * @param string $action
8455
     * @param int    $id         ID of the lp_item if already exists
8456
     * @param string $extra_info
8457
     *
8458
     * @throws Exception
8459
     *
8460
     * @return string HTML form
8461
     */
8462
    public function display_forum_form(
8463
        $action = 'add',
8464
        $id = 0,
8465
        $extra_info = '',
8466
        $excludeExtraFields = []
8467
    ) {
8468
        $course_id = api_get_course_int_id();
8469
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8470
8471
        $item_title = '';
8472
        $item_description = '';
8473
8474
        if ($id != 0 && is_array($extra_info)) {
8475
            $item_title = stripslashes($extra_info['title']);
8476
        } elseif (is_numeric($extra_info)) {
8477
            $sql = "SELECT forum_title as title, forum_comment as comment
8478
                    FROM $tbl_forum
8479
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
8480
8481
            $result = Database::query($sql);
8482
            $row = Database::fetch_array($result);
8483
8484
            $item_title = $row['title'];
8485
            $item_description = $row['comment'];
8486
        }
8487
        $parent = 0;
8488
        if ($id != 0 && is_array($extra_info)) {
8489
            $parent = $extra_info['parent_item_id'];
8490
        }
8491
        $arrLP = $this->getItemsForForm();
8492
        $this->tree_array($arrLP);
8493
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8494
        unset($this->arrMenu);
8495
8496
        if ($action == 'add') {
8497
            $legend = get_lang('CreateTheForum');
8498
        } elseif ($action == 'move') {
8499
            $legend = get_lang('MoveTheCurrentForum');
8500
        } else {
8501
            $legend = get_lang('EditCurrentForum');
8502
        }
8503
8504
        $form = new FormValidator(
8505
            'forum_form',
8506
            'POST',
8507
            $this->getCurrentBuildingModeURL()
8508
        );
8509
        $defaults = [];
8510
8511
        $form->addHeader($legend);
8512
8513
        if ($action != 'move') {
8514
            $this->setItemTitle($form);
8515
            $defaults['title'] = $item_title;
8516
        }
8517
8518
        $selectParent = $form->addSelect(
8519
            'parent',
8520
            get_lang('Parent'),
8521
            [],
8522
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8523
        );
8524
        $selectParent->addOption($this->name, 0);
8525
        $arrHide = [
8526
            $id,
8527
        ];
8528
        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...
8529
            if ($action != 'add') {
8530
                if ($arrLP[$i]['item_type'] == 'dir' &&
8531
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8532
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8533
                ) {
8534
                    $selectParent->addOption(
8535
                        $arrLP[$i]['title'],
8536
                        $arrLP[$i]['id'],
8537
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8538
                    );
8539
8540
                    if ($parent == $arrLP[$i]['id']) {
8541
                        $selectParent->setSelected($arrLP[$i]['id']);
8542
                    }
8543
                } else {
8544
                    $arrHide[] = $arrLP[$i]['id'];
8545
                }
8546
            } else {
8547
                if ($arrLP[$i]['item_type'] == 'dir') {
8548
                    $selectParent->addOption(
8549
                        $arrLP[$i]['title'],
8550
                        $arrLP[$i]['id'],
8551
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8552
                    );
8553
8554
                    if ($parent == $arrLP[$i]['id']) {
8555
                        $selectParent->setSelected($arrLP[$i]['id']);
8556
                    }
8557
                }
8558
            }
8559
        }
8560
8561
        if (is_array($arrLP)) {
8562
            reset($arrLP);
8563
        }
8564
8565
        $selectPrevious = $form->addSelect(
8566
            'previous',
8567
            get_lang('Position'),
8568
            [],
8569
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8570
        );
8571
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8572
8573
        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...
8574
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8575
                $arrLP[$i]['id'] != $id
8576
            ) {
8577
                $selectPrevious->addOption(
8578
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8579
                    $arrLP[$i]['id']
8580
                );
8581
8582
                if (isset($extra_info['previous_item_id']) &&
8583
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8584
                ) {
8585
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8586
                } elseif ($action == 'add') {
8587
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8588
                }
8589
            }
8590
        }
8591
8592
        if ($action != 'move') {
8593
            $id_prerequisite = 0;
8594
            if (is_array($arrLP)) {
8595
                foreach ($arrLP as $key => $value) {
8596
                    if ($value['id'] == $id) {
8597
                        $id_prerequisite = $value['prerequisite'];
8598
                        break;
8599
                    }
8600
                }
8601
            }
8602
8603
            $arrHide = [];
8604
            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...
8605
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8606
                    if (isset($extra_info['previous_item_id']) &&
8607
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8608
                    ) {
8609
                        $s_selected_position = $arrLP[$i]['id'];
8610
                    } elseif ($action == 'add') {
8611
                        $s_selected_position = 0;
8612
                    }
8613
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8614
                }
8615
            }
8616
        }
8617
8618
        if ('edit' === $action) {
8619
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8620
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8621
            }
8622
            $extraField = new ExtraField('lp_item');
8623
            $extraField->addElements($form, $id, $excludeExtraFields);
8624
        }
8625
8626
        if ($action == 'add') {
8627
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8628
        } else {
8629
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8630
        }
8631
8632
        if ($action == 'move') {
8633
            $form->addHidden('title', $item_title);
8634
            $form->addHidden('description', $item_description);
8635
        }
8636
8637
        if (is_numeric($extra_info)) {
8638
            $form->addHidden('path', $extra_info);
8639
        } elseif (is_array($extra_info)) {
8640
            $form->addHidden('path', $extra_info['path']);
8641
        }
8642
        $form->addHidden('type', TOOL_FORUM);
8643
        $form->addHidden('post_time', time());
8644
        $this->setAuthorLpItem($form);
8645
        $form->setDefaults($defaults);
8646
8647
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8648
    }
8649
8650
    /**
8651
     * Return HTML form to add/edit forum threads.
8652
     *
8653
     * @param string $action
8654
     * @param int    $id         Item ID if already exists in learning path
8655
     * @param string $extra_info
8656
     *
8657
     * @throws Exception
8658
     *
8659
     * @return string HTML form
8660
     */
8661
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8662
    {
8663
        $course_id = api_get_course_int_id();
8664
        if (empty($course_id)) {
8665
            return null;
8666
        }
8667
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8668
8669
        $item_title = '';
8670
        $item_description = '';
8671
        if ($id != 0 && is_array($extra_info)) {
8672
            $item_title = stripslashes($extra_info['title']);
8673
        } elseif (is_numeric($extra_info)) {
8674
            $sql = "SELECT thread_title as title FROM $tbl_forum
8675
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8676
8677
            $result = Database::query($sql);
8678
            $row = Database::fetch_array($result);
8679
8680
            $item_title = $row['title'];
8681
            $item_description = '';
8682
        }
8683
8684
        $parent = 0;
8685
        if ($id != 0 && is_array($extra_info)) {
8686
            $parent = $extra_info['parent_item_id'];
8687
        }
8688
8689
        $arrLP = $this->getItemsForForm();
8690
        $this->tree_array($arrLP);
8691
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8692
        unset($this->arrMenu);
8693
8694
        $form = new FormValidator(
8695
            'thread_form',
8696
            'POST',
8697
            $this->getCurrentBuildingModeURL()
8698
        );
8699
        $defaults = [];
8700
8701
        if ($action == 'add') {
8702
            $legend = get_lang('CreateTheForum');
8703
        } elseif ($action == 'move') {
8704
            $legend = get_lang('MoveTheCurrentForum');
8705
        } else {
8706
            $legend = get_lang('EditCurrentForum');
8707
        }
8708
8709
        $form->addHeader($legend);
8710
        $selectParent = $form->addSelect(
8711
            'parent',
8712
            get_lang('Parent'),
8713
            [],
8714
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8715
        );
8716
        $selectParent->addOption($this->name, 0);
8717
8718
        $arrHide = [
8719
            $id,
8720
        ];
8721
8722
        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...
8723
            if ($action != 'add') {
8724
                if (
8725
                    ($arrLP[$i]['item_type'] == 'dir') &&
8726
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8727
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8728
                ) {
8729
                    $selectParent->addOption(
8730
                        $arrLP[$i]['title'],
8731
                        $arrLP[$i]['id'],
8732
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8733
                    );
8734
8735
                    if ($parent == $arrLP[$i]['id']) {
8736
                        $selectParent->setSelected($arrLP[$i]['id']);
8737
                    }
8738
                } else {
8739
                    $arrHide[] = $arrLP[$i]['id'];
8740
                }
8741
            } else {
8742
                if ($arrLP[$i]['item_type'] == 'dir') {
8743
                    $selectParent->addOption(
8744
                        $arrLP[$i]['title'],
8745
                        $arrLP[$i]['id'],
8746
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8747
                    );
8748
8749
                    if ($parent == $arrLP[$i]['id']) {
8750
                        $selectParent->setSelected($arrLP[$i]['id']);
8751
                    }
8752
                }
8753
            }
8754
        }
8755
8756
        if ($arrLP != null) {
8757
            reset($arrLP);
8758
        }
8759
8760
        $selectPrevious = $form->addSelect(
8761
            'previous',
8762
            get_lang('Position'),
8763
            [],
8764
            ['id' => 'previous']
8765
        );
8766
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8767
8768
        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...
8769
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8770
                $selectPrevious->addOption(
8771
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8772
                    $arrLP[$i]['id']
8773
                );
8774
8775
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8776
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8777
                } elseif ($action == 'add') {
8778
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8779
                }
8780
            }
8781
        }
8782
8783
        if ($action != 'move') {
8784
            $this->setItemTitle($form);
8785
            $defaults['title'] = $item_title;
8786
8787
            $id_prerequisite = 0;
8788
            if ($arrLP != null) {
8789
                foreach ($arrLP as $key => $value) {
8790
                    if ($value['id'] == $id) {
8791
                        $id_prerequisite = $value['prerequisite'];
8792
                        break;
8793
                    }
8794
                }
8795
            }
8796
8797
            $arrHide = [];
8798
            $s_selected_position = 0;
8799
            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...
8800
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8801
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8802
                        $s_selected_position = $arrLP[$i]['id'];
8803
                    } elseif ($action == 'add') {
8804
                        $s_selected_position = 0;
8805
                    }
8806
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8807
                }
8808
            }
8809
8810
            $selectPrerequisites = $form->addSelect(
8811
                'prerequisites',
8812
                get_lang('LearnpathPrerequisites'),
8813
                [],
8814
                ['id' => 'prerequisites']
8815
            );
8816
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8817
8818
            foreach ($arrHide as $key => $value) {
8819
                $selectPrerequisites->addOption($value['value'], $key);
8820
8821
                if ($key == $s_selected_position && $action == 'add') {
8822
                    $selectPrerequisites->setSelected($key);
8823
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8824
                    $selectPrerequisites->setSelected($key);
8825
                }
8826
            }
8827
        }
8828
8829
        if ('edit' === $action) {
8830
            $excludeExtraFields = [];
8831
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8832
                $excludeExtraFields = ['start_date', 'end_date'];
8833
            }
8834
            $extraField = new ExtraField('lp_item');
8835
            $extraField->addElements($form, $id, $excludeExtraFields);
8836
        }
8837
8838
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8839
8840
        if ($action == 'move') {
8841
            $form->addHidden('title', $item_title);
8842
            $form->addHidden('description', $item_description);
8843
        }
8844
8845
        if (is_numeric($extra_info)) {
8846
            $form->addHidden('path', $extra_info);
8847
        } elseif (is_array($extra_info)) {
8848
            $form->addHidden('path', $extra_info['path']);
8849
        }
8850
8851
        $form->addHidden('type', TOOL_THREAD);
8852
        $form->addHidden('post_time', time());
8853
        $this->setAuthorLpItem($form);
8854
        $form->setDefaults($defaults);
8855
8856
        return $form->returnForm();
8857
    }
8858
8859
    /**
8860
     * Return the HTML form to display an item (generally a dir item).
8861
     *
8862
     * @param string $item_type
8863
     * @param string $title
8864
     * @param string $action
8865
     * @param int    $id
8866
     * @param string $extra_info
8867
     *
8868
     * @throws Exception
8869
     * @throws HTML_QuickForm_Error
8870
     *
8871
     * @return string HTML form
8872
     */
8873
    public function display_item_form(
8874
        $item_type,
8875
        $title = '',
8876
        $action = 'add_item',
8877
        $id = 0,
8878
        $extra_info = 'new'
8879
    ) {
8880
        $_course = api_get_course_info();
8881
8882
        global $charset;
8883
8884
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8885
        $item_title = '';
8886
        $item_description = '';
8887
        $item_path_fck = '';
8888
8889
        $parent = 0;
8890
        $previousId = null;
8891
        if ($id != 0 && is_array($extra_info)) {
8892
            $item_title = $extra_info['title'];
8893
            $item_description = $extra_info['description'];
8894
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8895
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8896
            $parent = $extra_info['parent_item_id'];
8897
            $previousId = $extra_info['previous_item_id'];
8898
        }
8899
8900
        if ($extra_info instanceof learnpathItem) {
8901
            $item_title = $extra_info->get_title();
8902
            $item_description = $extra_info->get_description();
8903
            $path = $extra_info->get_path();
8904
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8905
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8906
            $parent = $extra_info->get_parent();
8907
            $previousId = $extra_info->previous;
8908
        }
8909
8910
        $id = (int) $id;
8911
        $sql = "SELECT * FROM $tbl_lp_item
8912
                WHERE
8913
                    lp_id = ".$this->lp_id." AND
8914
                    iid != $id";
8915
8916
        if ($item_type === 'dir') {
8917
            $sql .= " AND parent_item_id = 0";
8918
        }
8919
8920
        $result = Database::query($sql);
8921
        $arrLP = [];
8922
        while ($row = Database::fetch_array($result)) {
8923
            $arrLP[] = [
8924
                'id' => $row['iid'],
8925
                'item_type' => $row['item_type'],
8926
                'title' => $this->cleanItemTitle($row['title']),
8927
                'title_raw' => $row['title'],
8928
                'path' => $row['path'],
8929
                'description' => $row['description'],
8930
                'parent_item_id' => $row['parent_item_id'],
8931
                'previous_item_id' => $row['previous_item_id'],
8932
                'next_item_id' => $row['next_item_id'],
8933
                'max_score' => $row['max_score'],
8934
                'min_score' => $row['min_score'],
8935
                'mastery_score' => $row['mastery_score'],
8936
                'prerequisite' => $row['prerequisite'],
8937
                'display_order' => $row['display_order'],
8938
            ];
8939
        }
8940
8941
        $this->tree_array($arrLP);
8942
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8943
        unset($this->arrMenu);
8944
8945
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8946
8947
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8948
        $defaults['title'] = api_html_entity_decode(
8949
            $item_title,
8950
            ENT_QUOTES,
8951
            $charset
8952
        );
8953
        $defaults['description'] = $item_description;
8954
8955
        $form->addHeader($title);
8956
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8957
        $arrHide[0]['padding'] = 20;
8958
        $charset = api_get_system_encoding();
8959
        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...
8960
            if ($action != 'add') {
8961
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8962
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8963
                ) {
8964
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8965
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8966
                    if ($parent == $arrLP[$i]['id']) {
8967
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8968
                    }
8969
                }
8970
            } else {
8971
                if ($arrLP[$i]['item_type'] === 'dir') {
8972
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8973
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8974
                    if ($parent == $arrLP[$i]['id']) {
8975
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8976
                    }
8977
                }
8978
            }
8979
        }
8980
8981
        if ($action !== 'move') {
8982
            $this->setItemTitle($form);
8983
        } else {
8984
            $form->addElement('hidden', 'title');
8985
        }
8986
8987
        $parentSelect = $form->addElement(
8988
            'select',
8989
            'parent',
8990
            get_lang('Parent'),
8991
            '',
8992
            [
8993
                'id' => 'idParent',
8994
                'onchange' => 'javascript: load_cbo(this.value);',
8995
            ]
8996
        );
8997
8998
        foreach ($arrHide as $key => $value) {
8999
            $parentSelect->addOption(
9000
                $value['value'],
9001
                $key,
9002
                'style="padding-left:'.$value['padding'].'px;"'
9003
            );
9004
            $lastPosition = $key;
9005
        }
9006
9007
        if (!empty($s_selected_parent)) {
9008
            $parentSelect->setSelected($s_selected_parent);
9009
        }
9010
9011
        if (is_array($arrLP)) {
9012
            reset($arrLP);
9013
        }
9014
9015
        $arrHide = [];
9016
        // POSITION
9017
        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...
9018
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
9019
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
9020
                //this is the same!
9021
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
9022
                    $s_selected_position = $arrLP[$i]['id'];
9023
                } elseif ($action === 'add') {
9024
                    $s_selected_position = $arrLP[$i]['id'];
9025
                }
9026
9027
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9028
            }
9029
        }
9030
9031
        $position = $form->addElement(
9032
            'select',
9033
            'previous',
9034
            get_lang('Position'),
9035
            '',
9036
            ['id' => 'previous']
9037
        );
9038
        $padding = isset($value['padding']) ? $value['padding'] : 0;
9039
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
9040
9041
        $lastPosition = null;
9042
        foreach ($arrHide as $key => $value) {
9043
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
9044
            $lastPosition = $key;
9045
        }
9046
9047
        if (!empty($s_selected_position)) {
9048
            $position->setSelected($s_selected_position);
9049
        }
9050
9051
        // When new chapter add at the end
9052
        if ($action === 'add_item') {
9053
            $position->setSelected($lastPosition);
9054
        }
9055
9056
        if (is_array($arrLP)) {
9057
            reset($arrLP);
9058
        }
9059
9060
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
9061
9062
        //fix in order to use the tab
9063
        if ($item_type === 'dir') {
9064
            $form->addElement('hidden', 'type', 'dir');
9065
        }
9066
9067
        $extension = null;
9068
        if (!empty($item_path)) {
9069
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
9070
        }
9071
9072
        //assets can't be modified
9073
        //$item_type == 'asset' ||
9074
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
9075
            if ($item_type === 'sco') {
9076
                $form->addElement(
9077
                    'html',
9078
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
9079
                );
9080
            }
9081
            $renderer = $form->defaultRenderer();
9082
            $renderer->setElementTemplate(
9083
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
9084
                'content_lp'
9085
            );
9086
9087
            $relative_prefix = '';
9088
            $editor_config = [
9089
                'ToolbarSet' => 'LearningPathDocuments',
9090
                'Width' => '100%',
9091
                'Height' => '500',
9092
                'FullPage' => true,
9093
                'CreateDocumentDir' => $relative_prefix,
9094
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
9095
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
9096
            ];
9097
9098
            $form->addHtmlEditor('content_lp', '', true, true, $editor_config);
9099
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
9100
            $defaults['content_lp'] = file_get_contents($content_path);
9101
        }
9102
9103
        if (!empty($id)) {
9104
            $form->addHidden('id', $id);
9105
        }
9106
9107
        $form->addElement('hidden', 'type', $item_type);
9108
        $form->addElement('hidden', 'post_time', time());
9109
        $form->setDefaults($defaults);
9110
9111
        return $form->returnForm();
9112
    }
9113
9114
    /**
9115
     * @return string
9116
     */
9117
    public function getCurrentBuildingModeURL()
9118
    {
9119
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
9120
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
9121
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
9122
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
9123
9124
        $currentUrl = api_get_self().'?'.api_get_cidreq().
9125
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
9126
9127
        return $currentUrl;
9128
    }
9129
9130
    /**
9131
     * Returns the form to update or create a document.
9132
     *
9133
     * @param string        $action     (add/edit)
9134
     * @param int           $id         ID of the lp_item (if already exists)
9135
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
9136
     * @param learnpathItem $item
9137
     *
9138
     * @return string HTML form
9139
     */
9140
    public function display_document_form(
9141
        $action = 'add',
9142
        $id = 0,
9143
        $extra_info = 'new',
9144
        $item = null,
9145
        $excludeExtraFields = []
9146
    ) {
9147
        $course_id = api_get_course_int_id();
9148
        $_course = api_get_course_info();
9149
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9150
9151
        $no_display_edit_textarea = false;
9152
        //If action==edit document
9153
        //We don't display the document form if it's not an editable document (html or txt file)
9154
        if ($action === 'edit') {
9155
            if (is_array($extra_info)) {
9156
                $path_parts = pathinfo($extra_info['dir']);
9157
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
9158
                    $no_display_edit_textarea = true;
9159
                }
9160
            }
9161
        }
9162
        $no_display_add = false;
9163
        // If action==add an existing document
9164
        // We don't display the document form if it's not an editable document (html or txt file).
9165
        if ($action === 'add') {
9166
            if (is_numeric($extra_info)) {
9167
                $extra_info = (int) $extra_info;
9168
                $sql_doc = "SELECT path FROM $tbl_doc
9169
                            WHERE c_id = $course_id AND iid = ".$extra_info;
9170
                $result = Database::query($sql_doc);
9171
                $path_file = Database::result($result, 0, 0);
9172
                $path_parts = pathinfo($path_file);
9173
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
9174
                    $no_display_add = true;
9175
                }
9176
            }
9177
        }
9178
9179
        $item_title = '';
9180
        $item_description = '';
9181
        if ($id != 0 && is_array($extra_info)) {
9182
            $item_title = stripslashes($extra_info['title']);
9183
            $item_description = stripslashes($extra_info['description']);
9184
            if (empty($item_title)) {
9185
                $path_parts = pathinfo($extra_info['path']);
9186
                $item_title = stripslashes($path_parts['filename']);
9187
            }
9188
        } elseif (is_numeric($extra_info)) {
9189
            $sql = "SELECT path, title FROM $tbl_doc
9190
                    WHERE
9191
                        c_id = ".$course_id." AND
9192
                        iid = ".intval($extra_info);
9193
            $result = Database::query($sql);
9194
            $row = Database::fetch_array($result);
9195
            $item_title = $row['title'];
9196
            $item_title = str_replace('_', ' ', $item_title);
9197
            if (empty($item_title)) {
9198
                $path_parts = pathinfo($row['path']);
9199
                $item_title = stripslashes($path_parts['filename']);
9200
            }
9201
        }
9202
9203
        $return = '<legend>';
9204
        $parent = 0;
9205
        if ($id != 0 && is_array($extra_info)) {
9206
            $parent = $extra_info['parent_item_id'];
9207
        }
9208
9209
        $selectedPosition = 0;
9210
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
9211
            $selectedPosition = $extra_info['previous_item_id'];
9212
        }
9213
9214
        if ($item instanceof learnpathItem) {
9215
            $item_title = stripslashes($item->get_title());
9216
            $item_description = stripslashes($item->get_description());
9217
            $selectedPosition = $item->previous;
9218
            $parent = $item->parent;
9219
        }
9220
9221
        $arrLP = $this->getItemsForForm();
9222
        $this->tree_array($arrLP);
9223
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9224
        unset($this->arrMenu);
9225
9226
        if ($action === 'add') {
9227
            $return .= get_lang('CreateTheDocument');
9228
        } elseif ($action === 'move') {
9229
            $return .= get_lang('MoveTheCurrentDocument');
9230
        } else {
9231
            $return .= get_lang('EditTheCurrentDocument');
9232
        }
9233
        $return .= '</legend>';
9234
9235
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
9236
            $return .= Display::return_message(
9237
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
9238
                false
9239
            );
9240
        }
9241
        $form = new FormValidator(
9242
            'form',
9243
            'POST',
9244
            $this->getCurrentBuildingModeURL(),
9245
            '',
9246
            ['enctype' => 'multipart/form-data']
9247
        );
9248
        $defaults['title'] = Security::remove_XSS($item_title);
9249
        if (empty($item_title)) {
9250
            $defaults['title'] = Security::remove_XSS($item_title);
9251
        }
9252
        $defaults['description'] = $item_description;
9253
        $form->addElement('html', $return);
9254
9255
        if ($action !== 'move') {
9256
            $data = $this->generate_lp_folder($_course);
9257
            if ($action !== 'edit') {
9258
                $folders = DocumentManager::get_all_document_folders(
9259
                    $_course,
9260
                    0,
9261
                    true
9262
                );
9263
                DocumentManager::build_directory_selector(
9264
                    $folders,
9265
                    '',
9266
                    [],
9267
                    true,
9268
                    $form,
9269
                    'directory_parent_id'
9270
                );
9271
            }
9272
9273
            if (isset($data['id'])) {
9274
                $defaults['directory_parent_id'] = $data['id'];
9275
            }
9276
            $this->setItemTitle($form);
9277
        }
9278
9279
        $arrHide[0]['value'] = $this->name;
9280
        $arrHide[0]['padding'] = 20;
9281
9282
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9283
            if ($action !== 'add') {
9284
                if ($arrLP[$i]['item_type'] === 'dir' &&
9285
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9286
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9287
                ) {
9288
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9289
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9290
                }
9291
            } else {
9292
                if ($arrLP[$i]['item_type'] == 'dir') {
9293
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9294
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9295
                }
9296
            }
9297
        }
9298
9299
        $parentSelect = $form->addSelect(
9300
            'parent',
9301
            get_lang('Parent'),
9302
            [],
9303
            [
9304
                'id' => 'idParent',
9305
                'onchange' => 'javascript: load_cbo(this.value);',
9306
            ]
9307
        );
9308
9309
        $my_count = 0;
9310
        foreach ($arrHide as $key => $value) {
9311
            if ($my_count != 0) {
9312
                // The LP name is also the first section and is not in the same charset like the other sections.
9313
                $value['value'] = Security::remove_XSS($value['value']);
9314
                $parentSelect->addOption(
9315
                    $value['value'],
9316
                    $key,
9317
                    'style="padding-left:'.$value['padding'].'px;"'
9318
                );
9319
            } else {
9320
                $value['value'] = Security::remove_XSS($value['value']);
9321
                $parentSelect->addOption(
9322
                    $value['value'],
9323
                    $key,
9324
                    'style="padding-left:'.$value['padding'].'px;"'
9325
                );
9326
            }
9327
            $my_count++;
9328
        }
9329
9330
        if (!empty($id)) {
9331
            $parentSelect->setSelected($parent);
9332
        } else {
9333
            $parent_item_id = Session::read('parent_item_id', 0);
9334
            $parentSelect->setSelected($parent_item_id);
9335
        }
9336
9337
        if (is_array($arrLP)) {
9338
            reset($arrLP);
9339
        }
9340
9341
        $arrHide = [];
9342
        // POSITION
9343
        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...
9344
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9345
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9346
            ) {
9347
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9348
            }
9349
        }
9350
9351
        $position = $form->addSelect(
9352
            'previous',
9353
            get_lang('Position'),
9354
            [],
9355
            ['id' => 'previous']
9356
        );
9357
9358
        $position->addOption(get_lang('FirstPosition'), 0);
9359
        foreach ($arrHide as $key => $value) {
9360
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9361
            $position->addOption(
9362
                $value['value'],
9363
                $key,
9364
                'style="padding-left:'.$padding.'px;"'
9365
            );
9366
        }
9367
        $position->setSelected($selectedPosition);
9368
9369
        if (is_array($arrLP)) {
9370
            reset($arrLP);
9371
        }
9372
9373
        if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
9374
            $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
9375
        }
9376
        $extraField = new ExtraField('lp_item');
9377
        $extraField->addElements($form, $id, $excludeExtraFields);
9378
9379
        if ($action !== 'move') {
9380
            $arrHide = [];
9381
            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...
9382
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
9383
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9384
                ) {
9385
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9386
                }
9387
            }
9388
9389
            if (!$no_display_add) {
9390
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9391
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9392
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
9393
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
9394
                ) {
9395
                    if (isset($_POST['content'])) {
9396
                        $content = stripslashes($_POST['content']);
9397
                    } elseif (is_array($extra_info)) {
9398
                        //If it's an html document or a text file
9399
                        if (!$no_display_edit_textarea) {
9400
                            $content = $this->display_document(
9401
                                $extra_info['path'],
9402
                                false,
9403
                                false
9404
                            );
9405
                        }
9406
                    } elseif (is_numeric($extra_info)) {
9407
                        $content = $this->display_document(
9408
                            $extra_info,
9409
                            false,
9410
                            false
9411
                        );
9412
                    } else {
9413
                        $content = '';
9414
                    }
9415
9416
                    if (!$no_display_edit_textarea) {
9417
                        // We need to calculate here some specific settings for the online editor.
9418
                        // The calculated settings work for documents in the Documents tool
9419
                        // (on the root or in subfolders).
9420
                        // For documents in native scorm packages it is unclear whether the
9421
                        // online editor should be activated or not.
9422
9423
                        // A new document, it is in the root of the repository.
9424
                        if (is_array($extra_info) && $extra_info != 'new') {
9425
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9426
                            $relative_path = explode('/', $extra_info['dir']);
9427
                            $cnt = count($relative_path) - 2;
9428
                            if ($cnt < 0) {
9429
                                $cnt = 0;
9430
                            }
9431
                            $relative_prefix = str_repeat('../', $cnt);
9432
                            $relative_path = array_slice($relative_path, 1, $cnt);
9433
                            $relative_path = implode('/', $relative_path);
9434
                            if (strlen($relative_path) > 0) {
9435
                                $relative_path = $relative_path.'/';
9436
                            }
9437
                        } else {
9438
                            $result = $this->generate_lp_folder($_course);
9439
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9440
                            $relative_prefix = '../../';
9441
                        }
9442
9443
                        $editor_config = [
9444
                            'ToolbarSet' => 'LearningPathDocuments',
9445
                            'Width' => '100%',
9446
                            'Height' => '500',
9447
                            'FullPage' => true,
9448
                            'CreateDocumentDir' => $relative_prefix,
9449
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9450
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9451
                        ];
9452
9453
                        if ($_GET['action'] === 'add_item') {
9454
                            $class = 'add';
9455
                            $text = get_lang('LPCreateDocument');
9456
                        } else {
9457
                            if ($_GET['action'] === 'edit_item') {
9458
                                $class = 'save';
9459
                                $text = get_lang('SaveDocument');
9460
                            }
9461
                        }
9462
9463
                        $form->addButtonSave($text, 'submit_button');
9464
                        $renderer = $form->defaultRenderer();
9465
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9466
                        $form->addElement('html', '<div class="editor-lp">');
9467
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9468
                        $form->addElement('html', '</div>');
9469
                        $defaults['content_lp'] = $content;
9470
                    }
9471
                } elseif (is_numeric($extra_info)) {
9472
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9473
9474
                    $return = $this->display_document($extra_info, true, true, true);
9475
                    $form->addElement('html', $return);
9476
                }
9477
            }
9478
        }
9479
        if (isset($extra_info['item_type']) &&
9480
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9481
        ) {
9482
            $parentSelect->freeze();
9483
            $position->freeze();
9484
        }
9485
9486
        if ($action === 'move') {
9487
            $form->addElement('hidden', 'title', $item_title);
9488
            $form->addElement('hidden', 'description', $item_description);
9489
        }
9490
        if (is_numeric($extra_info)) {
9491
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9492
            $form->addElement('hidden', 'path', $extra_info);
9493
        } elseif (is_array($extra_info)) {
9494
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9495
            $form->addElement('hidden', 'path', $extra_info['path']);
9496
        }
9497
9498
        if ($item instanceof learnpathItem) {
9499
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9500
            $form->addElement('hidden', 'path', $item->path);
9501
        }
9502
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9503
        $form->addElement('hidden', 'post_time', time());
9504
        $this->setAuthorLpItem($form);
9505
        $form->setDefaults($defaults);
9506
9507
        return $form->returnForm();
9508
    }
9509
9510
    /**
9511
     * Returns the form to update or create a read-out text.
9512
     *
9513
     * @param string $action     "add" or "edit"
9514
     * @param int    $id         ID of the lp_item (if already exists)
9515
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9516
     *
9517
     * @throws Exception
9518
     * @throws HTML_QuickForm_Error
9519
     *
9520
     * @return string HTML form
9521
     */
9522
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9523
    {
9524
        $course_id = api_get_course_int_id();
9525
        $_course = api_get_course_info();
9526
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9527
9528
        $no_display_edit_textarea = false;
9529
        //If action==edit document
9530
        //We don't display the document form if it's not an editable document (html or txt file)
9531
        if ($action == 'edit') {
9532
            if (is_array($extra_info)) {
9533
                $path_parts = pathinfo($extra_info['dir']);
9534
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9535
                    $no_display_edit_textarea = true;
9536
                }
9537
            }
9538
        }
9539
        $no_display_add = false;
9540
9541
        $item_title = '';
9542
        $item_description = '';
9543
        if ($id != 0 && is_array($extra_info)) {
9544
            $item_title = stripslashes($extra_info['title']);
9545
            $item_description = stripslashes($extra_info['description']);
9546
            $item_terms = stripslashes($extra_info['terms']);
9547
            if (empty($item_title)) {
9548
                $path_parts = pathinfo($extra_info['path']);
9549
                $item_title = stripslashes($path_parts['filename']);
9550
            }
9551
        } elseif (is_numeric($extra_info)) {
9552
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9553
            $result = Database::query($sql);
9554
            $row = Database::fetch_array($result);
9555
            $item_title = $row['title'];
9556
            $item_title = str_replace('_', ' ', $item_title);
9557
            if (empty($item_title)) {
9558
                $path_parts = pathinfo($row['path']);
9559
                $item_title = stripslashes($path_parts['filename']);
9560
            }
9561
        }
9562
9563
        $parent = 0;
9564
        if ($id != 0 && is_array($extra_info)) {
9565
            $parent = $extra_info['parent_item_id'];
9566
        }
9567
9568
        $arrLP = $this->getItemsForForm();
9569
        $this->tree_array($arrLP);
9570
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9571
        unset($this->arrMenu);
9572
9573
        if ($action === 'add') {
9574
            $formHeader = get_lang('CreateTheDocument');
9575
        } else {
9576
            $formHeader = get_lang('EditTheCurrentDocument');
9577
        }
9578
9579
        if ('edit' === $action) {
9580
            $urlAudioIcon = Display::url(
9581
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9582
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9583
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9584
            );
9585
        } else {
9586
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9587
        }
9588
9589
        $form = new FormValidator(
9590
            'frm_add_reading',
9591
            'POST',
9592
            $this->getCurrentBuildingModeURL(),
9593
            '',
9594
            ['enctype' => 'multipart/form-data']
9595
        );
9596
        $form->addHeader($formHeader);
9597
        $form->addHtml(
9598
            Display::return_message(
9599
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9600
                'normal',
9601
                false
9602
            )
9603
        );
9604
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9605
        $defaults['description'] = $item_description;
9606
9607
        $data = $this->generate_lp_folder($_course);
9608
9609
        if ($action != 'edit') {
9610
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9611
            DocumentManager::build_directory_selector(
9612
                $folders,
9613
                '',
9614
                [],
9615
                true,
9616
                $form,
9617
                'directory_parent_id'
9618
            );
9619
        }
9620
9621
        if (isset($data['id'])) {
9622
            $defaults['directory_parent_id'] = $data['id'];
9623
        }
9624
        $this->setItemTitle($form);
9625
9626
        $arrHide[0]['value'] = $this->name;
9627
        $arrHide[0]['padding'] = 20;
9628
9629
        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...
9630
            if ($action != 'add') {
9631
                if ($arrLP[$i]['item_type'] == 'dir' &&
9632
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9633
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9634
                ) {
9635
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9636
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9637
                }
9638
            } else {
9639
                if ($arrLP[$i]['item_type'] == 'dir') {
9640
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9641
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9642
                }
9643
            }
9644
        }
9645
9646
        $parent_select = $form->addSelect(
9647
            'parent',
9648
            get_lang('Parent'),
9649
            [],
9650
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9651
        );
9652
9653
        $my_count = 0;
9654
        foreach ($arrHide as $key => $value) {
9655
            if ($my_count != 0) {
9656
                // The LP name is also the first section and is not in the same charset like the other sections.
9657
                $value['value'] = Security::remove_XSS($value['value']);
9658
                $parent_select->addOption(
9659
                    $value['value'],
9660
                    $key,
9661
                    'style="padding-left:'.$value['padding'].'px;"'
9662
                );
9663
            } else {
9664
                $value['value'] = Security::remove_XSS($value['value']);
9665
                $parent_select->addOption(
9666
                    $value['value'],
9667
                    $key,
9668
                    'style="padding-left:'.$value['padding'].'px;"'
9669
                );
9670
            }
9671
            $my_count++;
9672
        }
9673
9674
        if (!empty($id)) {
9675
            $parent_select->setSelected($parent);
9676
        } else {
9677
            $parent_item_id = Session::read('parent_item_id', 0);
9678
            $parent_select->setSelected($parent_item_id);
9679
        }
9680
9681
        if (is_array($arrLP)) {
9682
            reset($arrLP);
9683
        }
9684
9685
        $arrHide = [];
9686
        $s_selected_position = null;
9687
9688
        // POSITION
9689
        $lastPosition = null;
9690
9691
        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...
9692
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9693
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9694
            ) {
9695
                if ((isset($extra_info['previous_item_id']) &&
9696
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9697
                ) {
9698
                    $s_selected_position = $arrLP[$i]['id'];
9699
                }
9700
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9701
            }
9702
            $lastPosition = $arrLP[$i]['id'];
9703
        }
9704
9705
        if (empty($s_selected_position)) {
9706
            $s_selected_position = $lastPosition;
9707
        }
9708
9709
        $position = $form->addSelect(
9710
            'previous',
9711
            get_lang('Position'),
9712
            []
9713
        );
9714
        $position->addOption(get_lang('FirstPosition'), 0);
9715
9716
        foreach ($arrHide as $key => $value) {
9717
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9718
            $position->addOption(
9719
                $value['value'],
9720
                $key,
9721
                'style="padding-left:'.$padding.'px;"'
9722
            );
9723
        }
9724
        $position->setSelected($s_selected_position);
9725
9726
        if (is_array($arrLP)) {
9727
            reset($arrLP);
9728
        }
9729
9730
        if ('edit' === $action) {
9731
            $excludeExtraFields = [];
9732
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
9733
                $excludeExtraFields = ['start_date', 'end_date'];
9734
            }
9735
            $extraField = new ExtraField('lp_item');
9736
            $extraField->addElements($form, $id, $excludeExtraFields);
9737
        }
9738
9739
        $arrHide = [];
9740
9741
        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...
9742
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9743
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9744
            ) {
9745
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9746
            }
9747
        }
9748
9749
        if (!$no_display_add) {
9750
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9751
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9752
9753
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9754
                if (!$no_display_edit_textarea) {
9755
                    $content = '';
9756
9757
                    if (isset($_POST['content'])) {
9758
                        $content = stripslashes($_POST['content']);
9759
                    } elseif (is_array($extra_info)) {
9760
                        $content = $this->display_document($extra_info['path'], false, false);
9761
                    } elseif (is_numeric($extra_info)) {
9762
                        $content = $this->display_document($extra_info, false, false);
9763
                    }
9764
9765
                    // A new document, it is in the root of the repository.
9766
                    if (is_array($extra_info) && $extra_info != 'new') {
9767
                    } else {
9768
                        $this->generate_lp_folder($_course);
9769
                    }
9770
9771
                    if ($_GET['action'] == 'add_item') {
9772
                        $text = get_lang('LPCreateDocument');
9773
                    } else {
9774
                        $text = get_lang('SaveDocument');
9775
                    }
9776
9777
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9778
                    $form
9779
                        ->defaultRenderer()
9780
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9781
                    $form->addButtonSave($text, 'submit_button');
9782
                    $defaults['content_lp'] = $content;
9783
                }
9784
            } elseif (is_numeric($extra_info)) {
9785
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9786
9787
                $return = $this->display_document($extra_info, true, true, true);
9788
                $form->addElement('html', $return);
9789
            }
9790
        }
9791
9792
        if (is_numeric($extra_info)) {
9793
            $form->addElement('hidden', 'path', $extra_info);
9794
        } elseif (is_array($extra_info)) {
9795
            $form->addElement('hidden', 'path', $extra_info['path']);
9796
        }
9797
9798
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9799
        $form->addElement('hidden', 'post_time', time());
9800
        $this->setAuthorLpItem($form);
9801
        $form->setDefaults($defaults);
9802
9803
        return $form->returnForm();
9804
    }
9805
9806
    /**
9807
     * @param array  $courseInfo
9808
     * @param string $content
9809
     * @param string $title
9810
     * @param int    $parentId
9811
     *
9812
     * @throws \Doctrine\ORM\ORMException
9813
     * @throws \Doctrine\ORM\OptimisticLockException
9814
     * @throws \Doctrine\ORM\TransactionRequiredException
9815
     *
9816
     * @return int
9817
     */
9818
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9819
    {
9820
        $creatorId = api_get_user_id();
9821
        $sessionId = api_get_session_id();
9822
9823
        // Generates folder
9824
        $result = $this->generate_lp_folder($courseInfo);
9825
        $dir = $result['dir'];
9826
9827
        if (empty($parentId) || $parentId == '/') {
9828
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9829
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9830
9831
            if ($parentId === '/') {
9832
                $dir = '/';
9833
            }
9834
9835
            // Please, do not modify this dirname formatting.
9836
            if (strstr($dir, '..')) {
9837
                $dir = '/';
9838
            }
9839
9840
            if (!empty($dir[0]) && $dir[0] == '.') {
9841
                $dir = substr($dir, 1);
9842
            }
9843
            if (!empty($dir[0]) && $dir[0] != '/') {
9844
                $dir = '/'.$dir;
9845
            }
9846
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9847
                $dir .= '/';
9848
            }
9849
        } else {
9850
            $parentInfo = DocumentManager::get_document_data_by_id(
9851
                $parentId,
9852
                $courseInfo['code']
9853
            );
9854
            if (!empty($parentInfo)) {
9855
                $dir = $parentInfo['path'].'/';
9856
            }
9857
        }
9858
9859
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9860
9861
        if (!is_dir($filepath)) {
9862
            $dir = '/';
9863
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9864
        }
9865
9866
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9867
9868
        if (!empty($title)) {
9869
            $title = api_replace_dangerous_char(stripslashes($title));
9870
        } else {
9871
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9872
        }
9873
9874
        $title = disable_dangerous_file($title);
9875
        $filename = $title;
9876
        $content = !empty($content) ? $content : $_POST['content_lp'];
9877
        $tmpFileName = $filename;
9878
9879
        $i = 0;
9880
        while (file_exists($filepath.$tmpFileName.'.html')) {
9881
            $tmpFileName = $filename.'_'.++$i;
9882
        }
9883
9884
        $filename = $tmpFileName.'.html';
9885
        $content = stripslashes($content);
9886
9887
        if (file_exists($filepath.$filename)) {
9888
            return 0;
9889
        }
9890
9891
        $putContent = file_put_contents($filepath.$filename, $content);
9892
9893
        if ($putContent === false) {
9894
            return 0;
9895
        }
9896
9897
        $fileSize = filesize($filepath.$filename);
9898
        $saveFilePath = $dir.$filename;
9899
9900
        $documentId = add_document(
9901
            $courseInfo,
9902
            $saveFilePath,
9903
            'file',
9904
            $fileSize,
9905
            $tmpFileName,
9906
            '',
9907
            0, //readonly
9908
            true,
9909
            null,
9910
            $sessionId,
9911
            $creatorId
9912
        );
9913
9914
        if (!$documentId) {
9915
            return 0;
9916
        }
9917
9918
        api_item_property_update(
9919
            $courseInfo,
9920
            TOOL_DOCUMENT,
9921
            $documentId,
9922
            'DocumentAdded',
9923
            $creatorId,
9924
            null,
9925
            null,
9926
            null,
9927
            null,
9928
            $sessionId
9929
        );
9930
9931
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9932
        $newTitle = $originalTitle;
9933
9934
        if ($newComment || $newTitle) {
9935
            $em = Database::getManager();
9936
9937
            /** @var CDocument $doc */
9938
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9939
9940
            if ($newComment) {
9941
                $doc->setComment($newComment);
9942
            }
9943
9944
            if ($newTitle) {
9945
                $doc->setTitle($newTitle);
9946
            }
9947
9948
            $em->persist($doc);
9949
            $em->flush();
9950
        }
9951
9952
        return $documentId;
9953
    }
9954
9955
    /**
9956
     * Return HTML form to add/edit a link item.
9957
     *
9958
     * @param string $action     (add/edit)
9959
     * @param int    $id         Item ID if exists
9960
     * @param mixed  $extra_info
9961
     *
9962
     * @throws Exception
9963
     * @throws HTML_QuickForm_Error
9964
     *
9965
     * @return string HTML form
9966
     */
9967
    public function display_link_form(
9968
        $action = 'add',
9969
        $id = 0,
9970
        $extra_info = '',
9971
        $item = null,
9972
        $excludeExtraFields = []
9973
    ) {
9974
        $course_id = api_get_course_int_id();
9975
        $tbl_link = Database::get_course_table(TABLE_LINK);
9976
9977
        $item_title = '';
9978
        $item_description = '';
9979
        $item_url = '';
9980
9981
        $previousId = 0;
9982
        if ($id != 0 && is_array($extra_info)) {
9983
            $item_title = stripslashes($extra_info['title']);
9984
            $item_description = stripslashes($extra_info['description']);
9985
            $item_url = stripslashes($extra_info['url']);
9986
            $previousId = $extra_info['previous_item_id'];
9987
        } elseif (is_numeric($extra_info)) {
9988
            $extra_info = (int) $extra_info;
9989
            $sql = "SELECT title, description, url
9990
                    FROM $tbl_link
9991
                    WHERE c_id = $course_id AND iid = $extra_info";
9992
            $result = Database::query($sql);
9993
            $row = Database::fetch_array($result);
9994
            $item_title = $row['title'];
9995
            $item_description = $row['description'];
9996
            $item_url = $row['url'];
9997
        }
9998
9999
        if ($item instanceof learnpathItem) {
10000
            $previousId = $extra_info->previous;
10001
        }
10002
10003
        $form = new FormValidator(
10004
            'edit_link',
10005
            'POST',
10006
            $this->getCurrentBuildingModeURL()
10007
        );
10008
        $defaults = [];
10009
        $parent = 0;
10010
        if ($id != 0 && is_array($extra_info)) {
10011
            $parent = $extra_info['parent_item_id'];
10012
        }
10013
10014
        $arrLP = $this->getItemsForForm();
10015
10016
        $this->tree_array($arrLP);
10017
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10018
        unset($this->arrMenu);
10019
10020
        if ($action === 'add') {
10021
            $legend = get_lang('CreateTheLink');
10022
        } elseif ($action === 'move') {
10023
            $legend = get_lang('MoveCurrentLink');
10024
        } else {
10025
            $legend = get_lang('EditCurrentLink');
10026
        }
10027
10028
        $form->addHeader($legend);
10029
10030
        if ($action !== 'move') {
10031
            $this->setItemTitle($form);
10032
            $defaults['title'] = $item_title;
10033
        }
10034
10035
        $selectParent = $form->addSelect(
10036
            'parent',
10037
            get_lang('Parent'),
10038
            [],
10039
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
10040
        );
10041
        $selectParent->addOption($this->name, 0);
10042
        $arrHide = [
10043
            $id,
10044
        ];
10045
10046
        $parent_item_id = Session::read('parent_item_id', 0);
10047
10048
        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...
10049
            if ($action != 'add') {
10050
                if (
10051
                    ($arrLP[$i]['item_type'] == 'dir') &&
10052
                    !in_array($arrLP[$i]['id'], $arrHide) &&
10053
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
10054
                ) {
10055
                    $selectParent->addOption(
10056
                        $arrLP[$i]['title'],
10057
                        $arrLP[$i]['id'],
10058
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
10059
                    );
10060
10061
                    if ($parent == $arrLP[$i]['id']) {
10062
                        $selectParent->setSelected($arrLP[$i]['id']);
10063
                    }
10064
                } else {
10065
                    $arrHide[] = $arrLP[$i]['id'];
10066
                }
10067
            } else {
10068
                if ($arrLP[$i]['item_type'] == 'dir') {
10069
                    $selectParent->addOption(
10070
                        $arrLP[$i]['title'],
10071
                        $arrLP[$i]['id'],
10072
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
10073
                    );
10074
10075
                    if ($parent_item_id == $arrLP[$i]['id']) {
10076
                        $selectParent->setSelected($arrLP[$i]['id']);
10077
                    }
10078
                }
10079
            }
10080
        }
10081
10082
        if (is_array($arrLP)) {
10083
            reset($arrLP);
10084
        }
10085
10086
        $selectPrevious = $form->addSelect(
10087
            'previous',
10088
            get_lang('Position'),
10089
            [],
10090
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10091
        );
10092
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
10093
10094
        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...
10095
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10096
                $selectPrevious->addOption(
10097
                    $arrLP[$i]['title'],
10098
                    $arrLP[$i]['id']
10099
                );
10100
10101
                if ($previousId == $arrLP[$i]['id']) {
10102
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10103
                } elseif ($action === 'add') {
10104
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10105
                }
10106
            }
10107
        }
10108
10109
        if ($action !== 'move') {
10110
            $urlAttributes = ['class' => 'learnpath_item_form'];
10111
10112
            if (is_numeric($extra_info)) {
10113
                $urlAttributes['disabled'] = 'disabled';
10114
            }
10115
10116
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
10117
            $defaults['url'] = $item_url;
10118
            $arrHide = [];
10119
            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...
10120
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
10121
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
10122
                }
10123
            }
10124
        }
10125
10126
        if ('edit' === $action) {
10127
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
10128
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
10129
            }
10130
            $extraField = new ExtraField('lp_item');
10131
            $extraField->addElements($form, $id, $excludeExtraFields);
10132
        }
10133
10134
        if ($action === 'add') {
10135
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
10136
        } else {
10137
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
10138
        }
10139
10140
        if ($action === 'move') {
10141
            $form->addHidden('title', $item_title);
10142
            $form->addHidden('description', $item_description);
10143
        }
10144
10145
        if (is_numeric($extra_info)) {
10146
            $form->addHidden('path', $extra_info);
10147
        } elseif (is_array($extra_info)) {
10148
            $form->addHidden('path', $extra_info['path']);
10149
        }
10150
        $form->addHidden('type', TOOL_LINK);
10151
        $form->addHidden('post_time', time());
10152
        $this->setAuthorLpItem($form);
10153
        $form->setDefaults($defaults);
10154
10155
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
10156
    }
10157
10158
    /**
10159
     * Return HTML form to add/edit a student publication (work).
10160
     *
10161
     * @param string $action
10162
     * @param int    $id         Item ID if already exists
10163
     * @param string $extra_info
10164
     *
10165
     * @throws Exception
10166
     *
10167
     * @return string HTML form
10168
     */
10169
    public function display_student_publication_form(
10170
        $action = 'add',
10171
        $id = 0,
10172
        $extra_info = '',
10173
        $item = null,
10174
        $excludeExtraFields = []
10175
    ) {
10176
        $course_id = api_get_course_int_id();
10177
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
10178
10179
        $item_title = get_lang('Student_publication');
10180
        $previousId = 0;
10181
        if ($id != 0 && is_array($extra_info)) {
10182
            $item_title = stripslashes($extra_info['title']);
10183
            $item_description = stripslashes($extra_info['description']);
10184
            $previousId = $extra_info['previous_item_id'];
10185
        } elseif (is_numeric($extra_info)) {
10186
            $extra_info = (int) $extra_info;
10187
            $sql = "SELECT title, description
10188
                    FROM $tbl_publication
10189
                    WHERE c_id = $course_id AND id = ".$extra_info;
10190
10191
            $result = Database::query($sql);
10192
            $row = Database::fetch_array($result);
10193
            if ($row) {
10194
                $item_title = $row['title'];
10195
            }
10196
        }
10197
10198
        if ($item instanceof learnpathItem) {
10199
            $item_title = $item->get_title();
10200
            $item_description = $item->get_description();
10201
            $previousId = $item->previous;
10202
        }
10203
10204
        $parent = 0;
10205
        if ($id != 0 && is_array($extra_info)) {
10206
            $parent = $extra_info['parent_item_id'];
10207
        }
10208
10209
        $arrLP = $this->getItemsForForm();
10210
10211
        $this->tree_array($arrLP);
10212
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10213
        unset($this->arrMenu);
10214
10215
        $form = new FormValidator('frm_student_publication', 'post', '#');
10216
10217
        if ($action === 'add') {
10218
            $form->addHeader(get_lang('Student_publication'));
10219
        } elseif ($action === 'move') {
10220
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
10221
        } else {
10222
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
10223
        }
10224
10225
        if ($action !== 'move') {
10226
            $this->setItemTitle($form);
10227
        }
10228
10229
        $parentSelect = $form->addSelect(
10230
            'parent',
10231
            get_lang('Parent'),
10232
            ['0' => $this->name],
10233
            [
10234
                'onchange' => 'javascript: load_cbo(this.value);',
10235
                'class' => 'learnpath_item_form',
10236
                'id' => 'idParent',
10237
            ]
10238
        );
10239
10240
        $arrHide = [$id];
10241
        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...
10242
            if ($action != 'add') {
10243
                if (
10244
                    ($arrLP[$i]['item_type'] == 'dir') &&
10245
                    !in_array($arrLP[$i]['id'], $arrHide) &&
10246
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
10247
                ) {
10248
                    $parentSelect->addOption(
10249
                        $arrLP[$i]['title'],
10250
                        $arrLP[$i]['id'],
10251
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
10252
                    );
10253
10254
                    if ($parent == $arrLP[$i]['id']) {
10255
                        $parentSelect->setSelected($arrLP[$i]['id']);
10256
                    }
10257
                } else {
10258
                    $arrHide[] = $arrLP[$i]['id'];
10259
                }
10260
            } else {
10261
                if ($arrLP[$i]['item_type'] === 'dir') {
10262
                    $parentSelect->addOption(
10263
                        $arrLP[$i]['title'],
10264
                        $arrLP[$i]['id'],
10265
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
10266
                    );
10267
10268
                    if ($parent == $arrLP[$i]['id']) {
10269
                        $parentSelect->setSelected($arrLP[$i]['id']);
10270
                    }
10271
                }
10272
            }
10273
        }
10274
10275
        if (is_array($arrLP)) {
10276
            reset($arrLP);
10277
        }
10278
10279
        $previousSelect = $form->addSelect(
10280
            'previous',
10281
            get_lang('Position'),
10282
            ['0' => get_lang('FirstPosition')],
10283
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10284
        );
10285
10286
        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...
10287
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10288
                $previousSelect->addOption(
10289
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
10290
                    $arrLP[$i]['id']
10291
                );
10292
10293
                if ($previousId == $arrLP[$i]['id']) {
10294
                    $previousSelect->setSelected($arrLP[$i]['id']);
10295
                } elseif ($action === 'add') {
10296
                    $previousSelect->setSelected($arrLP[$i]['id']);
10297
                }
10298
            }
10299
        }
10300
10301
        if ('edit' === $action) {
10302
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
10303
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
10304
            }
10305
            $extraField = new ExtraField('lp_item');
10306
            $extraField->addElements($form, $id, $excludeExtraFields);
10307
        }
10308
10309
        if ($action === 'add') {
10310
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
10311
        } else {
10312
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
10313
        }
10314
10315
        if ($action === 'move') {
10316
            $form->addHidden('title', $item_title);
10317
            $form->addHidden('description', $item_description);
10318
        }
10319
10320
        if (is_numeric($extra_info)) {
10321
            $form->addHidden('path', $extra_info);
10322
        } elseif (is_array($extra_info)) {
10323
            $form->addHidden('path', $extra_info['path']);
10324
        }
10325
10326
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
10327
        $form->addHidden('post_time', time());
10328
        $this->setAuthorLpItem($form);
10329
        $form->setDefaults(['title' => $item_title, 'start_date' => null]);
10330
10331
        $return = '<div class="sectioncomment">';
10332
        $return .= $form->returnForm();
10333
        $return .= '</div>';
10334
10335
        return $return;
10336
    }
10337
10338
    /**
10339
     * Displays the menu for manipulating a step.
10340
     *
10341
     * @param int    $item_id
10342
     * @param string $item_type
10343
     *
10344
     * @return string
10345
     */
10346
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
10347
    {
10348
        $_course = api_get_course_info();
10349
        $course_code = api_get_course_id();
10350
        $item_id = (int) $item_id;
10351
10352
        $return = '<div class="actions">';
10353
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10354
        $sql = "SELECT * FROM $tbl_lp_item
10355
                WHERE iid = ".$item_id;
10356
        $result = Database::query($sql);
10357
        $row = Database::fetch_assoc($result);
10358
10359
        $audio_player = null;
10360
        // We display an audio player if needed.
10361
        if (!empty($row['audio'])) {
10362
            $audio = learnpathItem::fixAudio($row['audio']);
10363
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
10364
            $audio_player .= '<div class="lp_mediaplayer" id="container">
10365
                            <audio src="'.$webAudioPath.'" controls>
10366
                            </div><br />';
10367
        }
10368
10369
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
10370
10371
        if ($item_type != TOOL_LP_FINAL_ITEM) {
10372
            $return .= Display::url(
10373
                Display::return_icon(
10374
                    'edit.png',
10375
                    get_lang('Edit'),
10376
                    [],
10377
                    ICON_SIZE_SMALL
10378
                ),
10379
                $url.'&action=edit_item&path_item='.$row['path']
10380
            );
10381
10382
            $return .= Display::url(
10383
                Display::return_icon(
10384
                    'move.png',
10385
                    get_lang('Move'),
10386
                    [],
10387
                    ICON_SIZE_SMALL
10388
                ),
10389
                $url.'&action=move_item'
10390
            );
10391
        }
10392
10393
        // Commented for now as prerequisites cannot be added to chapters.
10394
        if ($item_type != 'dir') {
10395
            $return .= Display::url(
10396
                Display::return_icon(
10397
                    'accept.png',
10398
                    get_lang('LearnpathPrerequisites'),
10399
                    [],
10400
                    ICON_SIZE_SMALL
10401
                ),
10402
                $url.'&action=edit_item_prereq'
10403
            );
10404
        }
10405
        $return .= Display::url(
10406
            Display::return_icon(
10407
                'delete.png',
10408
                get_lang('Delete'),
10409
                [],
10410
                ICON_SIZE_SMALL
10411
            ),
10412
            $url.'&action=delete_item'
10413
        );
10414
10415
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10416
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10417
            if (empty($documentData)) {
10418
                // Try with iid
10419
                $table = Database::get_course_table(TABLE_DOCUMENT);
10420
                $sql = "SELECT path FROM $table
10421
                        WHERE
10422
                              c_id = ".api_get_course_int_id()." AND
10423
                              iid = ".$row['path']." AND
10424
                              path NOT LIKE '%_DELETED_%'";
10425
                $result = Database::query($sql);
10426
                $documentData = Database::fetch_array($result);
10427
                if ($documentData) {
10428
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10429
                }
10430
            }
10431
            if (isset($documentData['absolute_path_from_document'])) {
10432
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10433
            }
10434
        }
10435
10436
        $return .= '</div>';
10437
10438
        if (!empty($audio_player)) {
10439
            $return .= $audio_player;
10440
        }
10441
10442
        return $return;
10443
    }
10444
10445
    /**
10446
     * Creates the javascript needed for filling up the checkboxes without page reload.
10447
     *
10448
     * @return string
10449
     */
10450
    public function get_js_dropdown_array()
10451
    {
10452
        $course_id = api_get_course_int_id();
10453
        $return = 'var child_name = new Array();'."\n";
10454
        $return .= 'var child_value = new Array();'."\n\n";
10455
        $return .= 'child_name[0] = new Array();'."\n";
10456
        $return .= 'child_value[0] = new Array();'."\n\n";
10457
10458
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10459
        $i = 0;
10460
        $list = $this->getItemsForForm(true);
10461
10462
        foreach ($list as $row_zero) {
10463
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10464
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10465
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10466
                }
10467
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10468
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10469
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10470
            }
10471
        }
10472
10473
        $return .= "\n";
10474
        $sql = "SELECT * FROM $tbl_lp_item
10475
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10476
        $res = Database::query($sql);
10477
        while ($row = Database::fetch_array($res)) {
10478
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10479
                           WHERE
10480
                                c_id = ".$course_id." AND
10481
                                parent_item_id = ".$row['iid']."
10482
                           ORDER BY display_order ASC";
10483
            $res_parent = Database::query($sql_parent);
10484
            $i = 0;
10485
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10486
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10487
10488
            while ($row_parent = Database::fetch_array($res_parent)) {
10489
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
10490
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10491
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10492
            }
10493
            $return .= "\n";
10494
        }
10495
10496
        $return .= "
10497
            function load_cbo(id) {
10498
                if (!id) {
10499
                    return false;
10500
                }
10501
10502
                var cbo = document.getElementById('previous');
10503
                for (var i = cbo.length - 1; i > 0; i--) {
10504
                    cbo.options[i] = null;
10505
                }
10506
10507
                var k=0;
10508
                for(var i = 1; i <= child_name[id].length; i++){
10509
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10510
                    option.style.paddingLeft = '40px';
10511
                    cbo.options[i] = option;
10512
                    k = i;
10513
                }
10514
10515
                cbo.options[k].selected = true;
10516
                $('#previous').selectpicker('refresh');
10517
            }";
10518
10519
        return $return;
10520
    }
10521
10522
    /**
10523
     * Display the form to allow moving an item.
10524
     *
10525
     * @param learnpathItem $item Item ID
10526
     *
10527
     * @throws Exception
10528
     * @throws HTML_QuickForm_Error
10529
     *
10530
     * @return string HTML form
10531
     */
10532
    public function display_move_item($item)
10533
    {
10534
        $return = '';
10535
        if ($item) {
10536
            $item_id = $item->getIid();
10537
            $type = $item->get_type();
10538
            $path = (int) $item->get_path();
10539
10540
            switch ($type) {
10541
                case 'dir':
10542
                case 'asset':
10543
                    $return .= $this->display_manipulate($item_id, $type);
10544
                    $return .= $this->display_item_form(
10545
                        $type,
10546
                        get_lang('MoveCurrentChapter'),
10547
                        'move',
10548
                        $item_id,
10549
                        $item
10550
                    );
10551
                    break;
10552
                case TOOL_DOCUMENT:
10553
                    $return .= $this->display_manipulate($item_id, $type);
10554
                    $return .= $this->display_document_form('move', $item_id, null, $item);
10555
                    break;
10556
                case TOOL_LINK:
10557
                    $return .= $this->display_manipulate($item_id, $type);
10558
                    $return .= $this->display_link_form('move', $item_id, $path, $item);
10559
                    break;
10560
                case TOOL_HOTPOTATOES:
10561
                    $return .= $this->display_manipulate($item_id, $type);
10562
                    $return .= $this->display_link_form('move', $item_id, $item);
10563
                    break;
10564
                case TOOL_QUIZ:
10565
                    $return .= $this->display_manipulate($item_id, $type);
10566
                    $return .= $this->display_quiz_form('move', $item_id, $item);
10567
                    break;
10568
                case TOOL_STUDENTPUBLICATION:
10569
                    $return .= $this->display_manipulate($item_id, $type);
10570
                    $return .= $this->display_student_publication_form('move', $item_id, $path, $item);
10571
                    break;
10572
                case TOOL_FORUM:
10573
                    $return .= $this->display_manipulate($item_id, $type);
10574
                    $return .= $this->display_forum_form('move', $item_id, $path);
10575
                    break;
10576
                case TOOL_THREAD:
10577
                    $return .= $this->display_manipulate($item_id, $type);
10578
                    $return .= $this->display_forum_form('move', $item_id, $path);
10579
                    break;
10580
            }
10581
        }
10582
10583
        return $return;
10584
    }
10585
10586
    /**
10587
     * Return HTML form to allow prerequisites selection.
10588
     *
10589
     * @todo use FormValidator
10590
     *
10591
     * @param int Item ID
10592
     *
10593
     * @return string HTML form
10594
     */
10595
    public function display_item_prerequisites_form($item_id = 0)
10596
    {
10597
        $course_id = api_get_course_int_id();
10598
        $item_id = (int) $item_id;
10599
10600
        if (empty($item_id)) {
10601
            return '';
10602
        }
10603
10604
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10605
10606
        /* Current prerequisite */
10607
        $sql = "SELECT * FROM $tbl_lp_item
10608
                WHERE iid = $item_id";
10609
        $result = Database::query($sql);
10610
        $row = Database::fetch_array($result);
10611
        $prerequisiteId = $row['prerequisite'];
10612
10613
        $return = '<legend>';
10614
        $return .= get_lang('AddEditPrerequisites');
10615
        $return .= '</legend>';
10616
        $return .= '<form method="POST">';
10617
        $return .= '<div class="table-responsive">';
10618
        $return .= '<table class="table table-hover">';
10619
        $return .= '<thead>';
10620
        $return .= '<tr>';
10621
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10622
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10623
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10624
        $return .= '</tr>';
10625
        $return .= '</thead>';
10626
10627
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10628
        $return .= '<tbody>';
10629
        $return .= '<tr>';
10630
        $return .= '<td colspan="3">';
10631
        $return .= '<div class="radio learnpath"><label for="idNone">';
10632
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10633
        $return .= get_lang('None').'</label>';
10634
        $return .= '</div>';
10635
        $return .= '</tr>';
10636
10637
        $sql = "SELECT * FROM $tbl_lp_item
10638
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10639
        $result = Database::query($sql);
10640
10641
        $selectedMinScore = [];
10642
        $selectedMaxScore = [];
10643
        $masteryScore = [];
10644
        while ($row = Database::fetch_array($result)) {
10645
            if ($row['iid'] == $item_id) {
10646
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10647
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10648
            }
10649
            $masteryScore[$row['iid']] = $row['mastery_score'];
10650
        }
10651
10652
        $arrLP = $this->getItemsForForm();
10653
        $this->tree_array($arrLP);
10654
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10655
        unset($this->arrMenu);
10656
10657
        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...
10658
            $item = $arrLP[$i];
10659
10660
            if ($item['id'] == $item_id) {
10661
                break;
10662
            }
10663
10664
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10665
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10666
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10667
10668
            $return .= '<tr>';
10669
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10670
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10671
            $return .= '<label for="id'.$item['id'].'">';
10672
10673
            $checked = '';
10674
            if (null !== $prerequisiteId) {
10675
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10676
            }
10677
10678
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10679
10680
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10681
10682
            $icon_name = str_replace(' ', '', $item['item_type']);
10683
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10684
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10685
            } else {
10686
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10687
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10688
                } else {
10689
                    $return .= Display::return_icon('folder_document.png');
10690
                }
10691
            }
10692
10693
            $return .= $item['title'].'</label>';
10694
            $return .= '</div>';
10695
            $return .= '</td>';
10696
10697
            if ($item['item_type'] == TOOL_QUIZ) {
10698
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10699
                $lpItemObj = new LpItem($course_id, $item['id']);
10700
                $exercise = new Exercise($course_id);
10701
                $exercise->read($lpItemObj->path);
10702
                $lpItemObj->max_score = $exercise->get_max_score();
10703
                $lpItemObj->update();
10704
                $item['max_score'] = $lpItemObj->max_score;
10705
10706
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10707
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10708
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10709
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10710
                }
10711
10712
                $return .= '<td>';
10713
                $return .= '<input
10714
                    class="form-control"
10715
                    size="4" maxlength="3"
10716
                    name="min_'.$item['id'].'"
10717
                    type="number"
10718
                    min="0"
10719
                    step="any"
10720
                    max="'.$item['max_score'].'"
10721
                    value="'.$selectedMinScoreValue.'"
10722
                />';
10723
                $return .= '</td>';
10724
                $return .= '<td>';
10725
                $return .= '<input
10726
                    class="form-control"
10727
                    size="4"
10728
                    maxlength="3"
10729
                    name="max_'.$item['id'].'"
10730
                    type="number"
10731
                    min="0"
10732
                    step="any"
10733
                    max="'.$item['max_score'].'"
10734
                    value="'.$selectedMaxScoreValue.'"
10735
                />';
10736
                $return .= '</td>';
10737
            }
10738
10739
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10740
                $return .= '<td>';
10741
                $return .= '<input
10742
                    size="4"
10743
                    maxlength="3"
10744
                    name="min_'.$item['id'].'"
10745
                    type="number"
10746
                    min="0"
10747
                    step="any"
10748
                    max="'.$item['max_score'].'"
10749
                    value="'.$selectedMinScoreValue.'"
10750
                />';
10751
                $return .= '</td>';
10752
                $return .= '<td>';
10753
                $return .= '<input
10754
                    size="4"
10755
                    maxlength="3"
10756
                    name="max_'.$item['id'].'"
10757
                    type="number"
10758
                    min="0"
10759
                    step="any"
10760
                    max="'.$item['max_score'].'"
10761
                    value="'.$selectedMaxScoreValue.'"
10762
                />';
10763
                $return .= '</td>';
10764
            }
10765
            $return .= '</tr>';
10766
        }
10767
        $return .= '<tr>';
10768
        $return .= '</tr>';
10769
        $return .= '</tbody>';
10770
        $return .= '</table>';
10771
        $return .= '</div>';
10772
        $return .= '<div class="form-group">';
10773
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10774
            get_lang('ModifyPrerequisites').'</button>';
10775
        $return .= '</form>';
10776
10777
        return $return;
10778
    }
10779
10780
    /**
10781
     * Return HTML list to allow prerequisites selection for lp.
10782
     *
10783
     * @return string HTML form
10784
     */
10785
    public function display_lp_prerequisites_list()
10786
    {
10787
        $course_id = api_get_course_int_id();
10788
        $lp_id = $this->lp_id;
10789
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10790
10791
        // get current prerequisite
10792
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10793
        $result = Database::query($sql);
10794
        $row = Database::fetch_array($result);
10795
        $prerequisiteId = $row['prerequisite'];
10796
        $session_id = api_get_session_id();
10797
        $session_condition = api_get_session_condition($session_id, true, true);
10798
        $sql = "SELECT * FROM $tbl_lp
10799
                WHERE c_id = $course_id $session_condition
10800
                ORDER BY display_order ";
10801
        $rs = Database::query($sql);
10802
        $return = '';
10803
        $return .= '<select name="prerequisites" class="form-control">';
10804
        $return .= '<option value="0">'.get_lang('None').'</option>';
10805
        if (Database::num_rows($rs) > 0) {
10806
            while ($row = Database::fetch_array($rs)) {
10807
                if ($row['id'] == $lp_id) {
10808
                    continue;
10809
                }
10810
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10811
            }
10812
        }
10813
        $return .= '</select>';
10814
10815
        return $return;
10816
    }
10817
10818
    /**
10819
     * Creates a list with all the documents in it.
10820
     *
10821
     * @param bool $showInvisibleFiles
10822
     *
10823
     * @throws Exception
10824
     * @throws HTML_QuickForm_Error
10825
     *
10826
     * @return string
10827
     */
10828
    public function get_documents($showInvisibleFiles = false)
10829
    {
10830
        $course_info = api_get_course_info();
10831
        $sessionId = api_get_session_id();
10832
        $documentTree = DocumentManager::get_document_preview(
10833
            $course_info,
10834
            $this->lp_id,
10835
            null,
10836
            $sessionId,
10837
            true,
10838
            null,
10839
            null,
10840
            $showInvisibleFiles,
10841
            true
10842
        );
10843
10844
        $headers = [
10845
            get_lang('Files'),
10846
            get_lang('CreateTheDocument'),
10847
            get_lang('CreateReadOutText'),
10848
            get_lang('Upload'),
10849
        ];
10850
10851
        $form = new FormValidator(
10852
            'form_upload',
10853
            'POST',
10854
            $this->getCurrentBuildingModeURL(),
10855
            '',
10856
            ['enctype' => 'multipart/form-data']
10857
        );
10858
10859
        $folders = DocumentManager::get_all_document_folders(
10860
            api_get_course_info(),
10861
            0,
10862
            true
10863
        );
10864
10865
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10866
10867
        DocumentManager::build_directory_selector(
10868
            $folders,
10869
            $lpPathInfo['id'],
10870
            [],
10871
            true,
10872
            $form,
10873
            'directory_parent_id'
10874
        );
10875
10876
        $group = [
10877
            $form->createElement(
10878
                'radio',
10879
                'if_exists',
10880
                get_lang('UplWhatIfFileExists'),
10881
                get_lang('UplDoNothing'),
10882
                'nothing'
10883
            ),
10884
            $form->createElement(
10885
                'radio',
10886
                'if_exists',
10887
                null,
10888
                get_lang('UplOverwriteLong'),
10889
                'overwrite'
10890
            ),
10891
            $form->createElement(
10892
                'radio',
10893
                'if_exists',
10894
                null,
10895
                get_lang('UplRenameLong'),
10896
                'rename'
10897
            ),
10898
        ];
10899
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10900
10901
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10902
        $defaultFileExistsOption = 'rename';
10903
        if (!empty($fileExistsOption)) {
10904
            $defaultFileExistsOption = $fileExistsOption;
10905
        }
10906
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10907
10908
        // Check box options
10909
        $form->addElement(
10910
            'checkbox',
10911
            'unzip',
10912
            get_lang('Options'),
10913
            get_lang('Uncompress')
10914
        );
10915
10916
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10917
        $form->addMultipleUpload($url);
10918
        $new = $this->display_document_form('add', 0);
10919
        $frmReadOutText = $this->displayFrmReadOutText('add');
10920
        $tabs = Display::tabs(
10921
            $headers,
10922
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10923
            'subtab'
10924
        );
10925
10926
        return $tabs;
10927
    }
10928
10929
    /**
10930
     * Creates a list with all the exercises (quiz) in it.
10931
     *
10932
     * @return string
10933
     */
10934
    public function get_exercises()
10935
    {
10936
        $course_id = api_get_course_int_id();
10937
        $session_id = api_get_session_id();
10938
        $userInfo = api_get_user_info();
10939
10940
        // New for hotpotatoes.
10941
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10942
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10943
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10944
        $condition_session = api_get_session_condition($session_id, true, true);
10945
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10946
10947
        $activeCondition = ' active <> -1 ';
10948
        if ($setting) {
10949
            $activeCondition = ' active = 1 ';
10950
        }
10951
10952
        $categoryCondition = '';
10953
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10954
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10955
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10956
        }
10957
10958
        $keywordCondition = '';
10959
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10960
10961
        if (!empty($keyword)) {
10962
            $keyword = Database::escape_string($keyword);
10963
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10964
        }
10965
10966
        $sql_quiz = "SELECT * FROM $tbl_quiz
10967
                     WHERE
10968
                            c_id = $course_id AND
10969
                            $activeCondition
10970
                            $condition_session
10971
                            $categoryCondition
10972
                            $keywordCondition
10973
                     ORDER BY title ASC";
10974
10975
        $sql_hot = "SELECT * FROM $tbl_doc
10976
                    WHERE
10977
                        c_id = $course_id AND
10978
                        path LIKE '".$uploadPath."/%/%htm%'
10979
                        $condition_session
10980
                     ORDER BY id ASC";
10981
10982
        $res_quiz = Database::query($sql_quiz);
10983
        $res_hot = Database::query($sql_hot);
10984
10985
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10986
10987
        // Create a search-box
10988
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10989
        $form->addHidden('action', 'add_item');
10990
        $form->addHidden('type', 'step');
10991
        $form->addHidden('lp_id', $this->lp_id);
10992
        $form->addHidden('lp_build_selected', '2');
10993
10994
        $form->addCourseHiddenParams();
10995
        $form->addText(
10996
            'keyword',
10997
            get_lang('Search'),
10998
            false,
10999
            [
11000
                'aria-label' => get_lang('Search'),
11001
            ]
11002
        );
11003
11004
        if (api_get_configuration_value('allow_exercise_categories')) {
11005
            $manager = new ExerciseCategoryManager();
11006
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
11007
            if (!empty($options)) {
11008
                $form->addSelect(
11009
                    'category_id',
11010
                    get_lang('Category'),
11011
                    $options,
11012
                    ['placeholder' => get_lang('SelectAnOption')]
11013
                );
11014
            }
11015
        }
11016
11017
        $form->addButtonSearch(get_lang('Search'));
11018
        $return = $form->returnForm();
11019
11020
        $return .= '<ul class="lp_resource">';
11021
11022
        $return .= '<li class="lp_resource_element">';
11023
        $return .= Display::return_icon('new_exercice.png');
11024
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
11025
            get_lang('NewExercise').'</a>';
11026
        $return .= '</li>';
11027
11028
        $previewIcon = Display::return_icon(
11029
            'preview_view.png',
11030
            get_lang('Preview')
11031
        );
11032
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
11033
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11034
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
11035
11036
        // Display hotpotatoes
11037
        while ($row_hot = Database::fetch_array($res_hot)) {
11038
            $link = Display::url(
11039
                $previewIcon,
11040
                $exerciseUrl.'&file='.$row_hot['path'],
11041
                ['target' => '_blank']
11042
            );
11043
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
11044
            $return .= '<a class="moved" href="#">';
11045
            $return .= Display::return_icon(
11046
                'move_everywhere.png',
11047
                get_lang('Move'),
11048
                [],
11049
                ICON_SIZE_TINY
11050
            );
11051
            $return .= '</a> ';
11052
            $return .= Display::return_icon('hotpotatoes_s.png');
11053
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
11054
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
11055
            $return .= '</li>';
11056
        }
11057
11058
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
11059
        while ($row_quiz = Database::fetch_array($res_quiz)) {
11060
            $title = strip_tags(
11061
                api_html_entity_decode($row_quiz['title'])
11062
            );
11063
11064
            $visibility = api_get_item_visibility(
11065
                ['real_id' => $course_id],
11066
                TOOL_QUIZ,
11067
                $row_quiz['iid'],
11068
                $session_id
11069
            );
11070
11071
            $link = Display::url(
11072
                $previewIcon,
11073
                $exerciseUrl.'&exerciseId='.$row_quiz['iid'],
11074
                ['target' => '_blank']
11075
            );
11076
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['iid'].'" data_type="quiz" title="'.$title.'" >';
11077
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
11078
            $return .= $quizIcon;
11079
            $sessionStar = api_get_session_image(
11080
                $row_quiz['session_id'],
11081
                $userInfo['status']
11082
            );
11083
            $return .= Display::url(
11084
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
11085
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['iid'].'&lp_id='.$this->lp_id,
11086
                [
11087
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
11088
                ]
11089
            );
11090
            $return .= '</li>';
11091
        }
11092
11093
        $return .= '</ul>';
11094
11095
        return $return;
11096
    }
11097
11098
    /**
11099
     * Creates a list with all the links in it.
11100
     *
11101
     * @return string
11102
     */
11103
    public function get_links()
11104
    {
11105
        $selfUrl = api_get_self();
11106
        $courseIdReq = api_get_cidreq();
11107
        $course = api_get_course_info();
11108
        $userInfo = api_get_user_info();
11109
11110
        $course_id = $course['real_id'];
11111
        $tbl_link = Database::get_course_table(TABLE_LINK);
11112
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
11113
        $moveEverywhereIcon = Display::return_icon(
11114
            'move_everywhere.png',
11115
            get_lang('Move'),
11116
            [],
11117
            ICON_SIZE_TINY
11118
        );
11119
11120
        $session_id = api_get_session_id();
11121
        $condition_session = api_get_session_condition(
11122
            $session_id,
11123
            true,
11124
            true,
11125
            'link.session_id'
11126
        );
11127
11128
        $sql = "SELECT
11129
                    link.id as link_id,
11130
                    link.title as link_title,
11131
                    link.session_id as link_session_id,
11132
                    link.category_id as category_id,
11133
                    link_category.category_title as category_title
11134
                FROM $tbl_link as link
11135
                LEFT JOIN $linkCategoryTable as link_category
11136
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
11137
                WHERE link.c_id = $course_id $condition_session
11138
                ORDER BY link_category.category_title ASC, link.title ASC";
11139
        $result = Database::query($sql);
11140
        $categorizedLinks = [];
11141
        $categories = [];
11142
11143
        while ($link = Database::fetch_array($result)) {
11144
            if (!$link['category_id']) {
11145
                $link['category_title'] = get_lang('Uncategorized');
11146
            }
11147
            $categories[$link['category_id']] = $link['category_title'];
11148
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
11149
        }
11150
11151
        $linksHtmlCode =
11152
            '<script>
11153
            function toggle_tool(tool, id) {
11154
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
11155
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
11156
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
11157
                } else {
11158
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
11159
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
11160
                }
11161
            }
11162
        </script>
11163
11164
        <ul class="lp_resource">
11165
            <li class="lp_resource_element">
11166
                '.Display::return_icon('linksnew.gif').'
11167
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
11168
                get_lang('LinkAdd').'
11169
                </a>
11170
            </li>';
11171
11172
        foreach ($categorizedLinks as $categoryId => $links) {
11173
            $linkNodes = null;
11174
            foreach ($links as $key => $linkInfo) {
11175
                $title = $linkInfo['link_title'];
11176
                $linkSessionId = $linkInfo['link_session_id'];
11177
11178
                $link = Display::url(
11179
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11180
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
11181
                    ['target' => '_blank']
11182
                );
11183
11184
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
11185
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
11186
                    $linkNodes .=
11187
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
11188
                        <a class="moved" href="#">'.
11189
                            $moveEverywhereIcon.
11190
                        '</a>
11191
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
11192
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
11193
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
11194
                        Security::remove_XSS($title).$sessionStar.$link.
11195
                        '</a>
11196
                    </li>';
11197
                }
11198
            }
11199
            $linksHtmlCode .=
11200
                '<li>
11201
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
11202
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
11203
                    align="absbottom" />
11204
                </a>
11205
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
11206
            </li>
11207
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
11208
        }
11209
        $linksHtmlCode .= '</ul>';
11210
11211
        return $linksHtmlCode;
11212
    }
11213
11214
    /**
11215
     * Creates a list with all the surveys in it.
11216
     *
11217
     * @return string
11218
     */
11219
    public function getSurveys()
11220
    {
11221
        $return = '<ul class="lp_resource">';
11222
        // First add link
11223
        $return .= '<li class="lp_resource_element">';
11224
        $return .= Display::return_icon('new_survey.png', get_lang('CreateNewSurvey'), '', ICON_SIZE_MEDIUM);
11225
        $return .= Display::url(
11226
            get_lang('CreateANewSurvey'),
11227
            api_get_path(WEB_CODE_PATH).'survey/create_new_survey.php?'.api_get_cidreq().'&'.http_build_query([
11228
                'action' => 'add',
11229
                'lp_id' => $this->lp_id,
11230
            ]),
11231
            ['title' => get_lang('CreateNewSurvey')]
11232
        );
11233
        $return .= '</li>';
11234
11235
        $surveys = SurveyManager::get_surveys(api_get_course_id(), api_get_session_id());
11236
11237
        foreach ($surveys as $survey) {
11238
            if (!empty($survey['survey_id'])) {
11239
                $surveyTitle = strip_tags($survey['title']);
11240
                $return .= '<li class="lp_resource_element" data_id="'.$survey['survey_id'].'" data_type="'.TOOL_SURVEY.'" title="'.$surveyTitle.'" >';
11241
                $return .= '<a class="moved" href="#">';
11242
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11243
                $return .= ' </a>';
11244
                $return .= Display::return_icon('survey.png', '', [], ICON_SIZE_TINY);
11245
                $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_SURVEY.'&survey_id='.$survey['survey_id'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.$surveyTitle.'</a>';
11246
                $return .= '</li>';
11247
            }
11248
        }
11249
11250
        $return .= '</ul>';
11251
11252
        return $return;
11253
    }
11254
11255
    /**
11256
     * Creates a list with all the student publications in it.
11257
     *
11258
     * @return string
11259
     */
11260
    public function get_student_publications()
11261
    {
11262
        $return = '<ul class="lp_resource">';
11263
        $return .= '<li class="lp_resource_element">';
11264
        $return .= Display::return_icon('works_new.gif');
11265
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
11266
            get_lang('AddAssignmentPage').'</a>';
11267
        $return .= '</li>';
11268
11269
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
11270
        $works = getWorkListTeacher(0, 100, null, null, null);
11271
        if (!empty($works)) {
11272
            foreach ($works as $work) {
11273
                $link = Display::url(
11274
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11275
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
11276
                    ['target' => '_blank']
11277
                );
11278
11279
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
11280
                $return .= '<a class="moved" href="#">';
11281
                $return .= Display::return_icon(
11282
                    'move_everywhere.png',
11283
                    get_lang('Move'),
11284
                    [],
11285
                    ICON_SIZE_TINY
11286
                );
11287
                $return .= '</a> ';
11288
11289
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
11290
                $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.'">'.
11291
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
11292
                </a>';
11293
11294
                $return .= '</li>';
11295
            }
11296
        }
11297
11298
        $return .= '</ul>';
11299
11300
        return $return;
11301
    }
11302
11303
    /**
11304
     * Creates a list with all the forums in it.
11305
     *
11306
     * @return string
11307
     */
11308
    public function get_forums()
11309
    {
11310
        require_once '../forum/forumfunction.inc.php';
11311
11312
        $forumCategories = get_forum_categories();
11313
        $forumsInNoCategory = get_forums_in_category(0);
11314
        if (!empty($forumsInNoCategory)) {
11315
            $forumCategories = array_merge(
11316
                $forumCategories,
11317
                [
11318
                    [
11319
                        'cat_id' => 0,
11320
                        'session_id' => 0,
11321
                        'visibility' => 1,
11322
                        'cat_comment' => null,
11323
                    ],
11324
                ]
11325
            );
11326
        }
11327
11328
        $forumList = get_forums();
11329
        $a_forums = [];
11330
        foreach ($forumCategories as $forumCategory) {
11331
            // The forums in this category.
11332
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
11333
            if (!empty($forumsInCategory)) {
11334
                foreach ($forumList as $forum) {
11335
                    if (isset($forum['forum_category']) &&
11336
                        $forum['forum_category'] == $forumCategory['cat_id']
11337
                    ) {
11338
                        $a_forums[] = $forum;
11339
                    }
11340
                }
11341
            }
11342
        }
11343
11344
        $return = '<ul class="lp_resource">';
11345
11346
        // First add link
11347
        $return .= '<li class="lp_resource_element">';
11348
        $return .= Display::return_icon('new_forum.png');
11349
        $return .= Display::url(
11350
            get_lang('CreateANewForum'),
11351
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
11352
                'action' => 'add',
11353
                'content' => 'forum',
11354
                'lp_id' => $this->lp_id,
11355
            ]),
11356
            ['title' => get_lang('CreateANewForum')]
11357
        );
11358
        $return .= '</li>';
11359
11360
        $return .= '<script>
11361
            function toggle_forum(forum_id) {
11362
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
11363
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
11364
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
11365
                } else {
11366
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
11367
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
11368
                }
11369
            }
11370
        </script>';
11371
11372
        foreach ($a_forums as $forum) {
11373
            if (!empty($forum['forum_id'])) {
11374
                $link = Display::url(
11375
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11376
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
11377
                    ['target' => '_blank']
11378
                );
11379
11380
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
11381
                $return .= '<a class="moved" href="#">';
11382
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11383
                $return .= ' </a>';
11384
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
11385
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
11386
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
11387
                            </a>
11388
                            <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">'.
11389
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
11390
11391
                $return .= '</li>';
11392
11393
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
11394
                $a_threads = get_threads($forum['forum_id']);
11395
                if (is_array($a_threads)) {
11396
                    foreach ($a_threads as $thread) {
11397
                        $link = Display::url(
11398
                            Display::return_icon('preview_view.png', get_lang('Preview')),
11399
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
11400
                            ['target' => '_blank']
11401
                        );
11402
11403
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
11404
                        $return .= '&nbsp;<a class="moved" href="#">';
11405
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11406
                        $return .= ' </a>';
11407
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
11408
                        $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.'">'.
11409
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
11410
                        $return .= '</li>';
11411
                    }
11412
                }
11413
                $return .= '</div>';
11414
            }
11415
        }
11416
        $return .= '</ul>';
11417
11418
        return $return;
11419
    }
11420
11421
    /**
11422
     * // TODO: The output encoding should be equal to the system encoding.
11423
     *
11424
     * Exports the learning path as a SCORM package. This is the main function that
11425
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
11426
     * whole thing and returns the zip.
11427
     *
11428
     * This method needs to be called in PHP5, as it will fail with non-adequate
11429
     * XML package (like the ones for PHP4), and it is *not* a static method, so
11430
     * you need to call it on a learnpath object.
11431
     *
11432
     * @TODO The method might be redefined later on in the scorm class itself to avoid
11433
     * creating a SCORM structure if there is one already. However, if the initial SCORM
11434
     * path has been modified, it should use the generic method here below.
11435
     *
11436
     * @return string Returns the zip package string, or null if error
11437
     */
11438
    public function scormExport()
11439
    {
11440
        api_set_more_memory_and_time_limits();
11441
11442
        $_course = api_get_course_info();
11443
        $course_id = $_course['real_id'];
11444
        // Create the zip handler (this will remain available throughout the method).
11445
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11446
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11447
        $temp_dir_short = uniqid('scorm_export', true);
11448
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11449
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11450
        $zip_folder = new PclZip($temp_zip_file);
11451
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11452
        $root_path = $main_path = api_get_path(SYS_PATH);
11453
        $files_cleanup = [];
11454
11455
        // Place to temporarily stash the zip file.
11456
        // create the temp dir if it doesn't exist
11457
        // or do a cleanup before creating the zip file.
11458
        if (!is_dir($temp_zip_dir)) {
11459
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11460
        } else {
11461
            // Cleanup: Check the temp dir for old files and delete them.
11462
            $handle = opendir($temp_zip_dir);
11463
            while (false !== ($file = readdir($handle))) {
11464
                if ($file != '.' && $file != '..') {
11465
                    unlink("$temp_zip_dir/$file");
11466
                }
11467
            }
11468
            closedir($handle);
11469
        }
11470
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11471
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11472
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11473
        ) {
11474
            // Remove the possible . at the end of the path.
11475
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11476
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11477
            mkdir(
11478
                $dest_path_to_scorm_folder,
11479
                api_get_permissions_for_new_directories(),
11480
                true
11481
            );
11482
            copyr(
11483
                $current_course_path.'/scorm/'.$this->path,
11484
                $dest_path_to_scorm_folder,
11485
                ['imsmanifest'],
11486
                $zip_files
11487
            );
11488
        }
11489
11490
        // Build a dummy imsmanifest structure.
11491
        // Do not add to the zip yet (we still need it).
11492
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11493
        // Aggregation Model official document, section "2.3 Content Packaging".
11494
        // We are going to build a UTF-8 encoded manifest.
11495
        // Later we will recode it to the desired (and supported) encoding.
11496
        $xmldoc = new DOMDocument('1.0');
11497
        $root = $xmldoc->createElement('manifest');
11498
        $root->setAttribute('identifier', 'SingleCourseManifest');
11499
        $root->setAttribute('version', '1.1');
11500
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11501
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11502
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11503
        $root->setAttribute(
11504
            'xsi:schemaLocation',
11505
            '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'
11506
        );
11507
        // Build mandatory sub-root container elements.
11508
        $metadata = $xmldoc->createElement('metadata');
11509
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11510
        $metadata->appendChild($md_schema);
11511
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11512
        $metadata->appendChild($md_schemaversion);
11513
        $root->appendChild($metadata);
11514
11515
        $organizations = $xmldoc->createElement('organizations');
11516
        $resources = $xmldoc->createElement('resources');
11517
11518
        // Build the only organization we will use in building our learnpaths.
11519
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11520
        $organization = $xmldoc->createElement('organization');
11521
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11522
        // To set the title of the SCORM entity (=organization), we take the name given
11523
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11524
        // learning path charset) as it is the encoding that defines how it is stored
11525
        // in the database. Then we convert it to HTML entities again as the "&" character
11526
        // alone is not authorized in XML (must be &amp;).
11527
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11528
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11529
        $organization->appendChild($org_title);
11530
        $folder_name = 'document';
11531
11532
        // Removes the learning_path/scorm_folder path when exporting see #4841
11533
        $path_to_remove = '';
11534
        $path_to_replace = '';
11535
        $result = $this->generate_lp_folder($_course);
11536
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11537
            $path_to_remove = 'document'.$result['dir'];
11538
            $path_to_replace = $folder_name.'/';
11539
        }
11540
11541
        // Fixes chamilo scorm exports
11542
        if ($this->ref === 'chamilo_scorm_export') {
11543
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11544
        }
11545
11546
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11547
        $link_updates = [];
11548
        $links_to_create = [];
11549
        foreach ($this->ordered_items as $index => $itemId) {
11550
            /** @var learnpathItem $item */
11551
            $item = $this->items[$itemId];
11552
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11553
                // Get included documents from this item.
11554
                if ($item->type === 'sco') {
11555
                    $inc_docs = $item->get_resources_from_source(
11556
                        null,
11557
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11558
                    );
11559
                } else {
11560
                    $inc_docs = $item->get_resources_from_source();
11561
                }
11562
11563
                // Give a child element <item> to the <organization> element.
11564
                $my_item_id = $item->get_id();
11565
                $my_item = $xmldoc->createElement('item');
11566
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11567
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11568
                $my_item->setAttribute('isvisible', 'true');
11569
                // Give a child element <title> to the <item> element.
11570
                $my_title = $xmldoc->createElement(
11571
                    'title',
11572
                    htmlspecialchars(
11573
                        api_utf8_encode($item->get_title()),
11574
                        ENT_QUOTES,
11575
                        'UTF-8'
11576
                    )
11577
                );
11578
                $my_item->appendChild($my_title);
11579
                // Give a child element <adlcp:prerequisites> to the <item> element.
11580
                $my_prereqs = $xmldoc->createElement(
11581
                    'adlcp:prerequisites',
11582
                    $this->get_scorm_prereq_string($my_item_id)
11583
                );
11584
                $my_prereqs->setAttribute('type', 'aicc_script');
11585
                $my_item->appendChild($my_prereqs);
11586
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11587
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11588
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11589
                //$xmldoc->createElement('adlcp:timelimitaction','');
11590
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11591
                //$xmldoc->createElement('adlcp:datafromlms','');
11592
                // Give a child element <adlcp:masteryscore> to the <item> element.
11593
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11594
                $my_item->appendChild($my_masteryscore);
11595
11596
                // Attach this item to the organization element or hits parent if there is one.
11597
                if (!empty($item->parent) && $item->parent != 0) {
11598
                    $children = $organization->childNodes;
11599
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11600
                    if (is_object($possible_parent)) {
11601
                        $possible_parent->appendChild($my_item);
11602
                    } else {
11603
                        if ($this->debug > 0) {
11604
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11605
                        }
11606
                    }
11607
                } else {
11608
                    if ($this->debug > 0) {
11609
                        error_log('No parent');
11610
                    }
11611
                    $organization->appendChild($my_item);
11612
                }
11613
11614
                // Get the path of the file(s) from the course directory root.
11615
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11616
                $my_xml_file_path = $my_file_path;
11617
                if (!empty($path_to_remove)) {
11618
                    // From docs
11619
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11620
11621
                    // From quiz
11622
                    if ($this->ref === 'chamilo_scorm_export') {
11623
                        $path_to_remove = 'scorm/'.$this->path.'/';
11624
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11625
                    }
11626
                }
11627
11628
                $my_sub_dir = dirname($my_file_path);
11629
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11630
                $my_xml_sub_dir = $my_sub_dir;
11631
                // Give a <resource> child to the <resources> element
11632
                $my_resource = $xmldoc->createElement('resource');
11633
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11634
                $my_resource->setAttribute('type', 'webcontent');
11635
                $my_resource->setAttribute('href', $my_xml_file_path);
11636
                // adlcp:scormtype can be either 'sco' or 'asset'.
11637
                if ($item->type === 'sco') {
11638
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11639
                } else {
11640
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11641
                }
11642
                // xml:base is the base directory to find the files declared in this resource.
11643
                $my_resource->setAttribute('xml:base', '');
11644
                // Give a <file> child to the <resource> element.
11645
                $my_file = $xmldoc->createElement('file');
11646
                $my_file->setAttribute('href', $my_xml_file_path);
11647
                $my_resource->appendChild($my_file);
11648
11649
                // Dependency to other files - not yet supported.
11650
                $i = 1;
11651
                if ($inc_docs) {
11652
                    foreach ($inc_docs as $doc_info) {
11653
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11654
                            continue;
11655
                        }
11656
                        $my_dep = $xmldoc->createElement('resource');
11657
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11658
                        $my_dep->setAttribute('identifier', $res_id);
11659
                        $my_dep->setAttribute('type', 'webcontent');
11660
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11661
                        $my_dep_file = $xmldoc->createElement('file');
11662
                        // Check type of URL.
11663
                        if ($doc_info[1] == 'remote') {
11664
                            // Remote file. Save url as is.
11665
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11666
                            $my_dep->setAttribute('xml:base', '');
11667
                        } elseif ($doc_info[1] === 'local') {
11668
                            switch ($doc_info[2]) {
11669
                                case 'url':
11670
                                    // Local URL - save path as url for now, don't zip file.
11671
                                    $abs_path = api_get_path(SYS_PATH).
11672
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11673
                                    $current_dir = dirname($abs_path);
11674
                                    $current_dir = str_replace('\\', '/', $current_dir);
11675
                                    $file_path = realpath($abs_path);
11676
                                    $file_path = str_replace('\\', '/', $file_path);
11677
                                    $my_dep_file->setAttribute('href', $file_path);
11678
                                    $my_dep->setAttribute('xml:base', '');
11679
                                    if (strstr($file_path, $main_path) !== false) {
11680
                                        // The calculated real path is really inside Chamilo's root path.
11681
                                        // Reduce file path to what's under the DocumentRoot.
11682
                                        $replace = $file_path;
11683
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11684
                                        $destinationFile = $file_path;
11685
11686
                                        if (strstr($file_path, 'upload/users') !== false) {
11687
                                            $pos = strpos($file_path, 'my_files/');
11688
                                            if ($pos !== false) {
11689
                                                $onlyDirectory = str_replace(
11690
                                                    'upload/users/',
11691
                                                    '',
11692
                                                    substr($file_path, $pos, strlen($file_path))
11693
                                                );
11694
                                            }
11695
                                            $replace = './'.$onlyDirectory;
11696
                                            $destinationFile = $replace;
11697
                                        }
11698
11699
                                        if (strpos($file_path, '/web') === 0) {
11700
                                            $replace = str_replace('/web', 'web', $file_path);
11701
                                        }
11702
11703
                                        $zip_files_abs[] = $file_path;
11704
                                        $link_updates[$my_file_path][] = [
11705
                                            'orig' => $doc_info[0],
11706
                                            'dest' => $destinationFile,
11707
                                            'replace' => $replace,
11708
                                        ];
11709
                                        $my_dep_file->setAttribute('href', $file_path);
11710
                                        $my_dep->setAttribute('xml:base', '');
11711
                                    } elseif (empty($file_path)) {
11712
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11713
                                        $file_path = str_replace('//', '/', $file_path);
11714
                                        if (file_exists($file_path)) {
11715
                                            // We get the relative path.
11716
                                            $file_path = substr($file_path, strlen($current_dir));
11717
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11718
                                            $link_updates[$my_file_path][] = [
11719
                                                'orig' => $doc_info[0],
11720
                                                'dest' => $file_path,
11721
                                            ];
11722
                                            $my_dep_file->setAttribute('href', $file_path);
11723
                                            $my_dep->setAttribute('xml:base', '');
11724
                                        }
11725
                                    }
11726
                                    break;
11727
                                case 'abs':
11728
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11729
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11730
                                    $my_dep->setAttribute('xml:base', '');
11731
11732
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11733
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11734
                                    $abs_img_path_without_subdir = $doc_info[0];
11735
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11736
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11737
                                    if ($pos === 0) {
11738
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11739
                                    }
11740
11741
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11742
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11743
11744
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11745
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11746
                                    // Check if the current document is in that path.
11747
                                    if (strstr($file_path, $cur_path) !== false) {
11748
                                        $destinationFile = substr($file_path, strlen($cur_path));
11749
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11750
11751
                                        $fileToTest = $cur_path.$my_file_path;
11752
                                        if (!empty($path_to_remove)) {
11753
                                            $fileToTest = str_replace(
11754
                                                $path_to_remove.'/',
11755
                                                $path_to_replace,
11756
                                                $cur_path.$my_file_path
11757
                                            );
11758
                                        }
11759
11760
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11761
11762
                                        // Put the current document in the zip (this array is the array
11763
                                        // that will manage documents already in the course folder - relative).
11764
                                        $zip_files[] = $filePathNoCoursePart;
11765
                                        // Update the links to the current document in the
11766
                                        // containing document (make them relative).
11767
                                        $link_updates[$my_file_path][] = [
11768
                                            'orig' => $doc_info[0],
11769
                                            'dest' => $destinationFile,
11770
                                            'replace' => $relative_path,
11771
                                        ];
11772
11773
                                        $my_dep_file->setAttribute('href', $file_path);
11774
                                        $my_dep->setAttribute('xml:base', '');
11775
                                    } elseif (strstr($file_path, $main_path) !== false) {
11776
                                        // The calculated real path is really inside Chamilo's root path.
11777
                                        // Reduce file path to what's under the DocumentRoot.
11778
                                        $file_path = substr($file_path, strlen($root_path));
11779
                                        $zip_files_abs[] = $file_path;
11780
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11781
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11782
                                        $my_dep->setAttribute('xml:base', '');
11783
                                    } elseif (empty($file_path)) {
11784
                                        // Probably this is an image inside "/main" directory
11785
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11786
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11787
11788
                                        if (file_exists($file_path)) {
11789
                                            $pos = strpos($file_path, 'main/default_course_document/');
11790
                                            if ($pos !== false) {
11791
                                                // We get the relative path.
11792
                                                $onlyDirectory = str_replace(
11793
                                                    'main/default_course_document/',
11794
                                                    '',
11795
                                                    substr($file_path, $pos, strlen($file_path))
11796
                                                );
11797
11798
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11799
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11800
                                                $zip_files_abs[] = $fileAbs;
11801
                                                $link_updates[$my_file_path][] = [
11802
                                                    'orig' => $doc_info[0],
11803
                                                    'dest' => $destinationFile,
11804
                                                ];
11805
                                                $my_dep_file->setAttribute('href', 'document/'.$destinationFile);
11806
                                                $my_dep->setAttribute('xml:base', '');
11807
                                            }
11808
                                        }
11809
                                    }
11810
                                    break;
11811
                                case 'rel':
11812
                                    // Path relative to the current document.
11813
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11814
                                    if (substr($doc_info[0], 0, 2) === '..') {
11815
                                        // Relative path going up.
11816
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11817
                                        $current_dir = str_replace('\\', '/', $current_dir);
11818
                                        $file_path = realpath($current_dir.$doc_info[0]);
11819
                                        $file_path = str_replace('\\', '/', $file_path);
11820
                                        if (strstr($file_path, $main_path) !== false) {
11821
                                            // The calculated real path is really inside Chamilo's root path.
11822
                                            // Reduce file path to what's under the DocumentRoot.
11823
                                            $file_path = substr($file_path, strlen($root_path));
11824
                                            $zip_files_abs[] = $file_path;
11825
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11826
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11827
                                            $my_dep->setAttribute('xml:base', '');
11828
                                        }
11829
                                    } else {
11830
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11831
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11832
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11833
                                    }
11834
                                    break;
11835
                                default:
11836
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11837
                                    $my_dep->setAttribute('xml:base', '');
11838
                                    break;
11839
                            }
11840
                        }
11841
                        $my_dep->appendChild($my_dep_file);
11842
                        $resources->appendChild($my_dep);
11843
                        $dependency = $xmldoc->createElement('dependency');
11844
                        $dependency->setAttribute('identifierref', $res_id);
11845
                        $my_resource->appendChild($dependency);
11846
                        $i++;
11847
                    }
11848
                }
11849
                $resources->appendChild($my_resource);
11850
                $zip_files[] = $my_file_path;
11851
            } else {
11852
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11853
                switch ($item->type) {
11854
                    case TOOL_LINK:
11855
                        $my_item = $xmldoc->createElement('item');
11856
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11857
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11858
                        $my_item->setAttribute('isvisible', 'true');
11859
                        // Give a child element <title> to the <item> element.
11860
                        $my_title = $xmldoc->createElement(
11861
                            'title',
11862
                            htmlspecialchars(
11863
                                api_utf8_encode($item->get_title()),
11864
                                ENT_QUOTES,
11865
                                'UTF-8'
11866
                            )
11867
                        );
11868
                        $my_item->appendChild($my_title);
11869
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11870
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11871
                        $my_prereqs->setAttribute('type', 'aicc_script');
11872
                        $my_item->appendChild($my_prereqs);
11873
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11874
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11875
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11876
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11877
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11878
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11879
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11880
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11881
                        $my_item->appendChild($my_masteryscore);
11882
11883
                        // Attach this item to the organization element or its parent if there is one.
11884
                        if (!empty($item->parent) && $item->parent != 0) {
11885
                            $children = $organization->childNodes;
11886
                            for ($i = 0; $i < $children->length; $i++) {
11887
                                $item_temp = $children->item($i);
11888
                                if ($item_temp->nodeName == 'item') {
11889
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11890
                                        $item_temp->appendChild($my_item);
11891
                                    }
11892
                                }
11893
                            }
11894
                        } else {
11895
                            $organization->appendChild($my_item);
11896
                        }
11897
11898
                        $my_file_path = 'link_'.$item->get_id().'.html';
11899
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11900
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11901
                        $rs = Database::query($sql);
11902
                        if ($link = Database::fetch_array($rs)) {
11903
                            $url = $link['url'];
11904
                            $title = stripslashes($link['title']);
11905
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11906
                            $my_xml_file_path = $my_file_path;
11907
                            $my_sub_dir = dirname($my_file_path);
11908
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11909
                            $my_xml_sub_dir = $my_sub_dir;
11910
                            // Give a <resource> child to the <resources> element.
11911
                            $my_resource = $xmldoc->createElement('resource');
11912
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11913
                            $my_resource->setAttribute('type', 'webcontent');
11914
                            $my_resource->setAttribute('href', $my_xml_file_path);
11915
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11916
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11917
                            // xml:base is the base directory to find the files declared in this resource.
11918
                            $my_resource->setAttribute('xml:base', '');
11919
                            // give a <file> child to the <resource> element.
11920
                            $my_file = $xmldoc->createElement('file');
11921
                            $my_file->setAttribute('href', $my_xml_file_path);
11922
                            $my_resource->appendChild($my_file);
11923
                            $resources->appendChild($my_resource);
11924
                        }
11925
                        break;
11926
                    case TOOL_QUIZ:
11927
                        $exe_id = $item->path;
11928
                        // Should be using ref when everything will be cleaned up in this regard.
11929
                        $exe = new Exercise();
11930
                        $exe->read($exe_id);
11931
                        $my_item = $xmldoc->createElement('item');
11932
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11933
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11934
                        $my_item->setAttribute('isvisible', 'true');
11935
                        // Give a child element <title> to the <item> element.
11936
                        $my_title = $xmldoc->createElement(
11937
                            'title',
11938
                            htmlspecialchars(
11939
                                api_utf8_encode($item->get_title()),
11940
                                ENT_QUOTES,
11941
                                'UTF-8'
11942
                            )
11943
                        );
11944
                        $my_item->appendChild($my_title);
11945
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11946
                        $my_item->appendChild($my_max_score);
11947
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11948
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11949
                        $my_prereqs->setAttribute('type', 'aicc_script');
11950
                        $my_item->appendChild($my_prereqs);
11951
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11952
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11953
                        $my_item->appendChild($my_masteryscore);
11954
11955
                        // Attach this item to the organization element or hits parent if there is one.
11956
                        if (!empty($item->parent) && $item->parent != 0) {
11957
                            $children = $organization->childNodes;
11958
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11959
                            if ($possible_parent) {
11960
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11961
                                    $possible_parent->appendChild($my_item);
11962
                                }
11963
                            }
11964
                        } else {
11965
                            $organization->appendChild($my_item);
11966
                        }
11967
11968
                        // Get the path of the file(s) from the course directory root
11969
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11970
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11971
                        // Write the contents of the exported exercise into a (big) html file
11972
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11973
                        $scormExercise = new ScormExercise($exe, true);
11974
                        $contents = $scormExercise->export();
11975
11976
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11977
                        $res = file_put_contents($tmp_file_path, $contents);
11978
                        if ($res === false) {
11979
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11980
                        }
11981
                        $files_cleanup[] = $tmp_file_path;
11982
                        $my_xml_file_path = $my_file_path;
11983
                        $my_sub_dir = dirname($my_file_path);
11984
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11985
                        $my_xml_sub_dir = $my_sub_dir;
11986
                        // Give a <resource> child to the <resources> element.
11987
                        $my_resource = $xmldoc->createElement('resource');
11988
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11989
                        $my_resource->setAttribute('type', 'webcontent');
11990
                        $my_resource->setAttribute('href', $my_xml_file_path);
11991
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11992
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11993
                        // xml:base is the base directory to find the files declared in this resource.
11994
                        $my_resource->setAttribute('xml:base', '');
11995
                        // Give a <file> child to the <resource> element.
11996
                        $my_file = $xmldoc->createElement('file');
11997
                        $my_file->setAttribute('href', $my_xml_file_path);
11998
                        $my_resource->appendChild($my_file);
11999
12000
                        // Get included docs.
12001
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
12002
12003
                        // Dependency to other files - not yet supported.
12004
                        $i = 1;
12005
                        foreach ($inc_docs as $doc_info) {
12006
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
12007
                                continue;
12008
                            }
12009
                            $my_dep = $xmldoc->createElement('resource');
12010
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
12011
                            $my_dep->setAttribute('identifier', $res_id);
12012
                            $my_dep->setAttribute('type', 'webcontent');
12013
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
12014
                            $my_dep_file = $xmldoc->createElement('file');
12015
                            // Check type of URL.
12016
                            if ($doc_info[1] == 'remote') {
12017
                                // Remote file. Save url as is.
12018
                                $my_dep_file->setAttribute('href', $doc_info[0]);
12019
                                $my_dep->setAttribute('xml:base', '');
12020
                            } elseif ($doc_info[1] == 'local') {
12021
                                switch ($doc_info[2]) {
12022
                                    case 'url': // Local URL - save path as url for now, don't zip file.
12023
                                        // Save file but as local file (retrieve from URL).
12024
                                        $abs_path = api_get_path(SYS_PATH).
12025
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
12026
                                        $current_dir = dirname($abs_path);
12027
                                        $current_dir = str_replace('\\', '/', $current_dir);
12028
                                        $file_path = realpath($abs_path);
12029
                                        $file_path = str_replace('\\', '/', $file_path);
12030
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
12031
                                        $my_dep->setAttribute('xml:base', '');
12032
                                        if (strstr($file_path, $main_path) !== false) {
12033
                                            // The calculated real path is really inside the chamilo root path.
12034
                                            // Reduce file path to what's under the DocumentRoot.
12035
                                            $file_path = substr($file_path, strlen($root_path));
12036
                                            $zip_files_abs[] = $file_path;
12037
                                            $link_updates[$my_file_path][] = [
12038
                                                'orig' => $doc_info[0],
12039
                                                'dest' => 'document/'.$file_path,
12040
                                            ];
12041
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
12042
                                            $my_dep->setAttribute('xml:base', '');
12043
                                        } elseif (empty($file_path)) {
12044
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
12045
                                            $file_path = str_replace('//', '/', $file_path);
12046
                                            if (file_exists($file_path)) {
12047
                                                $file_path = substr($file_path, strlen($current_dir));
12048
                                                // We get the relative path.
12049
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
12050
                                                $link_updates[$my_file_path][] = [
12051
                                                    'orig' => $doc_info[0],
12052
                                                    'dest' => 'document/'.$file_path,
12053
                                                ];
12054
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12055
                                                $my_dep->setAttribute('xml:base', '');
12056
                                            }
12057
                                        }
12058
                                        break;
12059
                                    case 'abs':
12060
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
12061
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
12062
                                        $current_dir = str_replace('\\', '/', $current_dir);
12063
                                        $file_path = realpath($doc_info[0]);
12064
                                        $file_path = str_replace('\\', '/', $file_path);
12065
                                        $my_dep_file->setAttribute('href', $file_path);
12066
                                        $my_dep->setAttribute('xml:base', '');
12067
12068
                                        if (strstr($file_path, $main_path) !== false) {
12069
                                            // The calculated real path is really inside the chamilo root path.
12070
                                            // Reduce file path to what's under the DocumentRoot.
12071
                                            $file_path = substr($file_path, strlen($root_path));
12072
                                            $zip_files_abs[] = $file_path;
12073
                                            $link_updates[$my_file_path][] = [
12074
                                                'orig' => $doc_info[0],
12075
                                                'dest' => $file_path,
12076
                                            ];
12077
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
12078
                                            $my_dep->setAttribute('xml:base', '');
12079
                                        } elseif (empty($file_path)) {
12080
                                            $docSysPartPath = str_replace(
12081
                                                api_get_path(REL_COURSE_PATH),
12082
                                                '',
12083
                                                $doc_info[0]
12084
                                            );
12085
12086
                                            $docSysPartPathNoCourseCode = str_replace(
12087
                                                $_course['directory'].'/',
12088
                                                '',
12089
                                                $docSysPartPath
12090
                                            );
12091
12092
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
12093
                                            if (file_exists($docSysPath)) {
12094
                                                $file_path = $docSysPartPathNoCourseCode;
12095
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
12096
                                                $link_updates[$my_file_path][] = [
12097
                                                    'orig' => $doc_info[0],
12098
                                                    'dest' => $file_path,
12099
                                                ];
12100
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12101
                                                $my_dep->setAttribute('xml:base', '');
12102
                                            }
12103
                                        }
12104
                                        break;
12105
                                    case 'rel':
12106
                                        // Path relative to the current document. Save xml:base as current document's
12107
                                        // directory and save file in zip as subdir.file_path
12108
                                        if (substr($doc_info[0], 0, 2) === '..') {
12109
                                            // Relative path going up.
12110
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
12111
                                            $current_dir = str_replace('\\', '/', $current_dir);
12112
                                            $file_path = realpath($current_dir.$doc_info[0]);
12113
                                            $file_path = str_replace('\\', '/', $file_path);
12114
                                            if (strstr($file_path, $main_path) !== false) {
12115
                                                // The calculated real path is really inside Chamilo's root path.
12116
                                                // Reduce file path to what's under the DocumentRoot.
12117
12118
                                                $file_path = substr($file_path, strlen($root_path));
12119
                                                $file_path_dest = $file_path;
12120
12121
                                                // File path is courses/CHAMILO/document/....
12122
                                                $info_file_path = explode('/', $file_path);
12123
                                                if ($info_file_path[0] == 'courses') {
12124
                                                    // Add character "/" in file path.
12125
                                                    $file_path_dest = 'document/'.$file_path;
12126
                                                }
12127
                                                $zip_files_abs[] = $file_path;
12128
12129
                                                $link_updates[$my_file_path][] = [
12130
                                                    'orig' => $doc_info[0],
12131
                                                    'dest' => $file_path_dest,
12132
                                                ];
12133
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12134
                                                $my_dep->setAttribute('xml:base', '');
12135
                                            }
12136
                                        } else {
12137
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
12138
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
12139
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
12140
                                        }
12141
                                        break;
12142
                                    default:
12143
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
12144
                                        $my_dep->setAttribute('xml:base', '');
12145
                                        break;
12146
                                }
12147
                            }
12148
                            $my_dep->appendChild($my_dep_file);
12149
                            $resources->appendChild($my_dep);
12150
                            $dependency = $xmldoc->createElement('dependency');
12151
                            $dependency->setAttribute('identifierref', $res_id);
12152
                            $my_resource->appendChild($dependency);
12153
                            $i++;
12154
                        }
12155
                        $resources->appendChild($my_resource);
12156
                        $zip_files[] = $my_file_path;
12157
                        break;
12158
                    default:
12159
                        // Get the path of the file(s) from the course directory root
12160
                        $my_file_path = 'non_exportable.html';
12161
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
12162
                        $my_xml_file_path = $my_file_path;
12163
                        $my_sub_dir = dirname($my_file_path);
12164
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
12165
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
12166
                        $my_xml_sub_dir = $my_sub_dir;
12167
                        // Give a <resource> child to the <resources> element.
12168
                        $my_resource = $xmldoc->createElement('resource');
12169
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
12170
                        $my_resource->setAttribute('type', 'webcontent');
12171
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
12172
                        // adlcp:scormtype can be either 'sco' or 'asset'.
12173
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
12174
                        // xml:base is the base directory to find the files declared in this resource.
12175
                        $my_resource->setAttribute('xml:base', '');
12176
                        // Give a <file> child to the <resource> element.
12177
                        $my_file = $xmldoc->createElement('file');
12178
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
12179
                        $my_resource->appendChild($my_file);
12180
                        $resources->appendChild($my_resource);
12181
                        break;
12182
                }
12183
            }
12184
        }
12185
        $organizations->appendChild($organization);
12186
        $root->appendChild($organizations);
12187
        $root->appendChild($resources);
12188
        $xmldoc->appendChild($root);
12189
12190
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
12191
12192
        // then add the file to the zip, then destroy the file (this is done automatically).
12193
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
12194
        foreach ($zip_files as $file_path) {
12195
            if (empty($file_path)) {
12196
                continue;
12197
            }
12198
12199
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
12200
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
12201
12202
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
12203
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
12204
            }
12205
12206
            $this->create_path($dest_file);
12207
            @copy($filePath, $dest_file);
12208
12209
            // Check if the file needs a link update.
12210
            if (in_array($file_path, array_keys($link_updates))) {
12211
                $string = file_get_contents($dest_file);
12212
                unlink($dest_file);
12213
                foreach ($link_updates[$file_path] as $old_new) {
12214
                    // This is an ugly hack that allows .flv files to be found by the flv player that
12215
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
12216
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
12217
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
12218
                    if (substr($old_new['dest'], -3) === 'flv' &&
12219
                        substr($old_new['dest'], 0, 5) === 'main/'
12220
                    ) {
12221
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
12222
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
12223
                        substr($old_new['dest'], 0, 6) === 'video/'
12224
                    ) {
12225
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
12226
                    }
12227
12228
                    // Fix to avoid problems with default_course_document
12229
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
12230
                        $newDestination = $old_new['dest'];
12231
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
12232
                            $newDestination = $old_new['replace'];
12233
                        }
12234
                    } else {
12235
                        $newDestination = str_replace('document/', '', $old_new['dest']);
12236
                    }
12237
                    $string = str_replace($old_new['orig'], $newDestination, $string);
12238
12239
                    // Add files inside the HTMLs
12240
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
12241
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
12242
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
12243
                        copy(
12244
                            $sys_course_path.$new_path,
12245
                            $destinationFile
12246
                        );
12247
                    }
12248
                }
12249
                file_put_contents($dest_file, $string);
12250
            }
12251
12252
            if (file_exists($filePath) && $copyAll) {
12253
                $extension = $this->get_extension($filePath);
12254
                if (in_array($extension, ['html', 'html'])) {
12255
                    $containerOrigin = dirname($filePath);
12256
                    $containerDestination = dirname($dest_file);
12257
12258
                    $finder = new Finder();
12259
                    $finder->files()->in($containerOrigin)
12260
                        ->notName('*_DELETED_*')
12261
                        ->exclude('share_folder')
12262
                        ->exclude('chat_files')
12263
                        ->exclude('certificates')
12264
                    ;
12265
12266
                    if (is_dir($containerOrigin) &&
12267
                        is_dir($containerDestination)
12268
                    ) {
12269
                        $fs = new Filesystem();
12270
                        $fs->mirror(
12271
                            $containerOrigin,
12272
                            $containerDestination,
12273
                            $finder
12274
                        );
12275
                    }
12276
                }
12277
            }
12278
        }
12279
12280
        foreach ($zip_files_abs as $file_path) {
12281
            if (empty($file_path)) {
12282
                continue;
12283
            }
12284
12285
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
12286
                continue;
12287
            }
12288
12289
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
12290
            if (strstr($file_path, 'upload/users') !== false) {
12291
                $pos = strpos($file_path, 'my_files/');
12292
                if ($pos !== false) {
12293
                    $onlyDirectory = str_replace(
12294
                        'upload/users/',
12295
                        '',
12296
                        substr($file_path, $pos, strlen($file_path))
12297
                    );
12298
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
12299
                }
12300
            }
12301
12302
            if (strstr($file_path, 'default_course_document/') !== false) {
12303
                $replace = str_replace('/main', '', $file_path);
12304
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
12305
            }
12306
12307
            if (empty($dest_file)) {
12308
                continue;
12309
            }
12310
12311
            $this->create_path($dest_file);
12312
            copy($main_path.$file_path, $dest_file);
12313
            // Check if the file needs a link update.
12314
            if (in_array($file_path, array_keys($link_updates))) {
12315
                $string = file_get_contents($dest_file);
12316
                unlink($dest_file);
12317
                foreach ($link_updates[$file_path] as $old_new) {
12318
                    // This is an ugly hack that allows .flv files to be found by the flv player that
12319
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
12320
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
12321
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
12322
                    if (substr($old_new['dest'], -3) == 'flv' &&
12323
                        substr($old_new['dest'], 0, 5) == 'main/'
12324
                    ) {
12325
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
12326
                    }
12327
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
12328
                }
12329
                file_put_contents($dest_file, $string);
12330
            }
12331
        }
12332
12333
        if (is_array($links_to_create)) {
12334
            foreach ($links_to_create as $file => $link) {
12335
                $content = '<!DOCTYPE html><head>
12336
                            <meta charset="'.api_get_language_isocode().'" />
12337
                            <title>'.$link['title'].'</title>
12338
                            </head>
12339
                            <body dir="'.api_get_text_direction().'">
12340
                            <div style="text-align:center">
12341
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
12342
                            </body>
12343
                            </html>';
12344
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
12345
            }
12346
        }
12347
12348
        // Add non exportable message explanation.
12349
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
12350
        $file_content = '<!DOCTYPE html><head>
12351
                        <meta charset="'.api_get_language_isocode().'" />
12352
                        <title>'.$lang_not_exportable.'</title>
12353
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
12354
                        </head>
12355
                        <body dir="'.api_get_text_direction().'">';
12356
        $file_content .=
12357
            <<<EOD
12358
                    <style>
12359
            .error-message {
12360
                font-family: arial, verdana, helvetica, sans-serif;
12361
                border-width: 1px;
12362
                border-style: solid;
12363
                left: 50%;
12364
                margin: 10px auto;
12365
                min-height: 30px;
12366
                padding: 5px;
12367
                right: 50%;
12368
                width: 500px;
12369
                background-color: #FFD1D1;
12370
                border-color: #FF0000;
12371
                color: #000;
12372
            }
12373
        </style>
12374
    <body>
12375
        <div class="error-message">
12376
            $lang_not_exportable
12377
        </div>
12378
    </body>
12379
</html>
12380
EOD;
12381
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
12382
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
12383
        }
12384
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
12385
12386
        // Add the extra files that go along with a SCORM package.
12387
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
12388
12389
        $fs = new Filesystem();
12390
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
12391
12392
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
12393
        $manifest = @$xmldoc->saveXML();
12394
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
12395
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
12396
12397
        $htmlIndex = new LpIndexGenerator($this);
12398
12399
        file_put_contents(
12400
            $archivePath.'/'.$temp_dir_short.'/index.html',
12401
            $htmlIndex->generate()
12402
        );
12403
12404
        $zip_folder->add(
12405
            $archivePath.'/'.$temp_dir_short,
12406
            PCLZIP_OPT_REMOVE_PATH,
12407
            $archivePath.'/'.$temp_dir_short.'/'
12408
        );
12409
12410
        // Clean possible temporary files.
12411
        foreach ($files_cleanup as $file) {
12412
            $res = unlink($file);
12413
            if ($res === false) {
12414
                error_log(
12415
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
12416
                    0
12417
                );
12418
            }
12419
        }
12420
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
12421
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
12422
    }
12423
12424
    /**
12425
     * @param int $lp_id
12426
     *
12427
     * @return bool
12428
     */
12429
    public function scorm_export_to_pdf($lp_id)
12430
    {
12431
        $lp_id = (int) $lp_id;
12432
        $files_to_export = [];
12433
12434
        $sessionId = api_get_session_id();
12435
        $course_data = api_get_course_info($this->cc);
12436
12437
        if (!empty($course_data)) {
12438
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
12439
            $list = self::get_flat_ordered_items_list($lp_id);
12440
            if (!empty($list)) {
12441
                foreach ($list as $item_id) {
12442
                    $item = $this->items[$item_id];
12443
                    switch ($item->type) {
12444
                        case 'document':
12445
                            // Getting documents from a LP with chamilo documents
12446
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
12447
                            // Try loading document from the base course.
12448
                            if (empty($file_data) && !empty($sessionId)) {
12449
                                $file_data = DocumentManager::get_document_data_by_id(
12450
                                    $item->path,
12451
                                    $this->cc,
12452
                                    false,
12453
                                    0
12454
                                );
12455
                            }
12456
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12457
                            if (file_exists($file_path)) {
12458
                                $files_to_export[] = [
12459
                                    'title' => $item->get_title(),
12460
                                    'path' => $file_path,
12461
                                ];
12462
                            }
12463
                            break;
12464
                        case 'asset': //commes from a scorm package generated by chamilo
12465
                        case 'sco':
12466
                            $file_path = $scorm_path.'/'.$item->path;
12467
                            if (file_exists($file_path)) {
12468
                                $files_to_export[] = [
12469
                                    'title' => $item->get_title(),
12470
                                    'path' => $file_path,
12471
                                ];
12472
                            }
12473
                            break;
12474
                        case 'dir':
12475
                            $files_to_export[] = [
12476
                                'title' => $item->get_title(),
12477
                                'path' => null,
12478
                            ];
12479
                            break;
12480
                    }
12481
                }
12482
            }
12483
12484
            $pdf = new PDF();
12485
            $result = $pdf->html_to_pdf(
12486
                $files_to_export,
12487
                $this->name,
12488
                $this->cc,
12489
                true,
12490
                true,
12491
                true,
12492
                $this->get_name()
12493
            );
12494
12495
            return $result;
12496
        }
12497
12498
        return false;
12499
    }
12500
12501
    /**
12502
     * Temp function to be moved in main_api or the best place around for this.
12503
     * Creates a file path if it doesn't exist.
12504
     *
12505
     * @param string $path
12506
     */
12507
    public function create_path($path)
12508
    {
12509
        $path_bits = explode('/', dirname($path));
12510
12511
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12512
        $path_built = IS_WINDOWS_OS ? '' : '/';
12513
        foreach ($path_bits as $bit) {
12514
            if (!empty($bit)) {
12515
                $new_path = $path_built.$bit;
12516
                if (is_dir($new_path)) {
12517
                    $path_built = $new_path.'/';
12518
                } else {
12519
                    mkdir($new_path, api_get_permissions_for_new_directories());
12520
                    $path_built = $new_path.'/';
12521
                }
12522
            }
12523
        }
12524
    }
12525
12526
    /**
12527
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12528
     *
12529
     * @return bool The results of the unlink function, or false if there was no image to start with
12530
     */
12531
    public function delete_lp_image()
12532
    {
12533
        $img = $this->get_preview_image();
12534
        if ($img != '') {
12535
            $del_file = $this->get_preview_image_path(null, 'sys');
12536
            if (isset($del_file) && file_exists($del_file)) {
12537
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12538
                if (file_exists($del_file_2)) {
12539
                    unlink($del_file_2);
12540
                }
12541
                $this->set_preview_image('');
12542
12543
                return @unlink($del_file);
12544
            }
12545
        }
12546
12547
        return false;
12548
    }
12549
12550
    /**
12551
     * Uploads an author image to the upload/learning_path/images directory.
12552
     *
12553
     * @param array    The image array, coming from the $_FILES superglobal
12554
     *
12555
     * @return bool True on success, false on error
12556
     */
12557
    public function upload_image($image_array)
12558
    {
12559
        if (!empty($image_array['name'])) {
12560
            $upload_ok = process_uploaded_file($image_array);
12561
            $has_attachment = true;
12562
        }
12563
12564
        if ($upload_ok && $has_attachment) {
12565
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12566
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12567
            $updir = $sys_course_path.$courseDir;
12568
            // Try to add an extension to the file if it hasn't one.
12569
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12570
12571
            if (filter_extension($new_file_name)) {
12572
                $file_extension = explode('.', $image_array['name']);
12573
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
12574
                $filename = uniqid('');
12575
                $new_file_name = $filename.'.'.$file_extension;
12576
                $new_path = $updir.'/'.$new_file_name;
12577
12578
                // Resize the image.
12579
                $temp = new Image($image_array['tmp_name']);
12580
                $temp->resize(104);
12581
                $result = $temp->send_image($new_path);
12582
12583
                // Storing the image filename.
12584
                if ($result) {
12585
                    $this->set_preview_image($new_file_name);
12586
12587
                    //Resize to 64px to use on course homepage
12588
                    $temp->resize(64);
12589
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12590
12591
                    return true;
12592
                }
12593
            }
12594
        }
12595
12596
        return false;
12597
    }
12598
12599
    /**
12600
     * @param int    $lp_id
12601
     * @param string $status
12602
     */
12603
    public function set_autolaunch($lp_id, $status)
12604
    {
12605
        $course_id = api_get_course_int_id();
12606
        $lp_id = (int) $lp_id;
12607
        $status = (int) $status;
12608
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12609
12610
        // Setting everything to autolaunch = 0
12611
        $attributes['autolaunch'] = 0;
12612
        $where = [
12613
            'session_id = ? AND c_id = ? ' => [
12614
                api_get_session_id(),
12615
                $course_id,
12616
            ],
12617
        ];
12618
        Database::update($lp_table, $attributes, $where);
12619
        if ($status == 1) {
12620
            //Setting my lp_id to autolaunch = 1
12621
            $attributes['autolaunch'] = 1;
12622
            $where = [
12623
                'iid = ? AND session_id = ? AND c_id = ?' => [
12624
                    $lp_id,
12625
                    api_get_session_id(),
12626
                    $course_id,
12627
                ],
12628
            ];
12629
            Database::update($lp_table, $attributes, $where);
12630
        }
12631
    }
12632
12633
    /**
12634
     * Gets previous_item_id for the next element of the lp_item table.
12635
     *
12636
     * @author Isaac flores paz
12637
     *
12638
     * @return int Previous item ID
12639
     */
12640
    public function select_previous_item_id()
12641
    {
12642
        $course_id = api_get_course_int_id();
12643
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12644
12645
        // Get the max order of the items
12646
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12647
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12648
        $rs_max_order = Database::query($sql);
12649
        $row_max_order = Database::fetch_object($rs_max_order);
12650
        $max_order = $row_max_order->display_order;
12651
        // Get the previous item ID
12652
        $sql = "SELECT iid as previous FROM $table_lp_item
12653
                WHERE
12654
                    c_id = $course_id AND
12655
                    lp_id = ".$this->lp_id." AND
12656
                    display_order = '$max_order' ";
12657
        $rs_max = Database::query($sql);
12658
        $row_max = Database::fetch_object($rs_max);
12659
12660
        // Return the previous item ID
12661
        return $row_max->previous;
12662
    }
12663
12664
    /**
12665
     * Copies an LP.
12666
     */
12667
    public function copy()
12668
    {
12669
        // Course builder
12670
        $cb = new CourseBuilder();
12671
12672
        //Setting tools that will be copied
12673
        $cb->set_tools_to_build(['learnpaths']);
12674
12675
        //Setting elements that will be copied
12676
        $cb->set_tools_specific_id_list(['learnpaths' => [$this->lp_id]]);
12677
12678
        $course = $cb->build($this->lp_session_id);
12679
12680
        //Course restorer
12681
        $course_restorer = new CourseRestorer($course);
12682
        $course_restorer->set_add_text_in_items(true);
12683
        $course_restorer->set_tool_copy_settings(
12684
            ['learnpaths' => ['reset_dates' => true]]
12685
        );
12686
        $course_restorer->restore(
12687
            api_get_course_id(),
12688
            api_get_session_id(),
12689
            false,
12690
            false
12691
        );
12692
    }
12693
12694
    /**
12695
     * Verify document size.
12696
     *
12697
     * @param string $s
12698
     *
12699
     * @return bool
12700
     */
12701
    public static function verify_document_size($s)
12702
    {
12703
        $post_max = ini_get('post_max_size');
12704
        if (substr($post_max, -1, 1) == 'M') {
12705
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12706
        } elseif (substr($post_max, -1, 1) == 'G') {
12707
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12708
        }
12709
        $upl_max = ini_get('upload_max_filesize');
12710
        if (substr($upl_max, -1, 1) == 'M') {
12711
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12712
        } elseif (substr($upl_max, -1, 1) == 'G') {
12713
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12714
        }
12715
        $documents_total_space = DocumentManager::documents_total_space();
12716
        $course_max_space = DocumentManager::get_course_quota();
12717
        $total_size = filesize($s) + $documents_total_space;
12718
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12719
            return true;
12720
        }
12721
12722
        return false;
12723
    }
12724
12725
    /**
12726
     * Clear LP prerequisites.
12727
     */
12728
    public function clear_prerequisites()
12729
    {
12730
        $course_id = $this->get_course_int_id();
12731
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12732
        $lp_id = $this->get_id();
12733
        // Cleaning prerequisites
12734
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12735
                WHERE c_id = $course_id AND lp_id = $lp_id";
12736
        Database::query($sql);
12737
12738
        // Cleaning mastery score for exercises
12739
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12740
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12741
        Database::query($sql);
12742
    }
12743
12744
    public function set_previous_step_as_prerequisite_for_all_items()
12745
    {
12746
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12747
        $course_id = $this->get_course_int_id();
12748
        $lp_id = $this->get_id();
12749
12750
        if (!empty($this->items)) {
12751
            $previous_item_id = null;
12752
            $previous_item_max = 0;
12753
            $previous_item_type = null;
12754
            $last_item_not_dir = null;
12755
            $last_item_not_dir_type = null;
12756
            $last_item_not_dir_max = null;
12757
12758
            foreach ($this->ordered_items as $itemId) {
12759
                $item = $this->getItem($itemId);
12760
                // if there was a previous item... (otherwise jump to set it)
12761
                if (!empty($previous_item_id)) {
12762
                    $current_item_id = $item->get_id(); //save current id
12763
                    if ($item->get_type() != 'dir') {
12764
                        // Current item is not a folder, so it qualifies to get a prerequisites
12765
                        if ($last_item_not_dir_type == 'quiz') {
12766
                            // if previous is quiz, mark its max score as default score to be achieved
12767
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12768
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12769
                            Database::query($sql);
12770
                        }
12771
                        // now simply update the prerequisite to set it to the last non-chapter item
12772
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12773
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12774
                        Database::query($sql);
12775
                        // record item as 'non-chapter' reference
12776
                        $last_item_not_dir = $item->get_id();
12777
                        $last_item_not_dir_type = $item->get_type();
12778
                        $last_item_not_dir_max = $item->get_max();
12779
                    }
12780
                } else {
12781
                    if ($item->get_type() != 'dir') {
12782
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12783
                        $last_item_not_dir = $item->get_id();
12784
                        $last_item_not_dir_type = $item->get_type();
12785
                        $last_item_not_dir_max = $item->get_max();
12786
                    }
12787
                }
12788
                // Saving the item as "previous item" for the next loop
12789
                $previous_item_id = $item->get_id();
12790
                $previous_item_max = $item->get_max();
12791
                $previous_item_type = $item->get_type();
12792
            }
12793
        }
12794
    }
12795
12796
    /**
12797
     * @param array $params
12798
     *
12799
     * @throws \Doctrine\ORM\OptimisticLockException
12800
     *
12801
     * @return int
12802
     */
12803
    public static function createCategory($params)
12804
    {
12805
        $em = Database::getManager();
12806
        $item = new CLpCategory();
12807
        $item->setName($params['name']);
12808
        $item->setCId($params['c_id']);
12809
        $em->persist($item);
12810
        $em->flush();
12811
12812
        $id = $item->getId();
12813
12814
        $sessionId = api_get_session_id();
12815
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12816
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12817
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12818
            Database::query($sql);
12819
        }
12820
12821
        api_item_property_update(
12822
            api_get_course_info(),
12823
            TOOL_LEARNPATH_CATEGORY,
12824
            $id,
12825
            'visible',
12826
            api_get_user_id()
12827
        );
12828
12829
        return $item->getId();
12830
    }
12831
12832
    /**
12833
     * @param array $params
12834
     */
12835
    public static function updateCategory($params)
12836
    {
12837
        $em = Database::getManager();
12838
        $item = self::getCategory($params['id']);
12839
12840
        if ($item) {
12841
            $item->setName($params['name']);
12842
            $em->persist($item);
12843
            $em->flush();
12844
        }
12845
    }
12846
12847
    /**
12848
     * @param int $id
12849
     */
12850
    public static function moveUpCategory($id)
12851
    {
12852
        $item = self::getCategory($id);
12853
        if ($item) {
12854
            $em = Database::getManager();
12855
            $position = $item->getPosition() - 1;
12856
            $item->setPosition($position);
12857
            $em->persist($item);
12858
            $em->flush();
12859
        }
12860
    }
12861
12862
    /**
12863
     * @param int $id
12864
     *
12865
     * @throws \Doctrine\ORM\ORMException
12866
     * @throws \Doctrine\ORM\OptimisticLockException
12867
     * @throws \Doctrine\ORM\TransactionRequiredException
12868
     */
12869
    public static function moveDownCategory($id)
12870
    {
12871
        $item = self::getCategory($id);
12872
        if ($item) {
12873
            $em = Database::getManager();
12874
            $position = $item->getPosition() + 1;
12875
            $item->setPosition($position);
12876
            $em->persist($item);
12877
            $em->flush();
12878
        }
12879
    }
12880
12881
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12882
    {
12883
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12884
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12885
        $courseId = (int) $courseId;
12886
        $sessionId = (int) $sessionId;
12887
12888
        $sql = "SELECT lp.id, lp.name
12889
                FROM $TABLE_LP lp
12890
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12891
                ON lp.id = ip.ref
12892
                WHERE lp.c_id = $courseId ";
12893
12894
        if (!empty($sessionId)) {
12895
            $sql .= "AND ip.session_id = $sessionId ";
12896
        }
12897
12898
        if ($onlyActiveLp) {
12899
            $sql .= "AND ip.tool = 'learnpath' ";
12900
            $sql .= "AND ip.visibility = 1 ";
12901
        }
12902
12903
        $sql .= "GROUP BY lp.id";
12904
12905
        $result = Database::query($sql);
12906
12907
        return Database::store_result($result, 'ASSOC');
12908
    }
12909
12910
    /**
12911
     * @param int $courseId
12912
     *
12913
     * @throws \Doctrine\ORM\Query\QueryException
12914
     *
12915
     * @return int|mixed
12916
     */
12917
    public static function getCountCategories($courseId)
12918
    {
12919
        if (empty($courseId)) {
12920
            return 0;
12921
        }
12922
        $em = Database::getManager();
12923
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12924
        $query->setParameter('id', $courseId);
12925
12926
        return $query->getSingleScalarResult();
12927
    }
12928
12929
    /**
12930
     * @param int $courseId
12931
     *
12932
     * @return array|CLpCategory[]
12933
     */
12934
    public static function getCategories($courseId, $withNoneCategory = false)
12935
    {
12936
        $em = Database::getManager();
12937
12938
        // Using doctrine extensions
12939
        /** @var SortableRepository $repo */
12940
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12941
12942
        $categories = $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12943
12944
        if ($withNoneCategory) {
12945
            $categoryTest = new CLpCategory();
12946
            $categoryTest->setId(0)
12947
                ->setName(get_lang('WithOutCategory'))
12948
                ->setPosition(0);
12949
12950
            array_unshift($categories, $categoryTest);
12951
        }
12952
12953
        return $categories;
12954
    }
12955
12956
    public static function getCategorySessionId($id)
12957
    {
12958
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12959
            return 0;
12960
        }
12961
12962
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12963
        $id = (int) $id;
12964
12965
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12966
        $result = Database::query($sql);
12967
        $result = Database::fetch_array($result, 'ASSOC');
12968
12969
        if ($result) {
12970
            return (int) $result['session_id'];
12971
        }
12972
12973
        return 0;
12974
    }
12975
12976
    /**
12977
     * @param int $id
12978
     */
12979
    public static function getCategory($id): ?CLpCategory
12980
    {
12981
        $id = (int) $id;
12982
        $em = Database::getManager();
12983
12984
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12985
    }
12986
12987
    /**
12988
     * @param int $courseId
12989
     *
12990
     * @return CLpCategory[]
12991
     */
12992
    public static function getCategoryByCourse($courseId)
12993
    {
12994
        $em = Database::getManager();
12995
12996
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12997
    }
12998
12999
    /**
13000
     * @param int $id
13001
     *
13002
     * @return bool
13003
     */
13004
    public static function deleteCategory($id)
13005
    {
13006
        $em = Database::getManager();
13007
        $id = (int) $id;
13008
        $item = self::getCategory($id);
13009
        if ($item) {
13010
            $courseId = $item->getCId();
13011
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
13012
            $query->setParameter('id', $courseId);
13013
            $query->setParameter('catId', $item->getId());
13014
            $lps = $query->getResult();
13015
13016
            // Setting category = 0.
13017
            if ($lps) {
13018
                foreach ($lps as $lpItem) {
13019
                    $lpItem->setCategoryId(0);
13020
                }
13021
            }
13022
13023
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
13024
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
13025
                $sql = "DELETE FROM $table
13026
                        WHERE
13027
                            lp_category_id = $id AND
13028
                            c_id = $courseId ";
13029
                Database::query($sql);
13030
            }
13031
13032
            // Removing category.
13033
            $em->remove($item);
13034
            $em->flush();
13035
13036
            $courseInfo = api_get_course_info_by_id($courseId);
13037
            $sessionId = api_get_session_id();
13038
13039
            // Delete link tool
13040
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
13041
            $linkLikeValue = "lp/lp_controller.php?%&action=view_category&id=$id%";
13042
            // Delete tools
13043
            $sql = "DELETE FROM $tbl_tool
13044
                    WHERE c_id = ".$courseId." AND (link LIKE '$linkLikeValue' AND image='lp_category.gif')";
13045
            Database::query($sql);
13046
13047
            return true;
13048
        }
13049
13050
        return false;
13051
    }
13052
13053
    /**
13054
     * @param int  $courseId
13055
     * @param bool $addSelectOption
13056
     *
13057
     * @return mixed
13058
     */
13059
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
13060
    {
13061
        $items = self::getCategoryByCourse($courseId);
13062
        $cats = [];
13063
        if ($addSelectOption) {
13064
            $cats = [get_lang('SelectACategory')];
13065
        }
13066
13067
        $sessionId = api_get_session_id();
13068
        $avoidCategoryInSession = false;
13069
        if (empty($sessionId)) {
13070
            $avoidCategoryInSession = true;
13071
        }
13072
        /*$checkSession = false;
13073
        if (api_get_configuration_value('allow_session_lp_category')) {
13074
            $checkSession = true;
13075
        }*/
13076
13077
        if (!empty($items)) {
13078
            foreach ($items as $cat) {
13079
                $categoryId = $cat->getId();
13080
                if ($avoidCategoryInSession) {
13081
                    $inSession = self::getCategorySessionId($categoryId);
13082
                    if (!empty($inSession)) {
13083
                        continue;
13084
                    }
13085
                }
13086
                $cats[$categoryId] = $cat->getName();
13087
            }
13088
        }
13089
13090
        return $cats;
13091
    }
13092
13093
    /**
13094
     * @param string $courseCode
13095
     * @param int    $lpId
13096
     * @param int    $user_id
13097
     *
13098
     * @return learnpath
13099
     */
13100
    public static function getLpFromSession($courseCode, $lpId, $user_id)
13101
    {
13102
        $debug = 0;
13103
        $learnPath = null;
13104
        $lpObject = Session::read('lpobject');
13105
        if ($lpObject !== null) {
13106
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
13107
            if ($debug) {
13108
                error_log('getLpFromSession: unserialize');
13109
                error_log('------getLpFromSession------');
13110
                error_log('------unserialize------');
13111
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
13112
                error_log("api_get_sessionid: ".api_get_session_id());
13113
            }
13114
        }
13115
13116
        if (!is_object($learnPath)) {
13117
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
13118
            if ($debug) {
13119
                error_log('------getLpFromSession------');
13120
                error_log('getLpFromSession: create new learnpath');
13121
                error_log("create new LP with $courseCode - $lpId - $user_id");
13122
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
13123
                error_log("api_get_sessionid: ".api_get_session_id());
13124
            }
13125
        }
13126
13127
        return $learnPath;
13128
    }
13129
13130
    /**
13131
     * @param int $itemId
13132
     *
13133
     * @return learnpathItem|false
13134
     */
13135
    public function getItem($itemId)
13136
    {
13137
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
13138
            return $this->items[$itemId];
13139
        }
13140
13141
        return false;
13142
    }
13143
13144
    /**
13145
     * @return int
13146
     */
13147
    public function getCurrentAttempt()
13148
    {
13149
        $attempt = $this->getItem($this->get_current_item_id());
13150
        if ($attempt) {
13151
            $attemptId = $attempt->get_attempt_id();
13152
13153
            return $attemptId;
13154
        }
13155
13156
        return 0;
13157
    }
13158
13159
    /**
13160
     * @return int
13161
     */
13162
    public function getCategoryId()
13163
    {
13164
        return (int) $this->categoryId;
13165
    }
13166
13167
    /**
13168
     * @param int $categoryId
13169
     *
13170
     * @return bool
13171
     */
13172
    public function setCategoryId($categoryId)
13173
    {
13174
        $this->categoryId = (int) $categoryId;
13175
        $table = Database::get_course_table(TABLE_LP_MAIN);
13176
        $lp_id = $this->get_id();
13177
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
13178
                WHERE iid = $lp_id";
13179
        Database::query($sql);
13180
13181
        return true;
13182
    }
13183
13184
    /**
13185
     * Get whether this is a learning path with the possibility to subscribe
13186
     * users or not.
13187
     *
13188
     * @return int
13189
     */
13190
    public function getSubscribeUsers()
13191
    {
13192
        return $this->subscribeUsers;
13193
    }
13194
13195
    /**
13196
     * Set whether this is a learning path with the possibility to subscribe
13197
     * users or not.
13198
     *
13199
     * @param int $value (0 = false, 1 = true)
13200
     *
13201
     * @return bool
13202
     */
13203
    public function setSubscribeUsers($value)
13204
    {
13205
        $this->subscribeUsers = (int) $value;
13206
        $table = Database::get_course_table(TABLE_LP_MAIN);
13207
        $lp_id = $this->get_id();
13208
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
13209
                WHERE iid = $lp_id";
13210
        Database::query($sql);
13211
13212
        return true;
13213
    }
13214
13215
    /**
13216
     * Calculate the count of stars for a user in this LP
13217
     * This calculation is based on the following rules:
13218
     * - the student gets one star when he gets to 50% of the learning path
13219
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
13220
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
13221
     * - the student gets the final star when the score for the *last* test is >= 80%.
13222
     *
13223
     * @param int $sessionId Optional. The session ID
13224
     *
13225
     * @return int The count of stars
13226
     */
13227
    public function getCalculateStars($sessionId = 0)
13228
    {
13229
        $stars = 0;
13230
        $progress = self::getProgress(
13231
            $this->lp_id,
13232
            $this->user_id,
13233
            $this->course_int_id,
13234
            $sessionId
13235
        );
13236
13237
        if ($progress >= 50) {
13238
            $stars++;
13239
        }
13240
13241
        // Calculate stars chapters evaluation
13242
        $exercisesItems = $this->getExercisesItems();
13243
13244
        if (!empty($exercisesItems)) {
13245
            $totalResult = 0;
13246
13247
            foreach ($exercisesItems as $exerciseItem) {
13248
                $exerciseResultInfo = Event::getExerciseResultsByUser(
13249
                    $this->user_id,
13250
                    $exerciseItem->path,
13251
                    $this->course_int_id,
13252
                    $sessionId,
13253
                    $this->lp_id,
13254
                    $exerciseItem->db_id
13255
                );
13256
13257
                $exerciseResultInfo = end($exerciseResultInfo);
13258
13259
                if (!$exerciseResultInfo) {
13260
                    continue;
13261
                }
13262
13263
                if (!empty($exerciseResultInfo['exe_weighting'])) {
13264
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
13265
                } else {
13266
                    $exerciseResult = 0;
13267
                }
13268
                $totalResult += $exerciseResult;
13269
            }
13270
13271
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
13272
13273
            if ($totalExerciseAverage >= 50) {
13274
                $stars++;
13275
            }
13276
13277
            if ($totalExerciseAverage >= 80) {
13278
                $stars++;
13279
            }
13280
        }
13281
13282
        // Calculate star for final evaluation
13283
        $finalEvaluationItem = $this->getFinalEvaluationItem();
13284
13285
        if (!empty($finalEvaluationItem)) {
13286
            $evaluationResultInfo = Event::getExerciseResultsByUser(
13287
                $this->user_id,
13288
                $finalEvaluationItem->path,
13289
                $this->course_int_id,
13290
                $sessionId,
13291
                $this->lp_id,
13292
                $finalEvaluationItem->db_id
13293
            );
13294
13295
            $evaluationResultInfo = end($evaluationResultInfo);
13296
13297
            if ($evaluationResultInfo) {
13298
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
13299
13300
                if ($evaluationResult >= 80) {
13301
                    $stars++;
13302
                }
13303
            }
13304
        }
13305
13306
        return $stars;
13307
    }
13308
13309
    /**
13310
     * Get the items of exercise type.
13311
     *
13312
     * @return array The items. Otherwise return false
13313
     */
13314
    public function getExercisesItems()
13315
    {
13316
        $exercises = [];
13317
        foreach ($this->items as $item) {
13318
            if ($item->type != 'quiz') {
13319
                continue;
13320
            }
13321
            $exercises[] = $item;
13322
        }
13323
13324
        array_pop($exercises);
13325
13326
        return $exercises;
13327
    }
13328
13329
    /**
13330
     * Get the item of exercise type (evaluation type).
13331
     *
13332
     * @return array The final evaluation. Otherwise return false
13333
     */
13334
    public function getFinalEvaluationItem()
13335
    {
13336
        $exercises = [];
13337
        foreach ($this->items as $item) {
13338
            if ($item->type != 'quiz') {
13339
                continue;
13340
            }
13341
13342
            $exercises[] = $item;
13343
        }
13344
13345
        return array_pop($exercises);
13346
    }
13347
13348
    /**
13349
     * Calculate the total points achieved for the current user in this learning path.
13350
     *
13351
     * @param int $sessionId Optional. The session Id
13352
     *
13353
     * @return int
13354
     */
13355
    public function getCalculateScore($sessionId = 0)
13356
    {
13357
        // Calculate stars chapters evaluation
13358
        $exercisesItems = $this->getExercisesItems();
13359
        $finalEvaluationItem = $this->getFinalEvaluationItem();
13360
        $totalExercisesResult = 0;
13361
        $totalEvaluationResult = 0;
13362
13363
        if ($exercisesItems !== false) {
13364
            foreach ($exercisesItems as $exerciseItem) {
13365
                $exerciseResultInfo = Event::getExerciseResultsByUser(
13366
                    $this->user_id,
13367
                    $exerciseItem->path,
13368
                    $this->course_int_id,
13369
                    $sessionId,
13370
                    $this->lp_id,
13371
                    $exerciseItem->db_id
13372
                );
13373
13374
                $exerciseResultInfo = end($exerciseResultInfo);
13375
13376
                if (!$exerciseResultInfo) {
13377
                    continue;
13378
                }
13379
13380
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
13381
            }
13382
        }
13383
13384
        if (!empty($finalEvaluationItem)) {
13385
            $evaluationResultInfo = Event::getExerciseResultsByUser(
13386
                $this->user_id,
13387
                $finalEvaluationItem->path,
13388
                $this->course_int_id,
13389
                $sessionId,
13390
                $this->lp_id,
13391
                $finalEvaluationItem->db_id
13392
            );
13393
13394
            $evaluationResultInfo = end($evaluationResultInfo);
13395
13396
            if ($evaluationResultInfo) {
13397
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
13398
            }
13399
        }
13400
13401
        return $totalExercisesResult + $totalEvaluationResult;
13402
    }
13403
13404
    /**
13405
     * Check if URL is not allowed to be show in a iframe.
13406
     *
13407
     * @param string $src
13408
     *
13409
     * @return string
13410
     */
13411
    public function fixBlockedLinks($src)
13412
    {
13413
        $urlInfo = parse_url($src);
13414
13415
        $platformProtocol = 'https';
13416
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
13417
            $platformProtocol = 'http';
13418
        }
13419
13420
        $protocolFixApplied = false;
13421
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
13422
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
13423
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
13424
13425
        if ($platformProtocol != $scheme) {
13426
            Session::write('x_frame_source', $src);
13427
            $src = 'blank.php?error=x_frames_options';
13428
            $protocolFixApplied = true;
13429
        }
13430
13431
        if ($protocolFixApplied == false) {
13432
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
13433
                // Check X-Frame-Options
13434
                $ch = curl_init();
13435
                $options = [
13436
                    CURLOPT_URL => $src,
13437
                    CURLOPT_RETURNTRANSFER => true,
13438
                    CURLOPT_HEADER => true,
13439
                    CURLOPT_FOLLOWLOCATION => true,
13440
                    CURLOPT_ENCODING => "",
13441
                    CURLOPT_AUTOREFERER => true,
13442
                    CURLOPT_CONNECTTIMEOUT => 120,
13443
                    CURLOPT_TIMEOUT => 120,
13444
                    CURLOPT_MAXREDIRS => 10,
13445
                ];
13446
13447
                $proxySettings = api_get_configuration_value('proxy_settings');
13448
                if (!empty($proxySettings) &&
13449
                    isset($proxySettings['curl_setopt_array'])
13450
                ) {
13451
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
13452
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
13453
                }
13454
13455
                curl_setopt_array($ch, $options);
13456
                $response = curl_exec($ch);
13457
                $httpCode = curl_getinfo($ch);
13458
                $headers = substr($response, 0, $httpCode['header_size']);
13459
13460
                $error = false;
13461
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
13462
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
13463
                ) {
13464
                    $error = true;
13465
                }
13466
13467
                if ($error) {
13468
                    Session::write('x_frame_source', $src);
13469
                    $src = 'blank.php?error=x_frames_options';
13470
                }
13471
            }
13472
        }
13473
13474
        return $src;
13475
    }
13476
13477
    /**
13478
     * Check if this LP has a created forum in the basis course.
13479
     *
13480
     * @return bool
13481
     */
13482
    public function lpHasForum()
13483
    {
13484
        $forumTable = Database::get_course_table(TABLE_FORUM);
13485
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13486
13487
        $fakeFrom = "
13488
            $forumTable f
13489
            INNER JOIN $itemProperty ip
13490
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
13491
        ";
13492
13493
        $resultData = Database::select(
13494
            'COUNT(f.iid) AS qty',
13495
            $fakeFrom,
13496
            [
13497
                'where' => [
13498
                    'ip.visibility != ? AND ' => 2,
13499
                    'ip.tool = ? AND ' => TOOL_FORUM,
13500
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13501
                    'f.lp_id = ?' => intval($this->lp_id),
13502
                ],
13503
            ],
13504
            'first'
13505
        );
13506
13507
        return $resultData['qty'] > 0;
13508
    }
13509
13510
    /**
13511
     * Get the forum for this learning path.
13512
     *
13513
     * @param int $sessionId
13514
     *
13515
     * @return bool
13516
     */
13517
    public function getForum($sessionId = 0)
13518
    {
13519
        $forumTable = Database::get_course_table(TABLE_FORUM);
13520
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13521
13522
        $fakeFrom = "$forumTable f
13523
            INNER JOIN $itemProperty ip ";
13524
13525
        if ($this->lp_session_id == 0) {
13526
            $fakeFrom .= "
13527
                ON (
13528
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13529
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13530
                    )
13531
                )
13532
            ";
13533
        } else {
13534
            $fakeFrom .= "
13535
                ON (
13536
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13537
                )
13538
            ";
13539
        }
13540
13541
        $resultData = Database::select(
13542
            'f.*',
13543
            $fakeFrom,
13544
            [
13545
                'where' => [
13546
                    'ip.visibility != ? AND ' => 2,
13547
                    'ip.tool = ? AND ' => TOOL_FORUM,
13548
                    'f.session_id = ? AND ' => $sessionId,
13549
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13550
                    'f.lp_id = ?' => intval($this->lp_id),
13551
                ],
13552
            ],
13553
            'first'
13554
        );
13555
13556
        if (empty($resultData)) {
13557
            return false;
13558
        }
13559
13560
        return $resultData;
13561
    }
13562
13563
    /**
13564
     * Create a forum for this learning path.
13565
     *
13566
     * @param int $forumCategoryId
13567
     *
13568
     * @return int The forum ID if was created. Otherwise return false
13569
     */
13570
    public function createForum($forumCategoryId)
13571
    {
13572
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13573
13574
        $forumId = store_forum(
13575
            [
13576
                'lp_id' => $this->lp_id,
13577
                'forum_title' => $this->name,
13578
                'forum_comment' => null,
13579
                'forum_category' => (int) $forumCategoryId,
13580
                'students_can_edit_group' => ['students_can_edit' => 0],
13581
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13582
                'default_view_type_group' => ['default_view_type' => 'flat'],
13583
                'group_forum' => 0,
13584
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13585
            ],
13586
            [],
13587
            true
13588
        );
13589
13590
        return $forumId;
13591
    }
13592
13593
    /**
13594
     * Get the LP Final Item form.
13595
     *
13596
     * @throws Exception
13597
     * @throws HTML_QuickForm_Error
13598
     *
13599
     * @return string
13600
     */
13601
    public function getFinalItemForm()
13602
    {
13603
        $finalItem = $this->getFinalItem();
13604
        $title = '';
13605
13606
        if ($finalItem) {
13607
            $title = $finalItem->get_title();
13608
            $buttonText = get_lang('Save');
13609
            $content = $this->getSavedFinalItem();
13610
        } else {
13611
            $buttonText = get_lang('LPCreateDocument');
13612
            $content = $this->getFinalItemTemplate();
13613
        }
13614
13615
        $courseInfo = api_get_course_info();
13616
        $result = $this->generate_lp_folder($courseInfo);
13617
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13618
        $relative_prefix = '../../';
13619
13620
        $editorConfig = [
13621
            'ToolbarSet' => 'LearningPathDocuments',
13622
            'Width' => '100%',
13623
            'Height' => '500',
13624
            'FullPage' => true,
13625
            'CreateDocumentDir' => $relative_prefix,
13626
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13627
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13628
        ];
13629
13630
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13631
            'type' => 'document',
13632
            'lp_id' => $this->lp_id,
13633
        ]);
13634
13635
        $form = new FormValidator('final_item', 'POST', $url);
13636
        $form->addText('title', get_lang('Title'));
13637
        $form->addButtonSave($buttonText);
13638
13639
        $renderer = $form->defaultRenderer();
13640
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13641
13642
        $form->addHtmlEditor(
13643
            'content_lp_certificate',
13644
            null,
13645
            true,
13646
            false,
13647
            $editorConfig,
13648
            true
13649
        );
13650
        $form->addHtml(
13651
            Display::return_message(
13652
                get_lang('LPEndStepAddTagsToShowCertificateOrSkillAutomatically').'</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13653
                'normal',
13654
                false
13655
            )
13656
        );
13657
        $form->addHidden('action', 'add_final_item');
13658
        $form->addHidden('path', Session::read('pathItem'));
13659
        $form->addHidden('previous', $this->get_last());
13660
        $form->setDefaults(['title' => $title, 'content_lp_certificate' => $content]);
13661
13662
        if ($form->validate()) {
13663
            $values = $form->exportValues();
13664
            $lastItemId = $this->getLastInFirstLevel();
13665
13666
            if (!$finalItem) {
13667
                $documentId = $this->create_document(
13668
                    $this->course_info,
13669
                    $values['content_lp_certificate'],
13670
                    $values['title']
13671
                );
13672
                $this->add_item(
13673
                    0,
13674
                    $lastItemId,
13675
                    'final_item',
13676
                    $documentId,
13677
                    $values['title'],
13678
                    ''
13679
                );
13680
13681
                Display::addFlash(
13682
                    Display::return_message(get_lang('Added'))
13683
                );
13684
            } else {
13685
                $this->edit_document($this->course_info);
13686
            }
13687
        }
13688
13689
        return $form->returnForm();
13690
    }
13691
13692
    /**
13693
     * Check if the current lp item is first, both, last or none from lp list.
13694
     *
13695
     * @param int $currentItemId
13696
     *
13697
     * @return string
13698
     */
13699
    public function isFirstOrLastItem($currentItemId)
13700
    {
13701
        $lpItemId = [];
13702
        $typeListNotToVerify = self::getChapterTypes();
13703
13704
        // Using get_toc() function instead $this->items because returns the correct order of the items
13705
        foreach ($this->get_toc() as $item) {
13706
            if (!in_array($item['type'], $typeListNotToVerify)) {
13707
                $lpItemId[] = $item['id'];
13708
            }
13709
        }
13710
13711
        $lastLpItemIndex = count($lpItemId) - 1;
13712
        $position = array_search($currentItemId, $lpItemId);
13713
13714
        switch ($position) {
13715
            case 0:
13716
                if (!$lastLpItemIndex) {
13717
                    $answer = 'both';
13718
                    break;
13719
                }
13720
13721
                $answer = 'first';
13722
                break;
13723
            case $lastLpItemIndex:
13724
                $answer = 'last';
13725
                break;
13726
            default:
13727
                $answer = 'none';
13728
        }
13729
13730
        return $answer;
13731
    }
13732
13733
    /**
13734
     * Get whether this is a learning path with the accumulated SCORM time or not.
13735
     *
13736
     * @return int
13737
     */
13738
    public function getAccumulateScormTime()
13739
    {
13740
        return $this->accumulateScormTime;
13741
    }
13742
13743
    /**
13744
     * Set whether this is a learning path with the accumulated SCORM time or not.
13745
     *
13746
     * @param int $value (0 = false, 1 = true)
13747
     *
13748
     * @return bool Always returns true
13749
     */
13750
    public function setAccumulateScormTime($value)
13751
    {
13752
        $this->accumulateScormTime = (int) $value;
13753
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13754
        $lp_id = $this->get_id();
13755
        $sql = "UPDATE $lp_table
13756
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13757
                WHERE iid = $lp_id";
13758
        Database::query($sql);
13759
13760
        return true;
13761
    }
13762
13763
    /**
13764
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13765
     * the new learning path tool.
13766
     *
13767
     * The function is a big switch on tool type.
13768
     * In each case, we query the corresponding table for information and build the link
13769
     * with that information.
13770
     *
13771
     * @author Yannick Warnier <[email protected]> - rebranding based on
13772
     * previous work (display_addedresource_link_in_learnpath())
13773
     *
13774
     * @param int $course_id      Course code
13775
     * @param int $learningPathId The learning path ID (in lp table)
13776
     * @param int $id_in_path     the unique index in the items table
13777
     * @param int $lpViewId
13778
     * @param int $lpSessionId
13779
     *
13780
     * @return string
13781
     */
13782
    public static function rl_get_resource_link_for_learnpath(
13783
        $course_id,
13784
        $learningPathId,
13785
        $id_in_path,
13786
        $lpViewId,
13787
        $lpSessionId = 0
13788
    ) {
13789
        $session_id = api_get_session_id();
13790
        $course_info = api_get_course_info_by_id($course_id);
13791
13792
        $learningPathId = (int) $learningPathId;
13793
        $id_in_path = (int) $id_in_path;
13794
        $lpViewId = (int) $lpViewId;
13795
13796
        $em = Database::getManager();
13797
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13798
13799
        /** @var CLpItem $rowItem */
13800
        $rowItem = $lpItemRepo->findOneBy([
13801
            'cId' => $course_id,
13802
            'lpId' => $learningPathId,
13803
            'iid' => $id_in_path,
13804
        ]);
13805
13806
        if (!$rowItem) {
13807
            // Try one more time with "id"
13808
            /** @var CLpItem $rowItem */
13809
            $rowItem = $lpItemRepo->findOneBy([
13810
                'cId' => $course_id,
13811
                'lpId' => $learningPathId,
13812
                'id' => $id_in_path,
13813
            ]);
13814
13815
            if (!$rowItem) {
13816
                return -1;
13817
            }
13818
        }
13819
13820
        $course_code = $course_info['code'];
13821
        $type = $rowItem->getItemType();
13822
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13823
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13824
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13825
        $link = '';
13826
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13827
        // It adds lti parameter
13828
        if (isset($_REQUEST['lti_launch_id'])) {
13829
            $extraParams .= '&lti_launch_id='.Security::remove_XSS($_REQUEST['lti_launch_id']);
13830
        }
13831
13832
        switch ($type) {
13833
            case 'dir':
13834
                return $main_dir_path.'lp/blank.php';
13835
            case TOOL_CALENDAR_EVENT:
13836
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13837
            case TOOL_ANNOUNCEMENT:
13838
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13839
            case TOOL_LINK:
13840
                $linkInfo = Link::getLinkInfo($id);
13841
                if ($linkInfo) {
13842
                    $itemPropertyInfo = api_get_item_property_info($course_id, TOOL_LINK, $id, $session_id);
13843
                    if ($itemPropertyInfo && 0 === (int) $itemPropertyInfo['visibility']) {
13844
                        return '';
13845
                    }
13846
                    if (isset($linkInfo['url'])) {
13847
                        return $linkInfo['url'];
13848
                    }
13849
                }
13850
13851
                return '';
13852
            case TOOL_QUIZ:
13853
                if (empty($id)) {
13854
                    return '';
13855
                }
13856
13857
                // Get the lp_item_view with the highest view_count.
13858
                $learnpathItemViewResult = $em
13859
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13860
                    ->findBy(
13861
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13862
                        ['viewCount' => 'DESC'],
13863
                        1
13864
                    );
13865
                /** @var CLpItemView $learnpathItemViewData */
13866
                $learnpathItemViewData = current($learnpathItemViewResult);
13867
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13868
13869
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13870
                    .http_build_query([
13871
                        'lp_init' => 1,
13872
                        'learnpath_item_view_id' => (int) $learnpathItemViewId,
13873
                        'learnpath_id' => (int) $learningPathId,
13874
                        'learnpath_item_id' => (int) $id_in_path,
13875
                        'exerciseId' => (int) $id,
13876
                    ]);
13877
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13878
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13879
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13880
                $myrow = Database::fetch_array($result);
13881
                $path = $myrow['path'];
13882
13883
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13884
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13885
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13886
            case TOOL_FORUM:
13887
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13888
            case TOOL_THREAD:
13889
                // forum post
13890
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13891
                if (empty($id)) {
13892
                    return '';
13893
                }
13894
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13895
                $result = Database::query($sql);
13896
                $myrow = Database::fetch_array($result);
13897
13898
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13899
                    .$extraParams;
13900
            case TOOL_POST:
13901
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13902
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13903
                $myrow = Database::fetch_array($result);
13904
13905
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13906
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13907
            case TOOL_READOUT_TEXT:
13908
                return api_get_path(WEB_CODE_PATH).
13909
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13910
            case TOOL_DOCUMENT:
13911
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13912
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13913
13914
                if (empty($document)) {
13915
                    // Try with normal id
13916
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13917
13918
                    if (empty($document)) {
13919
                        return '';
13920
                    }
13921
                }
13922
13923
                $documentPathInfo = pathinfo($document->getPath());
13924
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v', 'webm', 'wav'];
13925
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13926
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13927
13928
                $openmethod = 2;
13929
                $officedoc = false;
13930
                Session::write('openmethod', $openmethod);
13931
                Session::write('officedoc', $officedoc);
13932
13933
                // Same validation as in document/download.php
13934
                $isVisible = DocumentManager::is_visible(
13935
                    $document->getPath(),
13936
                    api_get_course_info(),
13937
                    api_get_session_id()
13938
                );
13939
13940
                if (!api_is_allowed_to_edit() && !$isVisible) {
13941
                    return '';
13942
                }
13943
13944
                if ($showDirectUrl) {
13945
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13946
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13947
                        if (Link::isPdfLink($file)) {
13948
                            return api_get_path(WEB_LIBRARY_PATH).
13949
                                'javascript/ViewerJS/index.html?zoom=page-width#'.$file;
13950
                        }
13951
                    }
13952
13953
                    return $file;
13954
                }
13955
13956
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13957
            case TOOL_LP_FINAL_ITEM:
13958
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13959
                    .$extraParams;
13960
            case 'assignments':
13961
                return $main_dir_path.'work/work.php?'.$extraParams;
13962
            case TOOL_DROPBOX:
13963
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13964
            case 'introduction_text': //DEPRECATED
13965
                return '';
13966
            case TOOL_COURSE_DESCRIPTION:
13967
                return $main_dir_path.'course_description?'.$extraParams;
13968
            case TOOL_GROUP:
13969
                return $main_dir_path.'group/group.php?'.$extraParams;
13970
            case TOOL_USER:
13971
                return $main_dir_path.'user/user.php?'.$extraParams;
13972
            case TOOL_STUDENTPUBLICATION:
13973
                if (!empty($rowItem->getPath())) {
13974
                    $workId = $rowItem->getPath();
13975
                    if (empty($lpSessionId) && !empty($session_id)) {
13976
                        // Check if a student publication with the same name exists in this session see BT#17700
13977
                        $title = Database::escape_string($rowItem->getTitle());
13978
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13979
                        $sql = "SELECT * FROM $table
13980
                                WHERE
13981
                                    active = 1 AND
13982
                                    parent_id = 0 AND
13983
                                    c_id = $course_id AND
13984
                                    session_id = $session_id AND
13985
                                    title = '$title'
13986
                                LIMIT 1";
13987
                        $result = Database::query($sql);
13988
                        if (Database::num_rows($result)) {
13989
                            $work = Database::fetch_array($result, 'ASSOC');
13990
                            if ($work) {
13991
                                $workId = $work['iid'];
13992
                            }
13993
                        }
13994
                    }
13995
13996
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13997
                }
13998
13999
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
14000
            case TOOL_XAPI:
14001
                $toolLaunch = $em->find(ToolLaunch::class, $id);
14002
14003
                if (empty($toolLaunch)) {
14004
                    break;
14005
                }
14006
14007
                return api_get_path(WEB_PLUGIN_PATH).'xapi/'
14008
                    .('cmi5' === $toolLaunch->getActivityType() ? 'cmi5/view.php' : 'tincan/view.php')
14009
                    ."?id=$id&$extraParams";
14010
            case TOOL_H5P:
14011
                if ('true' === api_get_plugin_setting('h5pimport', 'tool_enable')) {
14012
                    $toolLaunch = $em->find(H5pImport::class, $id);
14013
14014
                    if (empty($toolLaunch)) {
14015
                        break;
14016
                    }
14017
14018
                    return api_get_path(WEB_PLUGIN_PATH).'h5pimport/view.php'."?id=$id&$extraParams";
14019
                }
14020
                break;
14021
            case TOOL_SURVEY:
14022
                $table = Database::get_course_table(TABLE_SURVEY);
14023
                $sql = "SELECT code FROM $table WHERE c_id = $course_id AND survey_id =".(int) $id;
14024
                $result = Database::query($sql);
14025
                $surveyCode = Database::result($result, 0, 0);
14026
                $autoSurveyLink = SurveyUtil::generateFillSurveyLink(
14027
                    'auto',
14028
                    $course_info,
14029
                    api_get_session_id(),
14030
                    $surveyCode
14031
                );
14032
                $lpParams = [
14033
                    'lp_id' => $learningPathId,
14034
                    'lp_item_id' => $id_in_path,
14035
                    'origin' => 'learnpath',
14036
                ];
14037
14038
                return $autoSurveyLink.'&'.http_build_query($lpParams);
14039
        }
14040
14041
        return $link;
14042
    }
14043
14044
    /**
14045
     * Gets the name of a resource (generally used in learnpath when no name is provided).
14046
     *
14047
     * @author Yannick Warnier <[email protected]>
14048
     *
14049
     * @param string $course_code    Course code
14050
     * @param int    $learningPathId
14051
     * @param int    $id_in_path     The resource ID
14052
     *
14053
     * @return string
14054
     */
14055
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
14056
    {
14057
        $_course = api_get_course_info($course_code);
14058
        if (empty($_course)) {
14059
            return '';
14060
        }
14061
        $course_id = $_course['real_id'];
14062
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14063
        $learningPathId = (int) $learningPathId;
14064
        $id_in_path = (int) $id_in_path;
14065
14066
        $sql = "SELECT item_type, title, ref
14067
                FROM $tbl_lp_item
14068
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
14069
        $res_item = Database::query($sql);
14070
14071
        if (Database::num_rows($res_item) < 1) {
14072
            return '';
14073
        }
14074
        $row_item = Database::fetch_array($res_item);
14075
        $type = strtolower($row_item['item_type']);
14076
        $id = $row_item['ref'];
14077
        $output = '';
14078
14079
        switch ($type) {
14080
            case TOOL_CALENDAR_EVENT:
14081
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
14082
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
14083
                $myrow = Database::fetch_array($result);
14084
                $output = $myrow['title'];
14085
                break;
14086
            case TOOL_ANNOUNCEMENT:
14087
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
14088
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
14089
                $myrow = Database::fetch_array($result);
14090
                $output = $myrow['title'];
14091
                break;
14092
            case TOOL_LINK:
14093
                // Doesn't take $target into account.
14094
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
14095
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
14096
                $myrow = Database::fetch_array($result);
14097
                $output = $myrow['title'];
14098
                break;
14099
            case TOOL_QUIZ:
14100
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
14101
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE iid = $id");
14102
                $myrow = Database::fetch_array($result);
14103
                $output = $myrow['title'];
14104
                break;
14105
            case TOOL_FORUM:
14106
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
14107
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
14108
                $myrow = Database::fetch_array($result);
14109
                $output = $myrow['forum_name'];
14110
                break;
14111
            case TOOL_THREAD:
14112
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
14113
                // Grabbing the title of the post.
14114
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
14115
                $result_title = Database::query($sql_title);
14116
                $myrow_title = Database::fetch_array($result_title);
14117
                $output = $myrow_title['post_title'];
14118
                break;
14119
            case TOOL_POST:
14120
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
14121
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
14122
                $result = Database::query($sql);
14123
                $post = Database::fetch_array($result);
14124
                $output = $post['post_title'];
14125
                break;
14126
            case 'dir':
14127
            case TOOL_DOCUMENT:
14128
                $title = $row_item['title'];
14129
                $output = '-';
14130
                if (!empty($title)) {
14131
                    $output = $title;
14132
                }
14133
                break;
14134
            case 'hotpotatoes':
14135
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
14136
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
14137
                $myrow = Database::fetch_array($result);
14138
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
14139
                $last = count($pathname) - 1; // Making a correct name for the link.
14140
                $filename = $pathname[$last]; // Making a correct name for the link.
14141
                $myrow['path'] = rawurlencode($myrow['path']);
14142
                $output = $filename;
14143
                break;
14144
        }
14145
14146
        return stripslashes($output);
14147
    }
14148
14149
    /**
14150
     * Get the parent names for the current item.
14151
     *
14152
     * @param int $newItemId Optional. The item ID
14153
     *
14154
     * @return array
14155
     */
14156
    public function getCurrentItemParentNames($newItemId = 0)
14157
    {
14158
        $newItemId = $newItemId ?: $this->get_current_item_id();
14159
        $return = [];
14160
        $item = $this->getItem($newItemId);
14161
        $parent = $this->getItem($item->get_parent());
14162
14163
        while ($parent) {
14164
            $return[] = $parent->get_title();
14165
            $parent = $this->getItem($parent->get_parent());
14166
        }
14167
14168
        return array_reverse($return);
14169
    }
14170
14171
    /**
14172
     * Reads and process "lp_subscription_settings" setting.
14173
     *
14174
     * @return array
14175
     */
14176
    public static function getSubscriptionSettings()
14177
    {
14178
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
14179
        if (empty($subscriptionSettings)) {
14180
            // By default allow both settings
14181
            $subscriptionSettings = [
14182
                'allow_add_users_to_lp' => true,
14183
                'allow_add_users_to_lp_category' => true,
14184
            ];
14185
        } else {
14186
            $subscriptionSettings = $subscriptionSettings['options'];
14187
        }
14188
14189
        return $subscriptionSettings;
14190
    }
14191
14192
    /**
14193
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
14194
     */
14195
    public function exportToCourseBuildFormat()
14196
    {
14197
        if (!api_is_allowed_to_edit()) {
14198
            return false;
14199
        }
14200
14201
        $courseBuilder = new CourseBuilder();
14202
        $itemList = [];
14203
        /** @var learnpathItem $item */
14204
        foreach ($this->items as $item) {
14205
            $itemList[$item->get_type()][] = $item->get_path();
14206
        }
14207
14208
        if (empty($itemList)) {
14209
            return false;
14210
        }
14211
14212
        if (isset($itemList['document'])) {
14213
            // Get parents
14214
            foreach ($itemList['document'] as $documentId) {
14215
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
14216
                if (!empty($documentInfo['parents'])) {
14217
                    foreach ($documentInfo['parents'] as $parentInfo) {
14218
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
14219
                            continue;
14220
                        }
14221
                        $itemList['document'][] = $parentInfo['iid'];
14222
                    }
14223
                }
14224
            }
14225
14226
            $courseInfo = api_get_course_info();
14227
            foreach ($itemList['document'] as $documentId) {
14228
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
14229
                $items = DocumentManager::get_resources_from_source_html(
14230
                    $documentInfo['absolute_path'],
14231
                    true,
14232
                    TOOL_DOCUMENT
14233
                );
14234
14235
                if (!empty($items)) {
14236
                    foreach ($items as $item) {
14237
                        // Get information about source url
14238
                        $url = $item[0]; // url
14239
                        $scope = $item[1]; // scope (local, remote)
14240
                        $type = $item[2]; // type (rel, abs, url)
14241
14242
                        $origParseUrl = parse_url($url);
14243
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
14244
14245
                        if ($scope == 'local') {
14246
                            if ($type == 'abs' || $type == 'rel') {
14247
                                $documentFile = strstr($realOrigPath, 'document');
14248
                                if (strpos($realOrigPath, $documentFile) !== false) {
14249
                                    $documentFile = str_replace('document', '', $documentFile);
14250
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
14251
                                    // Document found! Add it to the list
14252
                                    if ($itemDocumentId) {
14253
                                        $itemList['document'][] = $itemDocumentId;
14254
                                    }
14255
                                }
14256
                            }
14257
                        }
14258
                    }
14259
                }
14260
            }
14261
14262
            $courseBuilder->build_documents(
14263
                api_get_session_id(),
14264
                $this->get_course_int_id(),
14265
                true,
14266
                $itemList['document']
14267
            );
14268
        }
14269
14270
        if (isset($itemList['quiz'])) {
14271
            $courseBuilder->build_quizzes(
14272
                api_get_session_id(),
14273
                $this->get_course_int_id(),
14274
                true,
14275
                $itemList['quiz']
14276
            );
14277
        }
14278
14279
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
14280
14281
        /*if (!empty($itemList['thread'])) {
14282
            $postList = [];
14283
            foreach ($itemList['thread'] as $postId) {
14284
                $post = get_post_information($postId);
14285
                if ($post) {
14286
                    if (!isset($itemList['forum'])) {
14287
                        $itemList['forum'] = [];
14288
                    }
14289
                    $itemList['forum'][] = $post['forum_id'];
14290
                    $postList[] = $postId;
14291
                }
14292
            }
14293
14294
            if (!empty($postList)) {
14295
                $courseBuilder->build_forum_posts(
14296
                    $this->get_course_int_id(),
14297
                    null,
14298
                    null,
14299
                    $postList
14300
                );
14301
            }
14302
        }*/
14303
14304
        if (!empty($itemList['thread'])) {
14305
            $threadList = [];
14306
            $em = Database::getManager();
14307
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
14308
            foreach ($itemList['thread'] as $threadId) {
14309
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
14310
                $thread = $repo->find($threadId);
14311
                if ($thread) {
14312
                    $itemList['forum'][] = $thread->getForumId();
14313
                    $threadList[] = $thread->getIid();
14314
                }
14315
            }
14316
14317
            if (!empty($threadList)) {
14318
                $courseBuilder->build_forum_topics(
14319
                    api_get_session_id(),
14320
                    $this->get_course_int_id(),
14321
                    null,
14322
                    $threadList
14323
                );
14324
            }
14325
        }
14326
14327
        $forumCategoryList = [];
14328
        if (isset($itemList['forum'])) {
14329
            foreach ($itemList['forum'] as $forumId) {
14330
                $forumInfo = get_forums($forumId);
14331
                $forumCategoryList[] = $forumInfo['forum_category'];
14332
            }
14333
        }
14334
14335
        if (!empty($forumCategoryList)) {
14336
            $courseBuilder->build_forum_category(
14337
                api_get_session_id(),
14338
                $this->get_course_int_id(),
14339
                true,
14340
                $forumCategoryList
14341
            );
14342
        }
14343
14344
        if (!empty($itemList['forum'])) {
14345
            $courseBuilder->build_forums(
14346
                api_get_session_id(),
14347
                $this->get_course_int_id(),
14348
                true,
14349
                $itemList['forum']
14350
            );
14351
        }
14352
14353
        if (isset($itemList['link'])) {
14354
            $courseBuilder->build_links(
14355
                api_get_session_id(),
14356
                $this->get_course_int_id(),
14357
                true,
14358
                $itemList['link']
14359
            );
14360
        }
14361
14362
        if (!empty($itemList['student_publication'])) {
14363
            $courseBuilder->build_works(
14364
                api_get_session_id(),
14365
                $this->get_course_int_id(),
14366
                true,
14367
                $itemList['student_publication']
14368
            );
14369
        }
14370
14371
        $courseBuilder->build_learnpaths(
14372
            api_get_session_id(),
14373
            $this->get_course_int_id(),
14374
            true,
14375
            [$this->get_id()],
14376
            false
14377
        );
14378
14379
        $courseBuilder->restoreDocumentsFromList();
14380
14381
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
14382
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
14383
        $result = DocumentManager::file_send_for_download(
14384
            $zipPath,
14385
            true,
14386
            $this->get_name().'.zip'
14387
        );
14388
14389
        if ($result) {
14390
            api_not_allowed();
14391
        }
14392
14393
        return true;
14394
    }
14395
14396
    /**
14397
     * Get whether this is a learning path with the accumulated work time or not.
14398
     *
14399
     * @return int
14400
     */
14401
    public function getAccumulateWorkTime()
14402
    {
14403
        return (int) $this->accumulateWorkTime;
14404
    }
14405
14406
    /**
14407
     * Get whether this is a learning path with the accumulated work time or not.
14408
     *
14409
     * @return int
14410
     */
14411
    public function getAccumulateWorkTimeTotalCourse()
14412
    {
14413
        $table = Database::get_course_table(TABLE_LP_MAIN);
14414
        $sql = "SELECT SUM(accumulate_work_time) AS total
14415
                FROM $table
14416
                WHERE c_id = ".$this->course_int_id;
14417
        $result = Database::query($sql);
14418
        $row = Database::fetch_array($result);
14419
14420
        return (int) $row['total'];
14421
    }
14422
14423
    /**
14424
     * Set whether this is a learning path with the accumulated work time or not.
14425
     *
14426
     * @param int $value (0 = false, 1 = true)
14427
     *
14428
     * @return bool
14429
     */
14430
    public function setAccumulateWorkTime($value)
14431
    {
14432
        if (!api_get_configuration_value('lp_minimum_time')) {
14433
            return false;
14434
        }
14435
14436
        $this->accumulateWorkTime = (int) $value;
14437
        $table = Database::get_course_table(TABLE_LP_MAIN);
14438
        $lp_id = $this->get_id();
14439
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
14440
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
14441
        Database::query($sql);
14442
14443
        return true;
14444
    }
14445
14446
    /**
14447
     * @param int $lpId
14448
     * @param int $courseId
14449
     *
14450
     * @return mixed
14451
     */
14452
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
14453
    {
14454
        $lpId = (int) $lpId;
14455
        $courseId = (int) $courseId;
14456
14457
        $table = Database::get_course_table(TABLE_LP_MAIN);
14458
        $sql = "SELECT accumulate_work_time
14459
                FROM $table
14460
                WHERE c_id = $courseId AND id = $lpId";
14461
        $result = Database::query($sql);
14462
        $row = Database::fetch_array($result);
14463
14464
        return $row['accumulate_work_time'];
14465
    }
14466
14467
    /**
14468
     * @param int $courseId
14469
     *
14470
     * @return int
14471
     */
14472
    public static function getAccumulateWorkTimeTotal($courseId)
14473
    {
14474
        $table = Database::get_course_table(TABLE_LP_MAIN);
14475
        $courseId = (int) $courseId;
14476
        $sql = "SELECT SUM(accumulate_work_time) AS total
14477
                FROM $table
14478
                WHERE c_id = $courseId";
14479
        $result = Database::query($sql);
14480
        $row = Database::fetch_array($result);
14481
14482
        return (int) $row['total'];
14483
    }
14484
14485
    /**
14486
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
14487
     * and put the images in.
14488
     *
14489
     * @return array
14490
     */
14491
    public static function getIconSelect()
14492
    {
14493
        $theme = api_get_visual_theme();
14494
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
14495
        $icons = ['' => get_lang('SelectAnOption')];
14496
14497
        if (is_dir($path)) {
14498
            $finder = new Finder();
14499
            $finder->files()->in($path);
14500
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
14501
            /** @var SplFileInfo $file */
14502
            foreach ($finder as $file) {
14503
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
14504
                    $icons[$file->getFilename()] = $file->getFilename();
14505
                }
14506
            }
14507
        }
14508
14509
        return $icons;
14510
    }
14511
14512
    /**
14513
     * Get the learnpaths availables for next lp.
14514
     *
14515
     * @param int $courseId
14516
     *
14517
     * @return array
14518
     */
14519
    public static function getNextLpsAvailable($courseId, $lpId)
14520
    {
14521
        $table = Database::get_course_table(TABLE_LP_MAIN);
14522
14523
        $sqlNextLp = "SELECT next_lp_id FROM $table WHERE c_id = $courseId AND next_lp_id > 0";
14524
        $sql = "SELECT iid, name
14525
                FROM $table
14526
            WHERE c_id = $courseId AND
14527
                  iid NOT IN($sqlNextLp)";
14528
        $rs = Database::query($sql);
14529
        $lpsAvailable = [0 => get_lang('None')];
14530
        if (Database::num_rows($rs) > 0) {
14531
            while ($row = Database::fetch_array($rs)) {
14532
                if ($row['iid'] == $lpId) {
14533
                    continue;
14534
                }
14535
                $lpsAvailable[$row['iid']] = $row['name'];
14536
            }
14537
        }
14538
14539
        return $lpsAvailable;
14540
    }
14541
14542
    /**
14543
     * Get The next lp id.
14544
     *
14545
     * @param $lpId
14546
     * @param $courseId
14547
     *
14548
     * @return int
14549
     */
14550
    public static function getFlowNextLpId($lpId, $courseId)
14551
    {
14552
        $table = Database::get_course_table(TABLE_LP_MAIN);
14553
14554
        $sql = "SELECT next_lp_id
14555
                FROM $table
14556
            WHERE c_id = $courseId AND iid = $lpId";
14557
        $rs = Database::query($sql);
14558
        $nextLpId = Database::result($rs, 0, 0);
14559
14560
        return $nextLpId;
14561
    }
14562
14563
    /**
14564
     * Get The previous lp id.
14565
     *
14566
     * @param $nextLpId
14567
     * @param $courseId
14568
     *
14569
     * @return int
14570
     */
14571
    public static function getFlowPrevLpId($nextLpId, $courseId)
14572
    {
14573
        $table = Database::get_course_table(TABLE_LP_MAIN);
14574
14575
        $sql = "SELECT iid
14576
                FROM $table
14577
            WHERE c_id = $courseId AND next_lp_id = $nextLpId";
14578
        $rs = Database::query($sql);
14579
        $prevLpId = Database::result($rs, 0, 0);
14580
14581
        return $prevLpId;
14582
    }
14583
14584
    /**
14585
     * Save the next lp id.
14586
     *
14587
     * @param $lpId
14588
     * @param $nextLpId
14589
     */
14590
    public static function saveTheNextLp($lpId, $nextLpId)
14591
    {
14592
        $nextLpId = (int) $nextLpId;
14593
        $table = Database::get_course_table(TABLE_LP_MAIN);
14594
        Database::query("UPDATE $table SET next_lp_id = $nextLpId WHERE iid = $lpId");
14595
    }
14596
14597
    /**
14598
     * @param int $lpId
14599
     *
14600
     * @return string
14601
     */
14602
    public static function getSelectedIcon($lpId)
14603
    {
14604
        $extraFieldValue = new ExtraFieldValue('lp');
14605
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
14606
        $icon = '';
14607
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
14608
            $icon = $lpIcon['value'];
14609
        }
14610
14611
        return $icon;
14612
    }
14613
14614
    /**
14615
     * @param int $lpId
14616
     *
14617
     * @return string
14618
     */
14619
    public static function getSelectedIconHtml($lpId)
14620
    {
14621
        $icon = self::getSelectedIcon($lpId);
14622
14623
        if (empty($icon)) {
14624
            return '';
14625
        }
14626
14627
        $theme = api_get_visual_theme();
14628
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
14629
14630
        return Display::img($path);
14631
    }
14632
14633
    /**
14634
     * @param string $value
14635
     *
14636
     * @return string
14637
     */
14638
    public function cleanItemTitle($value)
14639
    {
14640
        $value = Security::remove_XSS(strip_tags($value));
14641
14642
        return $value;
14643
    }
14644
14645
    public function setItemTitle(FormValidator $form)
14646
    {
14647
        if (api_get_configuration_value('save_titles_as_html')) {
14648
            $form->addHtmlEditor(
14649
                'title',
14650
                get_lang('Title'),
14651
                true,
14652
                false,
14653
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
14654
            );
14655
        } else {
14656
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
14657
            $form->applyFilter('title', 'trim');
14658
            $form->applyFilter('title', 'html_filter');
14659
        }
14660
    }
14661
14662
    /**
14663
     * @return array
14664
     */
14665
    public function getItemsForForm($addParentCondition = false)
14666
    {
14667
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14668
        $course_id = api_get_course_int_id();
14669
14670
        $sql = "SELECT * FROM $tbl_lp_item
14671
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
14672
14673
        if ($addParentCondition) {
14674
            $sql .= ' AND parent_item_id = 0 ';
14675
        }
14676
        $sql .= ' ORDER BY display_order ASC';
14677
14678
        $result = Database::query($sql);
14679
        $arrLP = [];
14680
        while ($row = Database::fetch_array($result)) {
14681
            $arrLP[] = [
14682
                'iid' => $row['iid'],
14683
                'id' => $row['iid'],
14684
                'item_type' => $row['item_type'],
14685
                'title' => $this->cleanItemTitle($row['title']),
14686
                'title_raw' => $row['title'],
14687
                'path' => $row['path'],
14688
                'description' => Security::remove_XSS($row['description']),
14689
                'parent_item_id' => $row['parent_item_id'],
14690
                'previous_item_id' => $row['previous_item_id'],
14691
                'next_item_id' => $row['next_item_id'],
14692
                'display_order' => $row['display_order'],
14693
                'max_score' => $row['max_score'],
14694
                'min_score' => $row['min_score'],
14695
                'mastery_score' => $row['mastery_score'],
14696
                'prerequisite' => $row['prerequisite'],
14697
                'max_time_allowed' => $row['max_time_allowed'],
14698
                'prerequisite_min_score' => $row['prerequisite_min_score'],
14699
                'prerequisite_max_score' => $row['prerequisite_max_score'],
14700
            ];
14701
        }
14702
14703
        return $arrLP;
14704
    }
14705
14706
    /**
14707
     * Gets whether this SCORM learning path has been marked to use the score
14708
     * as progress. Takes into account whether the learnpath matches (SCORM
14709
     * content + less than 2 items).
14710
     *
14711
     * @return bool True if the score should be used as progress, false otherwise
14712
     */
14713
    public function getUseScoreAsProgress()
14714
    {
14715
        // If not a SCORM, we don't care about the setting
14716
        if ($this->get_type() != 2) {
14717
            return false;
14718
        }
14719
        // If more than one step in the SCORM, we don't care about the setting
14720
        if ($this->get_total_items_count() > 1) {
14721
            return false;
14722
        }
14723
        $extraFieldValue = new ExtraFieldValue('lp');
14724
        $doUseScore = false;
14725
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
14726
        if (!empty($useScore) && isset($useScore['value'])) {
14727
            $doUseScore = $useScore['value'];
14728
        }
14729
14730
        return $doUseScore;
14731
    }
14732
14733
    /**
14734
     * Get the user identifier (user_id or username
14735
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
14736
     *
14737
     * @return string User ID or username, depending on configuration setting
14738
     */
14739
    public static function getUserIdentifierForExternalServices()
14740
    {
14741
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
14742
            return api_get_user_info(api_get_user_id())['username'];
14743
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
14744
            $extraFieldValue = new ExtraFieldValue('user');
14745
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(api_get_user_id(), api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id'));
14746
14747
            return $extrafield['value'];
14748
        } else {
14749
            return api_get_user_id();
14750
        }
14751
    }
14752
14753
    /**
14754
     * Save the new order for learning path items.
14755
     *
14756
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
14757
     *
14758
     * @param array $orderList A associative array with item ID as key and parent ID as value.
14759
     * @param int   $courseId
14760
     */
14761
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
14762
    {
14763
        $courseId = $courseId ?: api_get_course_int_id();
14764
        $itemList = new LpItemOrderList();
14765
14766
        foreach ($orderList as $id => $parentId) {
14767
            $item = new LpOrderItem($id, $parentId);
14768
            $itemList->add($item);
14769
        }
14770
14771
        $parents = $itemList->getListOfParents();
14772
14773
        foreach ($parents as $parentId) {
14774
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
14775
            $previous_item_id = 0;
14776
            for ($i = 0; $i < count($sameParentLpItemList->list); $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...
14777
                $item_id = $sameParentLpItemList->list[$i]->id;
14778
                // display_order
14779
                $display_order = $i + 1;
14780
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14781
                // previous_item_id
14782
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14783
                $previous_item_id = $item_id;
14784
                // next_item_id
14785
                $next_item_id = 0;
14786
                if ($i < count($sameParentLpItemList->list) - 1) {
14787
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14788
                }
14789
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14790
            }
14791
        }
14792
14793
        $table = Database::get_course_table(TABLE_LP_ITEM);
14794
14795
        foreach ($itemList->list as $item) {
14796
            $params = [];
14797
            $params['display_order'] = $item->display_order;
14798
            $params['previous_item_id'] = $item->previous_item_id;
14799
            $params['next_item_id'] = $item->next_item_id;
14800
            $params['parent_item_id'] = $item->parent_item_id;
14801
14802
            Database::update(
14803
                $table,
14804
                $params,
14805
                [
14806
                    'iid = ? AND c_id = ? ' => [
14807
                        (int) $item->id,
14808
                        (int) $courseId,
14809
                    ],
14810
                ]
14811
            );
14812
        }
14813
    }
14814
14815
    public static function findLastView(
14816
        int $lpId,
14817
        int $studentId,
14818
        int $courseId,
14819
        int $sessionId = 0,
14820
        bool $createIfNotExists = false
14821
    ): array {
14822
        $tblLpView = Database::get_course_table(TABLE_LP_VIEW);
14823
14824
        $sessionCondition = api_get_session_condition($sessionId);
14825
14826
        $sql = "SELECT iid FROM $tblLpView
14827
            WHERE c_id = $courseId AND lp_id = $lpId AND user_id = $studentId $sessionCondition
14828
            ORDER BY view_count DESC";
14829
        $result = Database::query($sql);
14830
14831
        $lpView = Database::fetch_assoc($result);
14832
14833
        if ($createIfNotExists && empty($lpView)) {
14834
            $lpViewId = Database::insert(
14835
                $tblLpView,
14836
                [
14837
                    'c_id' => $courseId,
14838
                    'lp_id' => $lpId,
14839
                    'user_id' => $studentId,
14840
                    'view_count' => 1,
14841
                    'session_id' => $sessionId,
14842
                ]
14843
            );
14844
            Database::update($tblLpView, ['id' => $lpViewId], ['iid = ?' => $lpViewId]);
14845
14846
            return ['iid' => $lpViewId];
14847
        }
14848
14849
        return empty($lpView) ? [] : $lpView;
14850
    }
14851
14852
    /**
14853
     * Check and obtain the lp final item if exist.
14854
     *
14855
     * @return learnpathItem
14856
     */
14857
    public function getFinalItem()
14858
    {
14859
        if (empty($this->items)) {
14860
            return null;
14861
        }
14862
14863
        foreach ($this->items as $item) {
14864
            if ($item->type !== 'final_item') {
14865
                continue;
14866
            }
14867
14868
            return $item;
14869
        }
14870
    }
14871
14872
    /**
14873
     * Get the depth level of LP item.
14874
     *
14875
     * @param array $items
14876
     * @param int   $currentItemId
14877
     *
14878
     * @return int
14879
     */
14880
    private static function get_level_for_item($items, $currentItemId)
14881
    {
14882
        $parentItemId = 0;
14883
        if (isset($items[$currentItemId])) {
14884
            $parentItemId = $items[$currentItemId]->parent;
14885
        }
14886
14887
        if ($parentItemId == 0) {
14888
            return 0;
14889
        } else {
14890
            return self::get_level_for_item($items, $parentItemId) + 1;
14891
        }
14892
    }
14893
14894
    /**
14895
     * Generate the link for a learnpath category as course tool.
14896
     *
14897
     * @param int $categoryId
14898
     *
14899
     * @return string
14900
     */
14901
    private static function getCategoryLinkForTool($categoryId)
14902
    {
14903
        $categoryId = (int) $categoryId;
14904
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14905
            .http_build_query(
14906
                [
14907
                    'action' => 'view_category',
14908
                    'id' => $categoryId,
14909
                ]
14910
            );
14911
14912
        return $link;
14913
    }
14914
14915
    /**
14916
     * Return the scorm item type object with spaces replaced with _
14917
     * The return result is use to build a css classname like scorm_type_$return.
14918
     *
14919
     * @param $in_type
14920
     *
14921
     * @return mixed
14922
     */
14923
    private static function format_scorm_type_item($in_type)
14924
    {
14925
        return str_replace(' ', '_', $in_type);
14926
    }
14927
14928
    /**
14929
     * Get the LP Final Item Template.
14930
     *
14931
     * @return string
14932
     */
14933
    private function getFinalItemTemplate()
14934
    {
14935
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14936
    }
14937
14938
    /**
14939
     * Get the LP Final Item Url.
14940
     *
14941
     * @return string
14942
     */
14943
    private function getSavedFinalItem()
14944
    {
14945
        $finalItem = $this->getFinalItem();
14946
        $doc = DocumentManager::get_document_data_by_id(
14947
            $finalItem->path,
14948
            $this->cc
14949
        );
14950
        if ($doc && file_exists($doc['absolute_path'])) {
14951
            return file_get_contents($doc['absolute_path']);
14952
        }
14953
14954
        return '';
14955
    }
14956
14957
    /**
14958
     * Gets the form to evaluate if it exists contains the extra field extra_authorlpitem
14959
     * to establish authors when editing an item of an LP.
14960
     */
14961
    private function setAuthorLpItem(FormValidator $form)
14962
    {
14963
        if ($form->hasElement('extra_authorlpitem')) {
14964
            /** @var HTML_QuickForm_select $author */
14965
            $author = $form->getElement('extra_authorlpitem');
14966
            $options = [];
14967
            $field = new ExtraField('user');
14968
            $authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
14969
            $extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
14970
            if ($extraFieldId != 0) {
14971
                $extraFieldValueUser = new ExtraFieldValue('user');
14972
                $values = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
14973
                    $authorLp['variable'],
14974
                    1,
14975
                    true,
14976
                    false,
14977
                    true
14978
                );
14979
14980
                if (!empty($values)) {
14981
                    foreach ($values as $item) {
14982
                        $teacher = api_get_user_info($item['item_id']);
14983
                        $options[$teacher['id']] = $teacher['complete_name'];
14984
                    }
14985
                }
14986
            }
14987
            $author->setOptions($options);
14988
        }
14989
    }
14990
}
14991