Passed
Push — 1.11.x ( 45e776...17f273 )
by Julito
09:30
created

learnpath::create_document()   F

Complexity

Conditions 30
Paths > 20000

Size

Total Lines 169
Code Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 108
nc 432640
nop 6
dl 0
loc 169
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
6
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CItemProperty;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CTool;
17
use Chamilo\UserBundle\Entity\User;
18
use ChamiloSession as Session;
19
use Gedmo\Sortable\Entity\Repository\SortableRepository;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\Finder\Finder;
22
23
/**
24
 * Class learnpath
25
 * This class defines the parent attributes and methods for Chamilo learnpaths
26
 * and SCORM learnpaths. It is used by the scorm class.
27
 *
28
 * @todo decouple class
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
    const STATUS_CSS_CLASS_NAME = [
37
        'not attempted' => 'scorm_not_attempted',
38
        'incomplete' => 'scorm_not_attempted',
39
        'failed' => 'scorm_failed',
40
        'completed' => 'scorm_completed',
41
        'passed' => 'scorm_completed',
42
        'succeeded' => 'scorm_completed',
43
        'browsed' => 'scorm_completed',
44
    ];
45
46
    public $attempt = 0; // The number for the current ID view.
47
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
48
    public $current; // Id of the current item the user is viewing.
49
    public $current_score; // The score of the current item.
50
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
51
    public $current_time_stop; // The time the user closed this resource.
52
    public $default_status = 'not attempted';
53
    public $encoding = 'UTF-8';
54
    public $error = '';
55
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
56
    public $index; // The index of the active learnpath_item in $ordered_items array.
57
    /** @var learnpathItem[] */
58
    public $items = [];
59
    public $last; // item_id of last item viewed in the learning path.
60
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
61
    public $license; // Which license this course has been given - not used yet on 20060522.
62
    public $lp_id; // DB iid for this learnpath.
63
    public $lp_view_id; // DB ID for lp_view
64
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
65
    public $message = '';
66
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
67
    public $name; // Learnpath name (they generally have one).
68
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
69
    public $path = ''; // Path inside the scorm directory (if scorm).
70
    public $theme; // The current theme of the learning path.
71
    public $preview_image; // The current image of the learning path.
72
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
73
    public $accumulateWorkTime; // The min time of learnpath
74
75
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
76
    public $prevent_reinit = 1;
77
78
    // Describes the mode of progress bar display.
79
    public $seriousgame_mode = 0;
80
    public $progress_bar_mode = '%';
81
82
    // Percentage progress as saved in the db.
83
    public $progress_db = 0;
84
    public $proximity; // Wether the content is distant or local or unknown.
85
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
86
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
87
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
88
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
89
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
90
    public $user_id; //ID of the user that is viewing/using the course
91
    public $update_queue = [];
92
    public $scorm_debug = 0;
93
    public $arrMenu = []; // Array for the menu items.
94
    public $debug = 0; // Logging level.
95
    public $lp_session_id = 0;
96
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
97
    public $prerequisite = 0;
98
    public $use_max_score = 1; // 1 or 0
99
    public $subscribeUsers = 0; // Subscribe users or not
100
    public $created_on = '';
101
    public $modified_on = '';
102
    public $publicated_on = '';
103
    public $expired_on = '';
104
    public $ref = null;
105
    public $course_int_id;
106
    public $course_info = [];
107
    public $categoryId;
108
109
    /**
110
     * Constructor.
111
     * Needs a database handler, a course code and a learnpath id from the database.
112
     * Also builds the list of items into $this->items.
113
     *
114
     * @param string $course  Course code
115
     * @param int    $lp_id   c_lp.iid
116
     * @param int    $user_id
117
     */
118
    public function __construct($course, $lp_id, $user_id)
119
    {
120
        $debug = $this->debug;
121
        $this->encoding = api_get_system_encoding();
122
        if ($debug) {
123
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
124
        }
125
        if (empty($course)) {
126
            $course = api_get_course_id();
127
        }
128
        $course_info = api_get_course_info($course);
129
        if (!empty($course_info)) {
130
            $this->cc = $course_info['code'];
131
            $this->course_info = $course_info;
132
            $course_id = $course_info['real_id'];
133
        } else {
134
            $this->error = 'Course code does not exist in database.';
135
        }
136
137
        $lp_id = (int) $lp_id;
138
        $course_id = (int) $course_id;
139
        $this->set_course_int_id($course_id);
140
        // Check learnpath ID.
141
        if (empty($lp_id) || empty($course_id)) {
142
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
143
        } else {
144
            // TODO: Make it flexible to use any course_code (still using env course code here).
145
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
146
            $sql = "SELECT * FROM $lp_table
147
                    WHERE iid = $lp_id";
148
            if ($debug) {
149
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
150
            }
151
            $res = Database::query($sql);
152
            if (Database::num_rows($res) > 0) {
153
                $this->lp_id = $lp_id;
154
                $row = Database::fetch_array($res);
155
                $this->type = $row['lp_type'];
156
                $this->name = stripslashes($row['name']);
157
                $this->proximity = $row['content_local'];
158
                $this->theme = $row['theme'];
159
                $this->maker = $row['content_maker'];
160
                $this->prevent_reinit = $row['prevent_reinit'];
161
                $this->seriousgame_mode = $row['seriousgame_mode'];
162
                $this->license = $row['content_license'];
163
                $this->scorm_debug = $row['debug'];
164
                $this->js_lib = $row['js_lib'];
165
                $this->path = $row['path'];
166
                $this->preview_image = $row['preview_image'];
167
                $this->author = $row['author'];
168
                $this->hide_toc_frame = $row['hide_toc_frame'];
169
                $this->lp_session_id = $row['session_id'];
170
                $this->use_max_score = $row['use_max_score'];
171
                $this->subscribeUsers = $row['subscribe_users'];
172
                $this->created_on = $row['created_on'];
173
                $this->modified_on = $row['modified_on'];
174
                $this->ref = $row['ref'];
175
                $this->categoryId = $row['category_id'];
176
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
177
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
178
179
                if (!empty($row['publicated_on'])) {
180
                    $this->publicated_on = $row['publicated_on'];
181
                }
182
183
                if (!empty($row['expired_on'])) {
184
                    $this->expired_on = $row['expired_on'];
185
                }
186
                if ($this->type == 2) {
187
                    if ($row['force_commit'] == 1) {
188
                        $this->force_commit = true;
189
                    }
190
                }
191
                $this->mode = $row['default_view_mod'];
192
193
                // Check user ID.
194
                if (empty($user_id)) {
195
                    $this->error = 'User ID is empty';
196
                } else {
197
                    $userInfo = api_get_user_info($user_id);
198
                    if (!empty($userInfo)) {
199
                        $this->user_id = $userInfo['user_id'];
200
                    } else {
201
                        $this->error = 'User ID does not exist in database #'.$user_id;
202
                    }
203
                }
204
205
                // End of variables checking.
206
                $session_id = api_get_session_id();
207
                //  Get the session condition for learning paths of the base + session.
208
                $session = api_get_session_condition($session_id);
209
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
210
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
211
212
                // Selecting by view_count descending allows to get the highest view_count first.
213
                $sql = "SELECT * FROM $lp_table
214
                        WHERE
215
                            c_id = $course_id AND
216
                            lp_id = $lp_id AND
217
                            user_id = $user_id
218
                            $session
219
                        ORDER BY view_count DESC";
220
                $res = Database::query($sql);
221
                if ($debug) {
222
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
223
                }
224
225
                if (Database::num_rows($res) > 0) {
226
                    if ($debug) {
227
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
228
                    }
229
                    $row = Database::fetch_array($res);
230
                    $this->attempt = $row['view_count'];
231
                    $this->lp_view_id = $row['id'];
232
                    $this->last_item_seen = $row['last_item'];
233
                    $this->progress_db = $row['progress'];
234
                    $this->lp_view_session_id = $row['session_id'];
235
                } elseif (!api_is_invitee()) {
236
                    if ($debug) {
237
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
238
                    }
239
                    $this->attempt = 1;
240
                    $params = [
241
                        'c_id' => $course_id,
242
                        'lp_id' => $lp_id,
243
                        'user_id' => $user_id,
244
                        'view_count' => 1,
245
                        'session_id' => $session_id,
246
                        'last_item' => 0,
247
                    ];
248
                    $this->last_item_seen = 0;
249
                    $this->lp_view_session_id = $session_id;
250
                    $this->lp_view_id = Database::insert($lp_table, $params);
251
                    if (!empty($this->lp_view_id)) {
252
                        $sql = "UPDATE $lp_table SET id = iid
253
                                WHERE iid = ".$this->lp_view_id;
254
                        Database::query($sql);
255
                    }
256
                }
257
258
                // Initialise items.
259
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
260
                $sql = "SELECT * FROM $lp_item_table
261
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
262
                        ORDER BY parent_item_id, display_order";
263
                $res = Database::query($sql);
264
265
                $lp_item_id_list = [];
266
                while ($row = Database::fetch_array($res)) {
267
                    $lp_item_id_list[] = $row['iid'];
268
                    switch ($this->type) {
269
                        case 3: //aicc
270
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
271
                            if (is_object($oItem)) {
272
                                $my_item_id = $oItem->get_id();
273
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
274
                                $oItem->set_prevent_reinit($this->prevent_reinit);
275
                                // Don't use reference here as the next loop will make the pointed object change.
276
                                $this->items[$my_item_id] = $oItem;
277
                                $this->refs_list[$oItem->ref] = $my_item_id;
278
                                if ($debug) {
279
                                    error_log(
280
                                        'learnpath::__construct() - '.
281
                                        'aicc object with id '.$my_item_id.
282
                                        ' set in items[]',
283
                                        0
284
                                    );
285
                                }
286
                            }
287
                            break;
288
                        case 2:
289
                            $oItem = new scormItem('db', $row['iid'], $course_id);
290
                            if (is_object($oItem)) {
291
                                $my_item_id = $oItem->get_id();
292
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
293
                                $oItem->set_prevent_reinit($this->prevent_reinit);
294
                                // Don't use reference here as the next loop will make the pointed object change.
295
                                $this->items[$my_item_id] = $oItem;
296
                                $this->refs_list[$oItem->ref] = $my_item_id;
297
                                if ($debug) {
298
                                    error_log('object with id '.$my_item_id.' set in items[]');
299
                                }
300
                            }
301
                            break;
302
                        case 1:
303
                        default:
304
                            if ($debug) {
305
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
306
                            }
307
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
308
309
                            if ($debug) {
310
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
311
                            }
312
                            if (is_object($oItem)) {
313
                                $my_item_id = $oItem->get_id();
314
                                // Moved down to when we are sure the item_view exists.
315
                                //$oItem->set_lp_view($this->lp_view_id);
316
                                $oItem->set_prevent_reinit($this->prevent_reinit);
317
                                // Don't use reference here as the next loop will make the pointed object change.
318
                                $this->items[$my_item_id] = $oItem;
319
                                $this->refs_list[$my_item_id] = $my_item_id;
320
                                if ($debug) {
321
                                    error_log(
322
                                        'learnpath::__construct() '.__LINE__.
323
                                        ' - object with id '.$my_item_id.' set in items[]'
324
                                    );
325
                                }
326
                            }
327
                            break;
328
                    }
329
330
                    // Setting the object level with variable $this->items[$i][parent]
331
                    foreach ($this->items as $itemLPObject) {
332
                        $level = self::get_level_for_item(
333
                            $this->items,
334
                            $itemLPObject->db_id
335
                        );
336
                        $itemLPObject->level = $level;
337
                    }
338
339
                    // Setting the view in the item object.
340
                    if (is_object($this->items[$row['iid']])) {
341
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
342
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
343
                            $this->items[$row['iid']]->current_start_time = 0;
344
                            $this->items[$row['iid']]->current_stop_time = 0;
345
                        }
346
                    }
347
                }
348
349
                if (!empty($lp_item_id_list)) {
350
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
351
                    if (!empty($lp_item_id_list_to_string)) {
352
                        // Get last viewing vars.
353
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
354
                        // This query should only return one or zero result.
355
                        $sql = "SELECT lp_item_id, status
356
                                FROM $itemViewTable
357
                                WHERE
358
                                    c_id = $course_id AND
359
                                    lp_view_id = ".$this->get_view_id()." AND
360
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
361
                                ORDER BY view_count DESC ";
362
                        $status_list = [];
363
                        $res = Database::query($sql);
364
                        while ($row = Database:: fetch_array($res)) {
365
                            $status_list[$row['lp_item_id']] = $row['status'];
366
                        }
367
368
                        foreach ($lp_item_id_list as $item_id) {
369
                            if (isset($status_list[$item_id])) {
370
                                $status = $status_list[$item_id];
371
                                if (is_object($this->items[$item_id])) {
372
                                    $this->items[$item_id]->set_status($status);
373
                                    if (empty($status)) {
374
                                        $this->items[$item_id]->set_status(
375
                                            $this->default_status
376
                                        );
377
                                    }
378
                                }
379
                            } else {
380
                                if (!api_is_invitee()) {
381
                                    if (is_object($this->items[$item_id])) {
382
                                        $this->items[$item_id]->set_status(
383
                                            $this->default_status
384
                                        );
385
                                    }
386
387
                                    if (!empty($this->lp_view_id)) {
388
                                        // Add that row to the lp_item_view table so that
389
                                        // we have something to show in the stats page.
390
                                        $params = [
391
                                            'c_id' => $course_id,
392
                                            'lp_item_id' => $item_id,
393
                                            'lp_view_id' => $this->lp_view_id,
394
                                            'view_count' => 1,
395
                                            'status' => 'not attempted',
396
                                            'start_time' => time(),
397
                                            'total_time' => 0,
398
                                            'score' => 0,
399
                                        ];
400
                                        $insertId = Database::insert($itemViewTable, $params);
401
402
                                        if ($insertId) {
403
                                            $sql = "UPDATE $itemViewTable SET id = iid
404
                                                    WHERE iid = $insertId";
405
                                            Database::query($sql);
406
                                        }
407
408
                                        $this->items[$item_id]->set_lp_view(
409
                                            $this->lp_view_id,
410
                                            $course_id
411
                                        );
412
                                    }
413
                                }
414
                            }
415
                        }
416
                    }
417
                }
418
419
                $this->ordered_items = self::get_flat_ordered_items_list(
420
                    $this->get_id(),
421
                    0,
422
                    $course_id
423
                );
424
                $this->max_ordered_items = 0;
425
                foreach ($this->ordered_items as $index => $dummy) {
426
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
427
                        $this->max_ordered_items = $index;
428
                    }
429
                }
430
                // TODO: Define the current item better.
431
                $this->first();
432
                if ($debug) {
433
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
434
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
435
                }
436
            } else {
437
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
438
            }
439
        }
440
    }
441
442
    /**
443
     * @return string
444
     */
445
    public function getCourseCode()
446
    {
447
        return $this->cc;
448
    }
449
450
    /**
451
     * @return int
452
     */
453
    public function get_course_int_id()
454
    {
455
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
456
    }
457
458
    /**
459
     * @param $course_id
460
     *
461
     * @return int
462
     */
463
    public function set_course_int_id($course_id)
464
    {
465
        return $this->course_int_id = (int) $course_id;
466
    }
467
468
    /**
469
     * Function rewritten based on old_add_item() from Yannick Warnier.
470
     * Due the fact that users can decide where the item should come, I had to overlook this function and
471
     * I found it better to rewrite it. Old function is still available.
472
     * Added also the possibility to add a description.
473
     *
474
     * @param int    $parent
475
     * @param int    $previous
476
     * @param string $type
477
     * @param int    $id               resource ID (ref)
478
     * @param string $title
479
     * @param string $description
480
     * @param int    $prerequisites
481
     * @param int    $max_time_allowed
482
     * @param int    $userId
483
     *
484
     * @return int
485
     */
486
    public function add_item(
487
        $parent,
488
        $previous,
489
        $type = 'dir',
490
        $id,
491
        $title,
492
        $description,
493
        $prerequisites = 0,
494
        $max_time_allowed = 0,
495
        $userId = 0
496
    ) {
497
        $course_id = $this->course_info['real_id'];
498
        if (empty($course_id)) {
499
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
500
            $this->course_info = api_get_course_info($this->cc);
501
            $course_id = $this->course_info['real_id'];
502
        }
503
        $userId = empty($userId) ? api_get_user_id() : $userId;
504
        $sessionId = api_get_session_id();
505
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
506
        $_course = $this->course_info;
507
        $parent = (int) $parent;
508
        $previous = (int) $previous;
509
        $id = (int) $id;
510
        $max_time_allowed = htmlentities($max_time_allowed);
511
        if (empty($max_time_allowed)) {
512
            $max_time_allowed = 0;
513
        }
514
        $sql = "SELECT COUNT(iid) AS num
515
                FROM $tbl_lp_item
516
                WHERE
517
                    c_id = $course_id AND
518
                    lp_id = ".$this->get_id()." AND
519
                    parent_item_id = $parent ";
520
521
        $res_count = Database::query($sql);
522
        $row = Database::fetch_array($res_count);
523
        $num = $row['num'];
524
525
        $tmp_previous = 0;
526
        $display_order = 0;
527
        $next = 0;
528
        if ($num > 0) {
529
            if (empty($previous)) {
530
                $sql = "SELECT iid, next_item_id, display_order
531
                        FROM $tbl_lp_item
532
                        WHERE
533
                            c_id = $course_id AND
534
                            lp_id = ".$this->get_id()." AND
535
                            parent_item_id = $parent AND
536
                            previous_item_id = 0 OR
537
                            previous_item_id = $parent";
538
                $result = Database::query($sql);
539
                $row = Database::fetch_array($result);
540
                if ($row) {
541
                    $next = $row['iid'];
542
                }
543
            } else {
544
                $previous = (int) $previous;
545
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
546
						FROM $tbl_lp_item
547
                        WHERE
548
                            c_id = $course_id AND
549
                            lp_id = ".$this->get_id()." AND
550
                            id = $previous";
551
                $result = Database::query($sql);
552
                $row = Database::fetch_array($result);
553
                if ($row) {
554
                    $tmp_previous = $row['iid'];
555
                    $next = $row['next_item_id'];
556
                    $display_order = $row['display_order'];
557
                }
558
            }
559
        }
560
561
        $id = (int) $id;
562
        $typeCleaned = Database::escape_string($type);
563
        $max_score = 100;
564
        if ($type === 'quiz' && $id) {
565
            $sql = 'SELECT SUM(ponderation)
566
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
567
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
568
                    ON
569
                        quiz_question.id = quiz_rel_question.question_id AND
570
                        quiz_question.c_id = quiz_rel_question.c_id
571
                    WHERE
572
                        quiz_rel_question.exercice_id = '.$id." AND
573
                        quiz_question.c_id = $course_id AND
574
                        quiz_rel_question.c_id = $course_id ";
575
            $rsQuiz = Database::query($sql);
576
            $max_score = Database::result($rsQuiz, 0, 0);
577
578
            // Disabling the exercise if we add it inside a LP
579
            $exercise = new Exercise($course_id);
580
            $exercise->read($id);
581
            $exercise->disable();
582
            $exercise->save();
583
        }
584
585
        $params = [
586
            'c_id' => $course_id,
587
            'lp_id' => $this->get_id(),
588
            'item_type' => $typeCleaned,
589
            'ref' => '',
590
            'title' => $title,
591
            'description' => $description,
592
            'path' => $id,
593
            'max_score' => $max_score,
594
            'parent_item_id' => $parent,
595
            'previous_item_id' => $previous,
596
            'next_item_id' => (int) $next,
597
            'display_order' => $display_order + 1,
598
            'prerequisite' => $prerequisites,
599
            'max_time_allowed' => $max_time_allowed,
600
            'min_score' => 0,
601
            'launch_data' => '',
602
        ];
603
604
        if ($prerequisites != 0) {
605
            $params['prerequisite'] = $prerequisites;
606
        }
607
608
        $new_item_id = Database::insert($tbl_lp_item, $params);
609
        if ($new_item_id) {
610
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
611
            Database::query($sql);
612
613
            if (!empty($next)) {
614
                $sql = "UPDATE $tbl_lp_item
615
                        SET previous_item_id = $new_item_id
616
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
617
                Database::query($sql);
618
            }
619
620
            // Update the item that should be before the new item.
621
            if (!empty($tmp_previous)) {
622
                $sql = "UPDATE $tbl_lp_item
623
                        SET next_item_id = $new_item_id
624
                        WHERE c_id = $course_id AND id = $tmp_previous";
625
                Database::query($sql);
626
            }
627
628
            // Update all the items after the new item.
629
            $sql = "UPDATE $tbl_lp_item
630
                        SET display_order = display_order + 1
631
                    WHERE
632
                        c_id = $course_id AND
633
                        lp_id = ".$this->get_id()." AND
634
                        iid <> $new_item_id AND
635
                        parent_item_id = $parent AND
636
                        display_order > $display_order";
637
            Database::query($sql);
638
639
            // Update the item that should come after the new item.
640
            $sql = "UPDATE $tbl_lp_item
641
                    SET ref = $new_item_id
642
                    WHERE c_id = $course_id AND iid = $new_item_id";
643
            Database::query($sql);
644
645
            $sql = "UPDATE $tbl_lp_item
646
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
647
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
648
            Database::query($sql);
649
650
            // Upload audio.
651
            if (!empty($_FILES['mp3']['name'])) {
652
                // Create the audio folder if it does not exist yet.
653
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
654
                if (!is_dir($filepath.'audio')) {
655
                    mkdir(
656
                        $filepath.'audio',
657
                        api_get_permissions_for_new_directories()
658
                    );
659
                    $audio_id = add_document(
660
                        $_course,
661
                        '/audio',
662
                        'folder',
663
                        0,
664
                        'audio',
665
                        '',
666
                        0,
667
                        true,
668
                        null,
669
                        $sessionId,
670
                        $userId
671
                    );
672
                    api_item_property_update(
673
                        $_course,
674
                        TOOL_DOCUMENT,
675
                        $audio_id,
676
                        'FolderCreated',
677
                        $userId,
678
                        null,
679
                        null,
680
                        null,
681
                        null,
682
                        $sessionId
683
                    );
684
                    api_item_property_update(
685
                        $_course,
686
                        TOOL_DOCUMENT,
687
                        $audio_id,
688
                        'invisible',
689
                        $userId,
690
                        null,
691
                        null,
692
                        null,
693
                        null,
694
                        $sessionId
695
                    );
696
                }
697
698
                $file_path = handle_uploaded_document(
699
                    $_course,
700
                    $_FILES['mp3'],
701
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
702
                    '/audio',
703
                    $userId,
704
                    '',
705
                    '',
706
                    '',
707
                    '',
708
                    false
709
                );
710
711
                // Store the mp3 file in the lp_item table.
712
                $sql = "UPDATE $tbl_lp_item SET
713
                          audio = '".Database::escape_string($file_path)."'
714
                        WHERE iid = '".intval($new_item_id)."'";
715
                Database::query($sql);
716
            }
717
        }
718
719
        return $new_item_id;
720
    }
721
722
    /**
723
     * Static admin function allowing addition of a learnpath to a course.
724
     *
725
     * @param string $courseCode
726
     * @param string $name
727
     * @param string $description
728
     * @param string $learnpath
729
     * @param string $origin
730
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
731
     * @param string $publicated_on
732
     * @param string $expired_on
733
     * @param int    $categoryId
734
     * @param int    $userId
735
     *
736
     * @return int The new learnpath ID on success, 0 on failure
737
     */
738
    public static function add_lp(
739
        $courseCode,
740
        $name,
741
        $description = '',
742
        $learnpath = 'guess',
743
        $origin = 'zip',
744
        $zipname = '',
745
        $publicated_on = '',
746
        $expired_on = '',
747
        $categoryId = 0,
748
        $userId = 0
749
    ) {
750
        global $charset;
751
752
        if (!empty($courseCode)) {
753
            $courseInfo = api_get_course_info($courseCode);
754
            $course_id = $courseInfo['real_id'];
755
        } else {
756
            $course_id = api_get_course_int_id();
757
            $courseInfo = api_get_course_info();
758
        }
759
760
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
761
        // Check course code exists.
762
        // Check lp_name doesn't exist, otherwise append something.
763
        $i = 0;
764
        $categoryId = (int) $categoryId;
765
        // Session id.
766
        $session_id = api_get_session_id();
767
        $userId = empty($userId) ? api_get_user_id() : $userId;
768
769
        if (empty($publicated_on)) {
770
            $publicated_on = null;
771
        } else {
772
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
773
        }
774
775
        if (empty($expired_on)) {
776
            $expired_on = null;
777
        } else {
778
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
779
        }
780
781
        $check_name = "SELECT * FROM $tbl_lp
782
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
783
        $res_name = Database::query($check_name);
784
785
        while (Database::num_rows($res_name)) {
786
            // There is already one such name, update the current one a bit.
787
            $i++;
788
            $name = $name.' - '.$i;
789
            $check_name = "SELECT * FROM $tbl_lp
790
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
791
            $res_name = Database::query($check_name);
792
        }
793
        // New name does not exist yet; keep it.
794
        // Escape description.
795
        // Kevin: added htmlentities().
796
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
797
        $type = 1;
798
        switch ($learnpath) {
799
            case 'guess':
800
                break;
801
            case 'dokeos':
802
            case 'chamilo':
803
                $type = 1;
804
                break;
805
            case 'aicc':
806
                break;
807
        }
808
809
        switch ($origin) {
810
            case 'zip':
811
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
812
                break;
813
            case 'manual':
814
            default:
815
                $get_max = "SELECT MAX(display_order)
816
                            FROM $tbl_lp WHERE c_id = $course_id";
817
                $res_max = Database::query($get_max);
818
                if (Database::num_rows($res_max) < 1) {
819
                    $dsp = 1;
820
                } else {
821
                    $row = Database::fetch_array($res_max);
822
                    $dsp = $row[0] + 1;
823
                }
824
825
                $params = [
826
                    'c_id' => $course_id,
827
                    'lp_type' => $type,
828
                    'name' => $name,
829
                    'description' => $description,
830
                    'path' => '',
831
                    'default_view_mod' => 'embedded',
832
                    'default_encoding' => 'UTF-8',
833
                    'display_order' => $dsp,
834
                    'content_maker' => 'Chamilo',
835
                    'content_local' => 'local',
836
                    'js_lib' => '',
837
                    'session_id' => $session_id,
838
                    'created_on' => api_get_utc_datetime(),
839
                    'modified_on' => api_get_utc_datetime(),
840
                    'publicated_on' => $publicated_on,
841
                    'expired_on' => $expired_on,
842
                    'category_id' => $categoryId,
843
                    'force_commit' => 0,
844
                    'content_license' => '',
845
                    'debug' => 0,
846
                    'theme' => '',
847
                    'preview_image' => '',
848
                    'author' => '',
849
                    'prerequisite' => 0,
850
                    'hide_toc_frame' => 0,
851
                    'seriousgame_mode' => 0,
852
                    'autolaunch' => 0,
853
                    'max_attempts' => 0,
854
                    'subscribe_users' => 0,
855
                    'accumulate_scorm_time' => 1,
856
                ];
857
                $id = Database::insert($tbl_lp, $params);
858
859
                if ($id > 0) {
860
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
861
                    Database::query($sql);
862
863
                    // Insert into item_property.
864
                    api_item_property_update(
865
                        $courseInfo,
866
                        TOOL_LEARNPATH,
867
                        $id,
868
                        'LearnpathAdded',
869
                        $userId
870
                    );
871
                    api_set_default_visibility(
872
                        $id,
873
                        TOOL_LEARNPATH,
874
                        0,
875
                        $courseInfo,
876
                        $session_id,
877
                        $userId
878
                    );
879
880
                    return $id;
881
                }
882
                break;
883
        }
884
    }
885
886
    /**
887
     * Auto completes the parents of an item in case it's been completed or passed.
888
     *
889
     * @param int $item Optional ID of the item from which to look for parents
890
     */
891
    public function autocomplete_parents($item)
892
    {
893
        $debug = $this->debug;
894
895
        if (empty($item)) {
896
            $item = $this->current;
897
        }
898
899
        $currentItem = $this->getItem($item);
900
        if ($currentItem) {
901
            $parent_id = $currentItem->get_parent();
902
            $parent = $this->getItem($parent_id);
903
            if ($parent) {
904
                // if $item points to an object and there is a parent.
905
                if ($debug) {
906
                    error_log(
907
                        'Autocompleting parent of item '.$item.' '.
908
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
909
                        0
910
                    );
911
                }
912
913
                // New experiment including failed and browsed in completed status.
914
                //$current_status = $currentItem->get_status();
915
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
916
                // Fixes chapter auto complete
917
                if (true) {
918
                    // If the current item is completed or passes or succeeded.
919
                    $updateParentStatus = true;
920
                    if ($debug) {
921
                        error_log('Status of current item is alright');
922
                    }
923
924
                    foreach ($parent->get_children() as $childItemId) {
925
                        $childItem = $this->getItem($childItemId);
926
927
                        // If children was not set try to get the info
928
                        if (empty($childItem->db_item_view_id)) {
929
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
930
                        }
931
932
                        // Check all his brothers (parent's children) for completion status.
933
                        if ($childItemId != $item) {
934
                            if ($debug) {
935
                                error_log(
936
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
937
                                    0
938
                                );
939
                            }
940
                            // Trying completing parents of failed and browsed items as well.
941
                            if ($childItem->status_is(
942
                                [
943
                                    'completed',
944
                                    'passed',
945
                                    'succeeded',
946
                                    'browsed',
947
                                    'failed',
948
                                ]
949
                            )
950
                            ) {
951
                                // Keep completion status to true.
952
                                continue;
953
                            } else {
954
                                if ($debug > 2) {
955
                                    error_log(
956
                                        'Found one incomplete child of parent #'.$parent_id.': child #'.$childItemId.' "'.$childItem->get_title().'", is '.$childItem->get_status().' db_item_view_id:#'.$childItem->db_item_view_id,
957
                                        0
958
                                    );
959
                                }
960
                                $updateParentStatus = false;
961
                                break;
962
                            }
963
                        }
964
                    }
965
966
                    if ($updateParentStatus) {
967
                        // If all the children were completed:
968
                        $parent->set_status('completed');
969
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
970
                        // Force the status to "completed"
971
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
972
                        $this->update_queue[$parent->get_id()] = 'completed';
973
                        if ($debug) {
974
                            error_log(
975
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
976
                                print_r($this->update_queue, 1),
977
                                0
978
                            );
979
                        }
980
                        // Recursive call.
981
                        $this->autocomplete_parents($parent->get_id());
982
                    }
983
                }
984
            } else {
985
                if ($debug) {
986
                    error_log("Parent #$parent_id does not exists");
987
                }
988
            }
989
        } else {
990
            if ($debug) {
991
                error_log("#$item is an item that doesn't have parents");
992
            }
993
        }
994
    }
995
996
    /**
997
     * Closes the current resource.
998
     *
999
     * Stops the timer
1000
     * Saves into the database if required
1001
     * Clears the current resource data from this object
1002
     *
1003
     * @return bool True on success, false on failure
1004
     */
1005
    public function close()
1006
    {
1007
        if (empty($this->lp_id)) {
1008
            $this->error = 'Trying to close this learnpath but no ID is set';
1009
1010
            return false;
1011
        }
1012
        $this->current_time_stop = time();
1013
        $this->ordered_items = [];
1014
        $this->index = 0;
1015
        unset($this->lp_id);
1016
        //unset other stuff
1017
        return true;
1018
    }
1019
1020
    /**
1021
     * Static admin function allowing removal of a learnpath.
1022
     *
1023
     * @param array  $courseInfo
1024
     * @param int    $id         Learnpath ID
1025
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1026
     *
1027
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1028
     */
1029
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1030
    {
1031
        $course_id = api_get_course_int_id();
1032
        if (!empty($courseInfo)) {
1033
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1034
        }
1035
1036
        // TODO: Implement a way of getting this to work when the current object is not set.
1037
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1038
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1039
        if (!empty($id) && ($id != $this->lp_id)) {
1040
            return false;
1041
        }
1042
1043
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1044
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1045
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1046
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1047
1048
        // Delete lp item id.
1049
        foreach ($this->items as $lpItemId => $dummy) {
1050
            $sql = "DELETE FROM $lp_item_view
1051
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1052
            Database::query($sql);
1053
        }
1054
1055
        // Proposed by Christophe (nickname: clefevre)
1056
        $sql = "DELETE FROM $lp_item
1057
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1058
        Database::query($sql);
1059
1060
        $sql = "DELETE FROM $lp_view
1061
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1062
        Database::query($sql);
1063
1064
        self::toggle_publish($this->lp_id, 'i');
1065
1066
        if ($this->type == 2 || $this->type == 3) {
1067
            // This is a scorm learning path, delete the files as well.
1068
            $sql = "SELECT path FROM $lp
1069
                    WHERE iid = ".$this->lp_id;
1070
            $res = Database::query($sql);
1071
            if (Database::num_rows($res) > 0) {
1072
                $row = Database::fetch_array($res);
1073
                $path = $row['path'];
1074
                $sql = "SELECT id FROM $lp
1075
                        WHERE
1076
                            c_id = $course_id AND
1077
                            path = '$path' AND
1078
                            iid != ".$this->lp_id;
1079
                $res = Database::query($sql);
1080
                if (Database::num_rows($res) > 0) {
1081
                    // Another learning path uses this directory, so don't delete it.
1082
                    if ($this->debug > 2) {
1083
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1084
                    }
1085
                } else {
1086
                    // No other LP uses that directory, delete it.
1087
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1088
                    // The absolute system path for this course.
1089
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1090
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1091
                        if ($this->debug > 2) {
1092
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1093
                        }
1094
                        // Proposed by Christophe (clefevre).
1095
                        if (strcmp(substr($path, -2), "/.") == 0) {
1096
                            $path = substr($path, 0, -1); // Remove "." at the end.
1097
                        }
1098
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1099
                        rmdirr($course_scorm_dir.$path);
1100
                    }
1101
                }
1102
            }
1103
        }
1104
1105
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1106
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1107
        // Delete tools
1108
        $sql = "DELETE FROM $tbl_tool
1109
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1110
        Database::query($sql);
1111
1112
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1113
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1114
            $sql = "DELETE FROM $table
1115
                    WHERE
1116
                        lp_id = {$this->lp_id} AND
1117
                        c_id = $course_id ";
1118
            Database::query($sql);
1119
        }
1120
1121
        $sql = "DELETE FROM $lp
1122
                WHERE iid = ".$this->lp_id;
1123
        Database::query($sql);
1124
        // Updates the display order of all lps.
1125
        $this->update_display_order();
1126
1127
        api_item_property_update(
1128
            api_get_course_info(),
1129
            TOOL_LEARNPATH,
1130
            $this->lp_id,
1131
            'delete',
1132
            api_get_user_id()
1133
        );
1134
1135
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1136
            api_get_course_id(),
1137
            4,
1138
            $id,
1139
            api_get_session_id()
1140
        );
1141
1142
        if ($link_info !== false) {
1143
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1144
        }
1145
1146
        if (api_get_setting('search_enabled') == 'true') {
1147
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1148
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1149
        }
1150
    }
1151
1152
    /**
1153
     * Removes all the children of one item - dangerous!
1154
     *
1155
     * @param int $id Element ID of which children have to be removed
1156
     *
1157
     * @return int Total number of children removed
1158
     */
1159
    public function delete_children_items($id)
1160
    {
1161
        $course_id = $this->course_info['real_id'];
1162
1163
        $num = 0;
1164
        $id = (int) $id;
1165
        if (empty($id) || empty($course_id)) {
1166
            return false;
1167
        }
1168
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1169
        $sql = "SELECT * FROM $lp_item
1170
                WHERE c_id = $course_id AND parent_item_id = $id";
1171
        $res = Database::query($sql);
1172
        while ($row = Database::fetch_array($res)) {
1173
            $num += $this->delete_children_items($row['iid']);
1174
            $sql = "DELETE FROM $lp_item
1175
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1176
            Database::query($sql);
1177
            $num++;
1178
        }
1179
1180
        return $num;
1181
    }
1182
1183
    /**
1184
     * Removes an item from the current learnpath.
1185
     *
1186
     * @param int $id Elem ID (0 if first)
1187
     *
1188
     * @return int Number of elements moved
1189
     *
1190
     * @todo implement resource removal
1191
     */
1192
    public function delete_item($id)
1193
    {
1194
        $course_id = api_get_course_int_id();
1195
        $id = (int) $id;
1196
        // TODO: Implement the resource removal.
1197
        if (empty($id) || empty($course_id)) {
1198
            return false;
1199
        }
1200
        // First select item to get previous, next, and display order.
1201
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1202
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1203
        $res_sel = Database::query($sql_sel);
1204
        if (Database::num_rows($res_sel) < 1) {
1205
            return false;
1206
        }
1207
        $row = Database::fetch_array($res_sel);
1208
        $previous = $row['previous_item_id'];
1209
        $next = $row['next_item_id'];
1210
        $display = $row['display_order'];
1211
        $parent = $row['parent_item_id'];
1212
        $lp = $row['lp_id'];
1213
        // Delete children items.
1214
        $this->delete_children_items($id);
1215
        // Now delete the item.
1216
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1217
        Database::query($sql_del);
1218
        // Now update surrounding items.
1219
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1220
                    WHERE iid = $previous";
1221
        Database::query($sql_upd);
1222
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1223
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1224
        Database::query($sql_upd);
1225
        // Now update all following items with new display order.
1226
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1227
                    WHERE
1228
                        c_id = $course_id AND
1229
                        lp_id = $lp AND
1230
                        parent_item_id = $parent AND
1231
                        display_order > $display";
1232
        Database::query($sql_all);
1233
1234
        //Removing prerequisites since the item will not longer exist
1235
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1236
                    WHERE c_id = $course_id AND prerequisite = $id";
1237
        Database::query($sql_all);
1238
1239
        $sql = "UPDATE $lp_item
1240
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1241
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1242
        Database::query($sql);
1243
1244
        // Remove from search engine if enabled.
1245
        if (api_get_setting('search_enabled') === 'true') {
1246
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1247
            $sql = 'SELECT * FROM %s
1248
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1249
                    LIMIT 1';
1250
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1251
            $res = Database::query($sql);
1252
            if (Database::num_rows($res) > 0) {
1253
                $row2 = Database::fetch_array($res);
1254
                $di = new ChamiloIndexer();
1255
                $di->remove_document($row2['search_did']);
1256
            }
1257
            $sql = 'DELETE FROM %s
1258
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1259
                    LIMIT 1';
1260
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1261
            Database::query($sql);
1262
        }
1263
    }
1264
1265
    /**
1266
     * Updates an item's content in place.
1267
     *
1268
     * @param int    $id               Element ID
1269
     * @param int    $parent           Parent item ID
1270
     * @param int    $previous         Previous item ID
1271
     * @param string $title            Item title
1272
     * @param string $description      Item description
1273
     * @param string $prerequisites    Prerequisites (optional)
1274
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1275
     * @param int    $max_time_allowed
1276
     * @param string $url
1277
     *
1278
     * @return bool True on success, false on error
1279
     */
1280
    public function edit_item(
1281
        $id,
1282
        $parent,
1283
        $previous,
1284
        $title,
1285
        $description,
1286
        $prerequisites = '0',
1287
        $audio = [],
1288
        $max_time_allowed = 0,
1289
        $url = ''
1290
    ) {
1291
        $course_id = api_get_course_int_id();
1292
        $_course = api_get_course_info();
1293
        $id = (int) $id;
1294
1295
        if (empty($max_time_allowed)) {
1296
            $max_time_allowed = 0;
1297
        }
1298
1299
        if (empty($id) || empty($_course)) {
1300
            return false;
1301
        }
1302
1303
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1304
        $sql = "SELECT * FROM $tbl_lp_item
1305
                WHERE iid = $id";
1306
        $res_select = Database::query($sql);
1307
        $row_select = Database::fetch_array($res_select);
1308
        $audio_update_sql = '';
1309
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1310
            // Create the audio folder if it does not exist yet.
1311
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1312
            if (!is_dir($filepath.'audio')) {
1313
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1314
                $audio_id = add_document(
1315
                    $_course,
1316
                    '/audio',
1317
                    'folder',
1318
                    0,
1319
                    'audio'
1320
                );
1321
                api_item_property_update(
1322
                    $_course,
1323
                    TOOL_DOCUMENT,
1324
                    $audio_id,
1325
                    'FolderCreated',
1326
                    api_get_user_id(),
1327
                    null,
1328
                    null,
1329
                    null,
1330
                    null,
1331
                    api_get_session_id()
1332
                );
1333
                api_item_property_update(
1334
                    $_course,
1335
                    TOOL_DOCUMENT,
1336
                    $audio_id,
1337
                    'invisible',
1338
                    api_get_user_id(),
1339
                    null,
1340
                    null,
1341
                    null,
1342
                    null,
1343
                    api_get_session_id()
1344
                );
1345
            }
1346
1347
            // Upload file in documents.
1348
            $pi = pathinfo($audio['name']);
1349
            if ($pi['extension'] === 'mp3') {
1350
                $c_det = api_get_course_info($this->cc);
1351
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1352
                $path = handle_uploaded_document(
1353
                    $c_det,
1354
                    $audio,
1355
                    $bp,
1356
                    '/audio',
1357
                    api_get_user_id(),
1358
                    0,
1359
                    null,
1360
                    0,
1361
                    'rename',
1362
                    false,
1363
                    0
1364
                );
1365
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1366
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1367
            }
1368
        }
1369
1370
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1371
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1372
1373
        // TODO: htmlspecialchars to be checked for encoding related problems.
1374
        if ($same_parent && $same_previous) {
1375
            // Only update title and description.
1376
            $sql = "UPDATE $tbl_lp_item
1377
                    SET title = '".Database::escape_string($title)."',
1378
                        prerequisite = '".$prerequisites."',
1379
                        description = '".Database::escape_string($description)."'
1380
                        ".$audio_update_sql.",
1381
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1382
                    WHERE iid = $id";
1383
            Database::query($sql);
1384
        } else {
1385
            $old_parent = $row_select['parent_item_id'];
1386
            $old_previous = $row_select['previous_item_id'];
1387
            $old_next = $row_select['next_item_id'];
1388
            $old_order = $row_select['display_order'];
1389
            $old_prerequisite = $row_select['prerequisite'];
1390
            $old_max_time_allowed = $row_select['max_time_allowed'];
1391
1392
            /* BEGIN -- virtually remove the current item id */
1393
            /* for the next and previous item it is like the current item doesn't exist anymore */
1394
            if ($old_previous != 0) {
1395
                // Next
1396
                $sql = "UPDATE $tbl_lp_item
1397
                        SET next_item_id = $old_next
1398
                        WHERE iid = $old_previous";
1399
                Database::query($sql);
1400
            }
1401
1402
            if (!empty($old_next)) {
1403
                // Previous
1404
                $sql = "UPDATE $tbl_lp_item
1405
                        SET previous_item_id = $old_previous
1406
                        WHERE iid = $old_next";
1407
                Database::query($sql);
1408
            }
1409
1410
            // display_order - 1 for every item with a display_order
1411
            // bigger then the display_order of the current item.
1412
            $sql = "UPDATE $tbl_lp_item
1413
                    SET display_order = display_order - 1
1414
                    WHERE
1415
                        c_id = $course_id AND
1416
                        display_order > $old_order AND
1417
                        lp_id = ".$this->lp_id." AND
1418
                        parent_item_id = $old_parent";
1419
            Database::query($sql);
1420
            /* END -- virtually remove the current item id */
1421
1422
            /* BEGIN -- update the current item id to his new location */
1423
            if ($previous == 0) {
1424
                // Select the data of the item that should come after the current item.
1425
                $sql = "SELECT id, display_order
1426
                        FROM $tbl_lp_item
1427
                        WHERE
1428
                            c_id = $course_id AND
1429
                            lp_id = ".$this->lp_id." AND
1430
                            parent_item_id = $parent AND
1431
                            previous_item_id = $previous";
1432
                $res_select_old = Database::query($sql);
1433
                $row_select_old = Database::fetch_array($res_select_old);
1434
1435
                // If the new parent didn't have children before.
1436
                if (Database::num_rows($res_select_old) == 0) {
1437
                    $new_next = 0;
1438
                    $new_order = 1;
1439
                } else {
1440
                    $new_next = $row_select_old['id'];
1441
                    $new_order = $row_select_old['display_order'];
1442
                }
1443
            } else {
1444
                // Select the data of the item that should come before the current item.
1445
                $sql = "SELECT next_item_id, display_order
1446
                        FROM $tbl_lp_item
1447
                        WHERE iid = $previous";
1448
                $res_select_old = Database::query($sql);
1449
                $row_select_old = Database::fetch_array($res_select_old);
1450
                $new_next = $row_select_old['next_item_id'];
1451
                $new_order = $row_select_old['display_order'] + 1;
1452
            }
1453
1454
            // TODO: htmlspecialchars to be checked for encoding related problems.
1455
            // Update the current item with the new data.
1456
            $sql = "UPDATE $tbl_lp_item
1457
                    SET
1458
                        title = '".Database::escape_string($title)."',
1459
                        description = '".Database::escape_string($description)."',
1460
                        parent_item_id = $parent,
1461
                        previous_item_id = $previous,
1462
                        next_item_id = $new_next,
1463
                        display_order = $new_order
1464
                        $audio_update_sql
1465
                    WHERE iid = $id";
1466
            Database::query($sql);
1467
1468
            if ($previous != 0) {
1469
                // Update the previous item's next_item_id.
1470
                $sql = "UPDATE $tbl_lp_item
1471
                        SET next_item_id = $id
1472
                        WHERE iid = $previous";
1473
                Database::query($sql);
1474
            }
1475
1476
            if (!empty($new_next)) {
1477
                // Update the next item's previous_item_id.
1478
                $sql = "UPDATE $tbl_lp_item
1479
                        SET previous_item_id = $id
1480
                        WHERE iid = $new_next";
1481
                Database::query($sql);
1482
            }
1483
1484
            if ($old_prerequisite != $prerequisites) {
1485
                $sql = "UPDATE $tbl_lp_item
1486
                        SET prerequisite = '$prerequisites'
1487
                        WHERE iid = $id";
1488
                Database::query($sql);
1489
            }
1490
1491
            if ($old_max_time_allowed != $max_time_allowed) {
1492
                // update max time allowed
1493
                $sql = "UPDATE $tbl_lp_item
1494
                        SET max_time_allowed = $max_time_allowed
1495
                        WHERE iid = $id";
1496
                Database::query($sql);
1497
            }
1498
1499
            // Update all the items with the same or a bigger display_order than the current item.
1500
            $sql = "UPDATE $tbl_lp_item
1501
                    SET display_order = display_order + 1
1502
                    WHERE
1503
                       c_id = $course_id AND
1504
                       lp_id = ".$this->get_id()." AND
1505
                       iid <> $id AND
1506
                       parent_item_id = $parent AND
1507
                       display_order >= $new_order";
1508
            Database::query($sql);
1509
        }
1510
1511
        if ($row_select['item_type'] == 'link') {
1512
            $link = new Link();
1513
            $linkId = $row_select['path'];
1514
            $link->updateLink($linkId, $url);
1515
        }
1516
    }
1517
1518
    /**
1519
     * Updates an item's prereq in place.
1520
     *
1521
     * @param int    $id              Element ID
1522
     * @param string $prerequisite_id Prerequisite Element ID
1523
     * @param int    $minScore        Prerequisite min score
1524
     * @param int    $maxScore        Prerequisite max score
1525
     *
1526
     * @return bool True on success, false on error
1527
     */
1528
    public function edit_item_prereq(
1529
        $id,
1530
        $prerequisite_id,
1531
        $minScore = 0,
1532
        $maxScore = 100
1533
    ) {
1534
        $id = (int) $id;
1535
1536
        if (empty($id)) {
1537
            return false;
1538
        }
1539
1540
        $prerequisite_id = (int) $prerequisite_id;
1541
1542
        if (empty($minScore) || $minScore < 0) {
1543
            $minScore = 0;
1544
        }
1545
1546
        if (empty($maxScore) || $maxScore < 0) {
1547
            $maxScore = 100;
1548
        }
1549
1550
        $minScore = (float) $minScore;
1551
        $maxScore = (float) $maxScore;
1552
1553
        if (empty($prerequisite_id)) {
1554
            $prerequisite_id = 'NULL';
1555
            $minScore = 0;
1556
            $maxScore = 100;
1557
        }
1558
1559
        $table = Database::get_course_table(TABLE_LP_ITEM);
1560
        $sql = " UPDATE $table
1561
                 SET
1562
                    prerequisite = $prerequisite_id ,
1563
                    prerequisite_min_score = $minScore ,
1564
                    prerequisite_max_score = $maxScore
1565
                 WHERE iid = $id";
1566
        Database::query($sql);
1567
1568
        return true;
1569
    }
1570
1571
    /**
1572
     * Get the specific prefix index terms of this learning path.
1573
     *
1574
     * @param string $prefix
1575
     *
1576
     * @return array Array of terms
1577
     */
1578
    public function get_common_index_terms_by_prefix($prefix)
1579
    {
1580
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1581
        $terms = get_specific_field_values_list_by_prefix(
1582
            $prefix,
1583
            $this->cc,
1584
            TOOL_LEARNPATH,
1585
            $this->lp_id
1586
        );
1587
        $prefix_terms = [];
1588
        if (!empty($terms)) {
1589
            foreach ($terms as $term) {
1590
                $prefix_terms[] = $term['value'];
1591
            }
1592
        }
1593
1594
        return $prefix_terms;
1595
    }
1596
1597
    /**
1598
     * Gets the number of items currently completed.
1599
     *
1600
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1601
     *
1602
     * @return int The number of items currently completed
1603
     */
1604
    public function get_complete_items_count($failedStatusException = false)
1605
    {
1606
        $i = 0;
1607
        $completedStatusList = [
1608
            'completed',
1609
            'passed',
1610
            'succeeded',
1611
            'browsed',
1612
        ];
1613
1614
        if (!$failedStatusException) {
1615
            $completedStatusList[] = 'failed';
1616
        }
1617
1618
        foreach ($this->items as $id => $dummy) {
1619
            // Trying failed and browsed considered "progressed" as well.
1620
            if ($this->items[$id]->status_is($completedStatusList) &&
1621
                $this->items[$id]->get_type() != 'dir'
1622
            ) {
1623
                $i++;
1624
            }
1625
        }
1626
1627
        return $i;
1628
    }
1629
1630
    /**
1631
     * Gets the current item ID.
1632
     *
1633
     * @return int The current learnpath item id
1634
     */
1635
    public function get_current_item_id()
1636
    {
1637
        $current = 0;
1638
        if (!empty($this->current)) {
1639
            $current = (int) $this->current;
1640
        }
1641
1642
        return $current;
1643
    }
1644
1645
    /**
1646
     * Force to get the first learnpath item id.
1647
     *
1648
     * @return int The current learnpath item id
1649
     */
1650
    public function get_first_item_id()
1651
    {
1652
        $current = 0;
1653
        if (is_array($this->ordered_items)) {
1654
            $current = $this->ordered_items[0];
1655
        }
1656
1657
        return $current;
1658
    }
1659
1660
    /**
1661
     * Gets the total number of items available for viewing in this SCORM.
1662
     *
1663
     * @return int The total number of items
1664
     */
1665
    public function get_total_items_count()
1666
    {
1667
        return count($this->items);
1668
    }
1669
1670
    /**
1671
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1672
     *
1673
     * @return int The total no-chapters number of items
1674
     */
1675
    public function getTotalItemsCountWithoutDirs()
1676
    {
1677
        $total = 0;
1678
        $typeListNotToCount = self::getChapterTypes();
1679
        foreach ($this->items as $temp2) {
1680
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1681
                $total++;
1682
            }
1683
        }
1684
1685
        return $total;
1686
    }
1687
1688
    /**
1689
     *  Sets the first element URL.
1690
     */
1691
    public function first()
1692
    {
1693
        if ($this->debug > 0) {
1694
            error_log('In learnpath::first()', 0);
1695
            error_log('$this->last_item_seen '.$this->last_item_seen);
1696
        }
1697
1698
        // Test if the last_item_seen exists and is not a dir.
1699
        if (count($this->ordered_items) == 0) {
1700
            $this->index = 0;
1701
        }
1702
1703
        if (!empty($this->last_item_seen) &&
1704
            !empty($this->items[$this->last_item_seen]) &&
1705
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1706
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1707
            //&& !$this->items[$this->last_item_seen]->is_done()
1708
        ) {
1709
            if ($this->debug > 2) {
1710
                error_log(
1711
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1712
                    $this->items[$this->last_item_seen]->get_type()
1713
                );
1714
            }
1715
            $index = -1;
1716
            foreach ($this->ordered_items as $myindex => $item_id) {
1717
                if ($item_id == $this->last_item_seen) {
1718
                    $index = $myindex;
1719
                    break;
1720
                }
1721
            }
1722
            if ($index == -1) {
1723
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1724
                if ($this->debug > 2) {
1725
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1726
                }
1727
1728
                return false;
1729
            } else {
1730
                $this->last = $this->last_item_seen;
1731
                $this->current = $this->last_item_seen;
1732
                $this->index = $index;
1733
            }
1734
        } else {
1735
            if ($this->debug > 2) {
1736
                error_log('In learnpath::first() - No last item seen', 0);
1737
            }
1738
            $index = 0;
1739
            // Loop through all ordered items and stop at the first item that is
1740
            // not a directory *and* that has not been completed yet.
1741
            while (!empty($this->ordered_items[$index]) &&
1742
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1743
                (
1744
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1745
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1746
                ) && $index < $this->max_ordered_items) {
1747
                $index++;
1748
            }
1749
1750
            $this->last = $this->current;
1751
            // current is
1752
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1753
            $this->index = $index;
1754
            if ($this->debug > 2) {
1755
                error_log('$index '.$index);
1756
                error_log('In learnpath::first() - No last item seen');
1757
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1758
            }
1759
        }
1760
        if ($this->debug > 2) {
1761
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1762
        }
1763
    }
1764
1765
    /**
1766
     * Gets the js library from the database.
1767
     *
1768
     * @return string The name of the javascript library to be used
1769
     */
1770
    public function get_js_lib()
1771
    {
1772
        $lib = '';
1773
        if (!empty($this->js_lib)) {
1774
            $lib = $this->js_lib;
1775
        }
1776
1777
        return $lib;
1778
    }
1779
1780
    /**
1781
     * Gets the learnpath database ID.
1782
     *
1783
     * @return int Learnpath ID in the lp table
1784
     */
1785
    public function get_id()
1786
    {
1787
        if (!empty($this->lp_id)) {
1788
            return (int) $this->lp_id;
1789
        }
1790
1791
        return 0;
1792
    }
1793
1794
    /**
1795
     * Gets the last element URL.
1796
     *
1797
     * @return string URL to load into the viewer
1798
     */
1799
    public function get_last()
1800
    {
1801
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1802
        if (count($this->ordered_items) > 0) {
1803
            $this->index = count($this->ordered_items) - 1;
1804
1805
            return $this->ordered_items[$this->index];
1806
        }
1807
1808
        return false;
1809
    }
1810
1811
    /**
1812
     * Get the last element in the first level.
1813
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1814
     *
1815
     * @return mixed
1816
     */
1817
    public function getLastInFirstLevel()
1818
    {
1819
        try {
1820
            $lastId = Database::getManager()
1821
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1822
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1823
                ->setMaxResults(1)
1824
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1825
                ->getSingleScalarResult();
1826
1827
            return $lastId;
1828
        } catch (Exception $exception) {
1829
            return 0;
1830
        }
1831
    }
1832
1833
    /**
1834
     * Get the learning path name by id.
1835
     *
1836
     * @param int $lpId
1837
     *
1838
     * @return mixed
1839
     */
1840
    public static function getLpNameById($lpId)
1841
    {
1842
        $em = Database::getManager();
1843
1844
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1845
            WHERE clp.iid = :iid')
1846
            ->setParameter('iid', $lpId)
1847
            ->getSingleScalarResult();
1848
    }
1849
1850
    /**
1851
     * Gets the navigation bar for the learnpath display screen.
1852
     *
1853
     * @param string $barId
1854
     *
1855
     * @return string The HTML string to use as a navigation bar
1856
     */
1857
    public function get_navigation_bar($barId = '')
1858
    {
1859
        if (empty($barId)) {
1860
            $barId = 'control-top';
1861
        }
1862
        $lpId = $this->lp_id;
1863
        $mycurrentitemid = $this->get_current_item_id();
1864
1865
        $reportingText = get_lang('Reporting');
1866
        $previousText = get_lang('ScormPrevious');
1867
        $nextText = get_lang('ScormNext');
1868
        $fullScreenText = get_lang('ScormExitFullScreen');
1869
1870
        $settings = api_get_configuration_value('lp_view_settings');
1871
        $display = isset($settings['display']) ? $settings['display'] : false;
1872
        $reportingIcon = '
1873
            <a class="icon-toolbar"
1874
                id="stats_link"
1875
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1876
                onclick="window.parent.API.save_asset(); return true;"
1877
                target="content_name" title="'.$reportingText.'">
1878
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1879
            </a>';
1880
1881
        if (!empty($display)) {
1882
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1883
            if ($showReporting === false) {
1884
                $reportingIcon = '';
1885
            }
1886
        }
1887
1888
        $hideArrows = false;
1889
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1890
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1891
        }
1892
1893
        $previousIcon = '';
1894
        $nextIcon = '';
1895
        if ($hideArrows === false) {
1896
            $previousIcon = '
1897
                <a class="icon-toolbar" id="scorm-previous" href="#"
1898
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1899
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1900
                </a>';
1901
1902
            $nextIcon = '
1903
                <a class="icon-toolbar" id="scorm-next" href="#"
1904
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1905
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1906
                </a>';
1907
        }
1908
1909
        if ($this->mode === 'fullscreen') {
1910
            $navbar = '
1911
                  <span id="'.$barId.'" class="buttons">
1912
                    '.$reportingIcon.'
1913
                    '.$previousIcon.'
1914
                    '.$nextIcon.'
1915
                    <a class="icon-toolbar" id="view-embedded"
1916
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1917
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1918
                    </a>
1919
                  </span>';
1920
        } else {
1921
            $navbar = '
1922
                 <span id="'.$barId.'" class="buttons text-right">
1923
                    '.$reportingIcon.'
1924
                    '.$previousIcon.'
1925
                    '.$nextIcon.'
1926
                </span>';
1927
        }
1928
1929
        return $navbar;
1930
    }
1931
1932
    /**
1933
     * Gets the next resource in queue (url).
1934
     *
1935
     * @return string URL to load into the viewer
1936
     */
1937
    public function get_next_index()
1938
    {
1939
        // TODO
1940
        $index = $this->index;
1941
        $index++;
1942
        while (
1943
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1944
            $index < $this->max_ordered_items
1945
        ) {
1946
            $index++;
1947
            if ($index == $this->max_ordered_items) {
1948
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1949
                    return $this->index;
1950
                }
1951
1952
                return $index;
1953
            }
1954
        }
1955
        if (empty($this->ordered_items[$index])) {
1956
            return $this->index;
1957
        }
1958
1959
        return $index;
1960
    }
1961
1962
    /**
1963
     * Gets item_id for the next element.
1964
     *
1965
     * @return int Next item (DB) ID
1966
     */
1967
    public function get_next_item_id()
1968
    {
1969
        $new_index = $this->get_next_index();
1970
        if (!empty($new_index)) {
1971
            if (isset($this->ordered_items[$new_index])) {
1972
                return $this->ordered_items[$new_index];
1973
            }
1974
        }
1975
1976
        return 0;
1977
    }
1978
1979
    /**
1980
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1981
     *
1982
     * Generally, the package provided is in the form of a zip file, so the function
1983
     * has been written to test a zip file. If not a zip, the function will return the
1984
     * default return value: ''
1985
     *
1986
     * @param string $file_path the path to the file
1987
     * @param string $file_name the original name of the file
1988
     *
1989
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1990
     */
1991
    public static function getPackageType($file_path, $file_name)
1992
    {
1993
        // Get name of the zip file without the extension.
1994
        $file_info = pathinfo($file_name);
1995
        $extension = $file_info['extension']; // Extension only.
1996
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1997
                'dll',
1998
                'exe',
1999
            ])) {
2000
            return 'oogie';
2001
        }
2002
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2003
                'dll',
2004
                'exe',
2005
            ])) {
2006
            return 'woogie';
2007
        }
2008
2009
        $zipFile = new PclZip($file_path);
2010
        // Check the zip content (real size and file extension).
2011
        $zipContentArray = $zipFile->listContent();
2012
        $package_type = '';
2013
        $manifest = '';
2014
        $aicc_match_crs = 0;
2015
        $aicc_match_au = 0;
2016
        $aicc_match_des = 0;
2017
        $aicc_match_cst = 0;
2018
        $countItems = 0;
2019
2020
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2021
        if (is_array($zipContentArray)) {
2022
            $countItems = count($zipContentArray);
2023
            if ($countItems > 0) {
2024
                foreach ($zipContentArray as $thisContent) {
2025
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2026
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2027
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2028
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2029
                        $package_type = 'scorm';
2030
                        break; // Exit the foreach loop.
2031
                    } elseif (
2032
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2033
                        in_array(
2034
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2035
                            ['crs', 'au', 'des', 'cst']
2036
                        )
2037
                    ) {
2038
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2039
                        switch ($ext) {
2040
                            case 'crs':
2041
                                $aicc_match_crs = 1;
2042
                                break;
2043
                            case 'au':
2044
                                $aicc_match_au = 1;
2045
                                break;
2046
                            case 'des':
2047
                                $aicc_match_des = 1;
2048
                                break;
2049
                            case 'cst':
2050
                                $aicc_match_cst = 1;
2051
                                break;
2052
                            default:
2053
                                break;
2054
                        }
2055
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2056
                    } else {
2057
                        $package_type = '';
2058
                    }
2059
                }
2060
            }
2061
        }
2062
2063
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2064
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2065
            $package_type = 'aicc';
2066
        }
2067
2068
        // Try with chamilo course builder
2069
        if (empty($package_type)) {
2070
            // Sometimes users will try to upload an empty zip, or a zip with
2071
            // only a folder. Catch that and make the calling function aware.
2072
            // If the single file was the imsmanifest.xml, then $package_type
2073
            // would be 'scorm' and we wouldn't be here.
2074
            if ($countItems < 2) {
2075
                return 'error-empty-package';
2076
            }
2077
            $package_type = 'chamilo';
2078
        }
2079
2080
        return $package_type;
2081
    }
2082
2083
    /**
2084
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2085
     *
2086
     * @return string URL to load into the viewer
2087
     */
2088
    public function get_previous_index()
2089
    {
2090
        $index = $this->index;
2091
        if (isset($this->ordered_items[$index - 1])) {
2092
            $index--;
2093
            while (isset($this->ordered_items[$index]) &&
2094
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2095
            ) {
2096
                $index--;
2097
                if ($index < 0) {
2098
                    return $this->index;
2099
                }
2100
            }
2101
        }
2102
2103
        return $index;
2104
    }
2105
2106
    /**
2107
     * Gets item_id for the next element.
2108
     *
2109
     * @return int Previous item (DB) ID
2110
     */
2111
    public function get_previous_item_id()
2112
    {
2113
        $index = $this->get_previous_index();
2114
2115
        return $this->ordered_items[$index];
2116
    }
2117
2118
    /**
2119
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2120
     *
2121
     * @param int    $lpItemId
2122
     * @param string $autostart
2123
     *
2124
     * @return string The mediaplayer HTML
2125
     */
2126
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2127
    {
2128
        $course_id = api_get_course_int_id();
2129
        $courseInfo = api_get_course_info();
2130
        $lpItemId = (int) $lpItemId;
2131
2132
        if (empty($courseInfo) || empty($lpItemId)) {
2133
            return '';
2134
        }
2135
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2136
2137
        if (empty($item)) {
2138
            return '';
2139
        }
2140
2141
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2142
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2143
        $itemViewId = (int) $item->db_item_view_id;
2144
2145
        // Getting all the information about the item.
2146
        $sql = "SELECT lp_view.status
2147
                FROM $tbl_lp_item as lpi
2148
                INNER JOIN $tbl_lp_item_view as lp_view
2149
                ON (lpi.iid = lp_view.lp_item_id)
2150
                WHERE
2151
                    lp_view.iid = $itemViewId AND
2152
                    lpi.iid = $lpItemId AND
2153
                    lp_view.c_id = $course_id";
2154
        $result = Database::query($sql);
2155
        $row = Database::fetch_assoc($result);
2156
        $output = '';
2157
        $audio = $item->audio;
2158
2159
        if (!empty($audio)) {
2160
            $list = $_SESSION['oLP']->get_toc();
2161
2162
            switch ($item->get_type()) {
2163
                case 'quiz':
2164
                    $type_quiz = false;
2165
                    foreach ($list as $toc) {
2166
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2167
                            $type_quiz = true;
2168
                        }
2169
                    }
2170
2171
                    if ($type_quiz) {
2172
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2173
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2174
                        } else {
2175
                            $autostart_audio = $autostart;
2176
                        }
2177
                    }
2178
                    break;
2179
                case TOOL_READOUT_TEXT:
2180
                    $autostart_audio = 'false';
2181
                    break;
2182
                default:
2183
                    $autostart_audio = 'true';
2184
            }
2185
2186
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2187
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2188
2189
            $player = Display::getMediaPlayer(
2190
                $file,
2191
                [
2192
                    'id' => 'lp_audio_media_player',
2193
                    'url' => $url,
2194
                    'autoplay' => $autostart_audio,
2195
                    'width' => '100%',
2196
                ]
2197
            );
2198
2199
            // The mp3 player.
2200
            $output = '<div id="container">';
2201
            $output .= $player;
2202
            $output .= '</div>';
2203
        }
2204
2205
        return $output;
2206
    }
2207
2208
    /**
2209
     * @param int   $studentId
2210
     * @param int   $prerequisite
2211
     * @param array $courseInfo
2212
     * @param int   $sessionId
2213
     *
2214
     * @return bool
2215
     */
2216
    public static function isBlockedByPrerequisite(
2217
        $studentId,
2218
        $prerequisite,
2219
        $courseInfo,
2220
        $sessionId
2221
    ) {
2222
        if (empty($courseInfo)) {
2223
            return false;
2224
        }
2225
2226
        $courseId = $courseInfo['real_id'];
2227
2228
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2229
        if ($allow) {
2230
            if (api_is_allowed_to_edit() ||
2231
                api_is_platform_admin(true) ||
2232
                api_is_drh() ||
2233
                api_is_coach($sessionId, $courseId, false)
2234
            ) {
2235
                return false;
2236
            }
2237
        }
2238
2239
        $isBlocked = false;
2240
        if (!empty($prerequisite)) {
2241
            $progress = self::getProgress(
2242
                $prerequisite,
2243
                $studentId,
2244
                $courseId,
2245
                $sessionId
2246
            );
2247
            if ($progress < 100) {
2248
                $isBlocked = true;
2249
            }
2250
2251
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2252
                // Block if it does not exceed minimum time
2253
                // Minimum time (in minutes) to pass the learning path
2254
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2255
2256
                if ($accumulateWorkTime > 0) {
2257
                    // Total time in course (sum of times in learning paths from course)
2258
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2259
2260
                    // Connect with the plugin_licences_course_session table
2261
                    // which indicates what percentage of the time applies
2262
                    // Minimum connection percentage
2263
                    $perc = 100;
2264
                    // Time from the course
2265
                    $tc = $accumulateWorkTimeTotal;
2266
2267
                    // Percentage of the learning paths
2268
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2269
                    // Minimum time for each learning path
2270
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2271
2272
                    // Spent time (in seconds) so far in the learning path
2273
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2274
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2275
2276
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2277
                        $isBlocked = true;
2278
                    }
2279
                }
2280
            }
2281
        }
2282
2283
        return $isBlocked;
2284
    }
2285
2286
    /**
2287
     * Checks if the learning path is visible for student after the progress
2288
     * of its prerequisite is completed, considering the time availability and
2289
     * the LP visibility.
2290
     *
2291
     * @param int   $lp_id
2292
     * @param int   $student_id
2293
     * @param array $courseInfo
2294
     * @param int   $sessionId
2295
     *
2296
     * @return bool
2297
     */
2298
    public static function is_lp_visible_for_student(
2299
        $lp_id,
2300
        $student_id,
2301
        $courseInfo = [],
2302
        $sessionId = 0
2303
    ) {
2304
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2305
        $lp_id = (int) $lp_id;
2306
        $sessionId = (int) $sessionId;
2307
2308
        if (empty($courseInfo)) {
2309
            return false;
2310
        }
2311
2312
        if (empty($sessionId)) {
2313
            $sessionId = api_get_session_id();
2314
        }
2315
2316
        $courseId = $courseInfo['real_id'];
2317
2318
        $itemInfo = api_get_item_property_info(
2319
            $courseId,
2320
            TOOL_LEARNPATH,
2321
            $lp_id,
2322
            $sessionId
2323
        );
2324
2325
        // If the item was deleted.
2326
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2327
            return false;
2328
        }
2329
2330
        // @todo remove this query and load the row info as a parameter
2331
        $table = Database::get_course_table(TABLE_LP_MAIN);
2332
        // Get current prerequisite
2333
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2334
                FROM $table
2335
                WHERE iid = $lp_id";
2336
        $rs = Database::query($sql);
2337
        $now = time();
2338
        if (Database::num_rows($rs) > 0) {
2339
            $row = Database::fetch_array($rs, 'ASSOC');
2340
2341
            if (!empty($row['category_id'])) {
2342
                $category = self::getCategory($row['category_id']);
2343
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2344
                    return false;
2345
                }
2346
            }
2347
2348
            $prerequisite = $row['prerequisite'];
2349
            $is_visible = true;
2350
2351
            $isBlocked = self::isBlockedByPrerequisite(
2352
                $student_id,
2353
                $prerequisite,
2354
                $courseInfo,
2355
                $sessionId
2356
            );
2357
2358
            if ($isBlocked) {
2359
                $is_visible = false;
2360
            }
2361
2362
            // Also check the time availability of the LP
2363
            if ($is_visible) {
2364
                // Adding visibility restrictions
2365
                if (!empty($row['publicated_on'])) {
2366
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2367
                        $is_visible = false;
2368
                    }
2369
                }
2370
                // Blocking empty start times see BT#2800
2371
                global $_custom;
2372
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2373
                    $_custom['lps_hidden_when_no_start_date']
2374
                ) {
2375
                    if (empty($row['publicated_on'])) {
2376
                        $is_visible = false;
2377
                    }
2378
                }
2379
2380
                if (!empty($row['expired_on'])) {
2381
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2382
                        $is_visible = false;
2383
                    }
2384
                }
2385
            }
2386
2387
            if ($is_visible) {
2388
                $subscriptionSettings = self::getSubscriptionSettings();
2389
2390
                // Check if the subscription users/group to a LP is ON
2391
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2392
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2393
                ) {
2394
                    // Try group
2395
                    $is_visible = false;
2396
                    // Checking only the user visibility
2397
                    $userVisibility = api_get_item_visibility(
2398
                        $courseInfo,
2399
                        'learnpath',
2400
                        $row['id'],
2401
                        $sessionId,
2402
                        $student_id,
2403
                        'LearnpathSubscription'
2404
                    );
2405
2406
                    if ($userVisibility == 1) {
2407
                        $is_visible = true;
2408
                    } else {
2409
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2410
                        if (!empty($userGroups)) {
2411
                            foreach ($userGroups as $groupInfo) {
2412
                                $groupId = $groupInfo['iid'];
2413
                                $userVisibility = api_get_item_visibility(
2414
                                    $courseInfo,
2415
                                    'learnpath',
2416
                                    $row['id'],
2417
                                    $sessionId,
2418
                                    null,
2419
                                    'LearnpathSubscription',
2420
                                    $groupId
2421
                                );
2422
2423
                                if ($userVisibility == 1) {
2424
                                    $is_visible = true;
2425
                                    break;
2426
                                }
2427
                            }
2428
                        }
2429
                    }
2430
                }
2431
            }
2432
2433
            return $is_visible;
2434
        }
2435
2436
        return false;
2437
    }
2438
2439
    /**
2440
     * @param int $lpId
2441
     * @param int $userId
2442
     * @param int $courseId
2443
     * @param int $sessionId
2444
     *
2445
     * @return int
2446
     */
2447
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2448
    {
2449
        $lpId = (int) $lpId;
2450
        $userId = (int) $userId;
2451
        $courseId = (int) $courseId;
2452
        $sessionId = (int) $sessionId;
2453
2454
        $sessionCondition = api_get_session_condition($sessionId);
2455
        $table = Database::get_course_table(TABLE_LP_VIEW);
2456
        $sql = "SELECT progress FROM $table
2457
                WHERE
2458
                    c_id = $courseId AND
2459
                    lp_id = $lpId AND
2460
                    user_id = $userId $sessionCondition ";
2461
        $res = Database::query($sql);
2462
2463
        $progress = 0;
2464
        if (Database::num_rows($res) > 0) {
2465
            $row = Database::fetch_array($res);
2466
            $progress = (int) $row['progress'];
2467
        }
2468
2469
        return $progress;
2470
    }
2471
2472
    /**
2473
     * @param array $lpList
2474
     * @param int   $userId
2475
     * @param int   $courseId
2476
     * @param int   $sessionId
2477
     *
2478
     * @return array
2479
     */
2480
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2481
    {
2482
        $lpList = array_map('intval', $lpList);
2483
        if (empty($lpList)) {
2484
            return [];
2485
        }
2486
2487
        $lpList = implode("','", $lpList);
2488
2489
        $userId = (int) $userId;
2490
        $courseId = (int) $courseId;
2491
        $sessionId = (int) $sessionId;
2492
2493
        $sessionCondition = api_get_session_condition($sessionId);
2494
        $table = Database::get_course_table(TABLE_LP_VIEW);
2495
        $sql = "SELECT lp_id, progress FROM $table
2496
                WHERE
2497
                    c_id = $courseId AND
2498
                    lp_id IN ('".$lpList."') AND
2499
                    user_id = $userId $sessionCondition ";
2500
        $res = Database::query($sql);
2501
2502
        if (Database::num_rows($res) > 0) {
2503
            $list = [];
2504
            while ($row = Database::fetch_array($res)) {
2505
                $list[$row['lp_id']] = $row['progress'];
2506
            }
2507
2508
            return $list;
2509
        }
2510
2511
        return [];
2512
    }
2513
2514
    /**
2515
     * Displays a progress bar
2516
     * completed so far.
2517
     *
2518
     * @param int    $percentage Progress value to display
2519
     * @param string $text_add   Text to display near the progress value
2520
     *
2521
     * @return string HTML string containing the progress bar
2522
     */
2523
    public static function get_progress_bar($percentage = -1, $text_add = '')
2524
    {
2525
        $text = $percentage.$text_add;
2526
        $output = '<div class="progress">
2527
            <div id="progress_bar_value"
2528
                class="progress-bar progress-bar-success" role="progressbar"
2529
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2530
            '.$text.'
2531
            </div>
2532
        </div>';
2533
2534
        return $output;
2535
    }
2536
2537
    /**
2538
     * @param string $mode can be '%' or 'abs'
2539
     *                     otherwise this value will be used $this->progress_bar_mode
2540
     *
2541
     * @return string
2542
     */
2543
    public function getProgressBar($mode = null)
2544
    {
2545
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2546
2547
        return self::get_progress_bar($percentage, $text_add);
2548
    }
2549
2550
    /**
2551
     * Gets the progress bar info to display inside the progress bar.
2552
     * Also used by scorm_api.php.
2553
     *
2554
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2555
     *                     we display a number of completed elements per total elements
2556
     * @param int    $add  Additional steps to fake as completed
2557
     *
2558
     * @return array Percentage or number and symbol (% or /xx)
2559
     */
2560
    public function get_progress_bar_text($mode = '', $add = 0)
2561
    {
2562
        if (empty($mode)) {
2563
            $mode = $this->progress_bar_mode;
2564
        }
2565
        $text = '';
2566
        $percentage = 0;
2567
        // If the option to use the score as progress is set for this learning
2568
        // path, then the rules are completely different: we assume only one
2569
        // item exists and the progress of the LP depends on the score
2570
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2571
        if ($scoreAsProgressSetting === true) {
2572
            $scoreAsProgress = $this->getUseScoreAsProgress();
2573
            if ($scoreAsProgress) {
2574
                // Get single item's score
2575
                $itemId = $this->get_current_item_id();
2576
                $item = $this->getItem($itemId);
2577
                $score = $item->get_score();
2578
                $maxScore = $item->get_max();
2579
                if ($mode = '%') {
2580
                    if (!empty($maxScore)) {
2581
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2582
                    }
2583
                    $percentage = number_format($percentage, 0);
2584
                    $text = '%';
2585
                } else {
2586
                    $percentage = $score;
2587
                    $text = '/'.$maxScore;
2588
                }
2589
2590
                return [$percentage, $text];
2591
            }
2592
        }
2593
        // otherwise just continue the normal processing of progress
2594
        $total_items = $this->getTotalItemsCountWithoutDirs();
2595
        $completeItems = $this->get_complete_items_count();
2596
        if ($add != 0) {
2597
            $completeItems += $add;
2598
        }
2599
        if ($completeItems > $total_items) {
2600
            $completeItems = $total_items;
2601
        }
2602
        if ($mode == '%') {
2603
            if ($total_items > 0) {
2604
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2605
            }
2606
            $percentage = number_format($percentage, 0);
2607
            $text = '%';
2608
        } elseif ($mode === 'abs') {
2609
            $percentage = $completeItems;
2610
            $text = '/'.$total_items;
2611
        }
2612
2613
        return [
2614
            $percentage,
2615
            $text,
2616
        ];
2617
    }
2618
2619
    /**
2620
     * Gets the progress bar mode.
2621
     *
2622
     * @return string The progress bar mode attribute
2623
     */
2624
    public function get_progress_bar_mode()
2625
    {
2626
        if (!empty($this->progress_bar_mode)) {
2627
            return $this->progress_bar_mode;
2628
        }
2629
2630
        return '%';
2631
    }
2632
2633
    /**
2634
     * Gets the learnpath theme (remote or local).
2635
     *
2636
     * @return string Learnpath theme
2637
     */
2638
    public function get_theme()
2639
    {
2640
        if (!empty($this->theme)) {
2641
            return $this->theme;
2642
        }
2643
2644
        return '';
2645
    }
2646
2647
    /**
2648
     * Gets the learnpath session id.
2649
     *
2650
     * @return int
2651
     */
2652
    public function get_lp_session_id()
2653
    {
2654
        if (!empty($this->lp_session_id)) {
2655
            return (int) $this->lp_session_id;
2656
        }
2657
2658
        return 0;
2659
    }
2660
2661
    /**
2662
     * Gets the learnpath image.
2663
     *
2664
     * @return string Web URL of the LP image
2665
     */
2666
    public function get_preview_image()
2667
    {
2668
        if (!empty($this->preview_image)) {
2669
            return $this->preview_image;
2670
        }
2671
2672
        return '';
2673
    }
2674
2675
    /**
2676
     * @param string $size
2677
     * @param string $path_type
2678
     *
2679
     * @return bool|string
2680
     */
2681
    public function get_preview_image_path($size = null, $path_type = 'web')
2682
    {
2683
        $preview_image = $this->get_preview_image();
2684
        if (isset($preview_image) && !empty($preview_image)) {
2685
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2686
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2687
2688
            if (isset($size)) {
2689
                $info = pathinfo($preview_image);
2690
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2691
2692
                if (file_exists($image_sys_path.$image_custom_size)) {
2693
                    if ($path_type == 'web') {
2694
                        return $image_path.$image_custom_size;
2695
                    } else {
2696
                        return $image_sys_path.$image_custom_size;
2697
                    }
2698
                }
2699
            } else {
2700
                if ($path_type == 'web') {
2701
                    return $image_path.$preview_image;
2702
                } else {
2703
                    return $image_sys_path.$preview_image;
2704
                }
2705
            }
2706
        }
2707
2708
        return false;
2709
    }
2710
2711
    /**
2712
     * Gets the learnpath author.
2713
     *
2714
     * @return string LP's author
2715
     */
2716
    public function get_author()
2717
    {
2718
        if (!empty($this->author)) {
2719
            return $this->author;
2720
        }
2721
2722
        return '';
2723
    }
2724
2725
    /**
2726
     * Gets hide table of contents.
2727
     *
2728
     * @return int
2729
     */
2730
    public function getHideTableOfContents()
2731
    {
2732
        return (int) $this->hide_toc_frame;
2733
    }
2734
2735
    /**
2736
     * Generate a new prerequisites string for a given item. If this item was a sco and
2737
     * its prerequisites were strings (instead of IDs), then transform those strings into
2738
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2739
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2740
     * same rule as the scormExport() method.
2741
     *
2742
     * @param int $item_id Item ID
2743
     *
2744
     * @return string Prerequisites string ready for the export as SCORM
2745
     */
2746
    public function get_scorm_prereq_string($item_id)
2747
    {
2748
        if ($this->debug > 0) {
2749
            error_log('In learnpath::get_scorm_prereq_string()');
2750
        }
2751
        if (!is_object($this->items[$item_id])) {
2752
            return false;
2753
        }
2754
        /** @var learnpathItem $oItem */
2755
        $oItem = $this->items[$item_id];
2756
        $prereq = $oItem->get_prereq_string();
2757
2758
        if (empty($prereq)) {
2759
            return '';
2760
        }
2761
        if (preg_match('/^\d+$/', $prereq) &&
2762
            isset($this->items[$prereq]) &&
2763
            is_object($this->items[$prereq])
2764
        ) {
2765
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2766
            // then simply return it (with the ITEM_ prefix).
2767
            //return 'ITEM_' . $prereq;
2768
            return $this->items[$prereq]->ref;
2769
        } else {
2770
            if (isset($this->refs_list[$prereq])) {
2771
                // It's a simple string item from which the ID can be found in the refs list,
2772
                // so we can transform it directly to an ID for export.
2773
                return $this->items[$this->refs_list[$prereq]]->ref;
2774
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2775
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2776
            } else {
2777
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2778
                // and replace them, one by one, by the internal IDs (chamilo db)
2779
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2780
                // by a space as well.
2781
                $find = [
2782
                    '&',
2783
                    '|',
2784
                    '~',
2785
                    '=',
2786
                    '<>',
2787
                    '{',
2788
                    '}',
2789
                    '*',
2790
                    '(',
2791
                    ')',
2792
                ];
2793
                $replace = [
2794
                    ' ',
2795
                    ' ',
2796
                    ' ',
2797
                    ' ',
2798
                    ' ',
2799
                    ' ',
2800
                    ' ',
2801
                    ' ',
2802
                    ' ',
2803
                    ' ',
2804
                ];
2805
                $prereq_mod = str_replace($find, $replace, $prereq);
2806
                $ids = explode(' ', $prereq_mod);
2807
                foreach ($ids as $id) {
2808
                    $id = trim($id);
2809
                    if (isset($this->refs_list[$id])) {
2810
                        $prereq = preg_replace(
2811
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2812
                            'ITEM_'.$this->refs_list[$id],
2813
                            $prereq
2814
                        );
2815
                    }
2816
                }
2817
2818
                return $prereq;
2819
            }
2820
        }
2821
    }
2822
2823
    /**
2824
     * Returns the XML DOM document's node.
2825
     *
2826
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2827
     * @param string   $id       The identifier to look for
2828
     *
2829
     * @return mixed The reference to the element found with that identifier. False if not found
2830
     */
2831
    public function get_scorm_xml_node(&$children, $id)
2832
    {
2833
        for ($i = 0; $i < $children->length; $i++) {
2834
            $item_temp = $children->item($i);
2835
            if ($item_temp->nodeName == 'item') {
2836
                if ($item_temp->getAttribute('identifier') == $id) {
2837
                    return $item_temp;
2838
                }
2839
            }
2840
            $subchildren = $item_temp->childNodes;
2841
            if ($subchildren && $subchildren->length > 0) {
2842
                $val = $this->get_scorm_xml_node($subchildren, $id);
2843
                if (is_object($val)) {
2844
                    return $val;
2845
                }
2846
            }
2847
        }
2848
2849
        return false;
2850
    }
2851
2852
    /**
2853
     * Gets the status list for all LP's items.
2854
     *
2855
     * @return array Array of [index] => [item ID => current status]
2856
     */
2857
    public function get_items_status_list()
2858
    {
2859
        $list = [];
2860
        foreach ($this->ordered_items as $item_id) {
2861
            $list[] = [
2862
                $item_id => $this->items[$item_id]->get_status(),
2863
            ];
2864
        }
2865
2866
        return $list;
2867
    }
2868
2869
    /**
2870
     * Return the number of interactions for the given learnpath Item View ID.
2871
     * This method can be used as static.
2872
     *
2873
     * @param int $lp_iv_id  Item View ID
2874
     * @param int $course_id course id
2875
     *
2876
     * @return int
2877
     */
2878
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2879
    {
2880
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2881
        $lp_iv_id = (int) $lp_iv_id;
2882
        $course_id = (int) $course_id;
2883
2884
        $sql = "SELECT count(*) FROM $table
2885
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2886
        $res = Database::query($sql);
2887
        $num = 0;
2888
        if (Database::num_rows($res)) {
2889
            $row = Database::fetch_array($res);
2890
            $num = $row[0];
2891
        }
2892
2893
        return $num;
2894
    }
2895
2896
    /**
2897
     * Return the interactions as an array for the given lp_iv_id.
2898
     * This method can be used as static.
2899
     *
2900
     * @param int $lp_iv_id Learnpath Item View ID
2901
     *
2902
     * @return array
2903
     *
2904
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2905
     */
2906
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2907
    {
2908
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2909
        $list = [];
2910
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2911
        $lp_iv_id = (int) $lp_iv_id;
2912
2913
        if (empty($lp_iv_id) || empty($course_id)) {
2914
            return [];
2915
        }
2916
2917
        $sql = "SELECT * FROM $table
2918
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2919
                ORDER BY order_id ASC";
2920
        $res = Database::query($sql);
2921
        $num = Database::num_rows($res);
2922
        if ($num > 0) {
2923
            $list[] = [
2924
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2925
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2926
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2927
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2928
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2929
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2930
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2931
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2932
                'student_response_formatted' => '',
2933
            ];
2934
            while ($row = Database::fetch_array($res)) {
2935
                $studentResponseFormatted = urldecode($row['student_response']);
2936
                $content_student_response = explode('__|', $studentResponseFormatted);
2937
                if (count($content_student_response) > 0) {
2938
                    if (count($content_student_response) >= 3) {
2939
                        // Pop the element off the end of array.
2940
                        array_pop($content_student_response);
2941
                    }
2942
                    $studentResponseFormatted = implode(',', $content_student_response);
2943
                }
2944
2945
                $list[] = [
2946
                    'order_id' => $row['order_id'] + 1,
2947
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2948
                    'type' => $row['interaction_type'],
2949
                    'time' => $row['completion_time'],
2950
                    'correct_responses' => '', // Hide correct responses from students.
2951
                    'student_response' => $row['student_response'],
2952
                    'result' => $row['result'],
2953
                    'latency' => $row['latency'],
2954
                    'student_response_formatted' => $studentResponseFormatted,
2955
                ];
2956
            }
2957
        }
2958
2959
        return $list;
2960
    }
2961
2962
    /**
2963
     * Return the number of objectives for the given learnpath Item View ID.
2964
     * This method can be used as static.
2965
     *
2966
     * @param int $lp_iv_id  Item View ID
2967
     * @param int $course_id Course ID
2968
     *
2969
     * @return int Number of objectives
2970
     */
2971
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2972
    {
2973
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2974
        $course_id = (int) $course_id;
2975
        $lp_iv_id = (int) $lp_iv_id;
2976
        $sql = "SELECT count(*) FROM $table
2977
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2978
        //@todo seems that this always returns 0
2979
        $res = Database::query($sql);
2980
        $num = 0;
2981
        if (Database::num_rows($res)) {
2982
            $row = Database::fetch_array($res);
2983
            $num = $row[0];
2984
        }
2985
2986
        return $num;
2987
    }
2988
2989
    /**
2990
     * Return the objectives as an array for the given lp_iv_id.
2991
     * This method can be used as static.
2992
     *
2993
     * @param int $lpItemViewId Learnpath Item View ID
2994
     * @param int $course_id
2995
     *
2996
     * @return array
2997
     *
2998
     * @todo    Translate labels
2999
     */
3000
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3001
    {
3002
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3003
        $lpItemViewId = (int) $lpItemViewId;
3004
3005
        if (empty($course_id) || empty($lpItemViewId)) {
3006
            return [];
3007
        }
3008
3009
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3010
        $sql = "SELECT * FROM $table
3011
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3012
                ORDER BY order_id ASC";
3013
        $res = Database::query($sql);
3014
        $num = Database::num_rows($res);
3015
        $list = [];
3016
        if ($num > 0) {
3017
            $list[] = [
3018
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3019
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3020
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3021
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3022
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3023
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3024
            ];
3025
            while ($row = Database::fetch_array($res)) {
3026
                $list[] = [
3027
                    'order_id' => $row['order_id'] + 1,
3028
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3029
                    'score_raw' => $row['score_raw'],
3030
                    'score_max' => $row['score_max'],
3031
                    'score_min' => $row['score_min'],
3032
                    'status' => $row['status'],
3033
                ];
3034
            }
3035
        }
3036
3037
        return $list;
3038
    }
3039
3040
    /**
3041
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3042
     * used by get_html_toc() to be ready to display.
3043
     *
3044
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3045
     */
3046
    public function get_toc()
3047
    {
3048
        $toc = [];
3049
        foreach ($this->ordered_items as $item_id) {
3050
            // TODO: Change this link generation and use new function instead.
3051
            $toc[] = [
3052
                'id' => $item_id,
3053
                'title' => $this->items[$item_id]->get_title(),
3054
                'status' => $this->items[$item_id]->get_status(),
3055
                'level' => $this->items[$item_id]->get_level(),
3056
                'type' => $this->items[$item_id]->get_type(),
3057
                'description' => $this->items[$item_id]->get_description(),
3058
                'path' => $this->items[$item_id]->get_path(),
3059
                'parent' => $this->items[$item_id]->get_parent(),
3060
            ];
3061
        }
3062
3063
        return $toc;
3064
    }
3065
3066
    /**
3067
     * Returns the CSS class name associated with a given item status.
3068
     *
3069
     * @param $status string an item status
3070
     *
3071
     * @return string CSS class name
3072
     */
3073
    public static function getStatusCSSClassName($status)
3074
    {
3075
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3076
            return self::STATUS_CSS_CLASS_NAME[$status];
3077
        }
3078
3079
        return '';
3080
    }
3081
3082
    /**
3083
     * Generate the tree of contents for this learnpath as an associative array tree
3084
     * with keys id, title, status, type, description, path, parent_id, children
3085
     * (title and descriptions as secured)
3086
     * and clues for CSS class composition:
3087
     *  - booleans is_current, is_parent_of_current, is_chapter
3088
     *  - string status_css_class_name.
3089
     *
3090
     * @param $parentId int restrict returned list to children of this parent
3091
     *
3092
     * @return array TOC as a table
3093
     */
3094
    public function getTOCTree($parentId = 0)
3095
    {
3096
        $toc = [];
3097
        $currentItemId = $this->get_current_item_id();
3098
3099
        foreach ($this->ordered_items as $itemId) {
3100
            $item = $this->items[$itemId];
3101
            if ($item->get_parent() == $parentId) {
3102
                $title = $item->get_title();
3103
                if (empty($title)) {
3104
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3105
                }
3106
3107
                $itemData = [
3108
                    'id' => $itemId,
3109
                    'title' => Security::remove_XSS($title),
3110
                    'status' => $item->get_status(),
3111
                    'level' => $item->get_level(), // FIXME should not be needed
3112
                    'type' => $item->get_type(),
3113
                    'description' => Security::remove_XSS($item->get_description()),
3114
                    'path' => $item->get_path(),
3115
                    'parent_id' => $item->get_parent(),
3116
                    'children' => $this->getTOCTree($itemId),
3117
                    'is_current' => ($itemId == $currentItemId),
3118
                    'is_parent_of_current' => false,
3119
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3120
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3121
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3122
                ];
3123
3124
                if (!empty($itemData['children'])) {
3125
                    foreach ($itemData['children'] as $child) {
3126
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3127
                            $itemData['is_parent_of_current'] = true;
3128
                            break;
3129
                        }
3130
                    }
3131
                }
3132
3133
                $toc[] = $itemData;
3134
            }
3135
        }
3136
3137
        return $toc;
3138
    }
3139
3140
    /**
3141
     * Generate and return the table of contents for this learnpath. The JS
3142
     * table returned is used inside of scorm_api.php.
3143
     *
3144
     * @param string $varname
3145
     *
3146
     * @return string A JS array variable construction
3147
     */
3148
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3149
    {
3150
        $toc = $varname.' = new Array();';
3151
        foreach ($this->ordered_items as $item_id) {
3152
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3153
        }
3154
3155
        return $toc;
3156
    }
3157
3158
    /**
3159
     * Gets the learning path type.
3160
     *
3161
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3162
     *
3163
     * @return mixed Type ID or name, depending on the parameter
3164
     */
3165
    public function get_type($get_name = false)
3166
    {
3167
        $res = false;
3168
        if (!empty($this->type) && (!$get_name)) {
3169
            $res = $this->type;
3170
        }
3171
3172
        return $res;
3173
    }
3174
3175
    /**
3176
     * Gets the learning path type as static method.
3177
     *
3178
     * @param int $lp_id
3179
     *
3180
     * @return mixed Type ID or name, depending on the parameter
3181
     */
3182
    public static function get_type_static($lp_id = 0)
3183
    {
3184
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3185
        $lp_id = (int) $lp_id;
3186
        $sql = "SELECT lp_type FROM $tbl_lp
3187
                WHERE iid = $lp_id";
3188
        $res = Database::query($sql);
3189
        if ($res === false) {
3190
            return null;
3191
        }
3192
        if (Database::num_rows($res) <= 0) {
3193
            return null;
3194
        }
3195
        $row = Database::fetch_array($res);
3196
3197
        return $row['lp_type'];
3198
    }
3199
3200
    /**
3201
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3202
     * This method can be used as abstract and is recursive.
3203
     *
3204
     * @param int $lp        Learnpath ID
3205
     * @param int $parent    Parent ID of the items to look for
3206
     * @param int $course_id
3207
     *
3208
     * @return array Ordered list of item IDs (empty array on error)
3209
     */
3210
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3211
    {
3212
        if (empty($course_id)) {
3213
            $course_id = api_get_course_int_id();
3214
        } else {
3215
            $course_id = (int) $course_id;
3216
        }
3217
        $list = [];
3218
3219
        if (empty($lp)) {
3220
            return $list;
3221
        }
3222
3223
        $lp = (int) $lp;
3224
        $parent = (int) $parent;
3225
3226
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3227
        $sql = "SELECT iid FROM $tbl_lp_item
3228
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3229
                ORDER BY display_order";
3230
3231
        $res = Database::query($sql);
3232
        while ($row = Database::fetch_array($res)) {
3233
            $sublist = self::get_flat_ordered_items_list(
3234
                $lp,
3235
                $row['iid'],
3236
                $course_id
3237
            );
3238
            $list[] = $row['iid'];
3239
            foreach ($sublist as $item) {
3240
                $list[] = $item;
3241
            }
3242
        }
3243
3244
        return $list;
3245
    }
3246
3247
    /**
3248
     * @return array
3249
     */
3250
    public static function getChapterTypes()
3251
    {
3252
        return [
3253
            'dir',
3254
        ];
3255
    }
3256
3257
    /**
3258
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3259
     *
3260
     * @param $tree
3261
     *
3262
     * @return array HTML TOC ready to display
3263
     */
3264
    public function getParentToc($tree)
3265
    {
3266
        if (empty($tree)) {
3267
            $tree = $this->get_toc();
3268
        }
3269
        $dirTypes = self::getChapterTypes();
3270
        $myCurrentId = $this->get_current_item_id();
3271
        $listParent = [];
3272
        $listChildren = [];
3273
        $listNotParent = [];
3274
        $list = [];
3275
        foreach ($tree as $subtree) {
3276
            if (in_array($subtree['type'], $dirTypes)) {
3277
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3278
                $subtree['children'] = $listChildren;
3279
                if (!empty($subtree['children'])) {
3280
                    foreach ($subtree['children'] as $subItem) {
3281
                        if ($subItem['id'] == $this->current) {
3282
                            $subtree['parent_current'] = 'in';
3283
                            $subtree['current'] = 'on';
3284
                        }
3285
                    }
3286
                }
3287
                $listParent[] = $subtree;
3288
            }
3289
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3290
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3291
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3292
                }
3293
3294
                $title = Security::remove_XSS($subtree['title']);
3295
                unset($subtree['title']);
3296
3297
                if (empty($title)) {
3298
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3299
                }
3300
                $classStyle = null;
3301
                if ($subtree['id'] == $this->current) {
3302
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3303
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3304
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3305
                }
3306
                $subtree['title'] = $title;
3307
                $subtree['class'] = $classStyle.' '.$cssStatus;
3308
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3309
                $subtree['current_id'] = $myCurrentId;
3310
                $listNotParent[] = $subtree;
3311
            }
3312
        }
3313
3314
        $list['are_parents'] = $listParent;
3315
        $list['not_parents'] = $listNotParent;
3316
3317
        return $list;
3318
    }
3319
3320
    /**
3321
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3322
     *
3323
     * @param array $tree
3324
     * @param int   $id
3325
     * @param bool  $parent
3326
     *
3327
     * @return array HTML TOC ready to display
3328
     */
3329
    public function getChildrenToc($tree, $id, $parent = true)
3330
    {
3331
        if (empty($tree)) {
3332
            $tree = $this->get_toc();
3333
        }
3334
3335
        $dirTypes = self::getChapterTypes();
3336
        $currentItemId = $this->get_current_item_id();
3337
        $list = [];
3338
3339
        foreach ($tree as $subtree) {
3340
            $subtree['tree'] = null;
3341
3342
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3343
                if ($subtree['id'] == $this->current) {
3344
                    $subtree['current'] = 'active';
3345
                } else {
3346
                    $subtree['current'] = null;
3347
                }
3348
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3349
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3350
                }
3351
3352
                $title = Security::remove_XSS($subtree['title']);
3353
                unset($subtree['title']);
3354
                if (empty($title)) {
3355
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3356
                }
3357
3358
                $classStyle = null;
3359
                if ($subtree['id'] == $this->current) {
3360
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3361
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3362
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3363
                }
3364
3365
                if (in_array($subtree['type'], $dirTypes)) {
3366
                    $subtree['title'] = stripslashes($title);
3367
                } else {
3368
                    $subtree['title'] = $title;
3369
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3370
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3371
                    $subtree['current_id'] = $currentItemId;
3372
                }
3373
                $list[] = $subtree;
3374
            }
3375
        }
3376
3377
        return $list;
3378
    }
3379
3380
    /**
3381
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3382
     *
3383
     * @param array $toc_list
3384
     *
3385
     * @return array HTML TOC ready to display
3386
     */
3387
    public function getListArrayToc($toc_list = [])
3388
    {
3389
        if (empty($toc_list)) {
3390
            $toc_list = $this->get_toc();
3391
        }
3392
        // Temporary variables.
3393
        $currentItemId = $this->get_current_item_id();
3394
        $list = [];
3395
        $arrayList = [];
3396
3397
        foreach ($toc_list as $item) {
3398
            $list['id'] = $item['id'];
3399
            $list['status'] = $item['status'];
3400
            $cssStatus = null;
3401
3402
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3403
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3404
            }
3405
3406
            $classStyle = ' ';
3407
            $dirTypes = self::getChapterTypes();
3408
3409
            if (in_array($item['type'], $dirTypes)) {
3410
                $classStyle = 'scorm_item_section ';
3411
            }
3412
            if ($item['id'] == $this->current) {
3413
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3414
            } elseif (!in_array($item['type'], $dirTypes)) {
3415
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3416
            }
3417
            $title = $item['title'];
3418
            if (empty($title)) {
3419
                $title = self::rl_get_resource_name(
3420
                    api_get_course_id(),
3421
                    $this->get_id(),
3422
                    $item['id']
3423
                );
3424
            }
3425
            $title = Security::remove_XSS($item['title']);
3426
3427
            if (empty($item['description'])) {
3428
                $list['description'] = $title;
3429
            } else {
3430
                $list['description'] = $item['description'];
3431
            }
3432
3433
            $list['class'] = $classStyle.' '.$cssStatus;
3434
            $list['level'] = $item['level'];
3435
            $list['type'] = $item['type'];
3436
3437
            if (in_array($item['type'], $dirTypes)) {
3438
                $list['css_level'] = 'level_'.$item['level'];
3439
            } else {
3440
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3441
            }
3442
3443
            if (in_array($item['type'], $dirTypes)) {
3444
                $list['title'] = stripslashes($title);
3445
            } else {
3446
                $list['title'] = stripslashes($title);
3447
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3448
                $list['current_id'] = $currentItemId;
3449
            }
3450
            $arrayList[] = $list;
3451
        }
3452
3453
        return $arrayList;
3454
    }
3455
3456
    /**
3457
     * Returns an HTML-formatted string ready to display with teacher buttons
3458
     * in LP view menu.
3459
     *
3460
     * @return string HTML TOC ready to display
3461
     */
3462
    public function get_teacher_toc_buttons()
3463
    {
3464
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3465
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3466
        $html = '';
3467
        if ($isAllow && $hideIcons == false) {
3468
            if ($this->get_lp_session_id() == api_get_session_id()) {
3469
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3470
                $html .= '<div class="btn-group">';
3471
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3472
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3473
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3474
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3475
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3476
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3477
                $html .= '</div>';
3478
                $html .= '</div>';
3479
            }
3480
        }
3481
3482
        return $html;
3483
    }
3484
3485
    /**
3486
     * Gets the learnpath maker name - generally the editor's name.
3487
     *
3488
     * @return string Learnpath maker name
3489
     */
3490
    public function get_maker()
3491
    {
3492
        if (!empty($this->maker)) {
3493
            return $this->maker;
3494
        }
3495
3496
        return '';
3497
    }
3498
3499
    /**
3500
     * Gets the learnpath name/title.
3501
     *
3502
     * @return string Learnpath name/title
3503
     */
3504
    public function get_name()
3505
    {
3506
        if (!empty($this->name)) {
3507
            return $this->name;
3508
        }
3509
3510
        return 'N/A';
3511
    }
3512
3513
    /**
3514
     * @return string
3515
     */
3516
    public function getNameNoTags()
3517
    {
3518
        return strip_tags($this->get_name());
3519
    }
3520
3521
    /**
3522
     * Gets a link to the resource from the present location, depending on item ID.
3523
     *
3524
     * @param string $type         Type of link expected
3525
     * @param int    $item_id      Learnpath item ID
3526
     * @param bool   $provided_toc
3527
     *
3528
     * @return string $provided_toc Link to the lp_item resource
3529
     */
3530
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3531
    {
3532
        $course_id = $this->get_course_int_id();
3533
        $item_id = (int) $item_id;
3534
3535
        if (empty($item_id)) {
3536
            $item_id = $this->get_current_item_id();
3537
3538
            if (empty($item_id)) {
3539
                //still empty, this means there was no item_id given and we are not in an object context or
3540
                //the object property is empty, return empty link
3541
                $this->first();
3542
3543
                return '';
3544
            }
3545
        }
3546
3547
        $file = '';
3548
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3549
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3550
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3551
3552
        $sql = "SELECT
3553
                    l.lp_type as ltype,
3554
                    l.path as lpath,
3555
                    li.item_type as litype,
3556
                    li.path as lipath,
3557
                    li.parameters as liparams
3558
        		FROM $lp_table l
3559
                INNER JOIN $lp_item_table li
3560
                ON (li.lp_id = l.iid)
3561
        		WHERE
3562
        		    li.iid = $item_id
3563
        		";
3564
        $res = Database::query($sql);
3565
        if (Database::num_rows($res) > 0) {
3566
            $row = Database::fetch_array($res);
3567
            $lp_type = $row['ltype'];
3568
            $lp_path = $row['lpath'];
3569
            $lp_item_type = $row['litype'];
3570
            $lp_item_path = $row['lipath'];
3571
            $lp_item_params = $row['liparams'];
3572
3573
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3574
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3575
            }
3576
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3577
            if ($type === 'http') {
3578
                //web path
3579
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3580
            } else {
3581
                $course_path = $sys_course_path; //system path
3582
            }
3583
3584
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3585
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3586
            if (in_array(
3587
                $lp_item_type,
3588
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3589
            )
3590
            ) {
3591
                $lp_type = 1;
3592
            }
3593
3594
            // Now go through the specific cases to get the end of the path
3595
            // @todo Use constants instead of int values.
3596
            switch ($lp_type) {
3597
                case 1:
3598
                    $file = self::rl_get_resource_link_for_learnpath(
3599
                        $course_id,
3600
                        $this->get_id(),
3601
                        $item_id,
3602
                        $this->get_view_id(),
3603
                        $this->get_lp_session_id()
3604
                    );
3605
                    switch ($lp_item_type) {
3606
                        case 'document':
3607
                            // Shows a button to download the file instead of just downloading the file directly.
3608
                            $documentPathInfo = pathinfo($file);
3609
                            if (isset($documentPathInfo['extension'])) {
3610
                                $parsed = parse_url($documentPathInfo['extension']);
3611
                                if (isset($parsed['path'])) {
3612
                                    $extension = $parsed['path'];
3613
                                    $extensionsToDownload = [
3614
                                        'zip',
3615
                                        'ppt',
3616
                                        'pptx',
3617
                                        'ods',
3618
                                        'xlsx',
3619
                                        'xls',
3620
                                        'csv',
3621
                                        'doc',
3622
                                        'docx',
3623
                                        'dot',
3624
                                    ];
3625
3626
                                    if (in_array($extension, $extensionsToDownload)) {
3627
                                        $file = api_get_path(WEB_CODE_PATH).
3628
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3629
                                    }
3630
                                }
3631
                            }
3632
                            break;
3633
                        case 'dir':
3634
                            $file = 'lp_content.php?type=dir';
3635
                            break;
3636
                        case 'link':
3637
                            if (Link::is_youtube_link($file)) {
3638
                                $src = Link::get_youtube_video_id($file);
3639
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3640
                            } elseif (Link::isVimeoLink($file)) {
3641
                                $src = Link::getVimeoLinkId($file);
3642
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3643
                            } else {
3644
                                // If the current site is HTTPS and the link is
3645
                                // HTTP, browsers will refuse opening the link
3646
                                $urlId = api_get_current_access_url_id();
3647
                                $url = api_get_access_url($urlId, false);
3648
                                $protocol = substr($url['url'], 0, 5);
3649
                                if ($protocol === 'https') {
3650
                                    $linkProtocol = substr($file, 0, 5);
3651
                                    if ($linkProtocol === 'http:') {
3652
                                        //this is the special intervention case
3653
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3654
                                    }
3655
                                }
3656
                            }
3657
                            break;
3658
                        case 'quiz':
3659
                            // Check how much attempts of a exercise exits in lp
3660
                            $lp_item_id = $this->get_current_item_id();
3661
                            $lp_view_id = $this->get_view_id();
3662
3663
                            $prevent_reinit = null;
3664
                            if (isset($this->items[$this->current])) {
3665
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3666
                            }
3667
3668
                            if (empty($provided_toc)) {
3669
                                if ($this->debug > 0) {
3670
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3671
                                }
3672
                                $list = $this->get_toc();
3673
                            } else {
3674
                                if ($this->debug > 0) {
3675
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3676
                                }
3677
                                $list = $provided_toc;
3678
                            }
3679
3680
                            $type_quiz = false;
3681
                            foreach ($list as $toc) {
3682
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3683
                                    $type_quiz = true;
3684
                                }
3685
                            }
3686
3687
                            if ($type_quiz) {
3688
                                $lp_item_id = (int) $lp_item_id;
3689
                                $lp_view_id = (int) $lp_view_id;
3690
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3691
                                        WHERE
3692
                                            c_id = $course_id AND
3693
                                            lp_item_id='".$lp_item_id."' AND
3694
                                            lp_view_id ='".$lp_view_id."' AND
3695
                                            status='completed'";
3696
                                $result = Database::query($sql);
3697
                                $row_count = Database:: fetch_row($result);
3698
                                $count_item_view = (int) $row_count[0];
3699
                                $not_multiple_attempt = 0;
3700
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3701
                                    $not_multiple_attempt = 1;
3702
                                }
3703
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3704
                            }
3705
                            break;
3706
                    }
3707
3708
                    $tmp_array = explode('/', $file);
3709
                    $document_name = $tmp_array[count($tmp_array) - 1];
3710
                    if (strpos($document_name, '_DELETED_')) {
3711
                        $file = 'blank.php?error=document_deleted';
3712
                    }
3713
                    break;
3714
                case 2:
3715
                    if ($this->debug > 2) {
3716
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3717
                    }
3718
3719
                    if ($lp_item_type != 'dir') {
3720
                        // Quite complex here:
3721
                        // We want to make sure 'http://' (and similar) links can
3722
                        // be loaded as is (withouth the Chamilo path in front) but
3723
                        // some contents use this form: resource.htm?resource=http://blablabla
3724
                        // which means we have to find a protocol at the path's start, otherwise
3725
                        // it should not be considered as an external URL.
3726
                        // if ($this->prerequisites_match($item_id)) {
3727
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3728
                            if ($this->debug > 2) {
3729
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3730
                            }
3731
                            // Distant url, return as is.
3732
                            $file = $lp_item_path;
3733
                        } else {
3734
                            if ($this->debug > 2) {
3735
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3736
                            }
3737
                            // Prevent getting untranslatable urls.
3738
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3739
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3740
                            // Prepare the path.
3741
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3742
                            // TODO: Fix this for urls with protocol header.
3743
                            $file = str_replace('//', '/', $file);
3744
                            $file = str_replace(':/', '://', $file);
3745
                            if (substr($lp_path, -1) == '/') {
3746
                                $lp_path = substr($lp_path, 0, -1);
3747
                            }
3748
3749
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3750
                                // if file not found.
3751
                                $decoded = html_entity_decode($lp_item_path);
3752
                                list($decoded) = explode('?', $decoded);
3753
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3754
                                    $file = self::rl_get_resource_link_for_learnpath(
3755
                                        $course_id,
3756
                                        $this->get_id(),
3757
                                        $item_id,
3758
                                        $this->get_view_id()
3759
                                    );
3760
                                    if (empty($file)) {
3761
                                        $file = 'blank.php?error=document_not_found';
3762
                                    } else {
3763
                                        $tmp_array = explode('/', $file);
3764
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3765
                                        if (strpos($document_name, '_DELETED_')) {
3766
                                            $file = 'blank.php?error=document_deleted';
3767
                                        } else {
3768
                                            $file = 'blank.php?error=document_not_found';
3769
                                        }
3770
                                    }
3771
                                } else {
3772
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3773
                                }
3774
                            }
3775
                        }
3776
3777
                        // We want to use parameters if they were defined in the imsmanifest
3778
                        if (strpos($file, 'blank.php') === false) {
3779
                            $lp_item_params = ltrim($lp_item_params, '?');
3780
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3781
                        }
3782
                    } else {
3783
                        $file = 'lp_content.php?type=dir';
3784
                    }
3785
                    break;
3786
                case 3:
3787
                    if ($this->debug > 2) {
3788
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3789
                    }
3790
                    // Formatting AICC HACP append URL.
3791
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3792
                    if (!empty($lp_item_params)) {
3793
                        $aicc_append .= $lp_item_params.'&';
3794
                    }
3795
                    if ($lp_item_type != 'dir') {
3796
                        // Quite complex here:
3797
                        // We want to make sure 'http://' (and similar) links can
3798
                        // be loaded as is (withouth the Chamilo path in front) but
3799
                        // some contents use this form: resource.htm?resource=http://blablabla
3800
                        // which means we have to find a protocol at the path's start, otherwise
3801
                        // it should not be considered as an external URL.
3802
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3803
                            if ($this->debug > 2) {
3804
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3805
                            }
3806
                            // Distant url, return as is.
3807
                            $file = $lp_item_path;
3808
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3809
                            /*
3810
                            if (stristr($file,'<servername>') !== false) {
3811
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3812
                            }
3813
                            */
3814
                            if (stripos($file, '<servername>') !== false) {
3815
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3816
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3817
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3818
                            }
3819
3820
                            $file .= $aicc_append;
3821
                        } else {
3822
                            if ($this->debug > 2) {
3823
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3824
                            }
3825
                            // Prevent getting untranslatable urls.
3826
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3827
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3828
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3829
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3830
                            // TODO: Fix this for urls with protocol header.
3831
                            $file = str_replace('//', '/', $file);
3832
                            $file = str_replace(':/', '://', $file);
3833
                            $file .= $aicc_append;
3834
                        }
3835
                    } else {
3836
                        $file = 'lp_content.php?type=dir';
3837
                    }
3838
                    break;
3839
                case 4:
3840
                    break;
3841
                default:
3842
                    break;
3843
            }
3844
            // Replace &amp; by & because &amp; will break URL with params
3845
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3846
        }
3847
        if ($this->debug > 2) {
3848
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3849
        }
3850
3851
        return $file;
3852
    }
3853
3854
    /**
3855
     * Gets the latest usable view or generate a new one.
3856
     *
3857
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3858
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3859
     *
3860
     * @return int DB lp_view id
3861
     */
3862
    public function get_view($attempt_num = 0, $userId = null)
3863
    {
3864
        $search = '';
3865
        // Use $attempt_num to enable multi-views management (disabled so far).
3866
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3867
            $search = 'AND view_count = '.$attempt_num;
3868
        }
3869
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3870
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3871
3872
        $course_id = api_get_course_int_id();
3873
        $sessionId = api_get_session_id();
3874
3875
        // Check user ID.
3876
        if (empty($userId)) {
3877
            if (empty($this->get_user_id())) {
3878
                $this->error = 'User ID is empty in learnpath::get_view()';
3879
3880
                return null;
3881
            } else {
3882
                $userId = $this->get_user_id();
3883
            }
3884
        }
3885
3886
        $sql = "SELECT iid, view_count FROM $lp_view_table
3887
        		WHERE
3888
        		    c_id = $course_id AND
3889
        		    lp_id = ".$this->get_id()." AND
3890
        		    user_id = ".$userId." AND
3891
        		    session_id = $sessionId
3892
        		    $search
3893
                ORDER BY view_count DESC";
3894
        $res = Database::query($sql);
3895
        if (Database::num_rows($res) > 0) {
3896
            $row = Database::fetch_array($res);
3897
            $this->lp_view_id = $row['iid'];
3898
        } elseif (!api_is_invitee()) {
3899
            // There is no database record, create one.
3900
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3901
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3902
            Database::query($sql);
3903
            $id = Database::insert_id();
3904
            $this->lp_view_id = $id;
3905
3906
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3907
            Database::query($sql);
3908
        }
3909
3910
        return $this->lp_view_id;
3911
    }
3912
3913
    /**
3914
     * Gets the current view id.
3915
     *
3916
     * @return int View ID (from lp_view)
3917
     */
3918
    public function get_view_id()
3919
    {
3920
        if (!empty($this->lp_view_id)) {
3921
            return (int) $this->lp_view_id;
3922
        }
3923
3924
        return 0;
3925
    }
3926
3927
    /**
3928
     * Gets the update queue.
3929
     *
3930
     * @return array Array containing IDs of items to be updated by JavaScript
3931
     */
3932
    public function get_update_queue()
3933
    {
3934
        return $this->update_queue;
3935
    }
3936
3937
    /**
3938
     * Gets the user ID.
3939
     *
3940
     * @return int User ID
3941
     */
3942
    public function get_user_id()
3943
    {
3944
        if (!empty($this->user_id)) {
3945
            return (int) $this->user_id;
3946
        }
3947
3948
        return false;
3949
    }
3950
3951
    /**
3952
     * Checks if any of the items has an audio element attached.
3953
     *
3954
     * @return bool True or false
3955
     */
3956
    public function has_audio()
3957
    {
3958
        $has = false;
3959
        foreach ($this->items as $i => $item) {
3960
            if (!empty($this->items[$i]->audio)) {
3961
                $has = true;
3962
                break;
3963
            }
3964
        }
3965
3966
        return $has;
3967
    }
3968
3969
    /**
3970
     * Moves an item up and down at its level.
3971
     *
3972
     * @param int    $id        Item to move up and down
3973
     * @param string $direction Direction 'up' or 'down'
3974
     *
3975
     * @return bool|int
3976
     */
3977
    public function move_item($id, $direction)
3978
    {
3979
        $course_id = api_get_course_int_id();
3980
        if (empty($id) || empty($direction)) {
3981
            return false;
3982
        }
3983
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3984
        $sql_sel = "SELECT *
3985
                    FROM $tbl_lp_item
3986
                    WHERE
3987
                        iid = $id
3988
                    ";
3989
        $res_sel = Database::query($sql_sel);
3990
        // Check if elem exists.
3991
        if (Database::num_rows($res_sel) < 1) {
3992
            return false;
3993
        }
3994
        // Gather data.
3995
        $row = Database::fetch_array($res_sel);
3996
        $previous = $row['previous_item_id'];
3997
        $next = $row['next_item_id'];
3998
        $display = $row['display_order'];
3999
        $parent = $row['parent_item_id'];
4000
        $lp = $row['lp_id'];
4001
        // Update the item (switch with previous/next one).
4002
        switch ($direction) {
4003
            case 'up':
4004
                if ($display > 1) {
4005
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4006
                                 WHERE iid = $previous";
4007
                    $res_sel2 = Database::query($sql_sel2);
4008
                    if (Database::num_rows($res_sel2) < 1) {
4009
                        $previous_previous = 0;
4010
                    }
4011
                    // Gather data.
4012
                    $row2 = Database::fetch_array($res_sel2);
4013
                    $previous_previous = $row2['previous_item_id'];
4014
                    // Update previous_previous item (switch "next" with current).
4015
                    if ($previous_previous != 0) {
4016
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4017
                                        next_item_id = $id
4018
                                    WHERE iid = $previous_previous";
4019
                        Database::query($sql_upd2);
4020
                    }
4021
                    // Update previous item (switch with current).
4022
                    if ($previous != 0) {
4023
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4024
                                    next_item_id = $next,
4025
                                    previous_item_id = $id,
4026
                                    display_order = display_order +1
4027
                                    WHERE iid = $previous";
4028
                        Database::query($sql_upd2);
4029
                    }
4030
4031
                    // Update current item (switch with previous).
4032
                    if ($id != 0) {
4033
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4034
                                        next_item_id = $previous,
4035
                                        previous_item_id = $previous_previous,
4036
                                        display_order = display_order-1
4037
                                    WHERE c_id = ".$course_id." AND id = $id";
4038
                        Database::query($sql_upd2);
4039
                    }
4040
                    // Update next item (new previous item).
4041
                    if (!empty($next)) {
4042
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4043
                                     WHERE iid = $next";
4044
                        Database::query($sql_upd2);
4045
                    }
4046
                    $display = $display - 1;
4047
                }
4048
                break;
4049
            case 'down':
4050
                if ($next != 0) {
4051
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4052
                                 WHERE iid = $next";
4053
                    $res_sel2 = Database::query($sql_sel2);
4054
                    if (Database::num_rows($res_sel2) < 1) {
4055
                        $next_next = 0;
4056
                    }
4057
                    // Gather data.
4058
                    $row2 = Database::fetch_array($res_sel2);
4059
                    $next_next = $row2['next_item_id'];
4060
                    // Update previous item (switch with current).
4061
                    if ($previous != 0) {
4062
                        $sql_upd2 = "UPDATE $tbl_lp_item
4063
                                     SET next_item_id = $next
4064
                                     WHERE iid = $previous";
4065
                        Database::query($sql_upd2);
4066
                    }
4067
                    // Update current item (switch with previous).
4068
                    if ($id != 0) {
4069
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4070
                                     previous_item_id = $next,
4071
                                     next_item_id = $next_next,
4072
                                     display_order = display_order + 1
4073
                                     WHERE iid = $id";
4074
                        Database::query($sql_upd2);
4075
                    }
4076
4077
                    // Update next item (new previous item).
4078
                    if ($next != 0) {
4079
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4080
                                     previous_item_id = $previous,
4081
                                     next_item_id = $id,
4082
                                     display_order = display_order-1
4083
                                     WHERE iid = $next";
4084
                        Database::query($sql_upd2);
4085
                    }
4086
4087
                    // Update next_next item (switch "previous" with current).
4088
                    if ($next_next != 0) {
4089
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4090
                                     previous_item_id = $id
4091
                                     WHERE iid = $next_next";
4092
                        Database::query($sql_upd2);
4093
                    }
4094
                    $display = $display + 1;
4095
                }
4096
                break;
4097
            default:
4098
                return false;
4099
        }
4100
4101
        return $display;
4102
    }
4103
4104
    /**
4105
     * Move a LP up (display_order).
4106
     *
4107
     * @param int $lp_id      Learnpath ID
4108
     * @param int $categoryId Category ID
4109
     *
4110
     * @return bool
4111
     */
4112
    public static function move_up($lp_id, $categoryId = 0)
4113
    {
4114
        $courseId = api_get_course_int_id();
4115
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4116
4117
        $categoryCondition = '';
4118
        if (!empty($categoryId)) {
4119
            $categoryId = (int) $categoryId;
4120
            $categoryCondition = " AND category_id = $categoryId";
4121
        }
4122
        $sql = "SELECT * FROM $lp_table
4123
                WHERE c_id = $courseId
4124
                $categoryCondition
4125
                ORDER BY display_order";
4126
        $res = Database::query($sql);
4127
        if ($res === false) {
4128
            return false;
4129
        }
4130
4131
        $lps = [];
4132
        $lp_order = [];
4133
        $num = Database::num_rows($res);
4134
        // First check the order is correct, globally (might be wrong because
4135
        // of versions < 1.8.4)
4136
        if ($num > 0) {
4137
            $i = 1;
4138
            while ($row = Database::fetch_array($res)) {
4139
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4140
                    $sql = "UPDATE $lp_table SET display_order = $i
4141
                            WHERE iid = ".$row['iid'];
4142
                    Database::query($sql);
4143
                }
4144
                $row['display_order'] = $i;
4145
                $lps[$row['iid']] = $row;
4146
                $lp_order[$i] = $row['iid'];
4147
                $i++;
4148
            }
4149
        }
4150
        if ($num > 1) { // If there's only one element, no need to sort.
4151
            $order = $lps[$lp_id]['display_order'];
4152
            if ($order > 1) { // If it's the first element, no need to move up.
4153
                $sql = "UPDATE $lp_table SET display_order = $order
4154
                        WHERE iid = ".$lp_order[$order - 1];
4155
                Database::query($sql);
4156
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4157
                        WHERE iid = $lp_id";
4158
                Database::query($sql);
4159
            }
4160
        }
4161
4162
        return true;
4163
    }
4164
4165
    /**
4166
     * Move a learnpath down (display_order).
4167
     *
4168
     * @param int $lp_id      Learnpath ID
4169
     * @param int $categoryId Category ID
4170
     *
4171
     * @return bool
4172
     */
4173
    public static function move_down($lp_id, $categoryId = 0)
4174
    {
4175
        $courseId = api_get_course_int_id();
4176
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4177
4178
        $categoryCondition = '';
4179
        if (!empty($categoryId)) {
4180
            $categoryId = (int) $categoryId;
4181
            $categoryCondition = " AND category_id = $categoryId";
4182
        }
4183
4184
        $sql = "SELECT * FROM $lp_table
4185
                WHERE c_id = $courseId
4186
                $categoryCondition
4187
                ORDER BY display_order";
4188
        $res = Database::query($sql);
4189
        if ($res === false) {
4190
            return false;
4191
        }
4192
        $lps = [];
4193
        $lp_order = [];
4194
        $num = Database::num_rows($res);
4195
        $max = 0;
4196
        // First check the order is correct, globally (might be wrong because
4197
        // of versions < 1.8.4).
4198
        if ($num > 0) {
4199
            $i = 1;
4200
            while ($row = Database::fetch_array($res)) {
4201
                $max = $i;
4202
                if ($row['display_order'] != $i) {
4203
                    // If we find a gap in the order, we need to fix it.
4204
                    $sql = "UPDATE $lp_table SET display_order = $i
4205
                              WHERE iid = ".$row['iid'];
4206
                    Database::query($sql);
4207
                }
4208
                $row['display_order'] = $i;
4209
                $lps[$row['iid']] = $row;
4210
                $lp_order[$i] = $row['iid'];
4211
                $i++;
4212
            }
4213
        }
4214
        if ($num > 1) { // If there's only one element, no need to sort.
4215
            $order = $lps[$lp_id]['display_order'];
4216
            if ($order < $max) { // If it's the first element, no need to move up.
4217
                $sql = "UPDATE $lp_table SET display_order = $order
4218
                        WHERE iid = ".$lp_order[$order + 1];
4219
                Database::query($sql);
4220
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4221
                        WHERE iid = $lp_id";
4222
                Database::query($sql);
4223
            }
4224
        }
4225
4226
        return true;
4227
    }
4228
4229
    /**
4230
     * Updates learnpath attributes to point to the next element
4231
     * The last part is similar to set_current_item but processing the other way around.
4232
     */
4233
    public function next()
4234
    {
4235
        if ($this->debug > 0) {
4236
            error_log('In learnpath::next()', 0);
4237
        }
4238
        $this->last = $this->get_current_item_id();
4239
        $this->items[$this->last]->save(
4240
            false,
4241
            $this->prerequisites_match($this->last)
4242
        );
4243
        $this->autocomplete_parents($this->last);
4244
        $new_index = $this->get_next_index();
4245
        if ($this->debug > 2) {
4246
            error_log('New index: '.$new_index, 0);
4247
        }
4248
        $this->index = $new_index;
4249
        if ($this->debug > 2) {
4250
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4251
        }
4252
        $this->current = $this->ordered_items[$new_index];
4253
        if ($this->debug > 2) {
4254
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4255
        }
4256
    }
4257
4258
    /**
4259
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4260
     * class, this might be redefined to allow several behaviours depending on the document type.
4261
     *
4262
     * @param int $id Resource ID
4263
     */
4264
    public function open($id)
4265
    {
4266
        // TODO:
4267
        // set the current resource attribute to this resource
4268
        // switch on element type (redefine in child class?)
4269
        // set status for this item to "opened"
4270
        // start timer
4271
        // initialise score
4272
        $this->index = 0; //or = the last item seen (see $this->last)
4273
    }
4274
4275
    /**
4276
     * Check that all prerequisites are fulfilled. Returns true and an
4277
     * empty string on success, returns false
4278
     * and the prerequisite string on error.
4279
     * This function is based on the rules for aicc_script language as
4280
     * described in the SCORM 1.2 CAM documentation page 108.
4281
     *
4282
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4283
     *
4284
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4285
     *              string otherwise
4286
     */
4287
    public function prerequisites_match($itemId = null)
4288
    {
4289
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4290
        if ($allow) {
4291
            if (api_is_allowed_to_edit() ||
4292
                api_is_platform_admin(true) ||
4293
                api_is_drh() ||
4294
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4295
            ) {
4296
                return true;
4297
            }
4298
        }
4299
4300
        $debug = $this->debug;
4301
        if ($debug > 0) {
4302
            error_log('In learnpath::prerequisites_match()');
4303
        }
4304
4305
        if (empty($itemId)) {
4306
            $itemId = $this->current;
4307
        }
4308
4309
        $currentItem = $this->getItem($itemId);
4310
4311
        if ($currentItem) {
4312
            if ($this->type == 2) {
4313
                // Getting prereq from scorm
4314
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4315
            } else {
4316
                $prereq_string = $currentItem->get_prereq_string();
4317
            }
4318
4319
            if (empty($prereq_string)) {
4320
                if ($debug > 0) {
4321
                    error_log('Found prereq_string is empty return true');
4322
                }
4323
4324
                return true;
4325
            }
4326
4327
            // Clean spaces.
4328
            $prereq_string = str_replace(' ', '', $prereq_string);
4329
            if ($debug > 0) {
4330
                error_log('Found prereq_string: '.$prereq_string, 0);
4331
            }
4332
4333
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4334
            $result = $currentItem->parse_prereq(
4335
                $prereq_string,
4336
                $this->items,
4337
                $this->refs_list,
4338
                $this->get_user_id()
4339
            );
4340
4341
            if ($result === false) {
4342
                $this->set_error_msg($currentItem->prereq_alert);
4343
            }
4344
        } else {
4345
            $result = true;
4346
            if ($debug > 1) {
4347
                error_log('$this->items['.$itemId.'] was not an object', 0);
4348
            }
4349
        }
4350
4351
        if ($debug > 1) {
4352
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4353
        }
4354
4355
        return $result;
4356
    }
4357
4358
    /**
4359
     * Updates learnpath attributes to point to the previous element
4360
     * The last part is similar to set_current_item but processing the other way around.
4361
     */
4362
    public function previous()
4363
    {
4364
        $this->last = $this->get_current_item_id();
4365
        $this->items[$this->last]->save(
4366
            false,
4367
            $this->prerequisites_match($this->last)
4368
        );
4369
        $this->autocomplete_parents($this->last);
4370
        $new_index = $this->get_previous_index();
4371
        $this->index = $new_index;
4372
        $this->current = $this->ordered_items[$new_index];
4373
    }
4374
4375
    /**
4376
     * Publishes a learnpath. This basically means show or hide the learnpath
4377
     * to normal users.
4378
     * Can be used as abstract.
4379
     *
4380
     * @param int $lp_id          Learnpath ID
4381
     * @param int $set_visibility New visibility
4382
     *
4383
     * @return bool
4384
     */
4385
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4386
    {
4387
        $action = 'visible';
4388
        if ($set_visibility != 1) {
4389
            $action = 'invisible';
4390
            self::toggle_publish($lp_id, 'i');
4391
        }
4392
4393
        return api_item_property_update(
4394
            api_get_course_info(),
4395
            TOOL_LEARNPATH,
4396
            $lp_id,
4397
            $action,
4398
            api_get_user_id()
4399
        );
4400
    }
4401
4402
    /**
4403
     * Publishes a learnpath category.
4404
     * This basically means show or hide the learnpath category to normal users.
4405
     *
4406
     * @param int $id
4407
     * @param int $visibility
4408
     *
4409
     * @throws \Doctrine\ORM\NonUniqueResultException
4410
     * @throws \Doctrine\ORM\ORMException
4411
     * @throws \Doctrine\ORM\OptimisticLockException
4412
     * @throws \Doctrine\ORM\TransactionRequiredException
4413
     *
4414
     * @return bool
4415
     */
4416
    public static function toggleCategoryVisibility($id, $visibility = 1)
4417
    {
4418
        $action = 'visible';
4419
        if ($visibility != 1) {
4420
            self::toggleCategoryPublish($id, 0);
4421
            $action = 'invisible';
4422
        }
4423
4424
        return api_item_property_update(
4425
            api_get_course_info(),
4426
            TOOL_LEARNPATH_CATEGORY,
4427
            $id,
4428
            $action,
4429
            api_get_user_id()
4430
        );
4431
    }
4432
4433
    /**
4434
     * Publishes a learnpath. This basically means show or hide the learnpath
4435
     * on the course homepage
4436
     * Can be used as abstract.
4437
     *
4438
     * @param int    $lp_id          Learnpath id
4439
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4440
     *
4441
     * @return bool
4442
     */
4443
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4444
    {
4445
        $course_id = api_get_course_int_id();
4446
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4447
        $lp_id = (int) $lp_id;
4448
        $sql = "SELECT * FROM $tbl_lp
4449
                WHERE iid = $lp_id";
4450
        $result = Database::query($sql);
4451
        if (Database::num_rows($result)) {
4452
            $row = Database::fetch_array($result);
4453
            $name = Database::escape_string($row['name']);
4454
            if ($set_visibility == 'i') {
4455
                $v = 0;
4456
            }
4457
            if ($set_visibility == 'v') {
4458
                $v = 1;
4459
            }
4460
4461
            $session_id = api_get_session_id();
4462
            $session_condition = api_get_session_condition($session_id);
4463
4464
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4465
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4466
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4467
4468
            $sql = "SELECT * FROM $tbl_tool
4469
                    WHERE
4470
                        c_id = $course_id AND
4471
                        (link = '$link' OR link = '$oldLink') AND
4472
                        image = 'scormbuilder.gif' AND
4473
                        (
4474
                            link LIKE '$link%' OR
4475
                            link LIKE '$oldLink%'
4476
                        )
4477
                        $session_condition
4478
                    ";
4479
4480
            $result = Database::query($sql);
4481
            $num = Database::num_rows($result);
4482
            if ($set_visibility == 'i' && $num > 0) {
4483
                $sql = "DELETE FROM $tbl_tool
4484
                        WHERE
4485
                            c_id = $course_id AND
4486
                            (link = '$link' OR link = '$oldLink') AND
4487
                            image='scormbuilder.gif'
4488
                            $session_condition";
4489
                Database::query($sql);
4490
            } elseif ($set_visibility == 'v' && $num == 0) {
4491
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4492
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4493
                Database::query($sql);
4494
                $insertId = Database::insert_id();
4495
                if ($insertId) {
4496
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4497
                    Database::query($sql);
4498
                }
4499
            } elseif ($set_visibility == 'v' && $num > 0) {
4500
                $sql = "UPDATE $tbl_tool SET
4501
                            c_id = $course_id,
4502
                            name = '$name',
4503
                            link = '$link',
4504
                            image = 'scormbuilder.gif',
4505
                            visibility = '$v',
4506
                            admin = '0',
4507
                            address = 'pastillegris.gif',
4508
                            added_tool = 0,
4509
                            session_id = $session_id
4510
                        WHERE
4511
                            c_id = ".$course_id." AND
4512
                            (link = '$link' OR link = '$oldLink') AND
4513
                            image='scormbuilder.gif'
4514
                            $session_condition
4515
                        ";
4516
                Database::query($sql);
4517
            }
4518
        }
4519
4520
        return false;
4521
    }
4522
4523
    /**
4524
     * Publishes a learnpath.
4525
     * Show or hide the learnpath category on the course homepage.
4526
     *
4527
     * @param int $id
4528
     * @param int $setVisibility
4529
     *
4530
     * @throws \Doctrine\ORM\NonUniqueResultException
4531
     * @throws \Doctrine\ORM\ORMException
4532
     * @throws \Doctrine\ORM\OptimisticLockException
4533
     * @throws \Doctrine\ORM\TransactionRequiredException
4534
     *
4535
     * @return bool
4536
     */
4537
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4538
    {
4539
        $courseId = api_get_course_int_id();
4540
        $sessionId = api_get_session_id();
4541
        $sessionCondition = api_get_session_condition(
4542
            $sessionId,
4543
            true,
4544
            false,
4545
            't.sessionId'
4546
        );
4547
4548
        $em = Database::getManager();
4549
        $category = self::getCategory($id);
4550
4551
        if (!$category) {
4552
            return false;
4553
        }
4554
4555
        if (empty($courseId)) {
4556
            return false;
4557
        }
4558
4559
        $link = self::getCategoryLinkForTool($id);
4560
4561
        /** @var CTool $tool */
4562
        $tool = $em->createQuery("
4563
                SELECT t FROM ChamiloCourseBundle:CTool t
4564
                WHERE
4565
                    t.cId = :course AND
4566
                    t.link = :link1 AND
4567
                    t.image = 'lp_category.gif' AND
4568
                    t.link LIKE :link2
4569
                    $sessionCondition
4570
            ")
4571
            ->setParameters([
4572
                'course' => $courseId,
4573
                'link1' => $link,
4574
                'link2' => "$link%",
4575
            ])
4576
            ->getOneOrNullResult();
4577
4578
        if ($setVisibility == 0 && $tool) {
4579
            $em->remove($tool);
4580
            $em->flush();
4581
4582
            return true;
4583
        }
4584
4585
        if ($setVisibility == 1 && !$tool) {
4586
            $tool = new CTool();
4587
            $tool
4588
                ->setCategory('authoring')
4589
                ->setCId($courseId)
4590
                ->setName(strip_tags($category->getName()))
4591
                ->setLink($link)
4592
                ->setImage('lp_category.gif')
4593
                ->setVisibility(1)
4594
                ->setAdmin(0)
4595
                ->setAddress('pastillegris.gif')
4596
                ->setAddedTool(0)
4597
                ->setSessionId($sessionId)
4598
                ->setTarget('_self');
4599
4600
            $em->persist($tool);
4601
            $em->flush();
4602
4603
            $tool->setId($tool->getIid());
4604
4605
            $em->persist($tool);
4606
            $em->flush();
4607
4608
            return true;
4609
        }
4610
4611
        if ($setVisibility == 1 && $tool) {
4612
            $tool
4613
                ->setName(strip_tags($category->getName()))
4614
                ->setVisibility(1);
4615
4616
            $em->persist($tool);
4617
            $em->flush();
4618
4619
            return true;
4620
        }
4621
4622
        return false;
4623
    }
4624
4625
    /**
4626
     * Check if the learnpath category is visible for a user.
4627
     *
4628
     * @param int
4629
     * @param int
4630
     *
4631
     * @return bool
4632
     */
4633
    public static function categoryIsVisibleForStudent(
4634
        CLpCategory $category,
4635
        User $user,
4636
        $courseId = 0,
4637
        $sessionId = 0
4638
    ) {
4639
        if (empty($category)) {
4640
            return false;
4641
        }
4642
4643
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4644
4645
        if ($isAllowedToEdit) {
4646
            return true;
4647
        }
4648
4649
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4650
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4651
4652
        $courseInfo = api_get_course_info_by_id($courseId);
4653
4654
        $categoryVisibility = api_get_item_visibility(
4655
            $courseInfo,
4656
            TOOL_LEARNPATH_CATEGORY,
4657
            $category->getId(),
4658
            $sessionId
4659
        );
4660
4661
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4662
            return false;
4663
        }
4664
4665
        $subscriptionSettings = self::getSubscriptionSettings();
4666
4667
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4668
            return true;
4669
        }
4670
4671
        $noUserSubscribed = false;
4672
        $noGroupSubscribed = true;
4673
        $users = $category->getUsers();
4674
        if (empty($users) || !$users->count()) {
4675
            $noUserSubscribed = true;
4676
        } elseif ($category->hasUserAdded($user)) {
4677
            return true;
4678
        }
4679
4680
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4681
        $em = Database::getManager();
4682
4683
        /** @var ItemPropertyRepository $itemRepo */
4684
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4685
4686
        /** @var CourseRepository $courseRepo */
4687
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4688
        $session = null;
4689
        if (!empty($sessionId)) {
4690
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4691
        }
4692
4693
        $course = $courseRepo->find($courseId);
4694
4695
        if ($courseId != 0) {
4696
            // Subscribed groups to a LP
4697
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4698
                TOOL_LEARNPATH_CATEGORY,
4699
                $category->getId(),
4700
                $course,
4701
                $session
4702
            );
4703
        }
4704
4705
        if (!empty($subscribedGroupsInLp)) {
4706
            $noGroupSubscribed = false;
4707
            if (!empty($groups)) {
4708
                $groups = array_column($groups, 'iid');
4709
                /** @var CItemProperty $item */
4710
                foreach ($subscribedGroupsInLp as $item) {
4711
                    if ($item->getGroup() &&
4712
                        in_array($item->getGroup()->getId(), $groups)
4713
                    ) {
4714
                        return true;
4715
                    }
4716
                }
4717
            }
4718
        }
4719
        $response = $noGroupSubscribed && $noUserSubscribed;
4720
4721
        return $response;
4722
    }
4723
4724
    /**
4725
     * Check if a learnpath category is published as course tool.
4726
     *
4727
     * @param int $courseId
4728
     *
4729
     * @return bool
4730
     */
4731
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4732
    {
4733
        $link = self::getCategoryLinkForTool($category->getId());
4734
        $em = Database::getManager();
4735
4736
        $tools = $em
4737
            ->createQuery("
4738
                SELECT t FROM ChamiloCourseBundle:CTool t
4739
                WHERE t.cId = :course AND
4740
                    t.name = :name AND
4741
                    t.image = 'lp_category.gif' AND
4742
                    t.link LIKE :link
4743
            ")
4744
            ->setParameters([
4745
                'course' => $courseId,
4746
                'name' => strip_tags($category->getName()),
4747
                'link' => "$link%",
4748
            ])
4749
            ->getResult();
4750
4751
        /** @var CTool $tool */
4752
        $tool = current($tools);
4753
4754
        return $tool ? $tool->getVisibility() : false;
4755
    }
4756
4757
    /**
4758
     * Restart the whole learnpath. Return the URL of the first element.
4759
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4760
     * To use a similar method  statically, use the create_new_attempt() method.
4761
     *
4762
     * @return bool
4763
     */
4764
    public function restart()
4765
    {
4766
        if ($this->debug > 0) {
4767
            error_log('In learnpath::restart()', 0);
4768
        }
4769
        // TODO
4770
        // Call autosave method to save the current progress.
4771
        //$this->index = 0;
4772
        if (api_is_invitee()) {
4773
            return false;
4774
        }
4775
        $session_id = api_get_session_id();
4776
        $course_id = api_get_course_int_id();
4777
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4778
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4779
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4780
        if ($this->debug > 2) {
4781
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4782
        }
4783
        Database::query($sql);
4784
        $view_id = Database::insert_id();
4785
4786
        if ($view_id) {
4787
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4788
            Database::query($sql);
4789
            $this->lp_view_id = $view_id;
4790
            $this->attempt = $this->attempt + 1;
4791
        } else {
4792
            $this->error = 'Could not insert into item_view table...';
4793
4794
            return false;
4795
        }
4796
        $this->autocomplete_parents($this->current);
4797
        foreach ($this->items as $index => $dummy) {
4798
            $this->items[$index]->restart();
4799
            $this->items[$index]->set_lp_view($this->lp_view_id);
4800
        }
4801
        $this->first();
4802
4803
        return true;
4804
    }
4805
4806
    /**
4807
     * Saves the current item.
4808
     *
4809
     * @return bool
4810
     */
4811
    public function save_current()
4812
    {
4813
        $debug = $this->debug;
4814
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4815
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4816
        if ($debug) {
4817
            error_log('save_current() saving item '.$this->current, 0);
4818
            error_log(''.print_r($this->items, true), 0);
4819
        }
4820
        if (isset($this->items[$this->current]) &&
4821
            is_object($this->items[$this->current])
4822
        ) {
4823
            if ($debug) {
4824
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4825
            }
4826
4827
            $res = $this->items[$this->current]->save(
4828
                false,
4829
                $this->prerequisites_match($this->current)
4830
            );
4831
            $this->autocomplete_parents($this->current);
4832
            $status = $this->items[$this->current]->get_status();
4833
            $this->update_queue[$this->current] = $status;
4834
4835
            if ($debug) {
4836
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4837
            }
4838
4839
            return $res;
4840
        }
4841
4842
        return false;
4843
    }
4844
4845
    /**
4846
     * Saves the given item.
4847
     *
4848
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4849
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4850
     *
4851
     * @return bool
4852
     */
4853
    public function save_item($item_id = null, $from_outside = true)
4854
    {
4855
        $debug = $this->debug;
4856
        if ($debug) {
4857
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4858
        }
4859
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4860
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4861
        if (empty($item_id)) {
4862
            $item_id = (int) $_REQUEST['id'];
4863
        }
4864
4865
        if (empty($item_id)) {
4866
            $item_id = $this->get_current_item_id();
4867
        }
4868
        if (isset($this->items[$item_id]) &&
4869
            is_object($this->items[$item_id])
4870
        ) {
4871
            if ($debug) {
4872
                error_log('Object exists');
4873
            }
4874
4875
            // Saving the item.
4876
            $res = $this->items[$item_id]->save(
4877
                $from_outside,
4878
                $this->prerequisites_match($item_id)
4879
            );
4880
4881
            if ($debug) {
4882
                error_log('update_queue before:');
4883
                error_log(print_r($this->update_queue, 1));
4884
            }
4885
            $this->autocomplete_parents($item_id);
4886
4887
            $status = $this->items[$item_id]->get_status();
4888
            $this->update_queue[$item_id] = $status;
4889
4890
            if ($debug) {
4891
                error_log('get_status(): '.$status);
4892
                error_log('update_queue after:');
4893
                error_log(print_r($this->update_queue, 1));
4894
            }
4895
4896
            return $res;
4897
        }
4898
4899
        return false;
4900
    }
4901
4902
    /**
4903
     * Saves the last item seen's ID only in case.
4904
     */
4905
    public function save_last($score = null)
4906
    {
4907
        $course_id = api_get_course_int_id();
4908
        $debug = $this->debug;
4909
        if ($debug) {
4910
            error_log('In learnpath::save_last()', 0);
4911
        }
4912
        $session_condition = api_get_session_condition(
4913
            api_get_session_id(),
4914
            true,
4915
            false
4916
        );
4917
        $table = Database::get_course_table(TABLE_LP_VIEW);
4918
4919
        $userId = $this->get_user_id();
4920
        if (empty($userId)) {
4921
            $userId = api_get_user_id();
4922
            if ($debug) {
4923
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4924
            }
4925
        }
4926
        if (isset($this->current) && !api_is_invitee()) {
4927
            if ($debug) {
4928
                error_log('Saving current item ('.$this->current.') for later review', 0);
4929
            }
4930
            $sql = "UPDATE $table SET
4931
                        last_item = ".$this->get_current_item_id()."
4932
                    WHERE
4933
                        c_id = $course_id AND
4934
                        lp_id = ".$this->get_id()." AND
4935
                        user_id = ".$userId." ".$session_condition;
4936
            if ($debug) {
4937
                error_log('Saving last item seen : '.$sql, 0);
4938
            }
4939
            Database::query($sql);
4940
        }
4941
4942
        if (!api_is_invitee()) {
4943
            // Save progress.
4944
            list($progress) = $this->get_progress_bar_text('%');
4945
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4946
            $scoreAsProgress = $this->getUseScoreAsProgress();
4947
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4948
                if ($debug) {
4949
                    error_log("Return false: Dont save score: $score");
4950
                    error_log("progress: $progress");
4951
                }
4952
4953
                return false;
4954
            }
4955
4956
            /*if ($course_id == 220 && $scoreAsProgress && $scoreAsProgressSetting) {
4957
                error_log("HOT FIX JULIO new score has been replaced from $progress to $score");
4958
                $progress = $score;
4959
            }*/
4960
4961
            if ($progress >= 0 && $progress <= 100) {
4962
                // Check database.
4963
                $progress = (int) $progress;
4964
                $sql = "UPDATE $table SET
4965
                            progress = $progress
4966
                        WHERE
4967
                            c_id = $course_id AND
4968
                            lp_id = ".$this->get_id()." AND
4969
                            user_id = ".$userId." ".$session_condition;
4970
                // Ignore errors as some tables might not have the progress field just yet.
4971
                Database::query($sql);
4972
                if ($debug) {
4973
                    error_log($sql);
4974
                }
4975
                $this->progress_db = $progress;
4976
            }
4977
        }
4978
    }
4979
4980
    /**
4981
     * Sets the current item ID (checks if valid and authorized first).
4982
     *
4983
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4984
     */
4985
    public function set_current_item($item_id = null)
4986
    {
4987
        $debug = $this->debug;
4988
        if ($debug) {
4989
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4990
        }
4991
        if (empty($item_id)) {
4992
            if ($debug) {
4993
                error_log('No new current item given, ignore...', 0);
4994
            }
4995
            // Do nothing.
4996
        } else {
4997
            if ($debug) {
4998
                error_log('New current item given is '.$item_id.'...', 0);
4999
            }
5000
            if (is_numeric($item_id)) {
5001
                $item_id = (int) $item_id;
5002
                // TODO: Check in database here.
5003
                $this->last = $this->current;
5004
                $this->current = $item_id;
5005
                // TODO: Update $this->index as well.
5006
                foreach ($this->ordered_items as $index => $item) {
5007
                    if ($item == $this->current) {
5008
                        $this->index = $index;
5009
                        break;
5010
                    }
5011
                }
5012
                if ($debug) {
5013
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5014
                }
5015
            } else {
5016
                if ($debug) {
5017
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5018
                }
5019
            }
5020
        }
5021
    }
5022
5023
    /**
5024
     * Sets the encoding.
5025
     *
5026
     * @param string $enc New encoding
5027
     *
5028
     * @return bool
5029
     *
5030
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5031
     */
5032
    public function set_encoding($enc = 'UTF-8')
5033
    {
5034
        $enc = api_refine_encoding_id($enc);
5035
        if (empty($enc)) {
5036
            $enc = api_get_system_encoding();
5037
        }
5038
        if (api_is_encoding_supported($enc)) {
5039
            $lp = $this->get_id();
5040
            if ($lp != 0) {
5041
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5042
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5043
                        WHERE iid = ".$lp;
5044
                $res = Database::query($sql);
5045
5046
                return $res;
5047
            }
5048
        }
5049
5050
        return false;
5051
    }
5052
5053
    /**
5054
     * Sets the JS lib setting in the database directly.
5055
     * This is the JavaScript library file this lp needs to load on startup.
5056
     *
5057
     * @param string $lib Proximity setting
5058
     *
5059
     * @return bool True on update success. False otherwise.
5060
     */
5061
    public function set_jslib($lib = '')
5062
    {
5063
        $lp = $this->get_id();
5064
5065
        if ($lp != 0) {
5066
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5067
            $lib = Database::escape_string($lib);
5068
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5069
                    WHERE iid = $lp";
5070
            $res = Database::query($sql);
5071
5072
            return $res;
5073
        }
5074
5075
        return false;
5076
    }
5077
5078
    /**
5079
     * Sets the name of the LP maker (publisher) (and save).
5080
     *
5081
     * @param string $name Optional string giving the new content_maker of this learnpath
5082
     *
5083
     * @return bool True
5084
     */
5085
    public function set_maker($name = '')
5086
    {
5087
        if (empty($name)) {
5088
            return false;
5089
        }
5090
        $this->maker = $name;
5091
        $table = Database::get_course_table(TABLE_LP_MAIN);
5092
        $lp_id = $this->get_id();
5093
        $sql = "UPDATE $table SET
5094
                content_maker = '".Database::escape_string($this->maker)."'
5095
                WHERE iid = $lp_id";
5096
        Database::query($sql);
5097
5098
        return true;
5099
    }
5100
5101
    /**
5102
     * Sets the name of the current learnpath (and save).
5103
     *
5104
     * @param string $name Optional string giving the new name of this learnpath
5105
     *
5106
     * @return bool True/False
5107
     */
5108
    public function set_name($name = null)
5109
    {
5110
        if (empty($name)) {
5111
            return false;
5112
        }
5113
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5114
        $name = Database::escape_string($name);
5115
5116
        $this->name = $name;
5117
5118
        $lp_id = $this->get_id();
5119
        $course_id = $this->course_info['real_id'];
5120
        $sql = "UPDATE $lp_table SET
5121
                name = '$name'
5122
                WHERE iid = $lp_id";
5123
        $result = Database::query($sql);
5124
        // If the lp is visible on the homepage, change his name there.
5125
        if (Database::affected_rows($result)) {
5126
            $session_id = api_get_session_id();
5127
            $session_condition = api_get_session_condition($session_id);
5128
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5129
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5130
            $sql = "UPDATE $tbl_tool SET name = '$name'
5131
            	    WHERE
5132
            	        c_id = $course_id AND
5133
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5134
            Database::query($sql);
5135
5136
            return true;
5137
        }
5138
5139
        return false;
5140
    }
5141
5142
    /**
5143
     * Set index specified prefix terms for all items in this path.
5144
     *
5145
     * @param string $terms_string Comma-separated list of terms
5146
     * @param string $prefix       Xapian term prefix
5147
     *
5148
     * @return bool False on error, true otherwise
5149
     */
5150
    public function set_terms_by_prefix($terms_string, $prefix)
5151
    {
5152
        $course_id = api_get_course_int_id();
5153
        if (api_get_setting('search_enabled') !== 'true') {
5154
            return false;
5155
        }
5156
5157
        if (!extension_loaded('xapian')) {
5158
            return false;
5159
        }
5160
5161
        $terms_string = trim($terms_string);
5162
        $terms = explode(',', $terms_string);
5163
        array_walk($terms, 'trim_value');
5164
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5165
5166
        // Don't do anything if no change, verify only at DB, not the search engine.
5167
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5168
            return false;
5169
        }
5170
5171
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5172
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5173
5174
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5175
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5176
        $lp_id = (int) $_POST['lp_id'];
5177
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5178
        $result = Database::query($sql);
5179
        $di = new ChamiloIndexer();
5180
5181
        while ($lp_item = Database::fetch_array($result)) {
5182
            // Get search_did.
5183
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5184
            $sql = 'SELECT * FROM %s
5185
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5186
                    LIMIT 1';
5187
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5188
5189
            //echo $sql; echo '<br>';
5190
            $res = Database::query($sql);
5191
            if (Database::num_rows($res) > 0) {
5192
                $se_ref = Database::fetch_array($res);
5193
                // Compare terms.
5194
                $doc = $di->get_document($se_ref['search_did']);
5195
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5196
                $xterms = [];
5197
                foreach ($xapian_terms as $xapian_term) {
5198
                    $xterms[] = substr($xapian_term['name'], 1);
5199
                }
5200
5201
                $dterms = $terms;
5202
                $missing_terms = array_diff($dterms, $xterms);
5203
                $deprecated_terms = array_diff($xterms, $dterms);
5204
5205
                // Save it to search engine.
5206
                foreach ($missing_terms as $term) {
5207
                    $doc->add_term($prefix.$term, 1);
5208
                }
5209
                foreach ($deprecated_terms as $term) {
5210
                    $doc->remove_term($prefix.$term);
5211
                }
5212
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5213
                $di->getDb()->flush();
5214
            }
5215
        }
5216
5217
        return true;
5218
    }
5219
5220
    /**
5221
     * Sets the theme of the LP (local/remote) (and save).
5222
     *
5223
     * @param string $name Optional string giving the new theme of this learnpath
5224
     *
5225
     * @return bool Returns true if theme name is not empty
5226
     */
5227
    public function set_theme($name = '')
5228
    {
5229
        $this->theme = $name;
5230
        $table = Database::get_course_table(TABLE_LP_MAIN);
5231
        $lp_id = $this->get_id();
5232
        $sql = "UPDATE $table
5233
                SET theme = '".Database::escape_string($this->theme)."'
5234
                WHERE iid = $lp_id";
5235
        Database::query($sql);
5236
5237
        return true;
5238
    }
5239
5240
    /**
5241
     * Sets the image of an LP (and save).
5242
     *
5243
     * @param string $name Optional string giving the new image of this learnpath
5244
     *
5245
     * @return bool Returns true if theme name is not empty
5246
     */
5247
    public function set_preview_image($name = '')
5248
    {
5249
        $this->preview_image = $name;
5250
        $table = Database::get_course_table(TABLE_LP_MAIN);
5251
        $lp_id = $this->get_id();
5252
        $sql = "UPDATE $table SET
5253
                preview_image = '".Database::escape_string($this->preview_image)."'
5254
                WHERE iid = $lp_id";
5255
        Database::query($sql);
5256
5257
        return true;
5258
    }
5259
5260
    /**
5261
     * Sets the author of a LP (and save).
5262
     *
5263
     * @param string $name Optional string giving the new author of this learnpath
5264
     *
5265
     * @return bool Returns true if author's name is not empty
5266
     */
5267
    public function set_author($name = '')
5268
    {
5269
        $this->author = $name;
5270
        $table = Database::get_course_table(TABLE_LP_MAIN);
5271
        $lp_id = $this->get_id();
5272
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5273
                WHERE iid = $lp_id";
5274
        Database::query($sql);
5275
5276
        return true;
5277
    }
5278
5279
    /**
5280
     * Sets the hide_toc_frame parameter of a LP (and save).
5281
     *
5282
     * @param int $hide 1 if frame is hidden 0 then else
5283
     *
5284
     * @return bool Returns true if author's name is not empty
5285
     */
5286
    public function set_hide_toc_frame($hide)
5287
    {
5288
        if (intval($hide) == $hide) {
5289
            $this->hide_toc_frame = $hide;
5290
            $table = Database::get_course_table(TABLE_LP_MAIN);
5291
            $lp_id = $this->get_id();
5292
            $sql = "UPDATE $table SET
5293
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5294
                    WHERE iid = $lp_id";
5295
            Database::query($sql);
5296
5297
            return true;
5298
        }
5299
5300
        return false;
5301
    }
5302
5303
    /**
5304
     * Sets the prerequisite of a LP (and save).
5305
     *
5306
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5307
     *
5308
     * @return bool returns true if prerequisite is not empty
5309
     */
5310
    public function set_prerequisite($prerequisite)
5311
    {
5312
        $this->prerequisite = (int) $prerequisite;
5313
        $table = Database::get_course_table(TABLE_LP_MAIN);
5314
        $lp_id = $this->get_id();
5315
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5316
                WHERE iid = $lp_id";
5317
        Database::query($sql);
5318
5319
        return true;
5320
    }
5321
5322
    /**
5323
     * Sets the location/proximity of the LP (local/remote) (and save).
5324
     *
5325
     * @param string $name Optional string giving the new location of this learnpath
5326
     *
5327
     * @return bool True on success / False on error
5328
     */
5329
    public function set_proximity($name = '')
5330
    {
5331
        if (empty($name)) {
5332
            return false;
5333
        }
5334
5335
        $this->proximity = $name;
5336
        $table = Database::get_course_table(TABLE_LP_MAIN);
5337
        $lp_id = $this->get_id();
5338
        $sql = "UPDATE $table SET
5339
                    content_local = '".Database::escape_string($name)."'
5340
                WHERE iid = $lp_id";
5341
        Database::query($sql);
5342
5343
        return true;
5344
    }
5345
5346
    /**
5347
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5348
     *
5349
     * @param int $id DB ID of the item
5350
     */
5351
    public function set_previous_item($id)
5352
    {
5353
        if ($this->debug > 0) {
5354
            error_log('In learnpath::set_previous_item()', 0);
5355
        }
5356
        $this->last = $id;
5357
    }
5358
5359
    /**
5360
     * Sets use_max_score.
5361
     *
5362
     * @param int $use_max_score Optional string giving the new location of this learnpath
5363
     *
5364
     * @return bool True on success / False on error
5365
     */
5366
    public function set_use_max_score($use_max_score = 1)
5367
    {
5368
        $use_max_score = (int) $use_max_score;
5369
        $this->use_max_score = $use_max_score;
5370
        $table = Database::get_course_table(TABLE_LP_MAIN);
5371
        $lp_id = $this->get_id();
5372
        $sql = "UPDATE $table SET
5373
                    use_max_score = '".$this->use_max_score."'
5374
                WHERE iid = $lp_id";
5375
        Database::query($sql);
5376
5377
        return true;
5378
    }
5379
5380
    /**
5381
     * Sets and saves the expired_on date.
5382
     *
5383
     * @param string $expired_on Optional string giving the new author of this learnpath
5384
     *
5385
     * @throws \Doctrine\ORM\OptimisticLockException
5386
     *
5387
     * @return bool Returns true if author's name is not empty
5388
     */
5389
    public function set_expired_on($expired_on)
5390
    {
5391
        $em = Database::getManager();
5392
        /** @var CLp $lp */
5393
        $lp = $em
5394
            ->getRepository('ChamiloCourseBundle:CLp')
5395
            ->findOneBy(
5396
                [
5397
                    'iid' => $this->get_id(),
5398
                ]
5399
            );
5400
5401
        if (!$lp) {
5402
            return false;
5403
        }
5404
5405
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5406
5407
        $lp->setExpiredOn($this->expired_on);
5408
        $em->persist($lp);
5409
        $em->flush();
5410
5411
        return true;
5412
    }
5413
5414
    /**
5415
     * Sets and saves the publicated_on date.
5416
     *
5417
     * @param string $publicated_on Optional string giving the new author of this learnpath
5418
     *
5419
     * @throws \Doctrine\ORM\OptimisticLockException
5420
     *
5421
     * @return bool Returns true if author's name is not empty
5422
     */
5423
    public function set_publicated_on($publicated_on)
5424
    {
5425
        $em = Database::getManager();
5426
        /** @var CLp $lp */
5427
        $lp = $em
5428
            ->getRepository('ChamiloCourseBundle:CLp')
5429
            ->findOneBy(
5430
                [
5431
                    'iid' => $this->get_id(),
5432
                ]
5433
            );
5434
5435
        if (!$lp) {
5436
            return false;
5437
        }
5438
5439
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5440
        $lp->setPublicatedOn($this->publicated_on);
5441
        $em->persist($lp);
5442
        $em->flush();
5443
5444
        return true;
5445
    }
5446
5447
    /**
5448
     * Sets and saves the expired_on date.
5449
     *
5450
     * @return bool Returns true if author's name is not empty
5451
     */
5452
    public function set_modified_on()
5453
    {
5454
        $this->modified_on = api_get_utc_datetime();
5455
        $table = Database::get_course_table(TABLE_LP_MAIN);
5456
        $lp_id = $this->get_id();
5457
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5458
                WHERE iid = $lp_id";
5459
        Database::query($sql);
5460
5461
        return true;
5462
    }
5463
5464
    /**
5465
     * Sets the object's error message.
5466
     *
5467
     * @param string $error Error message. If empty, reinits the error string
5468
     */
5469
    public function set_error_msg($error = '')
5470
    {
5471
        if ($this->debug > 0) {
5472
            error_log('In learnpath::set_error_msg()', 0);
5473
        }
5474
        if (empty($error)) {
5475
            $this->error = '';
5476
        } else {
5477
            $this->error .= $error;
5478
        }
5479
    }
5480
5481
    /**
5482
     * Launches the current item if not 'sco'
5483
     * (starts timer and make sure there is a record ready in the DB).
5484
     *
5485
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5486
     *
5487
     * @return bool
5488
     */
5489
    public function start_current_item($allow_new_attempt = false)
5490
    {
5491
        $debug = $this->debug;
5492
        if ($debug) {
5493
            error_log('In learnpath::start_current_item()');
5494
            error_log('current: '.$this->current);
5495
        }
5496
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5497
            $type = $this->get_type();
5498
            $item_type = $this->items[$this->current]->get_type();
5499
            if (($type == 2 && $item_type != 'sco') ||
5500
                ($type == 3 && $item_type != 'au') ||
5501
                (
5502
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5503
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5504
                )
5505
            ) {
5506
                if ($debug) {
5507
                    error_log('item type: '.$item_type);
5508
                    error_log('lp type: '.$type);
5509
                }
5510
                $this->items[$this->current]->open($allow_new_attempt);
5511
                $this->autocomplete_parents($this->current);
5512
                $prereq_check = $this->prerequisites_match($this->current);
5513
                if ($debug) {
5514
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5515
                }
5516
                $this->items[$this->current]->save(false, $prereq_check);
5517
            }
5518
            // If sco, then it is supposed to have been updated by some other call.
5519
            if ($item_type == 'sco') {
5520
                $this->items[$this->current]->restart();
5521
            }
5522
        }
5523
        if ($debug) {
5524
            error_log('lp_view_session_id');
5525
            error_log($this->lp_view_session_id);
5526
            error_log('api session id');
5527
            error_log(api_get_session_id());
5528
            error_log('End of learnpath::start_current_item()');
5529
        }
5530
5531
        return true;
5532
    }
5533
5534
    /**
5535
     * Stops the processing and counters for the old item (as held in $this->last).
5536
     *
5537
     * @return bool True/False
5538
     */
5539
    public function stop_previous_item()
5540
    {
5541
        $debug = $this->debug;
5542
        if ($debug) {
5543
            error_log('In learnpath::stop_previous_item()', 0);
5544
        }
5545
5546
        if ($this->last != 0 && $this->last != $this->current &&
5547
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5548
        ) {
5549
            if ($debug) {
5550
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5551
            }
5552
            switch ($this->get_type()) {
5553
                case '3':
5554
                    if ($this->items[$this->last]->get_type() != 'au') {
5555
                        if ($debug) {
5556
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5557
                        }
5558
                        $this->items[$this->last]->close();
5559
                    } else {
5560
                        if ($debug) {
5561
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5562
                        }
5563
                    }
5564
                    break;
5565
                case '2':
5566
                    if ($this->items[$this->last]->get_type() != 'sco') {
5567
                        if ($debug) {
5568
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5569
                        }
5570
                        $this->items[$this->last]->close();
5571
                    } else {
5572
                        if ($debug) {
5573
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5574
                        }
5575
                    }
5576
                    break;
5577
                case '1':
5578
                default:
5579
                    if ($debug) {
5580
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5581
                    }
5582
                    $this->items[$this->last]->close();
5583
                    break;
5584
            }
5585
        } else {
5586
            if ($debug) {
5587
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5588
            }
5589
5590
            return false;
5591
        }
5592
5593
        return true;
5594
    }
5595
5596
    /**
5597
     * Updates the default view mode from fullscreen to embedded and inversely.
5598
     *
5599
     * @return string The current default view mode ('fullscreen' or 'embedded')
5600
     */
5601
    public function update_default_view_mode()
5602
    {
5603
        $table = Database::get_course_table(TABLE_LP_MAIN);
5604
        $sql = "SELECT * FROM $table
5605
                WHERE iid = ".$this->get_id();
5606
        $res = Database::query($sql);
5607
        if (Database::num_rows($res) > 0) {
5608
            $row = Database::fetch_array($res);
5609
            $default_view_mode = $row['default_view_mod'];
5610
            $view_mode = $default_view_mode;
5611
            switch ($default_view_mode) {
5612
                case 'fullscreen': // default with popup
5613
                    $view_mode = 'embedded';
5614
                    break;
5615
                case 'embedded': // default view with left menu
5616
                    $view_mode = 'embedframe';
5617
                    break;
5618
                case 'embedframe': //folded menu
5619
                    $view_mode = 'impress';
5620
                    break;
5621
                case 'impress':
5622
                    $view_mode = 'fullscreen';
5623
                    break;
5624
            }
5625
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5626
                    WHERE iid = ".$this->get_id();
5627
            Database::query($sql);
5628
            $this->mode = $view_mode;
5629
5630
            return $view_mode;
5631
        }
5632
5633
        return -1;
5634
    }
5635
5636
    /**
5637
     * Updates the default behaviour about auto-commiting SCORM updates.
5638
     *
5639
     * @return bool True if auto-commit has been set to 'on', false otherwise
5640
     */
5641
    public function update_default_scorm_commit()
5642
    {
5643
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5644
        $sql = "SELECT * FROM $lp_table
5645
                WHERE iid = ".$this->get_id();
5646
        $res = Database::query($sql);
5647
        if (Database::num_rows($res) > 0) {
5648
            $row = Database::fetch_array($res);
5649
            $force = $row['force_commit'];
5650
            if ($force == 1) {
5651
                $force = 0;
5652
                $force_return = false;
5653
            } elseif ($force == 0) {
5654
                $force = 1;
5655
                $force_return = true;
5656
            }
5657
            $sql = "UPDATE $lp_table SET force_commit = $force
5658
                    WHERE iid = ".$this->get_id();
5659
            Database::query($sql);
5660
            $this->force_commit = $force_return;
5661
5662
            return $force_return;
5663
        }
5664
5665
        return -1;
5666
    }
5667
5668
    /**
5669
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5670
     *
5671
     * @return bool True on success, false on failure
5672
     */
5673
    public function update_display_order()
5674
    {
5675
        $course_id = api_get_course_int_id();
5676
        $table = Database::get_course_table(TABLE_LP_MAIN);
5677
        $sql = "SELECT * FROM $table
5678
                WHERE c_id = $course_id
5679
                ORDER BY display_order";
5680
        $res = Database::query($sql);
5681
        if ($res === false) {
5682
            return false;
5683
        }
5684
5685
        $num = Database::num_rows($res);
5686
        // First check the order is correct, globally (might be wrong because
5687
        // of versions < 1.8.4).
5688
        if ($num > 0) {
5689
            $i = 1;
5690
            while ($row = Database::fetch_array($res)) {
5691
                if ($row['display_order'] != $i) {
5692
                    // If we find a gap in the order, we need to fix it.
5693
                    $sql = "UPDATE $table SET display_order = $i
5694
                            WHERE iid = ".$row['iid'];
5695
                    Database::query($sql);
5696
                }
5697
                $i++;
5698
            }
5699
        }
5700
5701
        return true;
5702
    }
5703
5704
    /**
5705
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5706
     *
5707
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5708
     */
5709
    public function update_reinit()
5710
    {
5711
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5712
        $sql = "SELECT * FROM $lp_table
5713
                WHERE iid = ".$this->get_id();
5714
        $res = Database::query($sql);
5715
        if (Database::num_rows($res) > 0) {
5716
            $row = Database::fetch_array($res);
5717
            $force = $row['prevent_reinit'];
5718
            if ($force == 1) {
5719
                $force = 0;
5720
            } elseif ($force == 0) {
5721
                $force = 1;
5722
            }
5723
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5724
                    WHERE iid = ".$this->get_id();
5725
            Database::query($sql);
5726
            $this->prevent_reinit = $force;
5727
5728
            return $force;
5729
        }
5730
5731
        return -1;
5732
    }
5733
5734
    /**
5735
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5736
     *
5737
     * @return string 'single', 'multi' or 'seriousgame'
5738
     *
5739
     * @author ndiechburg <[email protected]>
5740
     */
5741
    public function get_attempt_mode()
5742
    {
5743
        //Set default value for seriousgame_mode
5744
        if (!isset($this->seriousgame_mode)) {
5745
            $this->seriousgame_mode = 0;
5746
        }
5747
        // Set default value for prevent_reinit
5748
        if (!isset($this->prevent_reinit)) {
5749
            $this->prevent_reinit = 1;
5750
        }
5751
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5752
            return 'seriousgame';
5753
        }
5754
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5755
            return 'single';
5756
        }
5757
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5758
            return 'multiple';
5759
        }
5760
5761
        return 'single';
5762
    }
5763
5764
    /**
5765
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5766
     *
5767
     * @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...
5768
     *
5769
     * @return bool
5770
     *
5771
     * @author ndiechburg <[email protected]>
5772
     */
5773
    public function set_attempt_mode($mode)
5774
    {
5775
        switch ($mode) {
5776
            case 'seriousgame':
5777
                $sg_mode = 1;
5778
                $prevent_reinit = 1;
5779
                break;
5780
            case 'single':
5781
                $sg_mode = 0;
5782
                $prevent_reinit = 1;
5783
                break;
5784
            case 'multiple':
5785
                $sg_mode = 0;
5786
                $prevent_reinit = 0;
5787
                break;
5788
            default:
5789
                $sg_mode = 0;
5790
                $prevent_reinit = 0;
5791
                break;
5792
        }
5793
        $this->prevent_reinit = $prevent_reinit;
5794
        $this->seriousgame_mode = $sg_mode;
5795
        $table = Database::get_course_table(TABLE_LP_MAIN);
5796
        $sql = "UPDATE $table SET
5797
                prevent_reinit = $prevent_reinit ,
5798
                seriousgame_mode = $sg_mode
5799
                WHERE iid = ".$this->get_id();
5800
        $res = Database::query($sql);
5801
        if ($res) {
5802
            return true;
5803
        } else {
5804
            return false;
5805
        }
5806
    }
5807
5808
    /**
5809
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5810
     *
5811
     * @author ndiechburg <[email protected]>
5812
     */
5813
    public function switch_attempt_mode()
5814
    {
5815
        $mode = $this->get_attempt_mode();
5816
        switch ($mode) {
5817
            case 'single':
5818
                $next_mode = 'multiple';
5819
                break;
5820
            case 'multiple':
5821
                $next_mode = 'seriousgame';
5822
                break;
5823
            case 'seriousgame':
5824
            default:
5825
                $next_mode = 'single';
5826
                break;
5827
        }
5828
        $this->set_attempt_mode($next_mode);
5829
    }
5830
5831
    /**
5832
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5833
     * but possibility to do again a completed item.
5834
     *
5835
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5836
     *
5837
     * @author ndiechburg <[email protected]>
5838
     */
5839
    public function set_seriousgame_mode()
5840
    {
5841
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5842
        $sql = "SELECT * FROM $lp_table
5843
                WHERE iid = ".$this->get_id();
5844
        $res = Database::query($sql);
5845
        if (Database::num_rows($res) > 0) {
5846
            $row = Database::fetch_array($res);
5847
            $force = $row['seriousgame_mode'];
5848
            if ($force == 1) {
5849
                $force = 0;
5850
            } elseif ($force == 0) {
5851
                $force = 1;
5852
            }
5853
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5854
			        WHERE iid = ".$this->get_id();
5855
            Database::query($sql);
5856
            $this->seriousgame_mode = $force;
5857
5858
            return $force;
5859
        }
5860
5861
        return -1;
5862
    }
5863
5864
    /**
5865
     * Updates the "scorm_debug" value that shows or hide the debug window.
5866
     *
5867
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5868
     */
5869
    public function update_scorm_debug()
5870
    {
5871
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5872
        $sql = "SELECT * FROM $lp_table
5873
                WHERE iid = ".$this->get_id();
5874
        $res = Database::query($sql);
5875
        if (Database::num_rows($res) > 0) {
5876
            $row = Database::fetch_array($res);
5877
            $force = $row['debug'];
5878
            if ($force == 1) {
5879
                $force = 0;
5880
            } elseif ($force == 0) {
5881
                $force = 1;
5882
            }
5883
            $sql = "UPDATE $lp_table SET debug = $force
5884
                    WHERE iid = ".$this->get_id();
5885
            Database::query($sql);
5886
            $this->scorm_debug = $force;
5887
5888
            return $force;
5889
        }
5890
5891
        return -1;
5892
    }
5893
5894
    /**
5895
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5896
     *
5897
     * @author Kevin Van Den Haute
5898
     *
5899
     * @param  array
5900
     */
5901
    public function tree_array($array)
5902
    {
5903
        $array = $this->sort_tree_array($array);
5904
        $this->create_tree_array($array);
5905
    }
5906
5907
    /**
5908
     * Creates an array with the elements of the learning path tree in it.
5909
     *
5910
     * @author Kevin Van Den Haute
5911
     *
5912
     * @param array $array
5913
     * @param int   $parent
5914
     * @param int   $depth
5915
     * @param array $tmp
5916
     */
5917
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5918
    {
5919
        if (is_array($array)) {
5920
            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...
5921
                if ($array[$i]['parent_item_id'] == $parent) {
5922
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5923
                        $tmp[] = $array[$i]['parent_item_id'];
5924
                        $depth++;
5925
                    }
5926
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5927
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5928
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5929
5930
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5931
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5932
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5933
                    $this->arrMenu[] = [
5934
                        'id' => $array[$i]['id'],
5935
                        'ref' => $ref,
5936
                        'item_type' => $array[$i]['item_type'],
5937
                        'title' => $array[$i]['title'],
5938
                        'title_raw' => $array[$i]['title_raw'],
5939
                        'path' => $path,
5940
                        'description' => $array[$i]['description'],
5941
                        'parent_item_id' => $array[$i]['parent_item_id'],
5942
                        'previous_item_id' => $array[$i]['previous_item_id'],
5943
                        'next_item_id' => $array[$i]['next_item_id'],
5944
                        'min_score' => $array[$i]['min_score'],
5945
                        'max_score' => $array[$i]['max_score'],
5946
                        'mastery_score' => $array[$i]['mastery_score'],
5947
                        'display_order' => $array[$i]['display_order'],
5948
                        'prerequisite' => $preq,
5949
                        'depth' => $depth,
5950
                        'audio' => $audio,
5951
                        'prerequisite_min_score' => $prerequisiteMinScore,
5952
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5953
                    ];
5954
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5955
                }
5956
            }
5957
        }
5958
    }
5959
5960
    /**
5961
     * Sorts a multi dimensional array by parent id and display order.
5962
     *
5963
     * @author Kevin Van Den Haute
5964
     *
5965
     * @param array $array (array with al the learning path items in it)
5966
     *
5967
     * @return array
5968
     */
5969
    public function sort_tree_array($array)
5970
    {
5971
        foreach ($array as $key => $row) {
5972
            $parent[$key] = $row['parent_item_id'];
5973
            $position[$key] = $row['display_order'];
5974
        }
5975
5976
        if (count($array) > 0) {
5977
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5978
        }
5979
5980
        return $array;
5981
    }
5982
5983
    /**
5984
     * Function that creates a html list of learning path items so that we can add audio files to them.
5985
     *
5986
     * @author Kevin Van Den Haute
5987
     *
5988
     * @return string
5989
     */
5990
    public function overview()
5991
    {
5992
        $return = '';
5993
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5994
5995
        // we need to start a form when we want to update all the mp3 files
5996
        if ($update_audio == 'true') {
5997
            $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">';
5998
        }
5999
        $return .= '<div id="message"></div>';
6000
        if (count($this->items) == 0) {
6001
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6002
        } else {
6003
            $return_audio = '<table class="table table-hover table-striped data_table">';
6004
            $return_audio .= '<tr>';
6005
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6006
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6007
            $return_audio .= '</tr>';
6008
6009
            if ($update_audio != 'true') {
6010
                $return .= '<div class="col-md-12">';
6011
                $return .= self::return_new_tree($update_audio);
6012
                $return .= '</div>';
6013
                $return .= Display::div(
6014
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6015
                    ['style' => 'float:left; margin-top:15px;width:100%']
6016
                );
6017
            } else {
6018
                $return_audio .= self::return_new_tree($update_audio);
6019
                $return .= $return_audio.'</table>';
6020
            }
6021
6022
            // We need to close the form when we are updating the mp3 files.
6023
            if ($update_audio == 'true') {
6024
                $return .= '<div class="footer-audio">';
6025
                $return .= Display::button(
6026
                    'save_audio',
6027
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6028
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6029
                );
6030
                $return .= '</div>';
6031
            }
6032
        }
6033
6034
        // We need to close the form when we are updating the mp3 files.
6035
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6036
            $return .= '</form>';
6037
        }
6038
6039
        return $return;
6040
    }
6041
6042
    /**
6043
     * @param string $update_audio
6044
     *
6045
     * @return array
6046
     */
6047
    public function processBuildMenuElements($update_audio = 'false')
6048
    {
6049
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6050
        $arrLP = $this->getItemsForForm();
6051
6052
        $this->tree_array($arrLP);
6053
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6054
        unset($this->arrMenu);
6055
        $default_data = null;
6056
        $default_content = null;
6057
        $elements = [];
6058
        $return_audio = null;
6059
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6060
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6061
        $countItems = count($arrLP);
6062
6063
        $upIcon = Display::return_icon(
6064
            'up.png',
6065
            get_lang('Up'),
6066
            [],
6067
            ICON_SIZE_TINY
6068
        );
6069
6070
        $disableUpIcon = Display::return_icon(
6071
            'up_na.png',
6072
            get_lang('Up'),
6073
            [],
6074
            ICON_SIZE_TINY
6075
        );
6076
6077
        $downIcon = Display::return_icon(
6078
            'down.png',
6079
            get_lang('Down'),
6080
            [],
6081
            ICON_SIZE_TINY
6082
        );
6083
6084
        $disableDownIcon = Display::return_icon(
6085
            'down_na.png',
6086
            get_lang('Down'),
6087
            [],
6088
            ICON_SIZE_TINY
6089
        );
6090
6091
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6092
6093
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6094
        $plugin = null;
6095
        if ($pluginCalendar) {
6096
            $plugin = LearningCalendarPlugin::create();
6097
        }
6098
6099
        for ($i = 0; $i < $countItems; $i++) {
6100
            $parent_id = $arrLP[$i]['parent_item_id'];
6101
            $title = $arrLP[$i]['title'];
6102
            $title_cut = $arrLP[$i]['title_raw'];
6103
            if ($show === false) {
6104
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6105
            }
6106
            // Link for the documents
6107
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6108
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6109
                $title_cut = Display::url(
6110
                    $title_cut,
6111
                    $url,
6112
                    [
6113
                        'class' => 'ajax moved',
6114
                        'data-title' => $title,
6115
                        'title' => $title,
6116
                    ]
6117
                );
6118
            }
6119
6120
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6121
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6122
                Session::write('pathItem', $arrLP[$i]['path']);
6123
            }
6124
6125
            $oddClass = 'row_even';
6126
            if (($i % 2) == 0) {
6127
                $oddClass = 'row_odd';
6128
            }
6129
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6130
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6131
6132
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6133
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6134
            } else {
6135
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6136
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6137
                } else {
6138
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6139
                        $icon = Display::return_icon('certificate.png');
6140
                    } else {
6141
                        $icon = Display::return_icon('folder_document.gif');
6142
                    }
6143
                }
6144
            }
6145
6146
            // The audio column.
6147
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6148
            $audio = '';
6149
            if (!$update_audio || $update_audio != 'true') {
6150
                if (empty($arrLP[$i]['audio'])) {
6151
                    $audio .= '';
6152
                }
6153
            } else {
6154
                $types = self::getChapterTypes();
6155
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6156
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6157
                    if (!empty($arrLP[$i]['audio'])) {
6158
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6159
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6160
                    }
6161
                }
6162
            }
6163
6164
            $return_audio .= Display::span($icon.' '.$title).
6165
                Display::tag(
6166
                    'td',
6167
                    $audio,
6168
                    ['style' => '']
6169
                );
6170
            $return_audio .= '</td>';
6171
            $move_icon = '';
6172
            $move_item_icon = '';
6173
            $edit_icon = '';
6174
            $delete_icon = '';
6175
            $audio_icon = '';
6176
            $prerequisities_icon = '';
6177
            $forumIcon = '';
6178
            $previewIcon = '';
6179
            $pluginCalendarIcon = '';
6180
            $orderIcons = '';
6181
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6182
6183
            if ($is_allowed_to_edit) {
6184
                if (!$update_audio || $update_audio != 'true') {
6185
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6186
                        $move_icon .= '<a class="moved" href="#">';
6187
                        $move_icon .= Display::return_icon(
6188
                            'move_everywhere.png',
6189
                            get_lang('Move'),
6190
                            [],
6191
                            ICON_SIZE_TINY
6192
                        );
6193
                        $move_icon .= '</a>';
6194
                    }
6195
                }
6196
6197
                // No edit for this item types
6198
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6199
                    if ($arrLP[$i]['item_type'] != 'dir') {
6200
                        $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">';
6201
                        $edit_icon .= Display::return_icon(
6202
                            'edit.png',
6203
                            get_lang('LearnpathEditModule'),
6204
                            [],
6205
                            ICON_SIZE_TINY
6206
                        );
6207
                        $edit_icon .= '</a>';
6208
6209
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6210
                            $forumThread = null;
6211
                            if (isset($this->items[$arrLP[$i]['id']])) {
6212
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6213
                                    $this->course_int_id,
6214
                                    $this->lp_session_id
6215
                                );
6216
                            }
6217
                            if ($forumThread) {
6218
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6219
                                        'action' => 'dissociate_forum',
6220
                                        'id' => $arrLP[$i]['id'],
6221
                                        'lp_id' => $this->lp_id,
6222
                                    ]);
6223
                                $forumIcon = Display::url(
6224
                                    Display::return_icon(
6225
                                        'forum.png',
6226
                                        get_lang('DissociateForumToLPItem'),
6227
                                        [],
6228
                                        ICON_SIZE_TINY
6229
                                    ),
6230
                                    $forumIconUrl,
6231
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6232
                                );
6233
                            } else {
6234
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6235
                                        'action' => 'create_forum',
6236
                                        'id' => $arrLP[$i]['id'],
6237
                                        'lp_id' => $this->lp_id,
6238
                                    ]);
6239
                                $forumIcon = Display::url(
6240
                                    Display::return_icon(
6241
                                        'forum.png',
6242
                                        get_lang('AssociateForumToLPItem'),
6243
                                        [],
6244
                                        ICON_SIZE_TINY
6245
                                    ),
6246
                                    $forumIconUrl,
6247
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6248
                                );
6249
                            }
6250
                        }
6251
                    } else {
6252
                        $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">';
6253
                        $edit_icon .= Display::return_icon(
6254
                            'edit.png',
6255
                            get_lang('LearnpathEditModule'),
6256
                            [],
6257
                            ICON_SIZE_TINY
6258
                        );
6259
                        $edit_icon .= '</a>';
6260
                    }
6261
                } else {
6262
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6263
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6264
                        $edit_icon .= Display::return_icon(
6265
                            'edit.png',
6266
                            get_lang('Edit'),
6267
                            [],
6268
                            ICON_SIZE_TINY
6269
                        );
6270
                        $edit_icon .= '</a>';
6271
                    }
6272
                }
6273
6274
                if ($pluginCalendar) {
6275
                    $pluginLink = $pluginUrl.
6276
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6277
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6278
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6279
                    if ($itemInfo && $itemInfo['value'] == 1) {
6280
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6281
                    }
6282
                    $pluginCalendarIcon = Display::url(
6283
                        $iconCalendar,
6284
                        $pluginLink,
6285
                        ['class' => 'btn btn-default']
6286
                    );
6287
                }
6288
6289
                if ($arrLP[$i]['item_type'] != 'final_item') {
6290
                    $orderIcons = Display::url(
6291
                        $upIcon,
6292
                        'javascript:void(0)',
6293
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6294
                    );
6295
                    $orderIcons .= Display::url(
6296
                        $downIcon,
6297
                        'javascript:void(0)',
6298
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6299
                    );
6300
                }
6301
6302
                $delete_icon .= ' <a
6303
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6304
                    onclick="return confirmation(\''.addslashes($title).'\');"
6305
                    class="btn btn-default">';
6306
                $delete_icon .= Display::return_icon(
6307
                    'delete.png',
6308
                    get_lang('LearnpathDeleteModule'),
6309
                    [],
6310
                    ICON_SIZE_TINY
6311
                );
6312
                $delete_icon .= '</a>';
6313
6314
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6315
                $previewImage = Display::return_icon(
6316
                    'preview_view.png',
6317
                    get_lang('Preview'),
6318
                    [],
6319
                    ICON_SIZE_TINY
6320
                );
6321
6322
                switch ($arrLP[$i]['item_type']) {
6323
                    case TOOL_DOCUMENT:
6324
                    case TOOL_LP_FINAL_ITEM:
6325
                    case TOOL_READOUT_TEXT:
6326
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6327
                        $previewIcon = Display::url(
6328
                            $previewImage,
6329
                            $urlPreviewLink,
6330
                            [
6331
                                'target' => '_blank',
6332
                                'class' => 'btn btn-default',
6333
                                'data-title' => $arrLP[$i]['title'],
6334
                                'title' => $arrLP[$i]['title'],
6335
                            ]
6336
                        );
6337
                        break;
6338
                    case TOOL_THREAD:
6339
                    case TOOL_FORUM:
6340
                    case TOOL_QUIZ:
6341
                    case TOOL_STUDENTPUBLICATION:
6342
                    case TOOL_LP_FINAL_ITEM:
6343
                    case TOOL_LINK:
6344
                        $class = 'btn btn-default';
6345
                        $target = '_blank';
6346
                        $link = self::rl_get_resource_link_for_learnpath(
6347
                            $this->course_int_id,
6348
                            $this->lp_id,
6349
                            $arrLP[$i]['id'],
6350
                            0
6351
                        );
6352
                        $previewIcon = Display::url(
6353
                            $previewImage,
6354
                            $link,
6355
                            [
6356
                                'class' => $class,
6357
                                'data-title' => $arrLP[$i]['title'],
6358
                                'title' => $arrLP[$i]['title'],
6359
                                'target' => $target,
6360
                            ]
6361
                        );
6362
                        break;
6363
                    default:
6364
                        $previewIcon = Display::url(
6365
                            $previewImage,
6366
                            $url.'&action=view_item',
6367
                            ['class' => 'btn btn-default', 'target' => '_blank']
6368
                        );
6369
                        break;
6370
                }
6371
6372
                if ($arrLP[$i]['item_type'] != 'dir') {
6373
                    $prerequisities_icon = Display::url(
6374
                        Display::return_icon(
6375
                            'accept.png',
6376
                            get_lang('LearnpathPrerequisites'),
6377
                            [],
6378
                            ICON_SIZE_TINY
6379
                        ),
6380
                        $url.'&action=edit_item_prereq',
6381
                        ['class' => 'btn btn-default']
6382
                    );
6383
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6384
                        $move_item_icon = Display::url(
6385
                            Display::return_icon(
6386
                                'move.png',
6387
                                get_lang('Move'),
6388
                                [],
6389
                                ICON_SIZE_TINY
6390
                            ),
6391
                            $url.'&action=move_item',
6392
                            ['class' => 'btn btn-default']
6393
                        );
6394
                    }
6395
                    $audio_icon = Display::url(
6396
                        Display::return_icon(
6397
                            'audio.png',
6398
                            get_lang('UplUpload'),
6399
                            [],
6400
                            ICON_SIZE_TINY
6401
                        ),
6402
                        $url.'&action=add_audio',
6403
                        ['class' => 'btn btn-default']
6404
                    );
6405
                }
6406
            }
6407
            if ($update_audio != 'true') {
6408
                $row = $move_icon.' '.$icon.
6409
                    Display::span($title_cut).
6410
                    Display::tag(
6411
                        'div',
6412
                        "<div class=\"btn-group btn-group-xs\">
6413
                                    $previewIcon
6414
                                    $audio
6415
                                    $edit_icon
6416
                                    $pluginCalendarIcon
6417
                                    $forumIcon
6418
                                    $prerequisities_icon
6419
                                    $move_item_icon
6420
                                    $audio_icon
6421
                                    $orderIcons
6422
                                    $delete_icon
6423
                                </div>",
6424
                        ['class' => 'btn-toolbar button_actions']
6425
                    );
6426
            } else {
6427
                $row =
6428
                    Display::span($title.$icon).
6429
                    Display::span($audio, ['class' => 'button_actions']);
6430
            }
6431
6432
            $default_data[$arrLP[$i]['id']] = $row;
6433
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6434
6435
            if (empty($parent_id)) {
6436
                $elements[$arrLP[$i]['id']]['data'] = $row;
6437
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6438
            } else {
6439
                $parent_arrays = [];
6440
                if ($arrLP[$i]['depth'] > 1) {
6441
                    // Getting list of parents
6442
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6443
                        foreach ($arrLP as $item) {
6444
                            if ($item['id'] == $parent_id) {
6445
                                if ($item['parent_item_id'] == 0) {
6446
                                    $parent_id = $item['id'];
6447
                                    break;
6448
                                } else {
6449
                                    $parent_id = $item['parent_item_id'];
6450
                                    if (empty($parent_arrays)) {
6451
                                        $parent_arrays[] = intval($item['id']);
6452
                                    }
6453
                                    $parent_arrays[] = $parent_id;
6454
                                    break;
6455
                                }
6456
                            }
6457
                        }
6458
                    }
6459
                }
6460
6461
                if (!empty($parent_arrays)) {
6462
                    $parent_arrays = array_reverse($parent_arrays);
6463
                    $val = '$elements';
6464
                    $x = 0;
6465
                    foreach ($parent_arrays as $item) {
6466
                        if ($x != count($parent_arrays) - 1) {
6467
                            $val .= '["'.$item.'"]["children"]';
6468
                        } else {
6469
                            $val .= '["'.$item.'"]["children"]';
6470
                        }
6471
                        $x++;
6472
                    }
6473
                    $val .= "";
6474
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6475
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6476
                } else {
6477
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6478
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6479
                }
6480
            }
6481
        }
6482
6483
        return [
6484
            'elements' => $elements,
6485
            'default_data' => $default_data,
6486
            'default_content' => $default_content,
6487
            'return_audio' => $return_audio,
6488
        ];
6489
    }
6490
6491
    /**
6492
     * @param string $updateAudio true/false strings
6493
     *
6494
     * @return string
6495
     */
6496
    public function returnLpItemList($updateAudio)
6497
    {
6498
        $result = $this->processBuildMenuElements($updateAudio);
6499
6500
        $html = self::print_recursive(
6501
            $result['elements'],
6502
            $result['default_data'],
6503
            $result['default_content']
6504
        );
6505
6506
        if (!empty($html)) {
6507
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6508
        }
6509
6510
        return $html;
6511
    }
6512
6513
    /**
6514
     * @param string $update_audio
6515
     * @param bool   $drop_element_here
6516
     *
6517
     * @return string
6518
     */
6519
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6520
    {
6521
        $result = $this->processBuildMenuElements($update_audio);
6522
6523
        $list = '<ul id="lp_item_list">';
6524
        $tree = $this->print_recursive(
6525
            $result['elements'],
6526
            $result['default_data'],
6527
            $result['default_content']
6528
        );
6529
6530
        if (!empty($tree)) {
6531
            $list .= $tree;
6532
        } else {
6533
            if ($drop_element_here) {
6534
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6535
            }
6536
        }
6537
        $list .= '</ul>';
6538
6539
        $return = Display::panelCollapse(
6540
            $this->name,
6541
            $list,
6542
            'scorm-list',
6543
            null,
6544
            'scorm-list-accordion',
6545
            'scorm-list-collapse'
6546
        );
6547
6548
        if ($update_audio === 'true') {
6549
            $return = $result['return_audio'];
6550
        }
6551
6552
        return $return;
6553
    }
6554
6555
    /**
6556
     * @param array $elements
6557
     * @param array $default_data
6558
     * @param array $default_content
6559
     *
6560
     * @return string
6561
     */
6562
    public function print_recursive($elements, $default_data, $default_content)
6563
    {
6564
        $return = '';
6565
        foreach ($elements as $key => $item) {
6566
            if (isset($item['load_data']) || empty($item['data'])) {
6567
                $item['data'] = $default_data[$item['load_data']];
6568
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6569
            }
6570
            $sub_list = '';
6571
            if (isset($item['type']) && $item['type'] === 'dir') {
6572
                // empty value
6573
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6574
            }
6575
            if (empty($item['children'])) {
6576
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6577
                $active = null;
6578
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6579
                    $active = 'active';
6580
                }
6581
                $return .= Display::tag(
6582
                    'li',
6583
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6584
                    ['id' => $key, 'class' => 'record li_container']
6585
                );
6586
            } else {
6587
                // Sections
6588
                $data = '';
6589
                if (isset($item['children'])) {
6590
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6591
                }
6592
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6593
                $return .= Display::tag(
6594
                    'li',
6595
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6596
                    ['id' => $key, 'class' => 'record li_container']
6597
                );
6598
            }
6599
        }
6600
6601
        return $return;
6602
    }
6603
6604
    /**
6605
     * This function builds the action menu.
6606
     *
6607
     * @param bool   $returnString           Optional
6608
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6609
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6610
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6611
     * @param string $action
6612
     * @param array  $extraField
6613
     *
6614
     * @return string
6615
     */
6616
    public function build_action_menu(
6617
        $returnString = false,
6618
        $showRequirementButtons = true,
6619
        $isConfigPage = false,
6620
        $allowExpand = true,
6621
        $action = '',
6622
        $extraField = []
6623
    ) {
6624
        $actionsRight = '';
6625
        $lpId = $this->lp_id;
6626
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6627
            $back = Display::url(
6628
                Display::return_icon(
6629
                    'back.png',
6630
                    get_lang('ReturnToLearningPaths'),
6631
                    '',
6632
                    ICON_SIZE_MEDIUM
6633
                ),
6634
                'lp_controller.php?'.api_get_cidreq()
6635
            );
6636
        } else {
6637
            $back = Display::url(
6638
                Display::return_icon(
6639
                    'back.png',
6640
                    get_lang('Back'),
6641
                    '',
6642
                    ICON_SIZE_MEDIUM
6643
                ),
6644
                $extraField['backTo']
6645
            );
6646
        }
6647
6648
        /*if ($backToBuild) {
6649
            $back = Display::url(
6650
                Display::return_icon(
6651
                    'back.png',
6652
                    get_lang('GoBack'),
6653
                    '',
6654
                    ICON_SIZE_MEDIUM
6655
                ),
6656
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6657
            );
6658
        }*/
6659
6660
        $actionsLeft = $back;
6661
6662
        $actionsLeft .= Display::url(
6663
            Display::return_icon(
6664
                'preview_view.png',
6665
                get_lang('Preview'),
6666
                '',
6667
                ICON_SIZE_MEDIUM
6668
            ),
6669
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6670
                'action' => 'view',
6671
                'lp_id' => $lpId,
6672
                'isStudentView' => 'true',
6673
            ])
6674
        );
6675
6676
        $actionsLeft .= Display::url(
6677
            Display::return_icon(
6678
                'upload_audio.png',
6679
                get_lang('UpdateAllAudioFragments'),
6680
                '',
6681
                ICON_SIZE_MEDIUM
6682
            ),
6683
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6684
                'action' => 'admin_view',
6685
                'lp_id' => $lpId,
6686
                'updateaudio' => 'true',
6687
            ])
6688
        );
6689
6690
        $subscriptionSettings = self::getSubscriptionSettings();
6691
6692
        $request = api_request_uri();
6693
        if (strpos($request, 'edit') === false) {
6694
            $actionsLeft .= Display::url(
6695
                Display::return_icon(
6696
                    'settings.png',
6697
                    get_lang('CourseSettings'),
6698
                    '',
6699
                    ICON_SIZE_MEDIUM
6700
                ),
6701
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6702
                    'action' => 'edit',
6703
                    'lp_id' => $lpId,
6704
                ])
6705
            );
6706
        }
6707
6708
        if ((strpos($request, 'build') === false &&
6709
            strpos($request, 'add_item') === false) ||
6710
            in_array($action, ['add_audio'])
6711
        ) {
6712
            $actionsLeft .= Display::url(
6713
                Display::return_icon(
6714
                    'edit.png',
6715
                    get_lang('Edit'),
6716
                    '',
6717
                    ICON_SIZE_MEDIUM
6718
                ),
6719
                'lp_controller.php?'.http_build_query([
6720
                    'action' => 'build',
6721
                    'lp_id' => $lpId,
6722
                ]).'&'.api_get_cidreq()
6723
            );
6724
        }
6725
6726
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6727
            if ($this->subscribeUsers == 1 &&
6728
                $subscriptionSettings['allow_add_users_to_lp']) {
6729
                $actionsLeft .= Display::url(
6730
                    Display::return_icon(
6731
                        'user.png',
6732
                        get_lang('SubscribeUsersToLp'),
6733
                        '',
6734
                        ICON_SIZE_MEDIUM
6735
                    ),
6736
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6737
                );
6738
            }
6739
        }
6740
6741
        if ($allowExpand) {
6742
            $actionsLeft .= Display::url(
6743
                Display::return_icon(
6744
                    'expand.png',
6745
                    get_lang('Expand'),
6746
                    ['id' => 'expand'],
6747
                    ICON_SIZE_MEDIUM
6748
                ).
6749
                Display::return_icon(
6750
                    'contract.png',
6751
                    get_lang('Collapse'),
6752
                    ['id' => 'contract', 'class' => 'hide'],
6753
                    ICON_SIZE_MEDIUM
6754
                ),
6755
                '#',
6756
                ['role' => 'button', 'id' => 'hide_bar_template']
6757
            );
6758
        }
6759
6760
        if ($showRequirementButtons) {
6761
            $buttons = [
6762
                [
6763
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6764
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6765
                        'action' => 'set_previous_step_as_prerequisite',
6766
                        'lp_id' => $lpId,
6767
                    ]),
6768
                ],
6769
                [
6770
                    'title' => get_lang('ClearAllPrerequisites'),
6771
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6772
                        'action' => 'clear_prerequisites',
6773
                        'lp_id' => $lpId,
6774
                    ]),
6775
                ],
6776
            ];
6777
            $actionsRight = Display::groupButtonWithDropDown(
6778
                get_lang('PrerequisitesOptions'),
6779
                $buttons,
6780
                true
6781
            );
6782
        }
6783
6784
        // see  BT#17943
6785
        if (api_is_platform_admin()) {
6786
            if (isset($extraField['authorlp'])) {
6787
                $actionsLeft .= Display::url(
6788
                    Display::return_icon(
6789
                        'add-groups.png',
6790
                        get_lang('Author'),
6791
                        '',
6792
                        ICON_SIZE_MEDIUM
6793
                    ),
6794
                    'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6795
                        'action' => 'author_view',
6796
                        'lp_id' => $lpId,
6797
                    ])
6798
                );
6799
            }
6800
        }
6801
6802
        $toolbar = Display::toolbarAction(
6803
            'actions-lp-controller',
6804
            [$actionsLeft, $actionsRight]
6805
        );
6806
6807
        if ($returnString) {
6808
            return $toolbar;
6809
        }
6810
6811
        echo $toolbar;
6812
    }
6813
6814
    /**
6815
     * Creates the default learning path folder.
6816
     *
6817
     * @param array $course
6818
     * @param int   $creatorId
6819
     *
6820
     * @return bool
6821
     */
6822
    public static function generate_learning_path_folder($course, $creatorId = 0)
6823
    {
6824
        // Creating learning_path folder
6825
        $dir = '/learning_path';
6826
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6827
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6828
6829
        $folder = false;
6830
        if (!is_dir($filepath.'/'.$dir)) {
6831
            $folderData = create_unexisting_directory(
6832
                $course,
6833
                $creatorId,
6834
                0,
6835
                null,
6836
                0,
6837
                $filepath,
6838
                $dir,
6839
                get_lang('LearningPaths'),
6840
                0
6841
            );
6842
            if (!empty($folderData)) {
6843
                $folder = true;
6844
            }
6845
        } else {
6846
            $folder = true;
6847
        }
6848
6849
        return $folder;
6850
    }
6851
6852
    /**
6853
     * @param array  $course
6854
     * @param string $lp_name
6855
     * @param int    $creatorId
6856
     *
6857
     * @return array
6858
     */
6859
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6860
    {
6861
        $filepath = '';
6862
        $dir = '/learning_path/';
6863
6864
        if (empty($lp_name)) {
6865
            $lp_name = $this->name;
6866
        }
6867
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6868
        $folder = self::generate_learning_path_folder($course, $creatorId);
6869
6870
        // Limits title size
6871
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6872
        $dir = $dir.$title;
6873
6874
        // Creating LP folder
6875
        $documentId = null;
6876
        if ($folder) {
6877
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6878
            if (!is_dir($filepath.'/'.$dir)) {
6879
                $folderData = create_unexisting_directory(
6880
                    $course,
6881
                    $creatorId,
6882
                    0,
6883
                    0,
6884
                    0,
6885
                    $filepath,
6886
                    $dir,
6887
                    $lp_name
6888
                );
6889
                if (!empty($folderData)) {
6890
                    $folder = true;
6891
                }
6892
6893
                $documentId = $folderData['id'];
6894
            } else {
6895
                $folder = true;
6896
            }
6897
            $dir = $dir.'/';
6898
            if ($folder) {
6899
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6900
            }
6901
        }
6902
6903
        if (empty($documentId)) {
6904
            $dir = api_remove_trailing_slash($dir);
6905
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6906
        }
6907
6908
        $array = [
6909
            'dir' => $dir,
6910
            'filepath' => $filepath,
6911
            'folder' => $folder,
6912
            'id' => $documentId,
6913
        ];
6914
6915
        return $array;
6916
    }
6917
6918
    /**
6919
     * Create a new document //still needs some finetuning.
6920
     *
6921
     * @param array  $courseInfo
6922
     * @param string $content
6923
     * @param string $title
6924
     * @param string $extension
6925
     * @param int    $parentId
6926
     * @param int    $creatorId  creator id
6927
     *
6928
     * @return int
6929
     */
6930
    public function create_document(
6931
        $courseInfo,
6932
        $content = '',
6933
        $title = '',
6934
        $extension = 'html',
6935
        $parentId = 0,
6936
        $creatorId = 0
6937
    ) {
6938
        if (!empty($courseInfo)) {
6939
            $course_id = $courseInfo['real_id'];
6940
        } else {
6941
            $course_id = api_get_course_int_id();
6942
        }
6943
6944
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6945
        $sessionId = api_get_session_id();
6946
6947
        // Generates folder
6948
        $result = $this->generate_lp_folder($courseInfo);
6949
        $dir = $result['dir'];
6950
6951
        if (empty($parentId) || $parentId == '/') {
6952
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6953
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6954
6955
            if ($parentId === '/') {
6956
                $dir = '/';
6957
            }
6958
6959
            // Please, do not modify this dirname formatting.
6960
            if (strstr($dir, '..')) {
6961
                $dir = '/';
6962
            }
6963
6964
            if (!empty($dir[0]) && $dir[0] == '.') {
6965
                $dir = substr($dir, 1);
6966
            }
6967
            if (!empty($dir[0]) && $dir[0] != '/') {
6968
                $dir = '/'.$dir;
6969
            }
6970
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6971
                $dir .= '/';
6972
            }
6973
        } else {
6974
            $parentInfo = DocumentManager::get_document_data_by_id(
6975
                $parentId,
6976
                $courseInfo['code']
6977
            );
6978
            if (!empty($parentInfo)) {
6979
                $dir = $parentInfo['path'].'/';
6980
            }
6981
        }
6982
6983
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6984
        if (!is_dir($filepath)) {
6985
            $dir = '/';
6986
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6987
        }
6988
6989
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6990
        // is already escaped twice when it gets here.
6991
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6992
        if (!empty($title)) {
6993
            $title = api_replace_dangerous_char(stripslashes($title));
6994
        } else {
6995
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6996
        }
6997
6998
        $title = disable_dangerous_file($title);
6999
        $filename = $title;
7000
        $content = !empty($content) ? $content : $_POST['content_lp'];
7001
        $tmp_filename = $filename;
7002
7003
        $i = 0;
7004
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7005
            $tmp_filename = $filename.'_'.++$i;
7006
        }
7007
7008
        $filename = $tmp_filename.'.'.$extension;
7009
        if ($extension == 'html') {
7010
            $content = stripslashes($content);
7011
            $content = str_replace(
7012
                api_get_path(WEB_COURSE_PATH),
7013
                api_get_path(REL_PATH).'courses/',
7014
                $content
7015
            );
7016
7017
            // Change the path of mp3 to absolute.
7018
            // The first regexp deals with :// urls.
7019
            $content = preg_replace(
7020
                "|(flashvars=\"file=)([^:/]+)/|",
7021
                "$1".api_get_path(
7022
                    REL_COURSE_PATH
7023
                ).$courseInfo['path'].'/document/',
7024
                $content
7025
            );
7026
            // The second regexp deals with audio/ urls.
7027
            $content = preg_replace(
7028
                "|(flashvars=\"file=)([^/]+)/|",
7029
                "$1".api_get_path(
7030
                    REL_COURSE_PATH
7031
                ).$courseInfo['path'].'/document/$2/',
7032
                $content
7033
            );
7034
            // For flv player: To prevent edition problem with firefox,
7035
            // we have to use a strange tip (don't blame me please).
7036
            $content = str_replace(
7037
                '</body>',
7038
                '<style type="text/css">body{}</style></body>',
7039
                $content
7040
            );
7041
        }
7042
7043
        if (!file_exists($filepath.$filename)) {
7044
            if ($fp = @fopen($filepath.$filename, 'w')) {
7045
                fputs($fp, $content);
7046
                fclose($fp);
7047
7048
                $file_size = filesize($filepath.$filename);
7049
                $save_file_path = $dir.$filename;
7050
7051
                $document_id = add_document(
7052
                    $courseInfo,
7053
                    $save_file_path,
7054
                    'file',
7055
                    $file_size,
7056
                    $tmp_filename,
7057
                    '',
7058
                    0, //readonly
7059
                    true,
7060
                    null,
7061
                    $sessionId,
7062
                    $creatorId
7063
                );
7064
7065
                if ($document_id) {
7066
                    api_item_property_update(
7067
                        $courseInfo,
7068
                        TOOL_DOCUMENT,
7069
                        $document_id,
7070
                        'DocumentAdded',
7071
                        $creatorId,
7072
                        null,
7073
                        null,
7074
                        null,
7075
                        null,
7076
                        $sessionId
7077
                    );
7078
7079
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7080
                    $new_title = $originalTitle;
7081
7082
                    if ($new_comment || $new_title) {
7083
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7084
                        $ct = '';
7085
                        if ($new_comment) {
7086
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7087
                        }
7088
                        if ($new_title) {
7089
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7090
                        }
7091
7092
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7093
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7094
                        Database::query($sql);
7095
                    }
7096
                }
7097
7098
                return $document_id;
7099
            }
7100
        }
7101
    }
7102
7103
    /**
7104
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7105
     *
7106
     * @param array $_course array
7107
     */
7108
    public function edit_document($_course)
7109
    {
7110
        $course_id = api_get_course_int_id();
7111
        $urlAppend = api_get_configuration_value('url_append');
7112
        // Please, do not modify this dirname formatting.
7113
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7114
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7115
7116
        if (strstr($dir, '..')) {
7117
            $dir = '/';
7118
        }
7119
7120
        if (isset($dir[0]) && $dir[0] == '.') {
7121
            $dir = substr($dir, 1);
7122
        }
7123
7124
        if (isset($dir[0]) && $dir[0] != '/') {
7125
            $dir = '/'.$dir;
7126
        }
7127
7128
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7129
            $dir .= '/';
7130
        }
7131
7132
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7133
        if (!is_dir($filepath)) {
7134
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7135
        }
7136
7137
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7138
7139
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7140
            $document_id = (int) $_POST['path'];
7141
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7142
            if (empty($documentInfo)) {
7143
                // Try with iid
7144
                $table = Database::get_course_table(TABLE_DOCUMENT);
7145
                $sql = "SELECT id, path FROM $table
7146
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7147
                $res_doc = Database::query($sql);
7148
                $row = Database::fetch_array($res_doc);
7149
                if ($row) {
7150
                    $document_id = $row['id'];
7151
                    $documentPath = $row['path'];
7152
                }
7153
            } else {
7154
                $documentPath = $documentInfo['path'];
7155
            }
7156
7157
            $content = stripslashes($_POST['content_lp']);
7158
            $file = $filepath.$documentPath;
7159
7160
            if (!file_exists($file)) {
7161
                return false;
7162
            }
7163
7164
            if ($fp = @fopen($file, 'w')) {
7165
                $content = str_replace(
7166
                    api_get_path(WEB_COURSE_PATH),
7167
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7168
                    $content
7169
                );
7170
                // Change the path of mp3 to absolute.
7171
                // The first regexp deals with :// urls.
7172
                $content = preg_replace(
7173
                    "|(flashvars=\"file=)([^:/]+)/|",
7174
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7175
                    $content
7176
                );
7177
                // The second regexp deals with audio/ urls.
7178
                $content = preg_replace(
7179
                    "|(flashvars=\"file=)([^:/]+)/|",
7180
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7181
                    $content
7182
                );
7183
                fputs($fp, $content);
7184
                fclose($fp);
7185
7186
                $sql = "UPDATE $table_doc SET
7187
                            title='".Database::escape_string($_POST['title'])."'
7188
                        WHERE c_id = $course_id AND id = ".$document_id;
7189
                Database::query($sql);
7190
            }
7191
        }
7192
    }
7193
7194
    /**
7195
     * Displays the selected item, with a panel for manipulating the item.
7196
     *
7197
     * @param int    $item_id
7198
     * @param string $msg
7199
     * @param bool   $show_actions
7200
     *
7201
     * @return string
7202
     */
7203
    public function display_item($item_id, $msg = null, $show_actions = true)
7204
    {
7205
        $course_id = api_get_course_int_id();
7206
        $return = '';
7207
        if (is_numeric($item_id)) {
7208
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7209
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7210
                    WHERE lp.iid = ".intval($item_id);
7211
            $result = Database::query($sql);
7212
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7213
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7214
7215
                // Prevents wrong parent selection for document, see Bug#1251.
7216
                if ($row['item_type'] != 'dir') {
7217
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7218
                }
7219
7220
                if ($show_actions) {
7221
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7222
                }
7223
                $return .= '<div style="padding:10px;">';
7224
7225
                if ($msg != '') {
7226
                    $return .= $msg;
7227
                }
7228
7229
                $return .= '<h3>'.$row['title'].'</h3>';
7230
7231
                switch ($row['item_type']) {
7232
                    case TOOL_THREAD:
7233
                        $link = $this->rl_get_resource_link_for_learnpath(
7234
                            $course_id,
7235
                            $row['lp_id'],
7236
                            $item_id,
7237
                            0
7238
                        );
7239
                        $return .= Display::url(
7240
                            get_lang('GoToThread'),
7241
                            $link,
7242
                            ['class' => 'btn btn-primary']
7243
                        );
7244
                        break;
7245
                    case TOOL_FORUM:
7246
                        $return .= Display::url(
7247
                            get_lang('GoToForum'),
7248
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7249
                            ['class' => 'btn btn-primary']
7250
                        );
7251
                        break;
7252
                    case TOOL_QUIZ:
7253
                        if (!empty($row['path'])) {
7254
                            $exercise = new Exercise();
7255
                            $exercise->read($row['path']);
7256
                            $return .= $exercise->description.'<br />';
7257
                            $return .= Display::url(
7258
                                get_lang('GoToExercise'),
7259
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7260
                                ['class' => 'btn btn-primary']
7261
                            );
7262
                        }
7263
                        break;
7264
                    case TOOL_LP_FINAL_ITEM:
7265
                        $return .= $this->getSavedFinalItem();
7266
                        break;
7267
                    case TOOL_DOCUMENT:
7268
                    case TOOL_READOUT_TEXT:
7269
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7270
                        $sql_doc = "SELECT path FROM $tbl_doc
7271
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7272
                        $result = Database::query($sql_doc);
7273
                        $path_file = Database::result($result, 0, 0);
7274
                        $path_parts = pathinfo($path_file);
7275
                        // TODO: Correct the following naive comparisons.
7276
                        if (in_array($path_parts['extension'], [
7277
                            'html',
7278
                            'txt',
7279
                            'png',
7280
                            'jpg',
7281
                            'JPG',
7282
                            'jpeg',
7283
                            'JPEG',
7284
                            'gif',
7285
                            'swf',
7286
                            'pdf',
7287
                            'htm',
7288
                        ])) {
7289
                            $return .= $this->display_document($row['path'], true, true);
7290
                        }
7291
                        break;
7292
                    case TOOL_HOTPOTATOES:
7293
                        $return .= $this->display_document($row['path'], false, true);
7294
                        break;
7295
                }
7296
                $return .= '</div>';
7297
            }
7298
        }
7299
7300
        return $return;
7301
    }
7302
7303
    /**
7304
     * Shows the needed forms for editing a specific item.
7305
     *
7306
     * @param int $item_id
7307
     *
7308
     * @throws Exception
7309
     * @throws HTML_QuickForm_Error
7310
     *
7311
     * @return string
7312
     */
7313
    public function display_edit_item($item_id)
7314
    {
7315
        $course_id = api_get_course_int_id();
7316
        $return = '';
7317
        $item_id = (int) $item_id;
7318
7319
        if (empty($item_id)) {
7320
            return '';
7321
        }
7322
7323
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7324
        $sql = "SELECT * FROM $tbl_lp_item
7325
                WHERE iid = ".$item_id;
7326
        $res = Database::query($sql);
7327
        $row = Database::fetch_array($res);
7328
        switch ($row['item_type']) {
7329
            case 'dir':
7330
            case 'asset':
7331
            case 'sco':
7332
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7333
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7334
                    $return .= $this->display_item_form(
7335
                        $row['item_type'],
7336
                        get_lang('EditCurrentChapter').' :',
7337
                        'edit',
7338
                        $item_id,
7339
                        $row
7340
                    );
7341
                } else {
7342
                    $return .= $this->display_item_form(
7343
                        $row['item_type'],
7344
                        get_lang('EditCurrentChapter').' :',
7345
                        'edit_item',
7346
                        $item_id,
7347
                        $row
7348
                    );
7349
                }
7350
                break;
7351
            case TOOL_DOCUMENT:
7352
            case TOOL_READOUT_TEXT:
7353
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7354
                $sql = "SELECT lp.*, doc.path as dir
7355
                        FROM $tbl_lp_item as lp
7356
                        LEFT JOIN $tbl_doc as doc
7357
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7358
                        WHERE
7359
                            doc.c_id = $course_id AND
7360
                            lp.iid = ".$item_id;
7361
                $res_step = Database::query($sql);
7362
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7363
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7364
7365
                if ($row['item_type'] === TOOL_DOCUMENT) {
7366
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7367
                }
7368
7369
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7370
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7371
                }
7372
                break;
7373
            case TOOL_LINK:
7374
                $linkId = (int) $row['path'];
7375
                if (!empty($linkId)) {
7376
                    $table = Database::get_course_table(TABLE_LINK);
7377
                    $sql = 'SELECT url FROM '.$table.'
7378
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7379
                    $res_link = Database::query($sql);
7380
                    $row_link = Database::fetch_array($res_link);
7381
                    if (empty($row_link)) {
7382
                        // Try with id
7383
                        $sql = 'SELECT url FROM '.$table.'
7384
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7385
                        $res_link = Database::query($sql);
7386
                        $row_link = Database::fetch_array($res_link);
7387
                    }
7388
7389
                    if (is_array($row_link)) {
7390
                        $row['url'] = $row_link['url'];
7391
                    }
7392
                }
7393
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7394
                $return .= $this->display_link_form('edit', $item_id, $row);
7395
                break;
7396
            case TOOL_LP_FINAL_ITEM:
7397
                Session::write('finalItem', true);
7398
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7399
                $sql = "SELECT lp.*, doc.path as dir
7400
                        FROM $tbl_lp_item as lp
7401
                        LEFT JOIN $tbl_doc as doc
7402
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7403
                        WHERE
7404
                            doc.c_id = $course_id AND
7405
                            lp.iid = ".$item_id;
7406
                $res_step = Database::query($sql);
7407
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7408
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7409
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7410
                break;
7411
            case TOOL_QUIZ:
7412
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7413
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7414
                break;
7415
            case TOOL_HOTPOTATOES:
7416
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7417
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7418
                break;
7419
            case TOOL_STUDENTPUBLICATION:
7420
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7421
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7422
                break;
7423
            case TOOL_FORUM:
7424
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7425
                $return .= $this->display_forum_form('edit', $item_id, $row);
7426
                break;
7427
            case TOOL_THREAD:
7428
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7429
                $return .= $this->display_thread_form('edit', $item_id, $row);
7430
                break;
7431
        }
7432
7433
        return $return;
7434
    }
7435
7436
    /**
7437
     * Function that displays a list with al the resources that
7438
     * could be added to the learning path.
7439
     *
7440
     * @throws Exception
7441
     * @throws HTML_QuickForm_Error
7442
     *
7443
     * @return bool
7444
     */
7445
    public function display_resources()
7446
    {
7447
        $course_code = api_get_course_id();
7448
7449
        // Get all the docs.
7450
        $documents = $this->get_documents(true);
7451
7452
        // Get all the exercises.
7453
        $exercises = $this->get_exercises();
7454
7455
        // Get all the links.
7456
        $links = $this->get_links();
7457
7458
        // Get all the student publications.
7459
        $works = $this->get_student_publications();
7460
7461
        // Get all the forums.
7462
        $forums = $this->get_forums(null, $course_code);
7463
7464
        // Get the final item form (see BT#11048) .
7465
        $finish = $this->getFinalItemForm();
7466
7467
        $headers = [
7468
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7469
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7470
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7471
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7472
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7473
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7474
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7475
        ];
7476
7477
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7478
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7479
7480
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7481
7482
        echo Display::tabs(
7483
            $headers,
7484
            [
7485
                $documents,
7486
                $exercises,
7487
                $links,
7488
                $works,
7489
                $forums,
7490
                $dir,
7491
                $finish,
7492
            ],
7493
            'resource_tab',
7494
            [],
7495
            [],
7496
            $selected
7497
        );
7498
7499
        return true;
7500
    }
7501
7502
    /**
7503
     * Returns the extension of a document.
7504
     *
7505
     * @param string $filename
7506
     *
7507
     * @return string Extension (part after the last dot)
7508
     */
7509
    public function get_extension($filename)
7510
    {
7511
        $explode = explode('.', $filename);
7512
7513
        return $explode[count($explode) - 1];
7514
    }
7515
7516
    /**
7517
     * Displays a document by id.
7518
     *
7519
     * @param int  $id
7520
     * @param bool $show_title
7521
     * @param bool $iframe
7522
     * @param bool $edit_link
7523
     *
7524
     * @return string
7525
     */
7526
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7527
    {
7528
        $_course = api_get_course_info();
7529
        $course_id = api_get_course_int_id();
7530
        $id = (int) $id;
7531
        $return = '';
7532
        $table = Database::get_course_table(TABLE_DOCUMENT);
7533
        $sql_doc = "SELECT * FROM $table
7534
                    WHERE c_id = $course_id AND iid = $id";
7535
        $res_doc = Database::query($sql_doc);
7536
        $row_doc = Database::fetch_array($res_doc);
7537
7538
        // TODO: Add a path filter.
7539
        if ($iframe) {
7540
            $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>';
7541
        } else {
7542
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7543
        }
7544
7545
        return $return;
7546
    }
7547
7548
    /**
7549
     * Return HTML form to add/edit a quiz.
7550
     *
7551
     * @param string $action     Action (add/edit)
7552
     * @param int    $id         Item ID if already exists
7553
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7554
     *
7555
     * @throws Exception
7556
     *
7557
     * @return string HTML form
7558
     */
7559
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7560
    {
7561
        $course_id = api_get_course_int_id();
7562
        $id = (int) $id;
7563
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7564
7565
        if ($id != 0 && is_array($extra_info)) {
7566
            $item_title = $extra_info['title'];
7567
            $item_description = $extra_info['description'];
7568
        } elseif (is_numeric($extra_info)) {
7569
            $sql = "SELECT title, description
7570
                    FROM $tbl_quiz
7571
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7572
7573
            $result = Database::query($sql);
7574
            $row = Database::fetch_array($result);
7575
            $item_title = $row['title'];
7576
            $item_description = $row['description'];
7577
        } else {
7578
            $item_title = '';
7579
            $item_description = '';
7580
        }
7581
        $item_title = Security::remove_XSS($item_title);
7582
        $item_description = Security::remove_XSS($item_description);
7583
7584
        $parent = 0;
7585
        if ($id != 0 && is_array($extra_info)) {
7586
            $parent = $extra_info['parent_item_id'];
7587
        }
7588
7589
        $arrLP = $this->getItemsForForm();
7590
        $this->tree_array($arrLP);
7591
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7592
        unset($this->arrMenu);
7593
7594
        $form = new FormValidator(
7595
            'quiz_form',
7596
            'POST',
7597
            $this->getCurrentBuildingModeURL()
7598
        );
7599
        $defaults = [];
7600
7601
        if ($action === 'add') {
7602
            $legend = get_lang('CreateTheExercise');
7603
        } elseif ($action === 'move') {
7604
            $legend = get_lang('MoveTheCurrentExercise');
7605
        } else {
7606
            $legend = get_lang('EditCurrentExecice');
7607
        }
7608
7609
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7610
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7611
        }
7612
7613
        $form->addHeader($legend);
7614
7615
        if ($action != 'move') {
7616
            $this->setItemTitle($form);
7617
            $defaults['title'] = $item_title;
7618
        }
7619
7620
        // Select for Parent item, root or chapter
7621
        $selectParent = $form->addSelect(
7622
            'parent',
7623
            get_lang('Parent'),
7624
            [],
7625
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7626
        );
7627
        $selectParent->addOption($this->name, 0);
7628
7629
        $arrHide = [
7630
            $id,
7631
        ];
7632
        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...
7633
            if ($action != 'add') {
7634
                if (
7635
                    ($arrLP[$i]['item_type'] == 'dir') &&
7636
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7637
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7638
                ) {
7639
                    $selectParent->addOption(
7640
                        $arrLP[$i]['title'],
7641
                        $arrLP[$i]['id'],
7642
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7643
                    );
7644
7645
                    if ($parent == $arrLP[$i]['id']) {
7646
                        $selectParent->setSelected($arrLP[$i]['id']);
7647
                    }
7648
                } else {
7649
                    $arrHide[] = $arrLP[$i]['id'];
7650
                }
7651
            } else {
7652
                if ($arrLP[$i]['item_type'] == 'dir') {
7653
                    $selectParent->addOption(
7654
                        $arrLP[$i]['title'],
7655
                        $arrLP[$i]['id'],
7656
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7657
                    );
7658
7659
                    if ($parent == $arrLP[$i]['id']) {
7660
                        $selectParent->setSelected($arrLP[$i]['id']);
7661
                    }
7662
                }
7663
            }
7664
        }
7665
7666
        if (is_array($arrLP)) {
7667
            reset($arrLP);
7668
        }
7669
7670
        $selectPrevious = $form->addSelect(
7671
            'previous',
7672
            get_lang('Position'),
7673
            [],
7674
            ['id' => 'previous']
7675
        );
7676
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7677
7678
        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...
7679
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7680
                $arrLP[$i]['id'] != $id
7681
            ) {
7682
                $selectPrevious->addOption(
7683
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7684
                    $arrLP[$i]['id']
7685
                );
7686
7687
                if (is_array($extra_info)) {
7688
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7689
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7690
                    }
7691
                } elseif ($action == 'add') {
7692
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7693
                }
7694
            }
7695
        }
7696
7697
        if ($action != 'move') {
7698
            $arrHide = [];
7699
            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...
7700
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7701
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7702
                }
7703
            }
7704
        }
7705
7706
        if ('edit' === $action) {
7707
            $extraField = new ExtraField('lp_item');
7708
            $extraField->addElements($form, $id);
7709
        }
7710
7711
        if ($action === 'add') {
7712
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7713
        } else {
7714
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7715
        }
7716
7717
        if ($action === 'move') {
7718
            $form->addHidden('title', $item_title);
7719
            $form->addHidden('description', $item_description);
7720
        }
7721
7722
        if (is_numeric($extra_info)) {
7723
            $form->addHidden('path', $extra_info);
7724
        } elseif (is_array($extra_info)) {
7725
            $form->addHidden('path', $extra_info['path']);
7726
        }
7727
7728
        $form->addHidden('type', TOOL_QUIZ);
7729
        $form->addHidden('post_time', time());
7730
        $form->setDefaults($defaults);
7731
7732
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7733
    }
7734
7735
    /**
7736
     * Addition of Hotpotatoes tests.
7737
     *
7738
     * @param string $action
7739
     * @param int    $id         Internal ID of the item
7740
     * @param string $extra_info
7741
     *
7742
     * @return string HTML structure to display the hotpotatoes addition formular
7743
     */
7744
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7745
    {
7746
        $course_id = api_get_course_int_id();
7747
        $uploadPath = DIR_HOTPOTATOES;
7748
7749
        if ($id != 0 && is_array($extra_info)) {
7750
            $item_title = stripslashes($extra_info['title']);
7751
            $item_description = stripslashes($extra_info['description']);
7752
        } elseif (is_numeric($extra_info)) {
7753
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7754
7755
            $sql = "SELECT * FROM $TBL_DOCUMENT
7756
                    WHERE
7757
                        c_id = $course_id AND
7758
                        path LIKE '".$uploadPath."/%/%htm%' AND
7759
                        iid = ".(int) $extra_info."
7760
                    ORDER BY iid ASC";
7761
7762
            $res_hot = Database::query($sql);
7763
            $row = Database::fetch_array($res_hot);
7764
7765
            $item_title = $row['title'];
7766
            $item_description = $row['description'];
7767
7768
            if (!empty($row['comment'])) {
7769
                $item_title = $row['comment'];
7770
            }
7771
        } else {
7772
            $item_title = '';
7773
            $item_description = '';
7774
        }
7775
7776
        $parent = 0;
7777
        if ($id != 0 && is_array($extra_info)) {
7778
            $parent = $extra_info['parent_item_id'];
7779
        }
7780
7781
        $arrLP = $this->getItemsForForm();
7782
        $legend = '<legend>';
7783
        if ($action == 'add') {
7784
            $legend .= get_lang('CreateTheExercise');
7785
        } elseif ($action == 'move') {
7786
            $legend .= get_lang('MoveTheCurrentExercise');
7787
        } else {
7788
            $legend .= get_lang('EditCurrentExecice');
7789
        }
7790
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7791
            $legend .= Display:: return_message(
7792
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7793
            );
7794
        }
7795
        $legend .= '</legend>';
7796
7797
        $return = '<form method="POST">';
7798
        $return .= $legend;
7799
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7800
        $return .= '<tr>';
7801
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7802
        $return .= '<td class="input">';
7803
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7804
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7805
        $arrHide = [$id];
7806
7807
        if (count($arrLP) > 0) {
7808
            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...
7809
                if ($action != 'add') {
7810
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7811
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7812
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7813
                    ) {
7814
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7815
                    } else {
7816
                        $arrHide[] = $arrLP[$i]['id'];
7817
                    }
7818
                } else {
7819
                    if ($arrLP[$i]['item_type'] == 'dir') {
7820
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7821
                    }
7822
                }
7823
            }
7824
            reset($arrLP);
7825
        }
7826
7827
        $return .= '</select>';
7828
        $return .= '</td>';
7829
        $return .= '</tr>';
7830
        $return .= '<tr>';
7831
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7832
        $return .= '<td class="input">';
7833
        $return .= '<select id="previous" name="previous" size="1">';
7834
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7835
7836
        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...
7837
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7838
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7839
                    $selected = 'selected="selected" ';
7840
                } elseif ($action == 'add') {
7841
                    $selected = 'selected="selected" ';
7842
                } else {
7843
                    $selected = '';
7844
                }
7845
7846
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7847
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7848
            }
7849
        }
7850
7851
        $return .= '</select>';
7852
        $return .= '</td>';
7853
        $return .= '</tr>';
7854
7855
        if ($action != 'move') {
7856
            $return .= '<tr>';
7857
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7858
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7859
            $return .= '</tr>';
7860
            $id_prerequisite = 0;
7861
            if (is_array($arrLP) && count($arrLP) > 0) {
7862
                foreach ($arrLP as $key => $value) {
7863
                    if ($value['id'] == $id) {
7864
                        $id_prerequisite = $value['prerequisite'];
7865
                        break;
7866
                    }
7867
                }
7868
7869
                $arrHide = [];
7870
                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...
7871
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7872
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7873
                    }
7874
                }
7875
            }
7876
        }
7877
7878
        $return .= '<tr>';
7879
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7880
            get_lang('SaveHotpotatoes').'</button></td>';
7881
        $return .= '</tr>';
7882
        $return .= '</table>';
7883
7884
        if ($action == 'move') {
7885
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7886
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7887
        }
7888
7889
        if (is_numeric($extra_info)) {
7890
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7891
        } elseif (is_array($extra_info)) {
7892
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7893
        }
7894
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7895
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7896
        $return .= '</form>';
7897
7898
        return $return;
7899
    }
7900
7901
    /**
7902
     * Return the form to display the forum edit/add option.
7903
     *
7904
     * @param string $action
7905
     * @param int    $id         ID of the lp_item if already exists
7906
     * @param string $extra_info
7907
     *
7908
     * @throws Exception
7909
     *
7910
     * @return string HTML form
7911
     */
7912
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7913
    {
7914
        $course_id = api_get_course_int_id();
7915
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7916
7917
        $item_title = '';
7918
        $item_description = '';
7919
7920
        if ($id != 0 && is_array($extra_info)) {
7921
            $item_title = stripslashes($extra_info['title']);
7922
        } elseif (is_numeric($extra_info)) {
7923
            $sql = "SELECT forum_title as title, forum_comment as comment
7924
                    FROM $tbl_forum
7925
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7926
7927
            $result = Database::query($sql);
7928
            $row = Database::fetch_array($result);
7929
7930
            $item_title = $row['title'];
7931
            $item_description = $row['comment'];
7932
        }
7933
        $parent = 0;
7934
        if ($id != 0 && is_array($extra_info)) {
7935
            $parent = $extra_info['parent_item_id'];
7936
        }
7937
        $arrLP = $this->getItemsForForm();
7938
        $this->tree_array($arrLP);
7939
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7940
        unset($this->arrMenu);
7941
7942
        if ($action == 'add') {
7943
            $legend = get_lang('CreateTheForum');
7944
        } elseif ($action == 'move') {
7945
            $legend = get_lang('MoveTheCurrentForum');
7946
        } else {
7947
            $legend = get_lang('EditCurrentForum');
7948
        }
7949
7950
        $form = new FormValidator(
7951
            'forum_form',
7952
            'POST',
7953
            $this->getCurrentBuildingModeURL()
7954
        );
7955
        $defaults = [];
7956
7957
        $form->addHeader($legend);
7958
7959
        if ($action != 'move') {
7960
            $this->setItemTitle($form);
7961
            $defaults['title'] = $item_title;
7962
        }
7963
7964
        $selectParent = $form->addSelect(
7965
            'parent',
7966
            get_lang('Parent'),
7967
            [],
7968
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7969
        );
7970
        $selectParent->addOption($this->name, 0);
7971
        $arrHide = [
7972
            $id,
7973
        ];
7974
        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...
7975
            if ($action != 'add') {
7976
                if ($arrLP[$i]['item_type'] == 'dir' &&
7977
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7978
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7979
                ) {
7980
                    $selectParent->addOption(
7981
                        $arrLP[$i]['title'],
7982
                        $arrLP[$i]['id'],
7983
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7984
                    );
7985
7986
                    if ($parent == $arrLP[$i]['id']) {
7987
                        $selectParent->setSelected($arrLP[$i]['id']);
7988
                    }
7989
                } else {
7990
                    $arrHide[] = $arrLP[$i]['id'];
7991
                }
7992
            } else {
7993
                if ($arrLP[$i]['item_type'] == 'dir') {
7994
                    $selectParent->addOption(
7995
                        $arrLP[$i]['title'],
7996
                        $arrLP[$i]['id'],
7997
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7998
                    );
7999
8000
                    if ($parent == $arrLP[$i]['id']) {
8001
                        $selectParent->setSelected($arrLP[$i]['id']);
8002
                    }
8003
                }
8004
            }
8005
        }
8006
8007
        if (is_array($arrLP)) {
8008
            reset($arrLP);
8009
        }
8010
8011
        $selectPrevious = $form->addSelect(
8012
            'previous',
8013
            get_lang('Position'),
8014
            [],
8015
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8016
        );
8017
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8018
8019
        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...
8020
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8021
                $arrLP[$i]['id'] != $id
8022
            ) {
8023
                $selectPrevious->addOption(
8024
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8025
                    $arrLP[$i]['id']
8026
                );
8027
8028
                if (isset($extra_info['previous_item_id']) &&
8029
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8030
                ) {
8031
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8032
                } elseif ($action == 'add') {
8033
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8034
                }
8035
            }
8036
        }
8037
8038
        if ($action != 'move') {
8039
            $id_prerequisite = 0;
8040
            if (is_array($arrLP)) {
8041
                foreach ($arrLP as $key => $value) {
8042
                    if ($value['id'] == $id) {
8043
                        $id_prerequisite = $value['prerequisite'];
8044
                        break;
8045
                    }
8046
                }
8047
            }
8048
8049
            $arrHide = [];
8050
            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...
8051
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8052
                    if (isset($extra_info['previous_item_id']) &&
8053
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8054
                    ) {
8055
                        $s_selected_position = $arrLP[$i]['id'];
8056
                    } elseif ($action == 'add') {
8057
                        $s_selected_position = 0;
8058
                    }
8059
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8060
                }
8061
            }
8062
        }
8063
8064
        if ('edit' === $action) {
8065
            $extraField = new ExtraField('lp_item');
8066
            $extraField->addElements($form, $id);
8067
        }
8068
8069
        if ($action == 'add') {
8070
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8071
        } else {
8072
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8073
        }
8074
8075
        if ($action == 'move') {
8076
            $form->addHidden('title', $item_title);
8077
            $form->addHidden('description', $item_description);
8078
        }
8079
8080
        if (is_numeric($extra_info)) {
8081
            $form->addHidden('path', $extra_info);
8082
        } elseif (is_array($extra_info)) {
8083
            $form->addHidden('path', $extra_info['path']);
8084
        }
8085
        $form->addHidden('type', TOOL_FORUM);
8086
        $form->addHidden('post_time', time());
8087
        $form->setDefaults($defaults);
8088
8089
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8090
    }
8091
8092
    /**
8093
     * Return HTML form to add/edit forum threads.
8094
     *
8095
     * @param string $action
8096
     * @param int    $id         Item ID if already exists in learning path
8097
     * @param string $extra_info
8098
     *
8099
     * @throws Exception
8100
     *
8101
     * @return string HTML form
8102
     */
8103
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8104
    {
8105
        $course_id = api_get_course_int_id();
8106
        if (empty($course_id)) {
8107
            return null;
8108
        }
8109
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8110
8111
        $item_title = '';
8112
        $item_description = '';
8113
        if ($id != 0 && is_array($extra_info)) {
8114
            $item_title = stripslashes($extra_info['title']);
8115
        } elseif (is_numeric($extra_info)) {
8116
            $sql = "SELECT thread_title as title FROM $tbl_forum
8117
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8118
8119
            $result = Database::query($sql);
8120
            $row = Database::fetch_array($result);
8121
8122
            $item_title = $row['title'];
8123
            $item_description = '';
8124
        }
8125
8126
        $parent = 0;
8127
        if ($id != 0 && is_array($extra_info)) {
8128
            $parent = $extra_info['parent_item_id'];
8129
        }
8130
8131
        $arrLP = $this->getItemsForForm();
8132
        $this->tree_array($arrLP);
8133
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8134
        unset($this->arrMenu);
8135
8136
        $form = new FormValidator(
8137
            'thread_form',
8138
            'POST',
8139
            $this->getCurrentBuildingModeURL()
8140
        );
8141
        $defaults = [];
8142
8143
        if ($action == 'add') {
8144
            $legend = get_lang('CreateTheForum');
8145
        } elseif ($action == 'move') {
8146
            $legend = get_lang('MoveTheCurrentForum');
8147
        } else {
8148
            $legend = get_lang('EditCurrentForum');
8149
        }
8150
8151
        $form->addHeader($legend);
8152
        $selectParent = $form->addSelect(
8153
            'parent',
8154
            get_lang('Parent'),
8155
            [],
8156
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8157
        );
8158
        $selectParent->addOption($this->name, 0);
8159
8160
        $arrHide = [
8161
            $id,
8162
        ];
8163
8164
        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...
8165
            if ($action != 'add') {
8166
                if (
8167
                    ($arrLP[$i]['item_type'] == 'dir') &&
8168
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8169
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8170
                ) {
8171
                    $selectParent->addOption(
8172
                        $arrLP[$i]['title'],
8173
                        $arrLP[$i]['id'],
8174
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8175
                    );
8176
8177
                    if ($parent == $arrLP[$i]['id']) {
8178
                        $selectParent->setSelected($arrLP[$i]['id']);
8179
                    }
8180
                } else {
8181
                    $arrHide[] = $arrLP[$i]['id'];
8182
                }
8183
            } else {
8184
                if ($arrLP[$i]['item_type'] == 'dir') {
8185
                    $selectParent->addOption(
8186
                        $arrLP[$i]['title'],
8187
                        $arrLP[$i]['id'],
8188
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8189
                    );
8190
8191
                    if ($parent == $arrLP[$i]['id']) {
8192
                        $selectParent->setSelected($arrLP[$i]['id']);
8193
                    }
8194
                }
8195
            }
8196
        }
8197
8198
        if ($arrLP != null) {
8199
            reset($arrLP);
8200
        }
8201
8202
        $selectPrevious = $form->addSelect(
8203
            'previous',
8204
            get_lang('Position'),
8205
            [],
8206
            ['id' => 'previous']
8207
        );
8208
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8209
8210
        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...
8211
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8212
                $selectPrevious->addOption(
8213
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8214
                    $arrLP[$i]['id']
8215
                );
8216
8217
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8218
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8219
                } elseif ($action == 'add') {
8220
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8221
                }
8222
            }
8223
        }
8224
8225
        if ($action != 'move') {
8226
            $this->setItemTitle($form);
8227
            $defaults['title'] = $item_title;
8228
8229
            $id_prerequisite = 0;
8230
            if ($arrLP != null) {
8231
                foreach ($arrLP as $key => $value) {
8232
                    if ($value['id'] == $id) {
8233
                        $id_prerequisite = $value['prerequisite'];
8234
                        break;
8235
                    }
8236
                }
8237
            }
8238
8239
            $arrHide = [];
8240
            $s_selected_position = 0;
8241
            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...
8242
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8243
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8244
                        $s_selected_position = $arrLP[$i]['id'];
8245
                    } elseif ($action == 'add') {
8246
                        $s_selected_position = 0;
8247
                    }
8248
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8249
                }
8250
            }
8251
8252
            $selectPrerequisites = $form->addSelect(
8253
                'prerequisites',
8254
                get_lang('LearnpathPrerequisites'),
8255
                [],
8256
                ['id' => 'prerequisites']
8257
            );
8258
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8259
8260
            foreach ($arrHide as $key => $value) {
8261
                $selectPrerequisites->addOption($value['value'], $key);
8262
8263
                if ($key == $s_selected_position && $action == 'add') {
8264
                    $selectPrerequisites->setSelected($key);
8265
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8266
                    $selectPrerequisites->setSelected($key);
8267
                }
8268
            }
8269
        }
8270
8271
        if ('edit' === $action) {
8272
            $extraField = new ExtraField('lp_item');
8273
            $extraField->addElements($form, $id);
8274
        }
8275
8276
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8277
8278
        if ($action == 'move') {
8279
            $form->addHidden('title', $item_title);
8280
            $form->addHidden('description', $item_description);
8281
        }
8282
8283
        if (is_numeric($extra_info)) {
8284
            $form->addHidden('path', $extra_info);
8285
        } elseif (is_array($extra_info)) {
8286
            $form->addHidden('path', $extra_info['path']);
8287
        }
8288
8289
        $form->addHidden('type', TOOL_THREAD);
8290
        $form->addHidden('post_time', time());
8291
        $form->setDefaults($defaults);
8292
8293
        return $form->returnForm();
8294
    }
8295
8296
    /**
8297
     * Return the HTML form to display an item (generally a dir item).
8298
     *
8299
     * @param string $item_type
8300
     * @param string $title
8301
     * @param string $action
8302
     * @param int    $id
8303
     * @param string $extra_info
8304
     *
8305
     * @throws Exception
8306
     * @throws HTML_QuickForm_Error
8307
     *
8308
     * @return string HTML form
8309
     */
8310
    public function display_item_form(
8311
        $item_type,
8312
        $title = '',
8313
        $action = 'add_item',
8314
        $id = 0,
8315
        $extra_info = 'new'
8316
    ) {
8317
        $_course = api_get_course_info();
8318
8319
        global $charset;
8320
8321
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8322
        $item_title = '';
8323
        $item_description = '';
8324
        $item_path_fck = '';
8325
8326
        $parent = 0;
8327
        $previousId = null;
8328
        if ($id != 0 && is_array($extra_info)) {
8329
            $item_title = $extra_info['title'];
8330
            $item_description = $extra_info['description'];
8331
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8332
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8333
            $parent = $extra_info['parent_item_id'];
8334
            $previousId = $extra_info['previous_item_id'];
8335
        }
8336
8337
        if ($extra_info instanceof learnpathItem) {
8338
            $item_title = $extra_info->get_title();
8339
            $item_description = $extra_info->get_description();
8340
            $path = $extra_info->get_path();
8341
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8342
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8343
            $parent = $extra_info->get_parent();
8344
            $previousId = $extra_info->previous;
8345
        }
8346
8347
        $id = (int) $id;
8348
        $sql = "SELECT * FROM $tbl_lp_item
8349
                WHERE
8350
                    lp_id = ".$this->lp_id." AND
8351
                    iid != $id";
8352
8353
        if ($item_type === 'dir') {
8354
            $sql .= " AND parent_item_id = 0";
8355
        }
8356
8357
        $result = Database::query($sql);
8358
        $arrLP = [];
8359
        while ($row = Database::fetch_array($result)) {
8360
            $arrLP[] = [
8361
                'id' => $row['iid'],
8362
                'item_type' => $row['item_type'],
8363
                'title' => $this->cleanItemTitle($row['title']),
8364
                'title_raw' => $row['title'],
8365
                'path' => $row['path'],
8366
                'description' => $row['description'],
8367
                'parent_item_id' => $row['parent_item_id'],
8368
                'previous_item_id' => $row['previous_item_id'],
8369
                'next_item_id' => $row['next_item_id'],
8370
                'max_score' => $row['max_score'],
8371
                'min_score' => $row['min_score'],
8372
                'mastery_score' => $row['mastery_score'],
8373
                'prerequisite' => $row['prerequisite'],
8374
                'display_order' => $row['display_order'],
8375
            ];
8376
        }
8377
8378
        $this->tree_array($arrLP);
8379
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8380
        unset($this->arrMenu);
8381
8382
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8383
8384
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8385
        $defaults['title'] = api_html_entity_decode(
8386
            $item_title,
8387
            ENT_QUOTES,
8388
            $charset
8389
        );
8390
        $defaults['description'] = $item_description;
8391
8392
        $form->addHeader($title);
8393
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8394
        $arrHide[0]['padding'] = 20;
8395
        $charset = api_get_system_encoding();
8396
        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...
8397
            if ($action != 'add') {
8398
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8399
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8400
                ) {
8401
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8402
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8403
                    if ($parent == $arrLP[$i]['id']) {
8404
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8405
                    }
8406
                }
8407
            } else {
8408
                if ($arrLP[$i]['item_type'] === 'dir') {
8409
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8410
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8411
                    if ($parent == $arrLP[$i]['id']) {
8412
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8413
                    }
8414
                }
8415
            }
8416
        }
8417
8418
        if ($action !== 'move') {
8419
            $this->setItemTitle($form);
8420
        } else {
8421
            $form->addElement('hidden', 'title');
8422
        }
8423
8424
        $parentSelect = $form->addElement(
8425
            'select',
8426
            'parent',
8427
            get_lang('Parent'),
8428
            '',
8429
            [
8430
                'id' => 'idParent',
8431
                'onchange' => 'javascript: load_cbo(this.value);',
8432
            ]
8433
        );
8434
8435
        foreach ($arrHide as $key => $value) {
8436
            $parentSelect->addOption(
8437
                $value['value'],
8438
                $key,
8439
                'style="padding-left:'.$value['padding'].'px;"'
8440
            );
8441
            $lastPosition = $key;
8442
        }
8443
8444
        if (!empty($s_selected_parent)) {
8445
            $parentSelect->setSelected($s_selected_parent);
8446
        }
8447
8448
        if (is_array($arrLP)) {
8449
            reset($arrLP);
8450
        }
8451
8452
        $arrHide = [];
8453
        // POSITION
8454
        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...
8455
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8456
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8457
                //this is the same!
8458
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8459
                    $s_selected_position = $arrLP[$i]['id'];
8460
                } elseif ($action === 'add') {
8461
                    $s_selected_position = $arrLP[$i]['id'];
8462
                }
8463
8464
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8465
            }
8466
        }
8467
8468
        $position = $form->addElement(
8469
            'select',
8470
            'previous',
8471
            get_lang('Position'),
8472
            '',
8473
            ['id' => 'previous']
8474
        );
8475
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8476
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8477
8478
        $lastPosition = null;
8479
        foreach ($arrHide as $key => $value) {
8480
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8481
            $lastPosition = $key;
8482
        }
8483
8484
        if (!empty($s_selected_position)) {
8485
            $position->setSelected($s_selected_position);
8486
        }
8487
8488
        // When new chapter add at the end
8489
        if ($action === 'add_item') {
8490
            $position->setSelected($lastPosition);
8491
        }
8492
8493
        if (is_array($arrLP)) {
8494
            reset($arrLP);
8495
        }
8496
8497
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8498
8499
        //fix in order to use the tab
8500
        if ($item_type === 'dir') {
8501
            $form->addElement('hidden', 'type', 'dir');
8502
        }
8503
8504
        $extension = null;
8505
        if (!empty($item_path)) {
8506
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8507
        }
8508
8509
        //assets can't be modified
8510
        //$item_type == 'asset' ||
8511
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8512
            if ($item_type === 'sco') {
8513
                $form->addElement(
8514
                    'html',
8515
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8516
                );
8517
            }
8518
            $renderer = $form->defaultRenderer();
8519
            $renderer->setElementTemplate(
8520
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8521
                'content_lp'
8522
            );
8523
8524
            $relative_prefix = '';
8525
            $editor_config = [
8526
                'ToolbarSet' => 'LearningPathDocuments',
8527
                'Width' => '100%',
8528
                'Height' => '500',
8529
                'FullPage' => true,
8530
                'CreateDocumentDir' => $relative_prefix,
8531
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8532
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8533
            ];
8534
8535
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8536
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8537
            $defaults['content_lp'] = file_get_contents($content_path);
8538
        }
8539
8540
        if (!empty($id)) {
8541
            $form->addHidden('id', $id);
8542
        }
8543
8544
        $form->addElement('hidden', 'type', $item_type);
8545
        $form->addElement('hidden', 'post_time', time());
8546
        $form->setDefaults($defaults);
8547
8548
        return $form->returnForm();
8549
    }
8550
8551
    /**
8552
     * @return string
8553
     */
8554
    public function getCurrentBuildingModeURL()
8555
    {
8556
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8557
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8558
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8559
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8560
8561
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8562
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8563
8564
        return $currentUrl;
8565
    }
8566
8567
    /**
8568
     * Returns the form to update or create a document.
8569
     *
8570
     * @param string        $action     (add/edit)
8571
     * @param int           $id         ID of the lp_item (if already exists)
8572
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
8573
     * @param learnpathItem $item
8574
     *
8575
     * @return string HTML form
8576
     */
8577
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new', $item = null)
8578
    {
8579
        $course_id = api_get_course_int_id();
8580
        $_course = api_get_course_info();
8581
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8582
8583
        $no_display_edit_textarea = false;
8584
        //If action==edit document
8585
        //We don't display the document form if it's not an editable document (html or txt file)
8586
        if ($action === 'edit') {
8587
            if (is_array($extra_info)) {
8588
                $path_parts = pathinfo($extra_info['dir']);
8589
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
8590
                    $no_display_edit_textarea = true;
8591
                }
8592
            }
8593
        }
8594
        $no_display_add = false;
8595
        // If action==add an existing document
8596
        // We don't display the document form if it's not an editable document (html or txt file).
8597
        if ($action === 'add') {
8598
            if (is_numeric($extra_info)) {
8599
                $extra_info = (int) $extra_info;
8600
                $sql_doc = "SELECT path FROM $tbl_doc
8601
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8602
                $result = Database::query($sql_doc);
8603
                $path_file = Database::result($result, 0, 0);
8604
                $path_parts = pathinfo($path_file);
8605
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8606
                    $no_display_add = true;
8607
                }
8608
            }
8609
        }
8610
8611
        $item_title = '';
8612
        $item_description = '';
8613
        if ($id != 0 && is_array($extra_info)) {
8614
            $item_title = stripslashes($extra_info['title']);
8615
            $item_description = stripslashes($extra_info['description']);
8616
            if (empty($item_title)) {
8617
                $path_parts = pathinfo($extra_info['path']);
8618
                $item_title = stripslashes($path_parts['filename']);
8619
            }
8620
        } elseif (is_numeric($extra_info)) {
8621
            $sql = "SELECT path, title FROM $tbl_doc
8622
                    WHERE
8623
                        c_id = ".$course_id." AND
8624
                        iid = ".intval($extra_info);
8625
            $result = Database::query($sql);
8626
            $row = Database::fetch_array($result);
8627
            $item_title = $row['title'];
8628
            $item_title = str_replace('_', ' ', $item_title);
8629
            if (empty($item_title)) {
8630
                $path_parts = pathinfo($row['path']);
8631
                $item_title = stripslashes($path_parts['filename']);
8632
            }
8633
        }
8634
8635
        $return = '<legend>';
8636
        $parent = 0;
8637
        if ($id != 0 && is_array($extra_info)) {
8638
            $parent = $extra_info['parent_item_id'];
8639
        }
8640
8641
        $selectedPosition = 0;
8642
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
8643
            $selectedPosition = $extra_info['previous_item_id'];
8644
        }
8645
8646
        if ($item instanceof learnpathItem) {
8647
            $item_title = stripslashes($item->get_title());
8648
            $item_description = stripslashes($item->get_description());
8649
            $selectedPosition = $item->previous;
8650
            $parent = $item->parent;
8651
        }
8652
8653
        $arrLP = $this->getItemsForForm();
8654
        $this->tree_array($arrLP);
8655
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8656
        unset($this->arrMenu);
8657
8658
        if ($action === 'add') {
8659
            $return .= get_lang('CreateTheDocument');
8660
        } elseif ($action === 'move') {
8661
            $return .= get_lang('MoveTheCurrentDocument');
8662
        } else {
8663
            $return .= get_lang('EditTheCurrentDocument');
8664
        }
8665
        $return .= '</legend>';
8666
8667
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8668
            $return .= Display::return_message(
8669
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8670
                false
8671
            );
8672
        }
8673
        $form = new FormValidator(
8674
            'form',
8675
            'POST',
8676
            $this->getCurrentBuildingModeURL(),
8677
            '',
8678
            ['enctype' => 'multipart/form-data']
8679
        );
8680
        $defaults['title'] = Security::remove_XSS($item_title);
8681
        if (empty($item_title)) {
8682
            $defaults['title'] = Security::remove_XSS($item_title);
8683
        }
8684
        $defaults['description'] = $item_description;
8685
        $form->addElement('html', $return);
8686
8687
        if ($action !== 'move') {
8688
            $data = $this->generate_lp_folder($_course);
8689
            if ($action !== 'edit') {
8690
                $folders = DocumentManager::get_all_document_folders(
8691
                    $_course,
8692
                    0,
8693
                    true
8694
                );
8695
                DocumentManager::build_directory_selector(
8696
                    $folders,
8697
                    '',
8698
                    [],
8699
                    true,
8700
                    $form,
8701
                    'directory_parent_id'
8702
                );
8703
            }
8704
8705
            if (isset($data['id'])) {
8706
                $defaults['directory_parent_id'] = $data['id'];
8707
            }
8708
            $this->setItemTitle($form);
8709
        }
8710
8711
        $arrHide[0]['value'] = $this->name;
8712
        $arrHide[0]['padding'] = 20;
8713
8714
        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...
8715
            if ($action !== 'add') {
8716
                if ($arrLP[$i]['item_type'] === 'dir' &&
8717
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8718
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8719
                ) {
8720
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8721
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8722
                }
8723
            } else {
8724
                if ($arrLP[$i]['item_type'] == 'dir') {
8725
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8726
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8727
                }
8728
            }
8729
        }
8730
8731
        $parentSelect = $form->addSelect(
8732
            'parent',
8733
            get_lang('Parent'),
8734
            [],
8735
            [
8736
                'id' => 'idParent',
8737
                'onchange' => 'javascript: load_cbo(this.value);',
8738
            ]
8739
        );
8740
8741
        $my_count = 0;
8742
        foreach ($arrHide as $key => $value) {
8743
            if ($my_count != 0) {
8744
                // The LP name is also the first section and is not in the same charset like the other sections.
8745
                $value['value'] = Security::remove_XSS($value['value']);
8746
                $parentSelect->addOption(
8747
                    $value['value'],
8748
                    $key,
8749
                    'style="padding-left:'.$value['padding'].'px;"'
8750
                );
8751
            } else {
8752
                $value['value'] = Security::remove_XSS($value['value']);
8753
                $parentSelect->addOption(
8754
                    $value['value'],
8755
                    $key,
8756
                    'style="padding-left:'.$value['padding'].'px;"'
8757
                );
8758
            }
8759
            $my_count++;
8760
        }
8761
8762
        if (!empty($id)) {
8763
            $parentSelect->setSelected($parent);
8764
        } else {
8765
            $parent_item_id = Session::read('parent_item_id', 0);
8766
            $parentSelect->setSelected($parent_item_id);
8767
        }
8768
8769
        if (is_array($arrLP)) {
8770
            reset($arrLP);
8771
        }
8772
8773
        $arrHide = [];
8774
        // POSITION
8775
        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...
8776
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8777
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8778
            ) {
8779
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8780
            }
8781
        }
8782
8783
        $position = $form->addSelect(
8784
            'previous',
8785
            get_lang('Position'),
8786
            [],
8787
            ['id' => 'previous']
8788
        );
8789
8790
        $position->addOption(get_lang('FirstPosition'), 0);
8791
        foreach ($arrHide as $key => $value) {
8792
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8793
            $position->addOption(
8794
                $value['value'],
8795
                $key,
8796
                'style="padding-left:'.$padding.'px;"'
8797
            );
8798
        }
8799
        $position->setSelected($selectedPosition);
8800
8801
        if (is_array($arrLP)) {
8802
            reset($arrLP);
8803
        }
8804
8805
        if ('edit' === $action) {
8806
            $extraField = new ExtraField('lp_item');
8807
            $extraField->addElements($form, $id);
8808
        }
8809
8810
        if ($action !== 'move') {
8811
            $arrHide = [];
8812
            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...
8813
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8814
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8815
                ) {
8816
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8817
                }
8818
            }
8819
8820
            if (!$no_display_add) {
8821
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8822
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8823
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8824
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8825
                ) {
8826
                    if (isset($_POST['content'])) {
8827
                        $content = stripslashes($_POST['content']);
8828
                    } elseif (is_array($extra_info)) {
8829
                        //If it's an html document or a text file
8830
                        if (!$no_display_edit_textarea) {
8831
                            $content = $this->display_document(
8832
                                $extra_info['path'],
8833
                                false,
8834
                                false
8835
                            );
8836
                        }
8837
                    } elseif (is_numeric($extra_info)) {
8838
                        $content = $this->display_document(
8839
                            $extra_info,
8840
                            false,
8841
                            false
8842
                        );
8843
                    } else {
8844
                        $content = '';
8845
                    }
8846
8847
                    if (!$no_display_edit_textarea) {
8848
                        // We need to calculate here some specific settings for the online editor.
8849
                        // The calculated settings work for documents in the Documents tool
8850
                        // (on the root or in subfolders).
8851
                        // For documents in native scorm packages it is unclear whether the
8852
                        // online editor should be activated or not.
8853
8854
                        // A new document, it is in the root of the repository.
8855
                        if (is_array($extra_info) && $extra_info != 'new') {
8856
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8857
                            $relative_path = explode('/', $extra_info['dir']);
8858
                            $cnt = count($relative_path) - 2;
8859
                            if ($cnt < 0) {
8860
                                $cnt = 0;
8861
                            }
8862
                            $relative_prefix = str_repeat('../', $cnt);
8863
                            $relative_path = array_slice($relative_path, 1, $cnt);
8864
                            $relative_path = implode('/', $relative_path);
8865
                            if (strlen($relative_path) > 0) {
8866
                                $relative_path = $relative_path.'/';
8867
                            }
8868
                        } else {
8869
                            $result = $this->generate_lp_folder($_course);
8870
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8871
                            $relative_prefix = '../../';
8872
                        }
8873
8874
                        $editor_config = [
8875
                            'ToolbarSet' => 'LearningPathDocuments',
8876
                            'Width' => '100%',
8877
                            'Height' => '500',
8878
                            'FullPage' => true,
8879
                            'CreateDocumentDir' => $relative_prefix,
8880
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8881
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8882
                        ];
8883
8884
                        if ($_GET['action'] === 'add_item') {
8885
                            $class = 'add';
8886
                            $text = get_lang('LPCreateDocument');
8887
                        } else {
8888
                            if ($_GET['action'] === 'edit_item') {
8889
                                $class = 'save';
8890
                                $text = get_lang('SaveDocument');
8891
                            }
8892
                        }
8893
8894
                        $form->addButtonSave($text, 'submit_button');
8895
                        $renderer = $form->defaultRenderer();
8896
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8897
                        $form->addElement('html', '<div class="editor-lp">');
8898
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8899
                        $form->addElement('html', '</div>');
8900
                        $defaults['content_lp'] = $content;
8901
                    }
8902
                } elseif (is_numeric($extra_info)) {
8903
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8904
8905
                    $return = $this->display_document($extra_info, true, true, true);
8906
                    $form->addElement('html', $return);
8907
                }
8908
            }
8909
        }
8910
        if (isset($extra_info['item_type']) &&
8911
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8912
        ) {
8913
            $parentSelect->freeze();
8914
            $position->freeze();
8915
        }
8916
8917
        if ($action === 'move') {
8918
            $form->addElement('hidden', 'title', $item_title);
8919
            $form->addElement('hidden', 'description', $item_description);
8920
        }
8921
        if (is_numeric($extra_info)) {
8922
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8923
            $form->addElement('hidden', 'path', $extra_info);
8924
        } elseif (is_array($extra_info)) {
8925
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8926
            $form->addElement('hidden', 'path', $extra_info['path']);
8927
        }
8928
8929
        if ($item instanceof learnpathItem) {
8930
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8931
            $form->addElement('hidden', 'path', $item->path);
8932
        }
8933
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8934
        $form->addElement('hidden', 'post_time', time());
8935
        $form->setDefaults($defaults);
8936
8937
        return $form->returnForm();
8938
    }
8939
8940
    /**
8941
     * Returns the form to update or create a read-out text.
8942
     *
8943
     * @param string $action     "add" or "edit"
8944
     * @param int    $id         ID of the lp_item (if already exists)
8945
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8946
     *
8947
     * @throws Exception
8948
     * @throws HTML_QuickForm_Error
8949
     *
8950
     * @return string HTML form
8951
     */
8952
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8953
    {
8954
        $course_id = api_get_course_int_id();
8955
        $_course = api_get_course_info();
8956
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8957
8958
        $no_display_edit_textarea = false;
8959
        //If action==edit document
8960
        //We don't display the document form if it's not an editable document (html or txt file)
8961
        if ($action == 'edit') {
8962
            if (is_array($extra_info)) {
8963
                $path_parts = pathinfo($extra_info['dir']);
8964
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8965
                    $no_display_edit_textarea = true;
8966
                }
8967
            }
8968
        }
8969
        $no_display_add = false;
8970
8971
        $item_title = '';
8972
        $item_description = '';
8973
        if ($id != 0 && is_array($extra_info)) {
8974
            $item_title = stripslashes($extra_info['title']);
8975
            $item_description = stripslashes($extra_info['description']);
8976
            $item_terms = stripslashes($extra_info['terms']);
8977
            if (empty($item_title)) {
8978
                $path_parts = pathinfo($extra_info['path']);
8979
                $item_title = stripslashes($path_parts['filename']);
8980
            }
8981
        } elseif (is_numeric($extra_info)) {
8982
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8983
            $result = Database::query($sql);
8984
            $row = Database::fetch_array($result);
8985
            $item_title = $row['title'];
8986
            $item_title = str_replace('_', ' ', $item_title);
8987
            if (empty($item_title)) {
8988
                $path_parts = pathinfo($row['path']);
8989
                $item_title = stripslashes($path_parts['filename']);
8990
            }
8991
        }
8992
8993
        $parent = 0;
8994
        if ($id != 0 && is_array($extra_info)) {
8995
            $parent = $extra_info['parent_item_id'];
8996
        }
8997
8998
        $arrLP = $this->getItemsForForm();
8999
        $this->tree_array($arrLP);
9000
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9001
        unset($this->arrMenu);
9002
9003
        if ($action === 'add') {
9004
            $formHeader = get_lang('CreateTheDocument');
9005
        } else {
9006
            $formHeader = get_lang('EditTheCurrentDocument');
9007
        }
9008
9009
        if ('edit' === $action) {
9010
            $urlAudioIcon = Display::url(
9011
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9012
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9013
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9014
            );
9015
        } else {
9016
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9017
        }
9018
9019
        $form = new FormValidator(
9020
            'frm_add_reading',
9021
            'POST',
9022
            $this->getCurrentBuildingModeURL(),
9023
            '',
9024
            ['enctype' => 'multipart/form-data']
9025
        );
9026
        $form->addHeader($formHeader);
9027
        $form->addHtml(
9028
            Display::return_message(
9029
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9030
                'normal',
9031
                false
9032
            )
9033
        );
9034
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9035
        $defaults['description'] = $item_description;
9036
9037
        $data = $this->generate_lp_folder($_course);
9038
9039
        if ($action != 'edit') {
9040
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9041
            DocumentManager::build_directory_selector(
9042
                $folders,
9043
                '',
9044
                [],
9045
                true,
9046
                $form,
9047
                'directory_parent_id'
9048
            );
9049
        }
9050
9051
        if (isset($data['id'])) {
9052
            $defaults['directory_parent_id'] = $data['id'];
9053
        }
9054
        $this->setItemTitle($form);
9055
9056
        $arrHide[0]['value'] = $this->name;
9057
        $arrHide[0]['padding'] = 20;
9058
9059
        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...
9060
            if ($action != 'add') {
9061
                if ($arrLP[$i]['item_type'] == 'dir' &&
9062
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9063
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9064
                ) {
9065
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9066
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9067
                }
9068
            } else {
9069
                if ($arrLP[$i]['item_type'] == 'dir') {
9070
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9071
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9072
                }
9073
            }
9074
        }
9075
9076
        $parent_select = $form->addSelect(
9077
            'parent',
9078
            get_lang('Parent'),
9079
            [],
9080
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9081
        );
9082
9083
        $my_count = 0;
9084
        foreach ($arrHide as $key => $value) {
9085
            if ($my_count != 0) {
9086
                // The LP name is also the first section and is not in the same charset like the other sections.
9087
                $value['value'] = Security::remove_XSS($value['value']);
9088
                $parent_select->addOption(
9089
                    $value['value'],
9090
                    $key,
9091
                    'style="padding-left:'.$value['padding'].'px;"'
9092
                );
9093
            } else {
9094
                $value['value'] = Security::remove_XSS($value['value']);
9095
                $parent_select->addOption(
9096
                    $value['value'],
9097
                    $key,
9098
                    'style="padding-left:'.$value['padding'].'px;"'
9099
                );
9100
            }
9101
            $my_count++;
9102
        }
9103
9104
        if (!empty($id)) {
9105
            $parent_select->setSelected($parent);
9106
        } else {
9107
            $parent_item_id = Session::read('parent_item_id', 0);
9108
            $parent_select->setSelected($parent_item_id);
9109
        }
9110
9111
        if (is_array($arrLP)) {
9112
            reset($arrLP);
9113
        }
9114
9115
        $arrHide = [];
9116
        $s_selected_position = null;
9117
9118
        // POSITION
9119
        $lastPosition = null;
9120
9121
        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...
9122
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9123
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9124
            ) {
9125
                if ((isset($extra_info['previous_item_id']) &&
9126
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9127
                ) {
9128
                    $s_selected_position = $arrLP[$i]['id'];
9129
                }
9130
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9131
            }
9132
            $lastPosition = $arrLP[$i]['id'];
9133
        }
9134
9135
        if (empty($s_selected_position)) {
9136
            $s_selected_position = $lastPosition;
9137
        }
9138
9139
        $position = $form->addSelect(
9140
            'previous',
9141
            get_lang('Position'),
9142
            []
9143
        );
9144
        $position->addOption(get_lang('FirstPosition'), 0);
9145
9146
        foreach ($arrHide as $key => $value) {
9147
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9148
            $position->addOption(
9149
                $value['value'],
9150
                $key,
9151
                'style="padding-left:'.$padding.'px;"'
9152
            );
9153
        }
9154
        $position->setSelected($s_selected_position);
9155
9156
        if (is_array($arrLP)) {
9157
            reset($arrLP);
9158
        }
9159
9160
        if ('edit' === $action) {
9161
            $extraField = new ExtraField('lp_item');
9162
            $extraField->addElements($form, $id);
9163
        }
9164
9165
        $arrHide = [];
9166
9167
        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...
9168
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9169
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9170
            ) {
9171
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9172
            }
9173
        }
9174
9175
        if (!$no_display_add) {
9176
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9177
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9178
9179
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9180
                if (!$no_display_edit_textarea) {
9181
                    $content = '';
9182
9183
                    if (isset($_POST['content'])) {
9184
                        $content = stripslashes($_POST['content']);
9185
                    } elseif (is_array($extra_info)) {
9186
                        $content = $this->display_document($extra_info['path'], false, false);
9187
                    } elseif (is_numeric($extra_info)) {
9188
                        $content = $this->display_document($extra_info, false, false);
9189
                    }
9190
9191
                    // A new document, it is in the root of the repository.
9192
                    if (is_array($extra_info) && $extra_info != 'new') {
9193
                    } else {
9194
                        $this->generate_lp_folder($_course);
9195
                    }
9196
9197
                    if ($_GET['action'] == 'add_item') {
9198
                        $text = get_lang('LPCreateDocument');
9199
                    } else {
9200
                        $text = get_lang('SaveDocument');
9201
                    }
9202
9203
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9204
                    $form
9205
                        ->defaultRenderer()
9206
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9207
                    $form->addButtonSave($text, 'submit_button');
9208
                    $defaults['content_lp'] = $content;
9209
                }
9210
            } elseif (is_numeric($extra_info)) {
9211
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9212
9213
                $return = $this->display_document($extra_info, true, true, true);
9214
                $form->addElement('html', $return);
9215
            }
9216
        }
9217
9218
        if (is_numeric($extra_info)) {
9219
            $form->addElement('hidden', 'path', $extra_info);
9220
        } elseif (is_array($extra_info)) {
9221
            $form->addElement('hidden', 'path', $extra_info['path']);
9222
        }
9223
9224
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9225
        $form->addElement('hidden', 'post_time', time());
9226
        $form->setDefaults($defaults);
9227
9228
        return $form->returnForm();
9229
    }
9230
9231
    /**
9232
     * @param array  $courseInfo
9233
     * @param string $content
9234
     * @param string $title
9235
     * @param int    $parentId
9236
     *
9237
     * @throws \Doctrine\ORM\ORMException
9238
     * @throws \Doctrine\ORM\OptimisticLockException
9239
     * @throws \Doctrine\ORM\TransactionRequiredException
9240
     *
9241
     * @return int
9242
     */
9243
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9244
    {
9245
        $creatorId = api_get_user_id();
9246
        $sessionId = api_get_session_id();
9247
9248
        // Generates folder
9249
        $result = $this->generate_lp_folder($courseInfo);
9250
        $dir = $result['dir'];
9251
9252
        if (empty($parentId) || $parentId == '/') {
9253
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9254
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9255
9256
            if ($parentId === '/') {
9257
                $dir = '/';
9258
            }
9259
9260
            // Please, do not modify this dirname formatting.
9261
            if (strstr($dir, '..')) {
9262
                $dir = '/';
9263
            }
9264
9265
            if (!empty($dir[0]) && $dir[0] == '.') {
9266
                $dir = substr($dir, 1);
9267
            }
9268
            if (!empty($dir[0]) && $dir[0] != '/') {
9269
                $dir = '/'.$dir;
9270
            }
9271
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9272
                $dir .= '/';
9273
            }
9274
        } else {
9275
            $parentInfo = DocumentManager::get_document_data_by_id(
9276
                $parentId,
9277
                $courseInfo['code']
9278
            );
9279
            if (!empty($parentInfo)) {
9280
                $dir = $parentInfo['path'].'/';
9281
            }
9282
        }
9283
9284
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9285
9286
        if (!is_dir($filepath)) {
9287
            $dir = '/';
9288
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9289
        }
9290
9291
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9292
9293
        if (!empty($title)) {
9294
            $title = api_replace_dangerous_char(stripslashes($title));
9295
        } else {
9296
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9297
        }
9298
9299
        $title = disable_dangerous_file($title);
9300
        $filename = $title;
9301
        $content = !empty($content) ? $content : $_POST['content_lp'];
9302
        $tmpFileName = $filename;
9303
9304
        $i = 0;
9305
        while (file_exists($filepath.$tmpFileName.'.html')) {
9306
            $tmpFileName = $filename.'_'.++$i;
9307
        }
9308
9309
        $filename = $tmpFileName.'.html';
9310
        $content = stripslashes($content);
9311
9312
        if (file_exists($filepath.$filename)) {
9313
            return 0;
9314
        }
9315
9316
        $putContent = file_put_contents($filepath.$filename, $content);
9317
9318
        if ($putContent === false) {
9319
            return 0;
9320
        }
9321
9322
        $fileSize = filesize($filepath.$filename);
9323
        $saveFilePath = $dir.$filename;
9324
9325
        $documentId = add_document(
9326
            $courseInfo,
9327
            $saveFilePath,
9328
            'file',
9329
            $fileSize,
9330
            $tmpFileName,
9331
            '',
9332
            0, //readonly
9333
            true,
9334
            null,
9335
            $sessionId,
9336
            $creatorId
9337
        );
9338
9339
        if (!$documentId) {
9340
            return 0;
9341
        }
9342
9343
        api_item_property_update(
9344
            $courseInfo,
9345
            TOOL_DOCUMENT,
9346
            $documentId,
9347
            'DocumentAdded',
9348
            $creatorId,
9349
            null,
9350
            null,
9351
            null,
9352
            null,
9353
            $sessionId
9354
        );
9355
9356
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9357
        $newTitle = $originalTitle;
9358
9359
        if ($newComment || $newTitle) {
9360
            $em = Database::getManager();
9361
9362
            /** @var CDocument $doc */
9363
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9364
9365
            if ($newComment) {
9366
                $doc->setComment($newComment);
9367
            }
9368
9369
            if ($newTitle) {
9370
                $doc->setTitle($newTitle);
9371
            }
9372
9373
            $em->persist($doc);
9374
            $em->flush();
9375
        }
9376
9377
        return $documentId;
9378
    }
9379
9380
    /**
9381
     * Return HTML form to add/edit a link item.
9382
     *
9383
     * @param string $action     (add/edit)
9384
     * @param int    $id         Item ID if exists
9385
     * @param mixed  $extra_info
9386
     *
9387
     * @throws Exception
9388
     * @throws HTML_QuickForm_Error
9389
     *
9390
     * @return string HTML form
9391
     */
9392
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9393
    {
9394
        $course_id = api_get_course_int_id();
9395
        $tbl_link = Database::get_course_table(TABLE_LINK);
9396
9397
        $item_title = '';
9398
        $item_description = '';
9399
        $item_url = '';
9400
9401
        if ($id != 0 && is_array($extra_info)) {
9402
            $item_title = stripslashes($extra_info['title']);
9403
            $item_description = stripslashes($extra_info['description']);
9404
            $item_url = stripslashes($extra_info['url']);
9405
        } elseif (is_numeric($extra_info)) {
9406
            $extra_info = (int) $extra_info;
9407
            $sql = "SELECT title, description, url
9408
                    FROM $tbl_link
9409
                    WHERE c_id = $course_id AND iid = $extra_info";
9410
            $result = Database::query($sql);
9411
            $row = Database::fetch_array($result);
9412
            $item_title = $row['title'];
9413
            $item_description = $row['description'];
9414
            $item_url = $row['url'];
9415
        }
9416
9417
        $form = new FormValidator(
9418
            'edit_link',
9419
            'POST',
9420
            $this->getCurrentBuildingModeURL()
9421
        );
9422
        $defaults = [];
9423
        $parent = 0;
9424
        if ($id != 0 && is_array($extra_info)) {
9425
            $parent = $extra_info['parent_item_id'];
9426
        }
9427
9428
        $arrLP = $this->getItemsForForm();
9429
9430
        $this->tree_array($arrLP);
9431
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9432
        unset($this->arrMenu);
9433
9434
        if ($action == 'add') {
9435
            $legend = get_lang('CreateTheLink');
9436
        } elseif ($action == 'move') {
9437
            $legend = get_lang('MoveCurrentLink');
9438
        } else {
9439
            $legend = get_lang('EditCurrentLink');
9440
        }
9441
9442
        $form->addHeader($legend);
9443
9444
        if ($action != 'move') {
9445
            $this->setItemTitle($form);
9446
            $defaults['title'] = $item_title;
9447
        }
9448
9449
        $selectParent = $form->addSelect(
9450
            'parent',
9451
            get_lang('Parent'),
9452
            [],
9453
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9454
        );
9455
        $selectParent->addOption($this->name, 0);
9456
        $arrHide = [
9457
            $id,
9458
        ];
9459
9460
        $parent_item_id = Session::read('parent_item_id', 0);
9461
9462
        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...
9463
            if ($action != 'add') {
9464
                if (
9465
                    ($arrLP[$i]['item_type'] == 'dir') &&
9466
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9467
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9468
                ) {
9469
                    $selectParent->addOption(
9470
                        $arrLP[$i]['title'],
9471
                        $arrLP[$i]['id'],
9472
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9473
                    );
9474
9475
                    if ($parent == $arrLP[$i]['id']) {
9476
                        $selectParent->setSelected($arrLP[$i]['id']);
9477
                    }
9478
                } else {
9479
                    $arrHide[] = $arrLP[$i]['id'];
9480
                }
9481
            } else {
9482
                if ($arrLP[$i]['item_type'] == 'dir') {
9483
                    $selectParent->addOption(
9484
                        $arrLP[$i]['title'],
9485
                        $arrLP[$i]['id'],
9486
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9487
                    );
9488
9489
                    if ($parent_item_id == $arrLP[$i]['id']) {
9490
                        $selectParent->setSelected($arrLP[$i]['id']);
9491
                    }
9492
                }
9493
            }
9494
        }
9495
9496
        if (is_array($arrLP)) {
9497
            reset($arrLP);
9498
        }
9499
9500
        $selectPrevious = $form->addSelect(
9501
            'previous',
9502
            get_lang('Position'),
9503
            [],
9504
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9505
        );
9506
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9507
9508
        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...
9509
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9510
                $selectPrevious->addOption(
9511
                    $arrLP[$i]['title'],
9512
                    $arrLP[$i]['id']
9513
                );
9514
9515
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9516
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9517
                } elseif ($action == 'add') {
9518
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9519
                }
9520
            }
9521
        }
9522
9523
        if ($action != 'move') {
9524
            $urlAttributes = ['class' => 'learnpath_item_form'];
9525
9526
            if (is_numeric($extra_info)) {
9527
                $urlAttributes['disabled'] = 'disabled';
9528
            }
9529
9530
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9531
            $defaults['url'] = $item_url;
9532
            $arrHide = [];
9533
            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...
9534
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9535
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9536
                }
9537
            }
9538
        }
9539
9540
        if ('edit' === $action) {
9541
            $extraField = new ExtraField('lp_item');
9542
            $extraField->addElements($form, $id);
9543
        }
9544
9545
        if ($action == 'add') {
9546
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9547
        } else {
9548
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9549
        }
9550
9551
        if ($action == 'move') {
9552
            $form->addHidden('title', $item_title);
9553
            $form->addHidden('description', $item_description);
9554
        }
9555
9556
        if (is_numeric($extra_info)) {
9557
            $form->addHidden('path', $extra_info);
9558
        } elseif (is_array($extra_info)) {
9559
            $form->addHidden('path', $extra_info['path']);
9560
        }
9561
        $form->addHidden('type', TOOL_LINK);
9562
        $form->addHidden('post_time', time());
9563
        $form->setDefaults($defaults);
9564
9565
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9566
    }
9567
9568
    /**
9569
     * Return HTML form to add/edit a student publication (work).
9570
     *
9571
     * @param string $action
9572
     * @param int    $id         Item ID if already exists
9573
     * @param string $extra_info
9574
     *
9575
     * @throws Exception
9576
     *
9577
     * @return string HTML form
9578
     */
9579
    public function display_student_publication_form(
9580
        $action = 'add',
9581
        $id = 0,
9582
        $extra_info = ''
9583
    ) {
9584
        $course_id = api_get_course_int_id();
9585
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9586
9587
        $item_title = get_lang('Student_publication');
9588
        if ($id != 0 && is_array($extra_info)) {
9589
            $item_title = stripslashes($extra_info['title']);
9590
            $item_description = stripslashes($extra_info['description']);
9591
        } elseif (is_numeric($extra_info)) {
9592
            $extra_info = (int) $extra_info;
9593
            $sql = "SELECT title, description
9594
                    FROM $tbl_publication
9595
                    WHERE c_id = $course_id AND id = ".$extra_info;
9596
9597
            $result = Database::query($sql);
9598
            $row = Database::fetch_array($result);
9599
            if ($row) {
9600
                $item_title = $row['title'];
9601
            }
9602
        }
9603
9604
        $parent = 0;
9605
        if ($id != 0 && is_array($extra_info)) {
9606
            $parent = $extra_info['parent_item_id'];
9607
        }
9608
9609
        $arrLP = $this->getItemsForForm();
9610
9611
        $this->tree_array($arrLP);
9612
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9613
        unset($this->arrMenu);
9614
9615
        $form = new FormValidator('frm_student_publication', 'post', '#');
9616
9617
        if ($action == 'add') {
9618
            $form->addHeader(get_lang('Student_publication'));
9619
        } elseif ($action == 'move') {
9620
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9621
        } else {
9622
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9623
        }
9624
9625
        if ($action != 'move') {
9626
            $this->setItemTitle($form);
9627
        }
9628
9629
        $parentSelect = $form->addSelect(
9630
            'parent',
9631
            get_lang('Parent'),
9632
            ['0' => $this->name],
9633
            [
9634
                'onchange' => 'javascript: load_cbo(this.value);',
9635
                'class' => 'learnpath_item_form',
9636
                'id' => 'idParent',
9637
            ]
9638
        );
9639
9640
        $arrHide = [$id];
9641
        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...
9642
            if ($action != 'add') {
9643
                if (
9644
                    ($arrLP[$i]['item_type'] == 'dir') &&
9645
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9646
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9647
                ) {
9648
                    $parentSelect->addOption(
9649
                        $arrLP[$i]['title'],
9650
                        $arrLP[$i]['id'],
9651
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9652
                    );
9653
9654
                    if ($parent == $arrLP[$i]['id']) {
9655
                        $parentSelect->setSelected($arrLP[$i]['id']);
9656
                    }
9657
                } else {
9658
                    $arrHide[] = $arrLP[$i]['id'];
9659
                }
9660
            } else {
9661
                if ($arrLP[$i]['item_type'] == 'dir') {
9662
                    $parentSelect->addOption(
9663
                        $arrLP[$i]['title'],
9664
                        $arrLP[$i]['id'],
9665
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9666
                    );
9667
9668
                    if ($parent == $arrLP[$i]['id']) {
9669
                        $parentSelect->setSelected($arrLP[$i]['id']);
9670
                    }
9671
                }
9672
            }
9673
        }
9674
9675
        if (is_array($arrLP)) {
9676
            reset($arrLP);
9677
        }
9678
9679
        $previousSelect = $form->addSelect(
9680
            'previous',
9681
            get_lang('Position'),
9682
            ['0' => get_lang('FirstPosition')],
9683
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9684
        );
9685
9686
        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...
9687
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9688
                $previousSelect->addOption(
9689
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9690
                    $arrLP[$i]['id']
9691
                );
9692
9693
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9694
                    $previousSelect->setSelected($arrLP[$i]['id']);
9695
                } elseif ($action == 'add') {
9696
                    $previousSelect->setSelected($arrLP[$i]['id']);
9697
                }
9698
            }
9699
        }
9700
9701
        if ('edit' === $action) {
9702
            $extraField = new ExtraField('lp_item');
9703
            $extraField->addElements($form, $id);
9704
        }
9705
9706
        if ($action == 'add') {
9707
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9708
        } else {
9709
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9710
        }
9711
9712
        if ($action == 'move') {
9713
            $form->addHidden('title', $item_title);
9714
            $form->addHidden('description', $item_description);
9715
        }
9716
9717
        if (is_numeric($extra_info)) {
9718
            $form->addHidden('path', $extra_info);
9719
        } elseif (is_array($extra_info)) {
9720
            $form->addHidden('path', $extra_info['path']);
9721
        }
9722
9723
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9724
        $form->addHidden('post_time', time());
9725
        $form->setDefaults(['title' => $item_title]);
9726
9727
        $return = '<div class="sectioncomment">';
9728
        $return .= $form->returnForm();
9729
        $return .= '</div>';
9730
9731
        return $return;
9732
    }
9733
9734
    /**
9735
     * Displays the menu for manipulating a step.
9736
     *
9737
     * @param int    $item_id
9738
     * @param string $item_type
9739
     *
9740
     * @return string
9741
     */
9742
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9743
    {
9744
        $_course = api_get_course_info();
9745
        $course_code = api_get_course_id();
9746
        $item_id = (int) $item_id;
9747
9748
        $return = '<div class="actions">';
9749
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9750
        $sql = "SELECT * FROM $tbl_lp_item
9751
                WHERE iid = ".$item_id;
9752
        $result = Database::query($sql);
9753
        $row = Database::fetch_assoc($result);
9754
9755
        $audio_player = null;
9756
        // We display an audio player if needed.
9757
        if (!empty($row['audio'])) {
9758
            $audio = learnpathItem::fixAudio($row['audio']);
9759
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
9760
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9761
                            <audio src="'.$webAudioPath.'" controls>
9762
                            </div><br />';
9763
        }
9764
9765
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9766
9767
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9768
            $return .= Display::url(
9769
                Display::return_icon(
9770
                    'edit.png',
9771
                    get_lang('Edit'),
9772
                    [],
9773
                    ICON_SIZE_SMALL
9774
                ),
9775
                $url.'&action=edit_item&path_item='.$row['path']
9776
            );
9777
9778
            $return .= Display::url(
9779
                Display::return_icon(
9780
                    'move.png',
9781
                    get_lang('Move'),
9782
                    [],
9783
                    ICON_SIZE_SMALL
9784
                ),
9785
                $url.'&action=move_item'
9786
            );
9787
        }
9788
9789
        // Commented for now as prerequisites cannot be added to chapters.
9790
        if ($item_type != 'dir') {
9791
            $return .= Display::url(
9792
                Display::return_icon(
9793
                    'accept.png',
9794
                    get_lang('LearnpathPrerequisites'),
9795
                    [],
9796
                    ICON_SIZE_SMALL
9797
                ),
9798
                $url.'&action=edit_item_prereq'
9799
            );
9800
        }
9801
        $return .= Display::url(
9802
            Display::return_icon(
9803
                'delete.png',
9804
                get_lang('Delete'),
9805
                [],
9806
                ICON_SIZE_SMALL
9807
            ),
9808
            $url.'&action=delete_item'
9809
        );
9810
9811
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9812
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9813
            if (empty($documentData)) {
9814
                // Try with iid
9815
                $table = Database::get_course_table(TABLE_DOCUMENT);
9816
                $sql = "SELECT path FROM $table
9817
                        WHERE
9818
                              c_id = ".api_get_course_int_id()." AND
9819
                              iid = ".$row['path']." AND
9820
                              path NOT LIKE '%_DELETED_%'";
9821
                $result = Database::query($sql);
9822
                $documentData = Database::fetch_array($result);
9823
                if ($documentData) {
9824
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9825
                }
9826
            }
9827
            if (isset($documentData['absolute_path_from_document'])) {
9828
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9829
            }
9830
        }
9831
9832
        $return .= '</div>';
9833
9834
        if (!empty($audio_player)) {
9835
            $return .= $audio_player;
9836
        }
9837
9838
        return $return;
9839
    }
9840
9841
    /**
9842
     * Creates the javascript needed for filling up the checkboxes without page reload.
9843
     *
9844
     * @return string
9845
     */
9846
    public function get_js_dropdown_array()
9847
    {
9848
        $course_id = api_get_course_int_id();
9849
        $return = 'var child_name = new Array();'."\n";
9850
        $return .= 'var child_value = new Array();'."\n\n";
9851
        $return .= 'child_name[0] = new Array();'."\n";
9852
        $return .= 'child_value[0] = new Array();'."\n\n";
9853
9854
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9855
        $i = 0;
9856
        $list = $this->getItemsForForm(true);
9857
9858
        foreach ($list as $row_zero) {
9859
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9860
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9861
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9862
                }
9863
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9864
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9865
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9866
            }
9867
        }
9868
9869
        $return .= "\n";
9870
        $sql = "SELECT * FROM $tbl_lp_item
9871
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9872
        $res = Database::query($sql);
9873
        while ($row = Database::fetch_array($res)) {
9874
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9875
                           WHERE
9876
                                c_id = ".$course_id." AND
9877
                                parent_item_id = ".$row['iid']."
9878
                           ORDER BY display_order ASC";
9879
            $res_parent = Database::query($sql_parent);
9880
            $i = 0;
9881
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9882
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9883
9884
            while ($row_parent = Database::fetch_array($res_parent)) {
9885
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9886
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9887
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9888
            }
9889
            $return .= "\n";
9890
        }
9891
9892
        $return .= "
9893
            function load_cbo(id) {
9894
                if (!id) {
9895
                    return false;
9896
                }
9897
9898
                var cbo = document.getElementById('previous');
9899
                for (var i = cbo.length - 1; i > 0; i--) {
9900
                    cbo.options[i] = null;
9901
                }
9902
9903
                var k=0;
9904
                for(var i = 1; i <= child_name[id].length; i++){
9905
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9906
                    option.style.paddingLeft = '40px';
9907
                    cbo.options[i] = option;
9908
                    k = i;
9909
                }
9910
9911
                cbo.options[k].selected = true;
9912
                $('#previous').selectpicker('refresh');
9913
            }";
9914
9915
        return $return;
9916
    }
9917
9918
    /**
9919
     * Display the form to allow moving an item.
9920
     *
9921
     * @param learnpathItem $item Item ID
9922
     *
9923
     * @throws Exception
9924
     * @throws HTML_QuickForm_Error
9925
     *
9926
     * @return string HTML form
9927
     */
9928
    public function display_move_item($item)
9929
    {
9930
        $return = '';
9931
        if ($item) {
9932
            $item_id = $item->getIid();
9933
            $type = $item->get_type();
9934
9935
            switch ($type) {
9936
                case 'dir':
9937
                case 'asset':
9938
                    $return .= $this->display_manipulate($item_id, $type);
9939
                    $return .= $this->display_item_form(
9940
                        $type,
9941
                        get_lang('MoveCurrentChapter'),
9942
                        'move',
9943
                        $item_id,
9944
                        $item
9945
                    );
9946
                    break;
9947
                case TOOL_DOCUMENT:
9948
                    $return .= $this->display_manipulate($item_id, $type);
9949
                    $return .= $this->display_document_form('move', $item_id, null, $item);
9950
                    break;
9951
                case TOOL_LINK:
9952
                    $return .= $this->display_manipulate($item_id, $type);
9953
                    $return .= $this->display_link_form('move', $item_id, $item);
9954
                    break;
9955
                case TOOL_HOTPOTATOES:
9956
                    $return .= $this->display_manipulate($item_id, $type);
9957
                    $return .= $this->display_link_form('move', $item_id, $item);
9958
                    break;
9959
                case TOOL_QUIZ:
9960
                    $return .= $this->display_manipulate($item_id, $type);
9961
                    $return .= $this->display_quiz_form('move', $item_id, $item);
9962
                    break;
9963
                case TOOL_STUDENTPUBLICATION:
9964
                    $return .= $this->display_manipulate($item_id, $type);
9965
                    $return .= $this->display_student_publication_form('move', $item_id, $item);
9966
                    break;
9967
                case TOOL_FORUM:
9968
                    $return .= $this->display_manipulate($item_id, $type);
9969
                    $return .= $this->display_forum_form('move', $item_id, $item);
9970
                    break;
9971
                case TOOL_THREAD:
9972
                    $return .= $this->display_manipulate($item_id, $type);
9973
                    $return .= $this->display_forum_form('move', $item_id, $item);
9974
                    break;
9975
            }
9976
        }
9977
9978
        return $return;
9979
    }
9980
9981
    /**
9982
     * Return HTML form to allow prerequisites selection.
9983
     *
9984
     * @todo use FormValidator
9985
     *
9986
     * @param int Item ID
9987
     *
9988
     * @return string HTML form
9989
     */
9990
    public function display_item_prerequisites_form($item_id = 0)
9991
    {
9992
        $course_id = api_get_course_int_id();
9993
        $item_id = (int) $item_id;
9994
9995
        if (empty($item_id)) {
9996
            return '';
9997
        }
9998
9999
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10000
10001
        /* Current prerequisite */
10002
        $sql = "SELECT * FROM $tbl_lp_item
10003
                WHERE iid = $item_id";
10004
        $result = Database::query($sql);
10005
        $row = Database::fetch_array($result);
10006
        $prerequisiteId = $row['prerequisite'];
10007
10008
        $return = '<legend>';
10009
        $return .= get_lang('AddEditPrerequisites');
10010
        $return .= '</legend>';
10011
        $return .= '<form method="POST">';
10012
        $return .= '<div class="table-responsive">';
10013
        $return .= '<table class="table table-hover">';
10014
        $return .= '<thead>';
10015
        $return .= '<tr>';
10016
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10017
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10018
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10019
        $return .= '</tr>';
10020
        $return .= '</thead>';
10021
10022
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10023
        $return .= '<tbody>';
10024
        $return .= '<tr>';
10025
        $return .= '<td colspan="3">';
10026
        $return .= '<div class="radio learnpath"><label for="idNone">';
10027
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10028
        $return .= get_lang('None').'</label>';
10029
        $return .= '</div>';
10030
        $return .= '</tr>';
10031
10032
        $sql = "SELECT * FROM $tbl_lp_item
10033
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10034
        $result = Database::query($sql);
10035
10036
        $selectedMinScore = [];
10037
        $selectedMaxScore = [];
10038
        $masteryScore = [];
10039
        while ($row = Database::fetch_array($result)) {
10040
            if ($row['iid'] == $item_id) {
10041
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10042
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10043
            }
10044
            $masteryScore[$row['iid']] = $row['mastery_score'];
10045
        }
10046
10047
        $arrLP = $this->getItemsForForm();
10048
        $this->tree_array($arrLP);
10049
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10050
        unset($this->arrMenu);
10051
10052
        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...
10053
            $item = $arrLP[$i];
10054
10055
            if ($item['id'] == $item_id) {
10056
                break;
10057
            }
10058
10059
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10060
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10061
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10062
10063
            $return .= '<tr>';
10064
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10065
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10066
            $return .= '<label for="id'.$item['id'].'">';
10067
10068
            $checked = '';
10069
            if (null !== $prerequisiteId) {
10070
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10071
            }
10072
10073
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10074
10075
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10076
10077
            $icon_name = str_replace(' ', '', $item['item_type']);
10078
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10079
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10080
            } else {
10081
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10082
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10083
                } else {
10084
                    $return .= Display::return_icon('folder_document.png');
10085
                }
10086
            }
10087
10088
            $return .= $item['title'].'</label>';
10089
            $return .= '</div>';
10090
            $return .= '</td>';
10091
10092
            if ($item['item_type'] == TOOL_QUIZ) {
10093
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10094
                $lpItemObj = new LpItem($course_id, $item['id']);
10095
                $exercise = new Exercise($course_id);
10096
                $exercise->read($lpItemObj->path);
10097
                $lpItemObj->max_score = $exercise->get_max_score();
10098
                $lpItemObj->update();
10099
                $item['max_score'] = $lpItemObj->max_score;
10100
10101
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10102
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10103
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10104
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10105
                }
10106
10107
                $return .= '<td>';
10108
                $return .= '<input
10109
                    class="form-control"
10110
                    size="4" maxlength="3"
10111
                    name="min_'.$item['id'].'"
10112
                    type="number"
10113
                    min="0"
10114
                    step="any"
10115
                    max="'.$item['max_score'].'"
10116
                    value="'.$selectedMinScoreValue.'"
10117
                />';
10118
                $return .= '</td>';
10119
                $return .= '<td>';
10120
                $return .= '<input
10121
                    class="form-control"
10122
                    size="4"
10123
                    maxlength="3"
10124
                    name="max_'.$item['id'].'"
10125
                    type="number"
10126
                    min="0"
10127
                    step="any"
10128
                    max="'.$item['max_score'].'"
10129
                    value="'.$selectedMaxScoreValue.'"
10130
                />';
10131
                $return .= '</td>';
10132
            }
10133
10134
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10135
                $return .= '<td>';
10136
                $return .= '<input
10137
                    size="4"
10138
                    maxlength="3"
10139
                    name="min_'.$item['id'].'"
10140
                    type="number"
10141
                    min="0"
10142
                    step="any"
10143
                    max="'.$item['max_score'].'"
10144
                    value="'.$selectedMinScoreValue.'"
10145
                />';
10146
                $return .= '</td>';
10147
                $return .= '<td>';
10148
                $return .= '<input
10149
                    size="4"
10150
                    maxlength="3"
10151
                    name="max_'.$item['id'].'"
10152
                    type="number"
10153
                    min="0"
10154
                    step="any"
10155
                    max="'.$item['max_score'].'"
10156
                    value="'.$selectedMaxScoreValue.'"
10157
                />';
10158
                $return .= '</td>';
10159
            }
10160
            $return .= '</tr>';
10161
        }
10162
        $return .= '<tr>';
10163
        $return .= '</tr>';
10164
        $return .= '</tbody>';
10165
        $return .= '</table>';
10166
        $return .= '</div>';
10167
        $return .= '<div class="form-group">';
10168
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10169
            get_lang('ModifyPrerequisites').'</button>';
10170
        $return .= '</form>';
10171
10172
        return $return;
10173
    }
10174
10175
    /**
10176
     * Return HTML list to allow prerequisites selection for lp.
10177
     *
10178
     * @return string HTML form
10179
     */
10180
    public function display_lp_prerequisites_list()
10181
    {
10182
        $course_id = api_get_course_int_id();
10183
        $lp_id = $this->lp_id;
10184
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10185
10186
        // get current prerequisite
10187
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10188
        $result = Database::query($sql);
10189
        $row = Database::fetch_array($result);
10190
        $prerequisiteId = $row['prerequisite'];
10191
        $session_id = api_get_session_id();
10192
        $session_condition = api_get_session_condition($session_id, true, true);
10193
        $sql = "SELECT * FROM $tbl_lp
10194
                WHERE c_id = $course_id $session_condition
10195
                ORDER BY display_order ";
10196
        $rs = Database::query($sql);
10197
        $return = '';
10198
        $return .= '<select name="prerequisites" class="form-control">';
10199
        $return .= '<option value="0">'.get_lang('None').'</option>';
10200
        if (Database::num_rows($rs) > 0) {
10201
            while ($row = Database::fetch_array($rs)) {
10202
                if ($row['id'] == $lp_id) {
10203
                    continue;
10204
                }
10205
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10206
            }
10207
        }
10208
        $return .= '</select>';
10209
10210
        return $return;
10211
    }
10212
10213
    /**
10214
     * Creates a list with all the documents in it.
10215
     *
10216
     * @param bool $showInvisibleFiles
10217
     *
10218
     * @throws Exception
10219
     * @throws HTML_QuickForm_Error
10220
     *
10221
     * @return string
10222
     */
10223
    public function get_documents($showInvisibleFiles = false)
10224
    {
10225
        $course_info = api_get_course_info();
10226
        $sessionId = api_get_session_id();
10227
        $documentTree = DocumentManager::get_document_preview(
10228
            $course_info,
10229
            $this->lp_id,
10230
            null,
10231
            $sessionId,
10232
            true,
10233
            null,
10234
            null,
10235
            $showInvisibleFiles,
10236
            true
10237
        );
10238
10239
        $headers = [
10240
            get_lang('Files'),
10241
            get_lang('CreateTheDocument'),
10242
            get_lang('CreateReadOutText'),
10243
            get_lang('Upload'),
10244
        ];
10245
10246
        $form = new FormValidator(
10247
            'form_upload',
10248
            'POST',
10249
            $this->getCurrentBuildingModeURL(),
10250
            '',
10251
            ['enctype' => 'multipart/form-data']
10252
        );
10253
10254
        $folders = DocumentManager::get_all_document_folders(
10255
            api_get_course_info(),
10256
            0,
10257
            true
10258
        );
10259
10260
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10261
10262
        DocumentManager::build_directory_selector(
10263
            $folders,
10264
            $lpPathInfo['id'],
10265
            [],
10266
            true,
10267
            $form,
10268
            'directory_parent_id'
10269
        );
10270
10271
        $group = [
10272
            $form->createElement(
10273
                'radio',
10274
                'if_exists',
10275
                get_lang('UplWhatIfFileExists'),
10276
                get_lang('UplDoNothing'),
10277
                'nothing'
10278
            ),
10279
            $form->createElement(
10280
                'radio',
10281
                'if_exists',
10282
                null,
10283
                get_lang('UplOverwriteLong'),
10284
                'overwrite'
10285
            ),
10286
            $form->createElement(
10287
                'radio',
10288
                'if_exists',
10289
                null,
10290
                get_lang('UplRenameLong'),
10291
                'rename'
10292
            ),
10293
        ];
10294
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10295
10296
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10297
        $defaultFileExistsOption = 'rename';
10298
        if (!empty($fileExistsOption)) {
10299
            $defaultFileExistsOption = $fileExistsOption;
10300
        }
10301
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10302
10303
        // Check box options
10304
        $form->addElement(
10305
            'checkbox',
10306
            'unzip',
10307
            get_lang('Options'),
10308
            get_lang('Uncompress')
10309
        );
10310
10311
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10312
        $form->addMultipleUpload($url);
10313
        $new = $this->display_document_form('add', 0);
10314
        $frmReadOutText = $this->displayFrmReadOutText('add');
10315
        $tabs = Display::tabs(
10316
            $headers,
10317
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10318
            'subtab'
10319
        );
10320
10321
        return $tabs;
10322
    }
10323
10324
    /**
10325
     * Creates a list with all the exercises (quiz) in it.
10326
     *
10327
     * @return string
10328
     */
10329
    public function get_exercises()
10330
    {
10331
        $course_id = api_get_course_int_id();
10332
        $session_id = api_get_session_id();
10333
        $userInfo = api_get_user_info();
10334
10335
        // New for hotpotatoes.
10336
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10337
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10338
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10339
        $condition_session = api_get_session_condition($session_id, true, true);
10340
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10341
10342
        $activeCondition = ' active <> -1 ';
10343
        if ($setting) {
10344
            $activeCondition = ' active = 1 ';
10345
        }
10346
10347
        $categoryCondition = '';
10348
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10349
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10350
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10351
        }
10352
10353
        $keywordCondition = '';
10354
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10355
10356
        if (!empty($keyword)) {
10357
            $keyword = Database::escape_string($keyword);
10358
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10359
        }
10360
10361
        $sql_quiz = "SELECT * FROM $tbl_quiz
10362
                     WHERE
10363
                            c_id = $course_id AND
10364
                            $activeCondition
10365
                            $condition_session
10366
                            $categoryCondition
10367
                            $keywordCondition
10368
                     ORDER BY title ASC";
10369
10370
        $sql_hot = "SELECT * FROM $tbl_doc
10371
                    WHERE
10372
                        c_id = $course_id AND
10373
                        path LIKE '".$uploadPath."/%/%htm%'
10374
                        $condition_session
10375
                     ORDER BY id ASC";
10376
10377
        $res_quiz = Database::query($sql_quiz);
10378
        $res_hot = Database::query($sql_hot);
10379
10380
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10381
10382
        // Create a search-box
10383
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10384
        $form->addHidden('action', 'add_item');
10385
        $form->addHidden('type', 'step');
10386
        $form->addHidden('lp_id', $this->lp_id);
10387
        $form->addHidden('lp_build_selected', '2');
10388
10389
        $form->addCourseHiddenParams();
10390
        $form->addText(
10391
            'keyword',
10392
            get_lang('Search'),
10393
            false,
10394
            [
10395
                'aria-label' => get_lang('Search'),
10396
            ]
10397
        );
10398
10399
        if (api_get_configuration_value('allow_exercise_categories')) {
10400
            $manager = new ExerciseCategoryManager();
10401
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10402
            if (!empty($options)) {
10403
                $form->addSelect(
10404
                    'category_id',
10405
                    get_lang('Category'),
10406
                    $options,
10407
                    ['placeholder' => get_lang('SelectAnOption')]
10408
                );
10409
            }
10410
        }
10411
10412
        $form->addButtonSearch(get_lang('Search'));
10413
        $return = $form->returnForm();
10414
10415
        $return .= '<ul class="lp_resource">';
10416
10417
        $return .= '<li class="lp_resource_element">';
10418
        $return .= Display::return_icon('new_exercice.png');
10419
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10420
            get_lang('NewExercise').'</a>';
10421
        $return .= '</li>';
10422
10423
        $previewIcon = Display::return_icon(
10424
            'preview_view.png',
10425
            get_lang('Preview')
10426
        );
10427
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10428
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10429
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10430
10431
        // Display hotpotatoes
10432
        while ($row_hot = Database::fetch_array($res_hot)) {
10433
            $link = Display::url(
10434
                $previewIcon,
10435
                $exerciseUrl.'&file='.$row_hot['path'],
10436
                ['target' => '_blank']
10437
            );
10438
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10439
            $return .= '<a class="moved" href="#">';
10440
            $return .= Display::return_icon(
10441
                'move_everywhere.png',
10442
                get_lang('Move'),
10443
                [],
10444
                ICON_SIZE_TINY
10445
            );
10446
            $return .= '</a> ';
10447
            $return .= Display::return_icon('hotpotatoes_s.png');
10448
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10449
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10450
            $return .= '</li>';
10451
        }
10452
10453
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10454
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10455
            $title = strip_tags(
10456
                api_html_entity_decode($row_quiz['title'])
10457
            );
10458
10459
            $visibility = api_get_item_visibility(
10460
                ['real_id' => $course_id],
10461
                TOOL_QUIZ,
10462
                $row_quiz['iid'],
10463
                $session_id
10464
            );
10465
10466
            $link = Display::url(
10467
                $previewIcon,
10468
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10469
                ['target' => '_blank']
10470
            );
10471
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10472
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10473
            $return .= $quizIcon;
10474
            $sessionStar = api_get_session_image(
10475
                $row_quiz['session_id'],
10476
                $userInfo['status']
10477
            );
10478
            $return .= Display::url(
10479
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10480
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10481
                [
10482
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10483
                ]
10484
            );
10485
            $return .= '</li>';
10486
        }
10487
10488
        $return .= '</ul>';
10489
10490
        return $return;
10491
    }
10492
10493
    /**
10494
     * Creates a list with all the links in it.
10495
     *
10496
     * @return string
10497
     */
10498
    public function get_links()
10499
    {
10500
        $selfUrl = api_get_self();
10501
        $courseIdReq = api_get_cidreq();
10502
        $course = api_get_course_info();
10503
        $userInfo = api_get_user_info();
10504
10505
        $course_id = $course['real_id'];
10506
        $tbl_link = Database::get_course_table(TABLE_LINK);
10507
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10508
        $moveEverywhereIcon = Display::return_icon(
10509
            'move_everywhere.png',
10510
            get_lang('Move'),
10511
            [],
10512
            ICON_SIZE_TINY
10513
        );
10514
10515
        $session_id = api_get_session_id();
10516
        $condition_session = api_get_session_condition(
10517
            $session_id,
10518
            true,
10519
            true,
10520
            'link.session_id'
10521
        );
10522
10523
        $sql = "SELECT
10524
                    link.id as link_id,
10525
                    link.title as link_title,
10526
                    link.session_id as link_session_id,
10527
                    link.category_id as category_id,
10528
                    link_category.category_title as category_title
10529
                FROM $tbl_link as link
10530
                LEFT JOIN $linkCategoryTable as link_category
10531
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10532
                WHERE link.c_id = $course_id $condition_session
10533
                ORDER BY link_category.category_title ASC, link.title ASC";
10534
        $result = Database::query($sql);
10535
        $categorizedLinks = [];
10536
        $categories = [];
10537
10538
        while ($link = Database::fetch_array($result)) {
10539
            if (!$link['category_id']) {
10540
                $link['category_title'] = get_lang('Uncategorized');
10541
            }
10542
            $categories[$link['category_id']] = $link['category_title'];
10543
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10544
        }
10545
10546
        $linksHtmlCode =
10547
            '<script>
10548
            function toggle_tool(tool, id) {
10549
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10550
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10551
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10552
                } else {
10553
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10554
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10555
                }
10556
            }
10557
        </script>
10558
10559
        <ul class="lp_resource">
10560
            <li class="lp_resource_element">
10561
                '.Display::return_icon('linksnew.gif').'
10562
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10563
                get_lang('LinkAdd').'
10564
                </a>
10565
            </li>';
10566
10567
        foreach ($categorizedLinks as $categoryId => $links) {
10568
            $linkNodes = null;
10569
            foreach ($links as $key => $linkInfo) {
10570
                $title = $linkInfo['link_title'];
10571
                $linkSessionId = $linkInfo['link_session_id'];
10572
10573
                $link = Display::url(
10574
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10575
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10576
                    ['target' => '_blank']
10577
                );
10578
10579
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10580
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10581
                    $linkNodes .=
10582
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10583
                        <a class="moved" href="#">'.
10584
                            $moveEverywhereIcon.
10585
                        '</a>
10586
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10587
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10588
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10589
                        Security::remove_XSS($title).$sessionStar.$link.
10590
                        '</a>
10591
                    </li>';
10592
                }
10593
            }
10594
            $linksHtmlCode .=
10595
                '<li>
10596
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10597
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10598
                    align="absbottom" />
10599
                </a>
10600
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10601
            </li>
10602
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10603
        }
10604
        $linksHtmlCode .= '</ul>';
10605
10606
        return $linksHtmlCode;
10607
    }
10608
10609
    /**
10610
     * Creates a list with all the student publications in it.
10611
     *
10612
     * @return string
10613
     */
10614
    public function get_student_publications()
10615
    {
10616
        $return = '<ul class="lp_resource">';
10617
        $return .= '<li class="lp_resource_element">';
10618
        $return .= Display::return_icon('works_new.gif');
10619
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10620
            get_lang('AddAssignmentPage').'</a>';
10621
        $return .= '</li>';
10622
10623
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10624
        $works = getWorkListTeacher(0, 100, null, null, null);
10625
        if (!empty($works)) {
10626
            foreach ($works as $work) {
10627
                $link = Display::url(
10628
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10629
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10630
                    ['target' => '_blank']
10631
                );
10632
10633
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10634
                $return .= '<a class="moved" href="#">';
10635
                $return .= Display::return_icon(
10636
                    'move_everywhere.png',
10637
                    get_lang('Move'),
10638
                    [],
10639
                    ICON_SIZE_TINY
10640
                );
10641
                $return .= '</a> ';
10642
10643
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10644
                $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.'">'.
10645
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10646
                </a>';
10647
10648
                $return .= '</li>';
10649
            }
10650
        }
10651
10652
        $return .= '</ul>';
10653
10654
        return $return;
10655
    }
10656
10657
    /**
10658
     * Creates a list with all the forums in it.
10659
     *
10660
     * @return string
10661
     */
10662
    public function get_forums()
10663
    {
10664
        require_once '../forum/forumfunction.inc.php';
10665
10666
        $forumCategories = get_forum_categories();
10667
        $forumsInNoCategory = get_forums_in_category(0);
10668
        if (!empty($forumsInNoCategory)) {
10669
            $forumCategories = array_merge(
10670
                $forumCategories,
10671
                [
10672
                    [
10673
                        'cat_id' => 0,
10674
                        'session_id' => 0,
10675
                        'visibility' => 1,
10676
                        'cat_comment' => null,
10677
                    ],
10678
                ]
10679
            );
10680
        }
10681
10682
        $forumList = get_forums();
10683
        $a_forums = [];
10684
        foreach ($forumCategories as $forumCategory) {
10685
            // The forums in this category.
10686
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10687
            if (!empty($forumsInCategory)) {
10688
                foreach ($forumList as $forum) {
10689
                    if (isset($forum['forum_category']) &&
10690
                        $forum['forum_category'] == $forumCategory['cat_id']
10691
                    ) {
10692
                        $a_forums[] = $forum;
10693
                    }
10694
                }
10695
            }
10696
        }
10697
10698
        $return = '<ul class="lp_resource">';
10699
10700
        // First add link
10701
        $return .= '<li class="lp_resource_element">';
10702
        $return .= Display::return_icon('new_forum.png');
10703
        $return .= Display::url(
10704
            get_lang('CreateANewForum'),
10705
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10706
                'action' => 'add',
10707
                'content' => 'forum',
10708
                'lp_id' => $this->lp_id,
10709
            ]),
10710
            ['title' => get_lang('CreateANewForum')]
10711
        );
10712
        $return .= '</li>';
10713
10714
        $return .= '<script>
10715
            function toggle_forum(forum_id) {
10716
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10717
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10718
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10719
                } else {
10720
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10721
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10722
                }
10723
            }
10724
        </script>';
10725
10726
        foreach ($a_forums as $forum) {
10727
            if (!empty($forum['forum_id'])) {
10728
                $link = Display::url(
10729
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10730
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10731
                    ['target' => '_blank']
10732
                );
10733
10734
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10735
                $return .= '<a class="moved" href="#">';
10736
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10737
                $return .= ' </a>';
10738
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10739
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10740
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10741
                            </a>
10742
                            <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">'.
10743
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10744
10745
                $return .= '</li>';
10746
10747
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10748
                $a_threads = get_threads($forum['forum_id']);
10749
                if (is_array($a_threads)) {
10750
                    foreach ($a_threads as $thread) {
10751
                        $link = Display::url(
10752
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10753
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10754
                            ['target' => '_blank']
10755
                        );
10756
10757
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10758
                        $return .= '&nbsp;<a class="moved" href="#">';
10759
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10760
                        $return .= ' </a>';
10761
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10762
                        $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.'">'.
10763
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10764
                        $return .= '</li>';
10765
                    }
10766
                }
10767
                $return .= '</div>';
10768
            }
10769
        }
10770
        $return .= '</ul>';
10771
10772
        return $return;
10773
    }
10774
10775
    /**
10776
     * // TODO: The output encoding should be equal to the system encoding.
10777
     *
10778
     * Exports the learning path as a SCORM package. This is the main function that
10779
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10780
     * whole thing and returns the zip.
10781
     *
10782
     * This method needs to be called in PHP5, as it will fail with non-adequate
10783
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10784
     * you need to call it on a learnpath object.
10785
     *
10786
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10787
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10788
     * path has been modified, it should use the generic method here below.
10789
     *
10790
     * @return string Returns the zip package string, or null if error
10791
     */
10792
    public function scormExport()
10793
    {
10794
        api_set_more_memory_and_time_limits();
10795
10796
        $_course = api_get_course_info();
10797
        $course_id = $_course['real_id'];
10798
        // Create the zip handler (this will remain available throughout the method).
10799
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10800
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10801
        $temp_dir_short = uniqid('scorm_export', true);
10802
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10803
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10804
        $zip_folder = new PclZip($temp_zip_file);
10805
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10806
        $root_path = $main_path = api_get_path(SYS_PATH);
10807
        $files_cleanup = [];
10808
10809
        // Place to temporarily stash the zip file.
10810
        // create the temp dir if it doesn't exist
10811
        // or do a cleanup before creating the zip file.
10812
        if (!is_dir($temp_zip_dir)) {
10813
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10814
        } else {
10815
            // Cleanup: Check the temp dir for old files and delete them.
10816
            $handle = opendir($temp_zip_dir);
10817
            while (false !== ($file = readdir($handle))) {
10818
                if ($file != '.' && $file != '..') {
10819
                    unlink("$temp_zip_dir/$file");
10820
                }
10821
            }
10822
            closedir($handle);
10823
        }
10824
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10825
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10826
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10827
        ) {
10828
            // Remove the possible . at the end of the path.
10829
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10830
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10831
            mkdir(
10832
                $dest_path_to_scorm_folder,
10833
                api_get_permissions_for_new_directories(),
10834
                true
10835
            );
10836
            copyr(
10837
                $current_course_path.'/scorm/'.$this->path,
10838
                $dest_path_to_scorm_folder,
10839
                ['imsmanifest'],
10840
                $zip_files
10841
            );
10842
        }
10843
10844
        // Build a dummy imsmanifest structure.
10845
        // Do not add to the zip yet (we still need it).
10846
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10847
        // Aggregation Model official document, section "2.3 Content Packaging".
10848
        // We are going to build a UTF-8 encoded manifest.
10849
        // Later we will recode it to the desired (and supported) encoding.
10850
        $xmldoc = new DOMDocument('1.0');
10851
        $root = $xmldoc->createElement('manifest');
10852
        $root->setAttribute('identifier', 'SingleCourseManifest');
10853
        $root->setAttribute('version', '1.1');
10854
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10855
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10856
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10857
        $root->setAttribute(
10858
            'xsi:schemaLocation',
10859
            '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'
10860
        );
10861
        // Build mandatory sub-root container elements.
10862
        $metadata = $xmldoc->createElement('metadata');
10863
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10864
        $metadata->appendChild($md_schema);
10865
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10866
        $metadata->appendChild($md_schemaversion);
10867
        $root->appendChild($metadata);
10868
10869
        $organizations = $xmldoc->createElement('organizations');
10870
        $resources = $xmldoc->createElement('resources');
10871
10872
        // Build the only organization we will use in building our learnpaths.
10873
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10874
        $organization = $xmldoc->createElement('organization');
10875
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10876
        // To set the title of the SCORM entity (=organization), we take the name given
10877
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10878
        // learning path charset) as it is the encoding that defines how it is stored
10879
        // in the database. Then we convert it to HTML entities again as the "&" character
10880
        // alone is not authorized in XML (must be &amp;).
10881
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10882
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10883
        $organization->appendChild($org_title);
10884
        $folder_name = 'document';
10885
10886
        // Removes the learning_path/scorm_folder path when exporting see #4841
10887
        $path_to_remove = '';
10888
        $path_to_replace = '';
10889
        $result = $this->generate_lp_folder($_course);
10890
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10891
            $path_to_remove = 'document'.$result['dir'];
10892
            $path_to_replace = $folder_name.'/';
10893
        }
10894
10895
        // Fixes chamilo scorm exports
10896
        if ($this->ref === 'chamilo_scorm_export') {
10897
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10898
        }
10899
10900
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10901
        $link_updates = [];
10902
        $links_to_create = [];
10903
        foreach ($this->ordered_items as $index => $itemId) {
10904
            /** @var learnpathItem $item */
10905
            $item = $this->items[$itemId];
10906
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10907
                // Get included documents from this item.
10908
                if ($item->type === 'sco') {
10909
                    $inc_docs = $item->get_resources_from_source(
10910
                        null,
10911
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10912
                    );
10913
                } else {
10914
                    $inc_docs = $item->get_resources_from_source();
10915
                }
10916
10917
                // Give a child element <item> to the <organization> element.
10918
                $my_item_id = $item->get_id();
10919
                $my_item = $xmldoc->createElement('item');
10920
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10921
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10922
                $my_item->setAttribute('isvisible', 'true');
10923
                // Give a child element <title> to the <item> element.
10924
                $my_title = $xmldoc->createElement(
10925
                    'title',
10926
                    htmlspecialchars(
10927
                        api_utf8_encode($item->get_title()),
10928
                        ENT_QUOTES,
10929
                        'UTF-8'
10930
                    )
10931
                );
10932
                $my_item->appendChild($my_title);
10933
                // Give a child element <adlcp:prerequisites> to the <item> element.
10934
                $my_prereqs = $xmldoc->createElement(
10935
                    'adlcp:prerequisites',
10936
                    $this->get_scorm_prereq_string($my_item_id)
10937
                );
10938
                $my_prereqs->setAttribute('type', 'aicc_script');
10939
                $my_item->appendChild($my_prereqs);
10940
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10941
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10942
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10943
                //$xmldoc->createElement('adlcp:timelimitaction','');
10944
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10945
                //$xmldoc->createElement('adlcp:datafromlms','');
10946
                // Give a child element <adlcp:masteryscore> to the <item> element.
10947
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10948
                $my_item->appendChild($my_masteryscore);
10949
10950
                // Attach this item to the organization element or hits parent if there is one.
10951
                if (!empty($item->parent) && $item->parent != 0) {
10952
                    $children = $organization->childNodes;
10953
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10954
                    if (is_object($possible_parent)) {
10955
                        $possible_parent->appendChild($my_item);
10956
                    } else {
10957
                        if ($this->debug > 0) {
10958
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10959
                        }
10960
                    }
10961
                } else {
10962
                    if ($this->debug > 0) {
10963
                        error_log('No parent');
10964
                    }
10965
                    $organization->appendChild($my_item);
10966
                }
10967
10968
                // Get the path of the file(s) from the course directory root.
10969
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10970
                $my_xml_file_path = $my_file_path;
10971
                if (!empty($path_to_remove)) {
10972
                    // From docs
10973
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10974
10975
                    // From quiz
10976
                    if ($this->ref === 'chamilo_scorm_export') {
10977
                        $path_to_remove = 'scorm/'.$this->path.'/';
10978
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10979
                    }
10980
                }
10981
10982
                $my_sub_dir = dirname($my_file_path);
10983
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10984
                $my_xml_sub_dir = $my_sub_dir;
10985
                // Give a <resource> child to the <resources> element
10986
                $my_resource = $xmldoc->createElement('resource');
10987
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10988
                $my_resource->setAttribute('type', 'webcontent');
10989
                $my_resource->setAttribute('href', $my_xml_file_path);
10990
                // adlcp:scormtype can be either 'sco' or 'asset'.
10991
                if ($item->type === 'sco') {
10992
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10993
                } else {
10994
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10995
                }
10996
                // xml:base is the base directory to find the files declared in this resource.
10997
                $my_resource->setAttribute('xml:base', '');
10998
                // Give a <file> child to the <resource> element.
10999
                $my_file = $xmldoc->createElement('file');
11000
                $my_file->setAttribute('href', $my_xml_file_path);
11001
                $my_resource->appendChild($my_file);
11002
11003
                // Dependency to other files - not yet supported.
11004
                $i = 1;
11005
                if ($inc_docs) {
11006
                    foreach ($inc_docs as $doc_info) {
11007
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11008
                            continue;
11009
                        }
11010
                        $my_dep = $xmldoc->createElement('resource');
11011
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11012
                        $my_dep->setAttribute('identifier', $res_id);
11013
                        $my_dep->setAttribute('type', 'webcontent');
11014
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11015
                        $my_dep_file = $xmldoc->createElement('file');
11016
                        // Check type of URL.
11017
                        if ($doc_info[1] == 'remote') {
11018
                            // Remote file. Save url as is.
11019
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11020
                            $my_dep->setAttribute('xml:base', '');
11021
                        } elseif ($doc_info[1] === 'local') {
11022
                            switch ($doc_info[2]) {
11023
                                case 'url':
11024
                                    // Local URL - save path as url for now, don't zip file.
11025
                                    $abs_path = api_get_path(SYS_PATH).
11026
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11027
                                    $current_dir = dirname($abs_path);
11028
                                    $current_dir = str_replace('\\', '/', $current_dir);
11029
                                    $file_path = realpath($abs_path);
11030
                                    $file_path = str_replace('\\', '/', $file_path);
11031
                                    $my_dep_file->setAttribute('href', $file_path);
11032
                                    $my_dep->setAttribute('xml:base', '');
11033
                                    if (strstr($file_path, $main_path) !== false) {
11034
                                        // The calculated real path is really inside Chamilo's root path.
11035
                                        // Reduce file path to what's under the DocumentRoot.
11036
                                        $replace = $file_path;
11037
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11038
                                        $destinationFile = $file_path;
11039
11040
                                        if (strstr($file_path, 'upload/users') !== false) {
11041
                                            $pos = strpos($file_path, 'my_files/');
11042
                                            if ($pos !== false) {
11043
                                                $onlyDirectory = str_replace(
11044
                                                    'upload/users/',
11045
                                                    '',
11046
                                                    substr($file_path, $pos, strlen($file_path))
11047
                                                );
11048
                                            }
11049
                                            $replace = $onlyDirectory;
11050
                                            $destinationFile = $replace;
11051
                                        }
11052
                                        $zip_files_abs[] = $file_path;
11053
                                        $link_updates[$my_file_path][] = [
11054
                                            'orig' => $doc_info[0],
11055
                                            'dest' => $destinationFile,
11056
                                            'replace' => $replace,
11057
                                        ];
11058
                                        $my_dep_file->setAttribute('href', $file_path);
11059
                                        $my_dep->setAttribute('xml:base', '');
11060
                                    } elseif (empty($file_path)) {
11061
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11062
                                        $file_path = str_replace('//', '/', $file_path);
11063
                                        if (file_exists($file_path)) {
11064
                                            // We get the relative path.
11065
                                            $file_path = substr($file_path, strlen($current_dir));
11066
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11067
                                            $link_updates[$my_file_path][] = [
11068
                                                'orig' => $doc_info[0],
11069
                                                'dest' => $file_path,
11070
                                            ];
11071
                                            $my_dep_file->setAttribute('href', $file_path);
11072
                                            $my_dep->setAttribute('xml:base', '');
11073
                                        }
11074
                                    }
11075
                                    break;
11076
                                case 'abs':
11077
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11078
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11079
                                    $my_dep->setAttribute('xml:base', '');
11080
11081
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11082
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11083
                                    $abs_img_path_without_subdir = $doc_info[0];
11084
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11085
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11086
                                    if ($pos === 0) {
11087
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11088
                                    }
11089
11090
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11091
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11092
11093
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11094
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11095
                                    // Check if the current document is in that path.
11096
                                    if (strstr($file_path, $cur_path) !== false) {
11097
                                        $destinationFile = substr($file_path, strlen($cur_path));
11098
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11099
11100
                                        $fileToTest = $cur_path.$my_file_path;
11101
                                        if (!empty($path_to_remove)) {
11102
                                            $fileToTest = str_replace(
11103
                                                $path_to_remove.'/',
11104
                                                $path_to_replace,
11105
                                                $cur_path.$my_file_path
11106
                                            );
11107
                                        }
11108
11109
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11110
11111
                                        // Put the current document in the zip (this array is the array
11112
                                        // that will manage documents already in the course folder - relative).
11113
                                        $zip_files[] = $filePathNoCoursePart;
11114
                                        // Update the links to the current document in the
11115
                                        // containing document (make them relative).
11116
                                        $link_updates[$my_file_path][] = [
11117
                                            'orig' => $doc_info[0],
11118
                                            'dest' => $destinationFile,
11119
                                            'replace' => $relative_path,
11120
                                        ];
11121
11122
                                        $my_dep_file->setAttribute('href', $file_path);
11123
                                        $my_dep->setAttribute('xml:base', '');
11124
                                    } elseif (strstr($file_path, $main_path) !== false) {
11125
                                        // The calculated real path is really inside Chamilo's root path.
11126
                                        // Reduce file path to what's under the DocumentRoot.
11127
                                        $file_path = substr($file_path, strlen($root_path));
11128
                                        $zip_files_abs[] = $file_path;
11129
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11130
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11131
                                        $my_dep->setAttribute('xml:base', '');
11132
                                    } elseif (empty($file_path)) {
11133
                                        // Probably this is an image inside "/main" directory
11134
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11135
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11136
11137
                                        if (file_exists($file_path)) {
11138
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11139
                                                // We get the relative path.
11140
                                                $pos = strpos($file_path, 'main/default_course_document/');
11141
                                                if ($pos !== false) {
11142
                                                    $onlyDirectory = str_replace(
11143
                                                        'main/default_course_document/',
11144
                                                        '',
11145
                                                        substr($file_path, $pos, strlen($file_path))
11146
                                                    );
11147
                                                }
11148
11149
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11150
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11151
                                                $zip_files_abs[] = $fileAbs;
11152
                                                $link_updates[$my_file_path][] = [
11153
                                                    'orig' => $doc_info[0],
11154
                                                    'dest' => $destinationFile,
11155
                                                ];
11156
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11157
                                                $my_dep->setAttribute('xml:base', '');
11158
                                            }
11159
                                        }
11160
                                    }
11161
                                    break;
11162
                                case 'rel':
11163
                                    // Path relative to the current document.
11164
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11165
                                    if (substr($doc_info[0], 0, 2) === '..') {
11166
                                        // Relative path going up.
11167
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11168
                                        $current_dir = str_replace('\\', '/', $current_dir);
11169
                                        $file_path = realpath($current_dir.$doc_info[0]);
11170
                                        $file_path = str_replace('\\', '/', $file_path);
11171
                                        if (strstr($file_path, $main_path) !== false) {
11172
                                            // The calculated real path is really inside Chamilo's root path.
11173
                                            // Reduce file path to what's under the DocumentRoot.
11174
                                            $file_path = substr($file_path, strlen($root_path));
11175
                                            $zip_files_abs[] = $file_path;
11176
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11177
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11178
                                            $my_dep->setAttribute('xml:base', '');
11179
                                        }
11180
                                    } else {
11181
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11182
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11183
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11184
                                    }
11185
                                    break;
11186
                                default:
11187
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11188
                                    $my_dep->setAttribute('xml:base', '');
11189
                                    break;
11190
                            }
11191
                        }
11192
                        $my_dep->appendChild($my_dep_file);
11193
                        $resources->appendChild($my_dep);
11194
                        $dependency = $xmldoc->createElement('dependency');
11195
                        $dependency->setAttribute('identifierref', $res_id);
11196
                        $my_resource->appendChild($dependency);
11197
                        $i++;
11198
                    }
11199
                }
11200
                $resources->appendChild($my_resource);
11201
                $zip_files[] = $my_file_path;
11202
            } else {
11203
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11204
                switch ($item->type) {
11205
                    case TOOL_LINK:
11206
                        $my_item = $xmldoc->createElement('item');
11207
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11208
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11209
                        $my_item->setAttribute('isvisible', 'true');
11210
                        // Give a child element <title> to the <item> element.
11211
                        $my_title = $xmldoc->createElement(
11212
                            'title',
11213
                            htmlspecialchars(
11214
                                api_utf8_encode($item->get_title()),
11215
                                ENT_QUOTES,
11216
                                'UTF-8'
11217
                            )
11218
                        );
11219
                        $my_item->appendChild($my_title);
11220
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11221
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11222
                        $my_prereqs->setAttribute('type', 'aicc_script');
11223
                        $my_item->appendChild($my_prereqs);
11224
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11225
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11226
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11227
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11228
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11229
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11230
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11231
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11232
                        $my_item->appendChild($my_masteryscore);
11233
11234
                        // Attach this item to the organization element or its parent if there is one.
11235
                        if (!empty($item->parent) && $item->parent != 0) {
11236
                            $children = $organization->childNodes;
11237
                            for ($i = 0; $i < $children->length; $i++) {
11238
                                $item_temp = $children->item($i);
11239
                                if ($item_temp->nodeName == 'item') {
11240
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11241
                                        $item_temp->appendChild($my_item);
11242
                                    }
11243
                                }
11244
                            }
11245
                        } else {
11246
                            $organization->appendChild($my_item);
11247
                        }
11248
11249
                        $my_file_path = 'link_'.$item->get_id().'.html';
11250
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11251
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11252
                        $rs = Database::query($sql);
11253
                        if ($link = Database::fetch_array($rs)) {
11254
                            $url = $link['url'];
11255
                            $title = stripslashes($link['title']);
11256
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11257
                            $my_xml_file_path = $my_file_path;
11258
                            $my_sub_dir = dirname($my_file_path);
11259
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11260
                            $my_xml_sub_dir = $my_sub_dir;
11261
                            // Give a <resource> child to the <resources> element.
11262
                            $my_resource = $xmldoc->createElement('resource');
11263
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11264
                            $my_resource->setAttribute('type', 'webcontent');
11265
                            $my_resource->setAttribute('href', $my_xml_file_path);
11266
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11267
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11268
                            // xml:base is the base directory to find the files declared in this resource.
11269
                            $my_resource->setAttribute('xml:base', '');
11270
                            // give a <file> child to the <resource> element.
11271
                            $my_file = $xmldoc->createElement('file');
11272
                            $my_file->setAttribute('href', $my_xml_file_path);
11273
                            $my_resource->appendChild($my_file);
11274
                            $resources->appendChild($my_resource);
11275
                        }
11276
                        break;
11277
                    case TOOL_QUIZ:
11278
                        $exe_id = $item->path;
11279
                        // Should be using ref when everything will be cleaned up in this regard.
11280
                        $exe = new Exercise();
11281
                        $exe->read($exe_id);
11282
                        $my_item = $xmldoc->createElement('item');
11283
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11284
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11285
                        $my_item->setAttribute('isvisible', 'true');
11286
                        // Give a child element <title> to the <item> element.
11287
                        $my_title = $xmldoc->createElement(
11288
                            'title',
11289
                            htmlspecialchars(
11290
                                api_utf8_encode($item->get_title()),
11291
                                ENT_QUOTES,
11292
                                'UTF-8'
11293
                            )
11294
                        );
11295
                        $my_item->appendChild($my_title);
11296
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11297
                        $my_item->appendChild($my_max_score);
11298
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11299
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11300
                        $my_prereqs->setAttribute('type', 'aicc_script');
11301
                        $my_item->appendChild($my_prereqs);
11302
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11303
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11304
                        $my_item->appendChild($my_masteryscore);
11305
11306
                        // Attach this item to the organization element or hits parent if there is one.
11307
                        if (!empty($item->parent) && $item->parent != 0) {
11308
                            $children = $organization->childNodes;
11309
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11310
                            if ($possible_parent) {
11311
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11312
                                    $possible_parent->appendChild($my_item);
11313
                                }
11314
                            }
11315
                        } else {
11316
                            $organization->appendChild($my_item);
11317
                        }
11318
11319
                        // Get the path of the file(s) from the course directory root
11320
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11321
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11322
                        // Write the contents of the exported exercise into a (big) html file
11323
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11324
                        $scormExercise = new ScormExercise($exe, true);
11325
                        $contents = $scormExercise->export();
11326
11327
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11328
                        $res = file_put_contents($tmp_file_path, $contents);
11329
                        if ($res === false) {
11330
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11331
                        }
11332
                        $files_cleanup[] = $tmp_file_path;
11333
                        $my_xml_file_path = $my_file_path;
11334
                        $my_sub_dir = dirname($my_file_path);
11335
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11336
                        $my_xml_sub_dir = $my_sub_dir;
11337
                        // Give a <resource> child to the <resources> element.
11338
                        $my_resource = $xmldoc->createElement('resource');
11339
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11340
                        $my_resource->setAttribute('type', 'webcontent');
11341
                        $my_resource->setAttribute('href', $my_xml_file_path);
11342
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11343
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11344
                        // xml:base is the base directory to find the files declared in this resource.
11345
                        $my_resource->setAttribute('xml:base', '');
11346
                        // Give a <file> child to the <resource> element.
11347
                        $my_file = $xmldoc->createElement('file');
11348
                        $my_file->setAttribute('href', $my_xml_file_path);
11349
                        $my_resource->appendChild($my_file);
11350
11351
                        // Get included docs.
11352
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11353
11354
                        // Dependency to other files - not yet supported.
11355
                        $i = 1;
11356
                        foreach ($inc_docs as $doc_info) {
11357
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11358
                                continue;
11359
                            }
11360
                            $my_dep = $xmldoc->createElement('resource');
11361
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11362
                            $my_dep->setAttribute('identifier', $res_id);
11363
                            $my_dep->setAttribute('type', 'webcontent');
11364
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11365
                            $my_dep_file = $xmldoc->createElement('file');
11366
                            // Check type of URL.
11367
                            if ($doc_info[1] == 'remote') {
11368
                                // Remote file. Save url as is.
11369
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11370
                                $my_dep->setAttribute('xml:base', '');
11371
                            } elseif ($doc_info[1] == 'local') {
11372
                                switch ($doc_info[2]) {
11373
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11374
                                        // Save file but as local file (retrieve from URL).
11375
                                        $abs_path = api_get_path(SYS_PATH).
11376
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11377
                                        $current_dir = dirname($abs_path);
11378
                                        $current_dir = str_replace('\\', '/', $current_dir);
11379
                                        $file_path = realpath($abs_path);
11380
                                        $file_path = str_replace('\\', '/', $file_path);
11381
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11382
                                        $my_dep->setAttribute('xml:base', '');
11383
                                        if (strstr($file_path, $main_path) !== false) {
11384
                                            // The calculated real path is really inside the chamilo root path.
11385
                                            // Reduce file path to what's under the DocumentRoot.
11386
                                            $file_path = substr($file_path, strlen($root_path));
11387
                                            $zip_files_abs[] = $file_path;
11388
                                            $link_updates[$my_file_path][] = [
11389
                                                'orig' => $doc_info[0],
11390
                                                'dest' => 'document/'.$file_path,
11391
                                            ];
11392
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11393
                                            $my_dep->setAttribute('xml:base', '');
11394
                                        } elseif (empty($file_path)) {
11395
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11396
                                            $file_path = str_replace('//', '/', $file_path);
11397
                                            if (file_exists($file_path)) {
11398
                                                $file_path = substr($file_path, strlen($current_dir));
11399
                                                // We get the relative path.
11400
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11401
                                                $link_updates[$my_file_path][] = [
11402
                                                    'orig' => $doc_info[0],
11403
                                                    'dest' => 'document/'.$file_path,
11404
                                                ];
11405
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11406
                                                $my_dep->setAttribute('xml:base', '');
11407
                                            }
11408
                                        }
11409
                                        break;
11410
                                    case 'abs':
11411
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11412
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11413
                                        $current_dir = str_replace('\\', '/', $current_dir);
11414
                                        $file_path = realpath($doc_info[0]);
11415
                                        $file_path = str_replace('\\', '/', $file_path);
11416
                                        $my_dep_file->setAttribute('href', $file_path);
11417
                                        $my_dep->setAttribute('xml:base', '');
11418
11419
                                        if (strstr($file_path, $main_path) !== false) {
11420
                                            // The calculated real path is really inside the chamilo root path.
11421
                                            // Reduce file path to what's under the DocumentRoot.
11422
                                            $file_path = substr($file_path, strlen($root_path));
11423
                                            $zip_files_abs[] = $file_path;
11424
                                            $link_updates[$my_file_path][] = [
11425
                                                'orig' => $doc_info[0],
11426
                                                'dest' => $file_path,
11427
                                            ];
11428
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11429
                                            $my_dep->setAttribute('xml:base', '');
11430
                                        } elseif (empty($file_path)) {
11431
                                            $docSysPartPath = str_replace(
11432
                                                api_get_path(REL_COURSE_PATH),
11433
                                                '',
11434
                                                $doc_info[0]
11435
                                            );
11436
11437
                                            $docSysPartPathNoCourseCode = str_replace(
11438
                                                $_course['directory'].'/',
11439
                                                '',
11440
                                                $docSysPartPath
11441
                                            );
11442
11443
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11444
                                            if (file_exists($docSysPath)) {
11445
                                                $file_path = $docSysPartPathNoCourseCode;
11446
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11447
                                                $link_updates[$my_file_path][] = [
11448
                                                    'orig' => $doc_info[0],
11449
                                                    'dest' => $file_path,
11450
                                                ];
11451
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11452
                                                $my_dep->setAttribute('xml:base', '');
11453
                                            }
11454
                                        }
11455
                                        break;
11456
                                    case 'rel':
11457
                                        // Path relative to the current document. Save xml:base as current document's
11458
                                        // directory and save file in zip as subdir.file_path
11459
                                        if (substr($doc_info[0], 0, 2) === '..') {
11460
                                            // Relative path going up.
11461
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11462
                                            $current_dir = str_replace('\\', '/', $current_dir);
11463
                                            $file_path = realpath($current_dir.$doc_info[0]);
11464
                                            $file_path = str_replace('\\', '/', $file_path);
11465
                                            if (strstr($file_path, $main_path) !== false) {
11466
                                                // The calculated real path is really inside Chamilo's root path.
11467
                                                // Reduce file path to what's under the DocumentRoot.
11468
11469
                                                $file_path = substr($file_path, strlen($root_path));
11470
                                                $file_path_dest = $file_path;
11471
11472
                                                // File path is courses/CHAMILO/document/....
11473
                                                $info_file_path = explode('/', $file_path);
11474
                                                if ($info_file_path[0] == 'courses') {
11475
                                                    // Add character "/" in file path.
11476
                                                    $file_path_dest = 'document/'.$file_path;
11477
                                                }
11478
                                                $zip_files_abs[] = $file_path;
11479
11480
                                                $link_updates[$my_file_path][] = [
11481
                                                    'orig' => $doc_info[0],
11482
                                                    'dest' => $file_path_dest,
11483
                                                ];
11484
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11485
                                                $my_dep->setAttribute('xml:base', '');
11486
                                            }
11487
                                        } else {
11488
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11489
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11490
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11491
                                        }
11492
                                        break;
11493
                                    default:
11494
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11495
                                        $my_dep->setAttribute('xml:base', '');
11496
                                        break;
11497
                                }
11498
                            }
11499
                            $my_dep->appendChild($my_dep_file);
11500
                            $resources->appendChild($my_dep);
11501
                            $dependency = $xmldoc->createElement('dependency');
11502
                            $dependency->setAttribute('identifierref', $res_id);
11503
                            $my_resource->appendChild($dependency);
11504
                            $i++;
11505
                        }
11506
                        $resources->appendChild($my_resource);
11507
                        $zip_files[] = $my_file_path;
11508
                        break;
11509
                    default:
11510
                        // Get the path of the file(s) from the course directory root
11511
                        $my_file_path = 'non_exportable.html';
11512
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11513
                        $my_xml_file_path = $my_file_path;
11514
                        $my_sub_dir = dirname($my_file_path);
11515
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11516
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11517
                        $my_xml_sub_dir = $my_sub_dir;
11518
                        // Give a <resource> child to the <resources> element.
11519
                        $my_resource = $xmldoc->createElement('resource');
11520
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11521
                        $my_resource->setAttribute('type', 'webcontent');
11522
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11523
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11524
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11525
                        // xml:base is the base directory to find the files declared in this resource.
11526
                        $my_resource->setAttribute('xml:base', '');
11527
                        // Give a <file> child to the <resource> element.
11528
                        $my_file = $xmldoc->createElement('file');
11529
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11530
                        $my_resource->appendChild($my_file);
11531
                        $resources->appendChild($my_resource);
11532
                        break;
11533
                }
11534
            }
11535
        }
11536
        $organizations->appendChild($organization);
11537
        $root->appendChild($organizations);
11538
        $root->appendChild($resources);
11539
        $xmldoc->appendChild($root);
11540
11541
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11542
11543
        // then add the file to the zip, then destroy the file (this is done automatically).
11544
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11545
        foreach ($zip_files as $file_path) {
11546
            if (empty($file_path)) {
11547
                continue;
11548
            }
11549
11550
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11551
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11552
11553
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11554
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11555
            }
11556
11557
            $this->create_path($dest_file);
11558
            @copy($filePath, $dest_file);
11559
11560
            // Check if the file needs a link update.
11561
            if (in_array($file_path, array_keys($link_updates))) {
11562
                $string = file_get_contents($dest_file);
11563
                unlink($dest_file);
11564
                foreach ($link_updates[$file_path] as $old_new) {
11565
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11566
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11567
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11568
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11569
                    if (substr($old_new['dest'], -3) === 'flv' &&
11570
                        substr($old_new['dest'], 0, 5) === 'main/'
11571
                    ) {
11572
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11573
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11574
                        substr($old_new['dest'], 0, 6) === 'video/'
11575
                    ) {
11576
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11577
                    }
11578
11579
                    // Fix to avoid problems with default_course_document
11580
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11581
                        $newDestination = $old_new['dest'];
11582
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11583
                            $newDestination = $old_new['replace'];
11584
                        }
11585
                    } else {
11586
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11587
                    }
11588
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11589
11590
                    // Add files inside the HTMLs
11591
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11592
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11593
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11594
                        copy(
11595
                            $sys_course_path.$new_path,
11596
                            $destinationFile
11597
                        );
11598
                    }
11599
                }
11600
                file_put_contents($dest_file, $string);
11601
            }
11602
11603
            if (file_exists($filePath) && $copyAll) {
11604
                $extension = $this->get_extension($filePath);
11605
                if (in_array($extension, ['html', 'html'])) {
11606
                    $containerOrigin = dirname($filePath);
11607
                    $containerDestination = dirname($dest_file);
11608
11609
                    $finder = new Finder();
11610
                    $finder->files()->in($containerOrigin)
11611
                        ->notName('*_DELETED_*')
11612
                        ->exclude('share_folder')
11613
                        ->exclude('chat_files')
11614
                        ->exclude('certificates')
11615
                    ;
11616
11617
                    if (is_dir($containerOrigin) &&
11618
                        is_dir($containerDestination)
11619
                    ) {
11620
                        $fs = new Filesystem();
11621
                        $fs->mirror(
11622
                            $containerOrigin,
11623
                            $containerDestination,
11624
                            $finder
11625
                        );
11626
                    }
11627
                }
11628
            }
11629
        }
11630
11631
        foreach ($zip_files_abs as $file_path) {
11632
            if (empty($file_path)) {
11633
                continue;
11634
            }
11635
11636
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11637
                continue;
11638
            }
11639
11640
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11641
            if (strstr($file_path, 'upload/users') !== false) {
11642
                $pos = strpos($file_path, 'my_files/');
11643
                if ($pos !== false) {
11644
                    $onlyDirectory = str_replace(
11645
                        'upload/users/',
11646
                        '',
11647
                        substr($file_path, $pos, strlen($file_path))
11648
                    );
11649
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11650
                }
11651
            }
11652
11653
            if (strstr($file_path, 'default_course_document/') !== false) {
11654
                $replace = str_replace('/main', '', $file_path);
11655
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11656
            }
11657
11658
            if (empty($dest_file)) {
11659
                continue;
11660
            }
11661
11662
            $this->create_path($dest_file);
11663
            copy($main_path.$file_path, $dest_file);
11664
            // Check if the file needs a link update.
11665
            if (in_array($file_path, array_keys($link_updates))) {
11666
                $string = file_get_contents($dest_file);
11667
                unlink($dest_file);
11668
                foreach ($link_updates[$file_path] as $old_new) {
11669
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11670
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11671
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11672
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11673
                    if (substr($old_new['dest'], -3) == 'flv' &&
11674
                        substr($old_new['dest'], 0, 5) == 'main/'
11675
                    ) {
11676
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11677
                    }
11678
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11679
                }
11680
                file_put_contents($dest_file, $string);
11681
            }
11682
        }
11683
11684
        if (is_array($links_to_create)) {
11685
            foreach ($links_to_create as $file => $link) {
11686
                $content = '<!DOCTYPE html><head>
11687
                            <meta charset="'.api_get_language_isocode().'" />
11688
                            <title>'.$link['title'].'</title>
11689
                            </head>
11690
                            <body dir="'.api_get_text_direction().'">
11691
                            <div style="text-align:center">
11692
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11693
                            </body>
11694
                            </html>';
11695
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11696
            }
11697
        }
11698
11699
        // Add non exportable message explanation.
11700
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11701
        $file_content = '<!DOCTYPE html><head>
11702
                        <meta charset="'.api_get_language_isocode().'" />
11703
                        <title>'.$lang_not_exportable.'</title>
11704
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11705
                        </head>
11706
                        <body dir="'.api_get_text_direction().'">';
11707
        $file_content .=
11708
            <<<EOD
11709
                    <style>
11710
            .error-message {
11711
                font-family: arial, verdana, helvetica, sans-serif;
11712
                border-width: 1px;
11713
                border-style: solid;
11714
                left: 50%;
11715
                margin: 10px auto;
11716
                min-height: 30px;
11717
                padding: 5px;
11718
                right: 50%;
11719
                width: 500px;
11720
                background-color: #FFD1D1;
11721
                border-color: #FF0000;
11722
                color: #000;
11723
            }
11724
        </style>
11725
    <body>
11726
        <div class="error-message">
11727
            $lang_not_exportable
11728
        </div>
11729
    </body>
11730
</html>
11731
EOD;
11732
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11733
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11734
        }
11735
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11736
11737
        // Add the extra files that go along with a SCORM package.
11738
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11739
11740
        $fs = new Filesystem();
11741
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11742
11743
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11744
        $manifest = @$xmldoc->saveXML();
11745
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11746
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11747
        $zip_folder->add(
11748
            $archivePath.'/'.$temp_dir_short,
11749
            PCLZIP_OPT_REMOVE_PATH,
11750
            $archivePath.'/'.$temp_dir_short.'/'
11751
        );
11752
11753
        // Clean possible temporary files.
11754
        foreach ($files_cleanup as $file) {
11755
            $res = unlink($file);
11756
            if ($res === false) {
11757
                error_log(
11758
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11759
                    0
11760
                );
11761
            }
11762
        }
11763
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11764
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11765
    }
11766
11767
    /**
11768
     * @param int $lp_id
11769
     *
11770
     * @return bool
11771
     */
11772
    public function scorm_export_to_pdf($lp_id)
11773
    {
11774
        $lp_id = (int) $lp_id;
11775
        $files_to_export = [];
11776
11777
        $sessionId = api_get_session_id();
11778
        $course_data = api_get_course_info($this->cc);
11779
11780
        if (!empty($course_data)) {
11781
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11782
            $list = self::get_flat_ordered_items_list($lp_id);
11783
            if (!empty($list)) {
11784
                foreach ($list as $item_id) {
11785
                    $item = $this->items[$item_id];
11786
                    switch ($item->type) {
11787
                        case 'document':
11788
                            // Getting documents from a LP with chamilo documents
11789
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11790
                            // Try loading document from the base course.
11791
                            if (empty($file_data) && !empty($sessionId)) {
11792
                                $file_data = DocumentManager::get_document_data_by_id(
11793
                                    $item->path,
11794
                                    $this->cc,
11795
                                    false,
11796
                                    0
11797
                                );
11798
                            }
11799
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11800
                            if (file_exists($file_path)) {
11801
                                $files_to_export[] = [
11802
                                    'title' => $item->get_title(),
11803
                                    'path' => $file_path,
11804
                                ];
11805
                            }
11806
                            break;
11807
                        case 'asset': //commes from a scorm package generated by chamilo
11808
                        case 'sco':
11809
                            $file_path = $scorm_path.'/'.$item->path;
11810
                            if (file_exists($file_path)) {
11811
                                $files_to_export[] = [
11812
                                    'title' => $item->get_title(),
11813
                                    'path' => $file_path,
11814
                                ];
11815
                            }
11816
                            break;
11817
                        case 'dir':
11818
                            $files_to_export[] = [
11819
                                'title' => $item->get_title(),
11820
                                'path' => null,
11821
                            ];
11822
                            break;
11823
                    }
11824
                }
11825
            }
11826
11827
            $pdf = new PDF();
11828
            $result = $pdf->html_to_pdf(
11829
                $files_to_export,
11830
                $this->name,
11831
                $this->cc,
11832
                true,
11833
                true,
11834
                true,
11835
                $this->get_name()
11836
            );
11837
11838
            return $result;
11839
        }
11840
11841
        return false;
11842
    }
11843
11844
    /**
11845
     * Temp function to be moved in main_api or the best place around for this.
11846
     * Creates a file path if it doesn't exist.
11847
     *
11848
     * @param string $path
11849
     */
11850
    public function create_path($path)
11851
    {
11852
        $path_bits = explode('/', dirname($path));
11853
11854
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11855
        $path_built = IS_WINDOWS_OS ? '' : '/';
11856
        foreach ($path_bits as $bit) {
11857
            if (!empty($bit)) {
11858
                $new_path = $path_built.$bit;
11859
                if (is_dir($new_path)) {
11860
                    $path_built = $new_path.'/';
11861
                } else {
11862
                    mkdir($new_path, api_get_permissions_for_new_directories());
11863
                    $path_built = $new_path.'/';
11864
                }
11865
            }
11866
        }
11867
    }
11868
11869
    /**
11870
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11871
     *
11872
     * @return bool The results of the unlink function, or false if there was no image to start with
11873
     */
11874
    public function delete_lp_image()
11875
    {
11876
        $img = $this->get_preview_image();
11877
        if ($img != '') {
11878
            $del_file = $this->get_preview_image_path(null, 'sys');
11879
            if (isset($del_file) && file_exists($del_file)) {
11880
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11881
                if (file_exists($del_file_2)) {
11882
                    unlink($del_file_2);
11883
                }
11884
                $this->set_preview_image('');
11885
11886
                return @unlink($del_file);
11887
            }
11888
        }
11889
11890
        return false;
11891
    }
11892
11893
    /**
11894
     * Uploads an author image to the upload/learning_path/images directory.
11895
     *
11896
     * @param array    The image array, coming from the $_FILES superglobal
11897
     *
11898
     * @return bool True on success, false on error
11899
     */
11900
    public function upload_image($image_array)
11901
    {
11902
        if (!empty($image_array['name'])) {
11903
            $upload_ok = process_uploaded_file($image_array);
11904
            $has_attachment = true;
11905
        }
11906
11907
        if ($upload_ok && $has_attachment) {
11908
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11909
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11910
            $updir = $sys_course_path.$courseDir;
11911
            // Try to add an extension to the file if it hasn't one.
11912
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11913
11914
            if (filter_extension($new_file_name)) {
11915
                $file_extension = explode('.', $image_array['name']);
11916
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11917
                $filename = uniqid('');
11918
                $new_file_name = $filename.'.'.$file_extension;
11919
                $new_path = $updir.'/'.$new_file_name;
11920
11921
                // Resize the image.
11922
                $temp = new Image($image_array['tmp_name']);
11923
                $temp->resize(104);
11924
                $result = $temp->send_image($new_path);
11925
11926
                // Storing the image filename.
11927
                if ($result) {
11928
                    $this->set_preview_image($new_file_name);
11929
11930
                    //Resize to 64px to use on course homepage
11931
                    $temp->resize(64);
11932
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11933
11934
                    return true;
11935
                }
11936
            }
11937
        }
11938
11939
        return false;
11940
    }
11941
11942
    /**
11943
     * @param int    $lp_id
11944
     * @param string $status
11945
     */
11946
    public function set_autolaunch($lp_id, $status)
11947
    {
11948
        $course_id = api_get_course_int_id();
11949
        $lp_id = (int) $lp_id;
11950
        $status = (int) $status;
11951
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11952
11953
        // Setting everything to autolaunch = 0
11954
        $attributes['autolaunch'] = 0;
11955
        $where = [
11956
            'session_id = ? AND c_id = ? ' => [
11957
                api_get_session_id(),
11958
                $course_id,
11959
            ],
11960
        ];
11961
        Database::update($lp_table, $attributes, $where);
11962
        if ($status == 1) {
11963
            //Setting my lp_id to autolaunch = 1
11964
            $attributes['autolaunch'] = 1;
11965
            $where = [
11966
                'iid = ? AND session_id = ? AND c_id = ?' => [
11967
                    $lp_id,
11968
                    api_get_session_id(),
11969
                    $course_id,
11970
                ],
11971
            ];
11972
            Database::update($lp_table, $attributes, $where);
11973
        }
11974
    }
11975
11976
    /**
11977
     * Gets previous_item_id for the next element of the lp_item table.
11978
     *
11979
     * @author Isaac flores paz
11980
     *
11981
     * @return int Previous item ID
11982
     */
11983
    public function select_previous_item_id()
11984
    {
11985
        $course_id = api_get_course_int_id();
11986
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11987
11988
        // Get the max order of the items
11989
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11990
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11991
        $rs_max_order = Database::query($sql);
11992
        $row_max_order = Database::fetch_object($rs_max_order);
11993
        $max_order = $row_max_order->display_order;
11994
        // Get the previous item ID
11995
        $sql = "SELECT iid as previous FROM $table_lp_item
11996
                WHERE
11997
                    c_id = $course_id AND
11998
                    lp_id = ".$this->lp_id." AND
11999
                    display_order = '$max_order' ";
12000
        $rs_max = Database::query($sql);
12001
        $row_max = Database::fetch_object($rs_max);
12002
12003
        // Return the previous item ID
12004
        return $row_max->previous;
12005
    }
12006
12007
    /**
12008
     * Copies an LP.
12009
     */
12010
    public function copy()
12011
    {
12012
        // Course builder
12013
        $cb = new CourseBuilder();
12014
12015
        //Setting tools that will be copied
12016
        $cb->set_tools_to_build(['learnpaths']);
12017
12018
        //Setting elements that will be copied
12019
        $cb->set_tools_specific_id_list(
12020
            ['learnpaths' => [$this->lp_id]]
12021
        );
12022
12023
        $course = $cb->build();
12024
12025
        //Course restorer
12026
        $course_restorer = new CourseRestorer($course);
12027
        $course_restorer->set_add_text_in_items(true);
12028
        $course_restorer->set_tool_copy_settings(
12029
            ['learnpaths' => ['reset_dates' => true]]
12030
        );
12031
        $course_restorer->restore(
12032
            api_get_course_id(),
12033
            api_get_session_id(),
12034
            false,
12035
            false
12036
        );
12037
    }
12038
12039
    /**
12040
     * Verify document size.
12041
     *
12042
     * @param string $s
12043
     *
12044
     * @return bool
12045
     */
12046
    public static function verify_document_size($s)
12047
    {
12048
        $post_max = ini_get('post_max_size');
12049
        if (substr($post_max, -1, 1) == 'M') {
12050
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12051
        } elseif (substr($post_max, -1, 1) == 'G') {
12052
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12053
        }
12054
        $upl_max = ini_get('upload_max_filesize');
12055
        if (substr($upl_max, -1, 1) == 'M') {
12056
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12057
        } elseif (substr($upl_max, -1, 1) == 'G') {
12058
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12059
        }
12060
        $documents_total_space = DocumentManager::documents_total_space();
12061
        $course_max_space = DocumentManager::get_course_quota();
12062
        $total_size = filesize($s) + $documents_total_space;
12063
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12064
            return true;
12065
        }
12066
12067
        return false;
12068
    }
12069
12070
    /**
12071
     * Clear LP prerequisites.
12072
     */
12073
    public function clear_prerequisites()
12074
    {
12075
        $course_id = $this->get_course_int_id();
12076
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12077
        $lp_id = $this->get_id();
12078
        // Cleaning prerequisites
12079
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12080
                WHERE c_id = $course_id AND lp_id = $lp_id";
12081
        Database::query($sql);
12082
12083
        // Cleaning mastery score for exercises
12084
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12085
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12086
        Database::query($sql);
12087
    }
12088
12089
    public function set_previous_step_as_prerequisite_for_all_items()
12090
    {
12091
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12092
        $course_id = $this->get_course_int_id();
12093
        $lp_id = $this->get_id();
12094
12095
        if (!empty($this->items)) {
12096
            $previous_item_id = null;
12097
            $previous_item_max = 0;
12098
            $previous_item_type = null;
12099
            $last_item_not_dir = null;
12100
            $last_item_not_dir_type = null;
12101
            $last_item_not_dir_max = null;
12102
12103
            foreach ($this->ordered_items as $itemId) {
12104
                $item = $this->getItem($itemId);
12105
                // if there was a previous item... (otherwise jump to set it)
12106
                if (!empty($previous_item_id)) {
12107
                    $current_item_id = $item->get_id(); //save current id
12108
                    if ($item->get_type() != 'dir') {
12109
                        // Current item is not a folder, so it qualifies to get a prerequisites
12110
                        if ($last_item_not_dir_type == 'quiz') {
12111
                            // if previous is quiz, mark its max score as default score to be achieved
12112
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12113
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12114
                            Database::query($sql);
12115
                        }
12116
                        // now simply update the prerequisite to set it to the last non-chapter item
12117
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12118
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12119
                        Database::query($sql);
12120
                        // record item as 'non-chapter' reference
12121
                        $last_item_not_dir = $item->get_id();
12122
                        $last_item_not_dir_type = $item->get_type();
12123
                        $last_item_not_dir_max = $item->get_max();
12124
                    }
12125
                } else {
12126
                    if ($item->get_type() != 'dir') {
12127
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12128
                        $last_item_not_dir = $item->get_id();
12129
                        $last_item_not_dir_type = $item->get_type();
12130
                        $last_item_not_dir_max = $item->get_max();
12131
                    }
12132
                }
12133
                // Saving the item as "previous item" for the next loop
12134
                $previous_item_id = $item->get_id();
12135
                $previous_item_max = $item->get_max();
12136
                $previous_item_type = $item->get_type();
12137
            }
12138
        }
12139
    }
12140
12141
    /**
12142
     * @param array $params
12143
     *
12144
     * @throws \Doctrine\ORM\OptimisticLockException
12145
     *
12146
     * @return int
12147
     */
12148
    public static function createCategory($params)
12149
    {
12150
        $em = Database::getManager();
12151
        $item = new CLpCategory();
12152
        $item->setName($params['name']);
12153
        $item->setCId($params['c_id']);
12154
        $em->persist($item);
12155
        $em->flush();
12156
12157
        $id = $item->getId();
12158
12159
        $sessionId = api_get_session_id();
12160
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12161
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12162
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12163
            Database::query($sql);
12164
        }
12165
12166
        api_item_property_update(
12167
            api_get_course_info(),
12168
            TOOL_LEARNPATH_CATEGORY,
12169
            $id,
12170
            'visible',
12171
            api_get_user_id()
12172
        );
12173
12174
        return $item->getId();
12175
    }
12176
12177
    /**
12178
     * @param array $params
12179
     */
12180
    public static function updateCategory($params)
12181
    {
12182
        $em = Database::getManager();
12183
        $item = self::getCategory($params['id']);
12184
12185
        if ($item) {
12186
            $item->setName($params['name']);
12187
            $em->merge($item);
12188
            $em->flush();
12189
        }
12190
    }
12191
12192
    /**
12193
     * @param int $id
12194
     */
12195
    public static function moveUpCategory($id)
12196
    {
12197
        $item = self::getCategory($id);
12198
        if ($item) {
12199
            $em = Database::getManager();
12200
            $position = $item->getPosition() - 1;
12201
            $item->setPosition($position);
12202
            $em->persist($item);
12203
            $em->flush();
12204
        }
12205
    }
12206
12207
    /**
12208
     * @param int $id
12209
     *
12210
     * @throws \Doctrine\ORM\ORMException
12211
     * @throws \Doctrine\ORM\OptimisticLockException
12212
     * @throws \Doctrine\ORM\TransactionRequiredException
12213
     */
12214
    public static function moveDownCategory($id)
12215
    {
12216
        $item = self::getCategory($id);
12217
        if ($item) {
12218
            $em = Database::getManager();
12219
            $position = $item->getPosition() + 1;
12220
            $item->setPosition($position);
12221
            $em->persist($item);
12222
            $em->flush();
12223
        }
12224
    }
12225
12226
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12227
    {
12228
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12229
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12230
        $courseId = (int) $courseId;
12231
        $sessionId = (int) $sessionId;
12232
12233
        $sql = "SELECT lp.id, lp.name
12234
                FROM $TABLE_LP lp
12235
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12236
                ON lp.id = ip.ref
12237
                WHERE lp.c_id = $courseId ";
12238
12239
        if (!empty($sessionId)) {
12240
            $sql .= "AND ip.session_id = $sessionId ";
12241
        }
12242
12243
        if ($onlyActiveLp) {
12244
            $sql .= "AND ip.tool = 'learnpath' ";
12245
            $sql .= "AND ip.visibility = 1 ";
12246
        }
12247
12248
        $sql .= "GROUP BY lp.id";
12249
12250
        $result = Database::query($sql);
12251
12252
        return Database::store_result($result, 'ASSOC');
12253
    }
12254
12255
    /**
12256
     * @param int $courseId
12257
     *
12258
     * @throws \Doctrine\ORM\Query\QueryException
12259
     *
12260
     * @return int|mixed
12261
     */
12262
    public static function getCountCategories($courseId)
12263
    {
12264
        if (empty($courseId)) {
12265
            return 0;
12266
        }
12267
        $em = Database::getManager();
12268
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12269
        $query->setParameter('id', $courseId);
12270
12271
        return $query->getSingleScalarResult();
12272
    }
12273
12274
    /**
12275
     * @param int $courseId
12276
     *
12277
     * @return CLpCategory[]
12278
     */
12279
    public static function getCategories($courseId)
12280
    {
12281
        $em = Database::getManager();
12282
12283
        // Using doctrine extensions
12284
        /** @var SortableRepository $repo */
12285
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12286
12287
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12288
    }
12289
12290
    public static function getCategorySessionId($id)
12291
    {
12292
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12293
            return 0;
12294
        }
12295
12296
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12297
        $id = (int) $id;
12298
12299
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12300
        $result = Database::query($sql);
12301
        $result = Database::fetch_array($result, 'ASSOC');
12302
12303
        if ($result) {
12304
            return (int) $result['session_id'];
12305
        }
12306
12307
        return 0;
12308
    }
12309
12310
    /**
12311
     * @param int $id
12312
     *
12313
     * @return CLpCategory
12314
     */
12315
    public static function getCategory($id)
12316
    {
12317
        $id = (int) $id;
12318
        $em = Database::getManager();
12319
12320
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12321
    }
12322
12323
    /**
12324
     * @param int $courseId
12325
     *
12326
     * @return array
12327
     */
12328
    public static function getCategoryByCourse($courseId)
12329
    {
12330
        $em = Database::getManager();
12331
12332
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12333
    }
12334
12335
    /**
12336
     * @param int $id
12337
     *
12338
     * @return bool
12339
     */
12340
    public static function deleteCategory($id)
12341
    {
12342
        $em = Database::getManager();
12343
        $id = (int) $id;
12344
        $item = self::getCategory($id);
12345
        if ($item) {
12346
            $courseId = $item->getCId();
12347
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12348
            $query->setParameter('id', $courseId);
12349
            $query->setParameter('catId', $item->getId());
12350
            $lps = $query->getResult();
12351
12352
            // Setting category = 0.
12353
            if ($lps) {
12354
                foreach ($lps as $lpItem) {
12355
                    $lpItem->setCategoryId(0);
12356
                }
12357
            }
12358
12359
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
12360
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
12361
                $sql = "DELETE FROM $table
12362
                        WHERE
12363
                            lp_category_id = $id AND
12364
                            c_id = $courseId ";
12365
                Database::query($sql);
12366
            }
12367
12368
            // Removing category.
12369
            $em->remove($item);
12370
            $em->flush();
12371
12372
            $courseInfo = api_get_course_info_by_id($courseId);
12373
            $sessionId = api_get_session_id();
12374
12375
            // Delete link tool
12376
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12377
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12378
            // Delete tools
12379
            $sql = "DELETE FROM $tbl_tool
12380
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12381
            Database::query($sql);
12382
12383
            return true;
12384
        }
12385
12386
        return false;
12387
    }
12388
12389
    /**
12390
     * @param int  $courseId
12391
     * @param bool $addSelectOption
12392
     *
12393
     * @return mixed
12394
     */
12395
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12396
    {
12397
        $items = self::getCategoryByCourse($courseId);
12398
        $cats = [];
12399
        if ($addSelectOption) {
12400
            $cats = [get_lang('SelectACategory')];
12401
        }
12402
12403
        if (!empty($items)) {
12404
            foreach ($items as $cat) {
12405
                $cats[$cat->getId()] = $cat->getName();
12406
            }
12407
        }
12408
12409
        return $cats;
12410
    }
12411
12412
    /**
12413
     * @param string $courseCode
12414
     * @param int    $lpId
12415
     * @param int    $user_id
12416
     *
12417
     * @return learnpath
12418
     */
12419
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12420
    {
12421
        $debug = 0;
12422
        $learnPath = null;
12423
        $lpObject = Session::read('lpobject');
12424
        if ($lpObject !== null) {
12425
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12426
            if ($debug) {
12427
                error_log('getLpFromSession: unserialize');
12428
                error_log('------getLpFromSession------');
12429
                error_log('------unserialize------');
12430
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12431
                error_log("api_get_sessionid: ".api_get_session_id());
12432
            }
12433
        }
12434
12435
        if (!is_object($learnPath)) {
12436
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12437
            if ($debug) {
12438
                error_log('------getLpFromSession------');
12439
                error_log('getLpFromSession: create new learnpath');
12440
                error_log("create new LP with $courseCode - $lpId - $user_id");
12441
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12442
                error_log("api_get_sessionid: ".api_get_session_id());
12443
            }
12444
        }
12445
12446
        return $learnPath;
12447
    }
12448
12449
    /**
12450
     * @param int $itemId
12451
     *
12452
     * @return learnpathItem|false
12453
     */
12454
    public function getItem($itemId)
12455
    {
12456
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12457
            return $this->items[$itemId];
12458
        }
12459
12460
        return false;
12461
    }
12462
12463
    /**
12464
     * @return int
12465
     */
12466
    public function getCurrentAttempt()
12467
    {
12468
        $attempt = $this->getItem($this->get_current_item_id());
12469
        if ($attempt) {
12470
            $attemptId = $attempt->get_attempt_id();
12471
12472
            return $attemptId;
12473
        }
12474
12475
        return 0;
12476
    }
12477
12478
    /**
12479
     * @return int
12480
     */
12481
    public function getCategoryId()
12482
    {
12483
        return (int) $this->categoryId;
12484
    }
12485
12486
    /**
12487
     * @param int $categoryId
12488
     *
12489
     * @return bool
12490
     */
12491
    public function setCategoryId($categoryId)
12492
    {
12493
        $this->categoryId = (int) $categoryId;
12494
        $table = Database::get_course_table(TABLE_LP_MAIN);
12495
        $lp_id = $this->get_id();
12496
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12497
                WHERE iid = $lp_id";
12498
        Database::query($sql);
12499
12500
        return true;
12501
    }
12502
12503
    /**
12504
     * Get whether this is a learning path with the possibility to subscribe
12505
     * users or not.
12506
     *
12507
     * @return int
12508
     */
12509
    public function getSubscribeUsers()
12510
    {
12511
        return $this->subscribeUsers;
12512
    }
12513
12514
    /**
12515
     * Set whether this is a learning path with the possibility to subscribe
12516
     * users or not.
12517
     *
12518
     * @param int $value (0 = false, 1 = true)
12519
     *
12520
     * @return bool
12521
     */
12522
    public function setSubscribeUsers($value)
12523
    {
12524
        $this->subscribeUsers = (int) $value;
12525
        $table = Database::get_course_table(TABLE_LP_MAIN);
12526
        $lp_id = $this->get_id();
12527
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12528
                WHERE iid = $lp_id";
12529
        Database::query($sql);
12530
12531
        return true;
12532
    }
12533
12534
    /**
12535
     * Calculate the count of stars for a user in this LP
12536
     * This calculation is based on the following rules:
12537
     * - the student gets one star when he gets to 50% of the learning path
12538
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12539
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12540
     * - the student gets the final star when the score for the *last* test is >= 80%.
12541
     *
12542
     * @param int $sessionId Optional. The session ID
12543
     *
12544
     * @return int The count of stars
12545
     */
12546
    public function getCalculateStars($sessionId = 0)
12547
    {
12548
        $stars = 0;
12549
        $progress = self::getProgress(
12550
            $this->lp_id,
12551
            $this->user_id,
12552
            $this->course_int_id,
12553
            $sessionId
12554
        );
12555
12556
        if ($progress >= 50) {
12557
            $stars++;
12558
        }
12559
12560
        // Calculate stars chapters evaluation
12561
        $exercisesItems = $this->getExercisesItems();
12562
12563
        if (!empty($exercisesItems)) {
12564
            $totalResult = 0;
12565
12566
            foreach ($exercisesItems as $exerciseItem) {
12567
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12568
                    $this->user_id,
12569
                    $exerciseItem->path,
12570
                    $this->course_int_id,
12571
                    $sessionId,
12572
                    $this->lp_id,
12573
                    $exerciseItem->db_id
12574
                );
12575
12576
                $exerciseResultInfo = end($exerciseResultInfo);
12577
12578
                if (!$exerciseResultInfo) {
12579
                    continue;
12580
                }
12581
12582
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12583
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12584
                } else {
12585
                    $exerciseResult = 0;
12586
                }
12587
                $totalResult += $exerciseResult;
12588
            }
12589
12590
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12591
12592
            if ($totalExerciseAverage >= 50) {
12593
                $stars++;
12594
            }
12595
12596
            if ($totalExerciseAverage >= 80) {
12597
                $stars++;
12598
            }
12599
        }
12600
12601
        // Calculate star for final evaluation
12602
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12603
12604
        if (!empty($finalEvaluationItem)) {
12605
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12606
                $this->user_id,
12607
                $finalEvaluationItem->path,
12608
                $this->course_int_id,
12609
                $sessionId,
12610
                $this->lp_id,
12611
                $finalEvaluationItem->db_id
12612
            );
12613
12614
            $evaluationResultInfo = end($evaluationResultInfo);
12615
12616
            if ($evaluationResultInfo) {
12617
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12618
12619
                if ($evaluationResult >= 80) {
12620
                    $stars++;
12621
                }
12622
            }
12623
        }
12624
12625
        return $stars;
12626
    }
12627
12628
    /**
12629
     * Get the items of exercise type.
12630
     *
12631
     * @return array The items. Otherwise return false
12632
     */
12633
    public function getExercisesItems()
12634
    {
12635
        $exercises = [];
12636
        foreach ($this->items as $item) {
12637
            if ($item->type != 'quiz') {
12638
                continue;
12639
            }
12640
            $exercises[] = $item;
12641
        }
12642
12643
        array_pop($exercises);
12644
12645
        return $exercises;
12646
    }
12647
12648
    /**
12649
     * Get the item of exercise type (evaluation type).
12650
     *
12651
     * @return array The final evaluation. Otherwise return false
12652
     */
12653
    public function getFinalEvaluationItem()
12654
    {
12655
        $exercises = [];
12656
        foreach ($this->items as $item) {
12657
            if ($item->type != 'quiz') {
12658
                continue;
12659
            }
12660
12661
            $exercises[] = $item;
12662
        }
12663
12664
        return array_pop($exercises);
12665
    }
12666
12667
    /**
12668
     * Calculate the total points achieved for the current user in this learning path.
12669
     *
12670
     * @param int $sessionId Optional. The session Id
12671
     *
12672
     * @return int
12673
     */
12674
    public function getCalculateScore($sessionId = 0)
12675
    {
12676
        // Calculate stars chapters evaluation
12677
        $exercisesItems = $this->getExercisesItems();
12678
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12679
        $totalExercisesResult = 0;
12680
        $totalEvaluationResult = 0;
12681
12682
        if ($exercisesItems !== false) {
12683
            foreach ($exercisesItems as $exerciseItem) {
12684
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12685
                    $this->user_id,
12686
                    $exerciseItem->path,
12687
                    $this->course_int_id,
12688
                    $sessionId,
12689
                    $this->lp_id,
12690
                    $exerciseItem->db_id
12691
                );
12692
12693
                $exerciseResultInfo = end($exerciseResultInfo);
12694
12695
                if (!$exerciseResultInfo) {
12696
                    continue;
12697
                }
12698
12699
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12700
            }
12701
        }
12702
12703
        if (!empty($finalEvaluationItem)) {
12704
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12705
                $this->user_id,
12706
                $finalEvaluationItem->path,
12707
                $this->course_int_id,
12708
                $sessionId,
12709
                $this->lp_id,
12710
                $finalEvaluationItem->db_id
12711
            );
12712
12713
            $evaluationResultInfo = end($evaluationResultInfo);
12714
12715
            if ($evaluationResultInfo) {
12716
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12717
            }
12718
        }
12719
12720
        return $totalExercisesResult + $totalEvaluationResult;
12721
    }
12722
12723
    /**
12724
     * Check if URL is not allowed to be show in a iframe.
12725
     *
12726
     * @param string $src
12727
     *
12728
     * @return string
12729
     */
12730
    public function fixBlockedLinks($src)
12731
    {
12732
        $urlInfo = parse_url($src);
12733
12734
        $platformProtocol = 'https';
12735
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12736
            $platformProtocol = 'http';
12737
        }
12738
12739
        $protocolFixApplied = false;
12740
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12741
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12742
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12743
12744
        if ($platformProtocol != $scheme) {
12745
            Session::write('x_frame_source', $src);
12746
            $src = 'blank.php?error=x_frames_options';
12747
            $protocolFixApplied = true;
12748
        }
12749
12750
        if ($protocolFixApplied == false) {
12751
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12752
                // Check X-Frame-Options
12753
                $ch = curl_init();
12754
                $options = [
12755
                    CURLOPT_URL => $src,
12756
                    CURLOPT_RETURNTRANSFER => true,
12757
                    CURLOPT_HEADER => true,
12758
                    CURLOPT_FOLLOWLOCATION => true,
12759
                    CURLOPT_ENCODING => "",
12760
                    CURLOPT_AUTOREFERER => true,
12761
                    CURLOPT_CONNECTTIMEOUT => 120,
12762
                    CURLOPT_TIMEOUT => 120,
12763
                    CURLOPT_MAXREDIRS => 10,
12764
                ];
12765
12766
                $proxySettings = api_get_configuration_value('proxy_settings');
12767
                if (!empty($proxySettings) &&
12768
                    isset($proxySettings['curl_setopt_array'])
12769
                ) {
12770
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12771
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12772
                }
12773
12774
                curl_setopt_array($ch, $options);
12775
                $response = curl_exec($ch);
12776
                $httpCode = curl_getinfo($ch);
12777
                $headers = substr($response, 0, $httpCode['header_size']);
12778
12779
                $error = false;
12780
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12781
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12782
                ) {
12783
                    $error = true;
12784
                }
12785
12786
                if ($error) {
12787
                    Session::write('x_frame_source', $src);
12788
                    $src = 'blank.php?error=x_frames_options';
12789
                }
12790
            }
12791
        }
12792
12793
        return $src;
12794
    }
12795
12796
    /**
12797
     * Check if this LP has a created forum in the basis course.
12798
     *
12799
     * @return bool
12800
     */
12801
    public function lpHasForum()
12802
    {
12803
        $forumTable = Database::get_course_table(TABLE_FORUM);
12804
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12805
12806
        $fakeFrom = "
12807
            $forumTable f
12808
            INNER JOIN $itemProperty ip
12809
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12810
        ";
12811
12812
        $resultData = Database::select(
12813
            'COUNT(f.iid) AS qty',
12814
            $fakeFrom,
12815
            [
12816
                'where' => [
12817
                    'ip.visibility != ? AND ' => 2,
12818
                    'ip.tool = ? AND ' => TOOL_FORUM,
12819
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12820
                    'f.lp_id = ?' => intval($this->lp_id),
12821
                ],
12822
            ],
12823
            'first'
12824
        );
12825
12826
        return $resultData['qty'] > 0;
12827
    }
12828
12829
    /**
12830
     * Get the forum for this learning path.
12831
     *
12832
     * @param int $sessionId
12833
     *
12834
     * @return bool
12835
     */
12836
    public function getForum($sessionId = 0)
12837
    {
12838
        $forumTable = Database::get_course_table(TABLE_FORUM);
12839
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12840
12841
        $fakeFrom = "$forumTable f
12842
            INNER JOIN $itemProperty ip ";
12843
12844
        if ($this->lp_session_id == 0) {
12845
            $fakeFrom .= "
12846
                ON (
12847
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12848
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12849
                    )
12850
                )
12851
            ";
12852
        } else {
12853
            $fakeFrom .= "
12854
                ON (
12855
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12856
                )
12857
            ";
12858
        }
12859
12860
        $resultData = Database::select(
12861
            'f.*',
12862
            $fakeFrom,
12863
            [
12864
                'where' => [
12865
                    'ip.visibility != ? AND ' => 2,
12866
                    'ip.tool = ? AND ' => TOOL_FORUM,
12867
                    'f.session_id = ? AND ' => $sessionId,
12868
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12869
                    'f.lp_id = ?' => intval($this->lp_id),
12870
                ],
12871
            ],
12872
            'first'
12873
        );
12874
12875
        if (empty($resultData)) {
12876
            return false;
12877
        }
12878
12879
        return $resultData;
12880
    }
12881
12882
    /**
12883
     * Create a forum for this learning path.
12884
     *
12885
     * @param int $forumCategoryId
12886
     *
12887
     * @return int The forum ID if was created. Otherwise return false
12888
     */
12889
    public function createForum($forumCategoryId)
12890
    {
12891
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12892
12893
        $forumId = store_forum(
12894
            [
12895
                'lp_id' => $this->lp_id,
12896
                'forum_title' => $this->name,
12897
                'forum_comment' => null,
12898
                'forum_category' => (int) $forumCategoryId,
12899
                'students_can_edit_group' => ['students_can_edit' => 0],
12900
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12901
                'default_view_type_group' => ['default_view_type' => 'flat'],
12902
                'group_forum' => 0,
12903
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12904
            ],
12905
            [],
12906
            true
12907
        );
12908
12909
        return $forumId;
12910
    }
12911
12912
    /**
12913
     * Get the LP Final Item form.
12914
     *
12915
     * @throws Exception
12916
     * @throws HTML_QuickForm_Error
12917
     *
12918
     * @return string
12919
     */
12920
    public function getFinalItemForm()
12921
    {
12922
        $finalItem = $this->getFinalItem();
12923
        $title = '';
12924
12925
        if ($finalItem) {
12926
            $title = $finalItem->get_title();
12927
            $buttonText = get_lang('Save');
12928
            $content = $this->getSavedFinalItem();
12929
        } else {
12930
            $buttonText = get_lang('LPCreateDocument');
12931
            $content = $this->getFinalItemTemplate();
12932
        }
12933
12934
        $courseInfo = api_get_course_info();
12935
        $result = $this->generate_lp_folder($courseInfo);
12936
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12937
        $relative_prefix = '../../';
12938
12939
        $editorConfig = [
12940
            'ToolbarSet' => 'LearningPathDocuments',
12941
            'Width' => '100%',
12942
            'Height' => '500',
12943
            'FullPage' => true,
12944
            'CreateDocumentDir' => $relative_prefix,
12945
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12946
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12947
        ];
12948
12949
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12950
            'type' => 'document',
12951
            'lp_id' => $this->lp_id,
12952
        ]);
12953
12954
        $form = new FormValidator('final_item', 'POST', $url);
12955
        $form->addText('title', get_lang('Title'));
12956
        $form->addButtonSave($buttonText);
12957
        $form->addHtml(
12958
            Display::return_message(
12959
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12960
                'normal',
12961
                false
12962
            )
12963
        );
12964
12965
        $renderer = $form->defaultRenderer();
12966
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12967
12968
        $form->addHtmlEditor(
12969
            'content_lp_certificate',
12970
            null,
12971
            true,
12972
            false,
12973
            $editorConfig,
12974
            true
12975
        );
12976
        $form->addHidden('action', 'add_final_item');
12977
        $form->addHidden('path', Session::read('pathItem'));
12978
        $form->addHidden('previous', $this->get_last());
12979
        $form->setDefaults(
12980
            ['title' => $title, 'content_lp_certificate' => $content]
12981
        );
12982
12983
        if ($form->validate()) {
12984
            $values = $form->exportValues();
12985
            $lastItemId = $this->getLastInFirstLevel();
12986
12987
            if (!$finalItem) {
12988
                $documentId = $this->create_document(
12989
                    $this->course_info,
12990
                    $values['content_lp_certificate'],
12991
                    $values['title']
12992
                );
12993
                $this->add_item(
12994
                    0,
12995
                    $lastItemId,
12996
                    'final_item',
12997
                    $documentId,
12998
                    $values['title'],
12999
                    ''
13000
                );
13001
13002
                Display::addFlash(
13003
                    Display::return_message(get_lang('Added'))
13004
                );
13005
            } else {
13006
                $this->edit_document($this->course_info);
13007
            }
13008
        }
13009
13010
        return $form->returnForm();
13011
    }
13012
13013
    /**
13014
     * Check if the current lp item is first, both, last or none from lp list.
13015
     *
13016
     * @param int $currentItemId
13017
     *
13018
     * @return string
13019
     */
13020
    public function isFirstOrLastItem($currentItemId)
13021
    {
13022
        $lpItemId = [];
13023
        $typeListNotToVerify = self::getChapterTypes();
13024
13025
        // Using get_toc() function instead $this->items because returns the correct order of the items
13026
        foreach ($this->get_toc() as $item) {
13027
            if (!in_array($item['type'], $typeListNotToVerify)) {
13028
                $lpItemId[] = $item['id'];
13029
            }
13030
        }
13031
13032
        $lastLpItemIndex = count($lpItemId) - 1;
13033
        $position = array_search($currentItemId, $lpItemId);
13034
13035
        switch ($position) {
13036
            case 0:
13037
                if (!$lastLpItemIndex) {
13038
                    $answer = 'both';
13039
                    break;
13040
                }
13041
13042
                $answer = 'first';
13043
                break;
13044
            case $lastLpItemIndex:
13045
                $answer = 'last';
13046
                break;
13047
            default:
13048
                $answer = 'none';
13049
        }
13050
13051
        return $answer;
13052
    }
13053
13054
    /**
13055
     * Get whether this is a learning path with the accumulated SCORM time or not.
13056
     *
13057
     * @return int
13058
     */
13059
    public function getAccumulateScormTime()
13060
    {
13061
        return $this->accumulateScormTime;
13062
    }
13063
13064
    /**
13065
     * Set whether this is a learning path with the accumulated SCORM time or not.
13066
     *
13067
     * @param int $value (0 = false, 1 = true)
13068
     *
13069
     * @return bool Always returns true
13070
     */
13071
    public function setAccumulateScormTime($value)
13072
    {
13073
        $this->accumulateScormTime = (int) $value;
13074
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13075
        $lp_id = $this->get_id();
13076
        $sql = "UPDATE $lp_table
13077
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13078
                WHERE iid = $lp_id";
13079
        Database::query($sql);
13080
13081
        return true;
13082
    }
13083
13084
    /**
13085
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13086
     * the new learning path tool.
13087
     *
13088
     * The function is a big switch on tool type.
13089
     * In each case, we query the corresponding table for information and build the link
13090
     * with that information.
13091
     *
13092
     * @author Yannick Warnier <[email protected]> - rebranding based on
13093
     * previous work (display_addedresource_link_in_learnpath())
13094
     *
13095
     * @param int $course_id      Course code
13096
     * @param int $learningPathId The learning path ID (in lp table)
13097
     * @param int $id_in_path     the unique index in the items table
13098
     * @param int $lpViewId
13099
     * @param int $lpSessionId
13100
     *
13101
     * @return string
13102
     */
13103
    public static function rl_get_resource_link_for_learnpath(
13104
        $course_id,
13105
        $learningPathId,
13106
        $id_in_path,
13107
        $lpViewId,
13108
        $lpSessionId = 0
13109
    ) {
13110
        $session_id = api_get_session_id();
13111
        $course_info = api_get_course_info_by_id($course_id);
13112
13113
        $learningPathId = (int) $learningPathId;
13114
        $id_in_path = (int) $id_in_path;
13115
        $lpViewId = (int) $lpViewId;
13116
13117
        $em = Database::getManager();
13118
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13119
13120
        /** @var CLpItem $rowItem */
13121
        $rowItem = $lpItemRepo->findOneBy([
13122
            'cId' => $course_id,
13123
            'lpId' => $learningPathId,
13124
            'iid' => $id_in_path,
13125
        ]);
13126
13127
        if (!$rowItem) {
13128
            // Try one more time with "id"
13129
            /** @var CLpItem $rowItem */
13130
            $rowItem = $lpItemRepo->findOneBy([
13131
                'cId' => $course_id,
13132
                'lpId' => $learningPathId,
13133
                'id' => $id_in_path,
13134
            ]);
13135
13136
            if (!$rowItem) {
13137
                return -1;
13138
            }
13139
        }
13140
13141
        $course_code = $course_info['code'];
13142
        $type = $rowItem->getItemType();
13143
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13144
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13145
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13146
        $link = '';
13147
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13148
13149
        switch ($type) {
13150
            case 'dir':
13151
                return $main_dir_path.'lp/blank.php';
13152
            case TOOL_CALENDAR_EVENT:
13153
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13154
            case TOOL_ANNOUNCEMENT:
13155
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13156
            case TOOL_LINK:
13157
                $linkInfo = Link::getLinkInfo($id);
13158
                if (isset($linkInfo['url'])) {
13159
                    return $linkInfo['url'];
13160
                }
13161
13162
                return '';
13163
            case TOOL_QUIZ:
13164
                if (empty($id)) {
13165
                    return '';
13166
                }
13167
13168
                // Get the lp_item_view with the highest view_count.
13169
                $learnpathItemViewResult = $em
13170
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13171
                    ->findBy(
13172
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13173
                        ['viewCount' => 'DESC'],
13174
                        1
13175
                    );
13176
                /** @var CLpItemView $learnpathItemViewData */
13177
                $learnpathItemViewData = current($learnpathItemViewResult);
13178
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13179
13180
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13181
                    .http_build_query([
13182
                        'lp_init' => 1,
13183
                        'learnpath_item_view_id' => $learnpathItemViewId,
13184
                        'learnpath_id' => $learningPathId,
13185
                        'learnpath_item_id' => $id_in_path,
13186
                        'exerciseId' => $id,
13187
                    ]);
13188
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13189
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13190
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13191
                $myrow = Database::fetch_array($result);
13192
                $path = $myrow['path'];
13193
13194
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13195
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13196
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13197
            case TOOL_FORUM:
13198
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13199
            case TOOL_THREAD:
13200
                // forum post
13201
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13202
                if (empty($id)) {
13203
                    return '';
13204
                }
13205
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13206
                $result = Database::query($sql);
13207
                $myrow = Database::fetch_array($result);
13208
13209
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13210
                    .$extraParams;
13211
            case TOOL_POST:
13212
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13213
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13214
                $myrow = Database::fetch_array($result);
13215
13216
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13217
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13218
            case TOOL_READOUT_TEXT:
13219
                return api_get_path(WEB_CODE_PATH).
13220
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13221
            case TOOL_DOCUMENT:
13222
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13223
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13224
13225
                if (empty($document)) {
13226
                    // Try with normal id
13227
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13228
13229
                    if (empty($document)) {
13230
                        return '';
13231
                    }
13232
                }
13233
13234
                $documentPathInfo = pathinfo($document->getPath());
13235
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13236
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13237
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13238
13239
                $openmethod = 2;
13240
                $officedoc = false;
13241
                Session::write('openmethod', $openmethod);
13242
                Session::write('officedoc', $officedoc);
13243
13244
                if ($showDirectUrl) {
13245
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13246
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13247
                        if (Link::isPdfLink($file)) {
13248
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13249
13250
                            return $pdfUrl;
13251
                        }
13252
                    }
13253
13254
                    return $file;
13255
                }
13256
13257
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13258
            case TOOL_LP_FINAL_ITEM:
13259
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13260
                    .$extraParams;
13261
            case 'assignments':
13262
                return $main_dir_path.'work/work.php?'.$extraParams;
13263
            case TOOL_DROPBOX:
13264
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13265
            case 'introduction_text': //DEPRECATED
13266
                return '';
13267
            case TOOL_COURSE_DESCRIPTION:
13268
                return $main_dir_path.'course_description?'.$extraParams;
13269
            case TOOL_GROUP:
13270
                return $main_dir_path.'group/group.php?'.$extraParams;
13271
            case TOOL_USER:
13272
                return $main_dir_path.'user/user.php?'.$extraParams;
13273
            case TOOL_STUDENTPUBLICATION:
13274
                if (!empty($rowItem->getPath())) {
13275
                    $workId = $rowItem->getPath();
13276
                    if (empty($lpSessionId) && !empty($session_id)) {
13277
                        // Check if a student publication with the same name exists in this session see BT#17700
13278
                        $title = Database::escape_string($rowItem->getTitle());
13279
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13280
                        $sql = "SELECT * FROM $table
13281
                                WHERE
13282
                                    active = 1 AND
13283
                                    parent_id = 0 AND
13284
                                    c_id = $course_id AND
13285
                                    session_id = $session_id AND
13286
                                    title = '$title'
13287
                                LIMIT 1";
13288
                        $result = Database::query($sql);
13289
                        if (Database::num_rows($result)) {
13290
                            $work = Database::fetch_array($result, 'ASSOC');
13291
                            if ($work) {
13292
                                $workId = $work['iid'];
13293
                            }
13294
                        }
13295
                    }
13296
13297
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13298
                }
13299
13300
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13301
        }
13302
13303
        return $link;
13304
    }
13305
13306
    /**
13307
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13308
     *
13309
     * @author Yannick Warnier <[email protected]>
13310
     *
13311
     * @param string $course_code    Course code
13312
     * @param int    $learningPathId
13313
     * @param int    $id_in_path     The resource ID
13314
     *
13315
     * @return string
13316
     */
13317
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13318
    {
13319
        $_course = api_get_course_info($course_code);
13320
        if (empty($_course)) {
13321
            return '';
13322
        }
13323
        $course_id = $_course['real_id'];
13324
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13325
        $learningPathId = (int) $learningPathId;
13326
        $id_in_path = (int) $id_in_path;
13327
13328
        $sql = "SELECT item_type, title, ref
13329
                FROM $tbl_lp_item
13330
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13331
        $res_item = Database::query($sql);
13332
13333
        if (Database::num_rows($res_item) < 1) {
13334
            return '';
13335
        }
13336
        $row_item = Database::fetch_array($res_item);
13337
        $type = strtolower($row_item['item_type']);
13338
        $id = $row_item['ref'];
13339
        $output = '';
13340
13341
        switch ($type) {
13342
            case TOOL_CALENDAR_EVENT:
13343
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13344
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13345
                $myrow = Database::fetch_array($result);
13346
                $output = $myrow['title'];
13347
                break;
13348
            case TOOL_ANNOUNCEMENT:
13349
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13350
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13351
                $myrow = Database::fetch_array($result);
13352
                $output = $myrow['title'];
13353
                break;
13354
            case TOOL_LINK:
13355
                // Doesn't take $target into account.
13356
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13357
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13358
                $myrow = Database::fetch_array($result);
13359
                $output = $myrow['title'];
13360
                break;
13361
            case TOOL_QUIZ:
13362
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13363
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13364
                $myrow = Database::fetch_array($result);
13365
                $output = $myrow['title'];
13366
                break;
13367
            case TOOL_FORUM:
13368
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13369
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13370
                $myrow = Database::fetch_array($result);
13371
                $output = $myrow['forum_name'];
13372
                break;
13373
            case TOOL_THREAD:
13374
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13375
                // Grabbing the title of the post.
13376
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13377
                $result_title = Database::query($sql_title);
13378
                $myrow_title = Database::fetch_array($result_title);
13379
                $output = $myrow_title['post_title'];
13380
                break;
13381
            case TOOL_POST:
13382
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13383
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13384
                $result = Database::query($sql);
13385
                $post = Database::fetch_array($result);
13386
                $output = $post['post_title'];
13387
                break;
13388
            case 'dir':
13389
            case TOOL_DOCUMENT:
13390
                $title = $row_item['title'];
13391
                $output = '-';
13392
                if (!empty($title)) {
13393
                    $output = $title;
13394
                }
13395
                break;
13396
            case 'hotpotatoes':
13397
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13398
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13399
                $myrow = Database::fetch_array($result);
13400
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13401
                $last = count($pathname) - 1; // Making a correct name for the link.
13402
                $filename = $pathname[$last]; // Making a correct name for the link.
13403
                $myrow['path'] = rawurlencode($myrow['path']);
13404
                $output = $filename;
13405
                break;
13406
        }
13407
13408
        return stripslashes($output);
13409
    }
13410
13411
    /**
13412
     * Get the parent names for the current item.
13413
     *
13414
     * @param int $newItemId Optional. The item ID
13415
     *
13416
     * @return array
13417
     */
13418
    public function getCurrentItemParentNames($newItemId = 0)
13419
    {
13420
        $newItemId = $newItemId ?: $this->get_current_item_id();
13421
        $return = [];
13422
        $item = $this->getItem($newItemId);
13423
        $parent = $this->getItem($item->get_parent());
13424
13425
        while ($parent) {
13426
            $return[] = $parent->get_title();
13427
            $parent = $this->getItem($parent->get_parent());
13428
        }
13429
13430
        return array_reverse($return);
13431
    }
13432
13433
    /**
13434
     * Reads and process "lp_subscription_settings" setting.
13435
     *
13436
     * @return array
13437
     */
13438
    public static function getSubscriptionSettings()
13439
    {
13440
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13441
        if (empty($subscriptionSettings)) {
13442
            // By default allow both settings
13443
            $subscriptionSettings = [
13444
                'allow_add_users_to_lp' => true,
13445
                'allow_add_users_to_lp_category' => true,
13446
            ];
13447
        } else {
13448
            $subscriptionSettings = $subscriptionSettings['options'];
13449
        }
13450
13451
        return $subscriptionSettings;
13452
    }
13453
13454
    /**
13455
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13456
     */
13457
    public function exportToCourseBuildFormat()
13458
    {
13459
        if (!api_is_allowed_to_edit()) {
13460
            return false;
13461
        }
13462
13463
        $courseBuilder = new CourseBuilder();
13464
        $itemList = [];
13465
        /** @var learnpathItem $item */
13466
        foreach ($this->items as $item) {
13467
            $itemList[$item->get_type()][] = $item->get_path();
13468
        }
13469
13470
        if (empty($itemList)) {
13471
            return false;
13472
        }
13473
13474
        if (isset($itemList['document'])) {
13475
            // Get parents
13476
            foreach ($itemList['document'] as $documentId) {
13477
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13478
                if (!empty($documentInfo['parents'])) {
13479
                    foreach ($documentInfo['parents'] as $parentInfo) {
13480
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13481
                            continue;
13482
                        }
13483
                        $itemList['document'][] = $parentInfo['iid'];
13484
                    }
13485
                }
13486
            }
13487
13488
            $courseInfo = api_get_course_info();
13489
            foreach ($itemList['document'] as $documentId) {
13490
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13491
                $items = DocumentManager::get_resources_from_source_html(
13492
                    $documentInfo['absolute_path'],
13493
                    true,
13494
                    TOOL_DOCUMENT
13495
                );
13496
13497
                if (!empty($items)) {
13498
                    foreach ($items as $item) {
13499
                        // Get information about source url
13500
                        $url = $item[0]; // url
13501
                        $scope = $item[1]; // scope (local, remote)
13502
                        $type = $item[2]; // type (rel, abs, url)
13503
13504
                        $origParseUrl = parse_url($url);
13505
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13506
13507
                        if ($scope == 'local') {
13508
                            if ($type == 'abs' || $type == 'rel') {
13509
                                $documentFile = strstr($realOrigPath, 'document');
13510
                                if (strpos($realOrigPath, $documentFile) !== false) {
13511
                                    $documentFile = str_replace('document', '', $documentFile);
13512
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13513
                                    // Document found! Add it to the list
13514
                                    if ($itemDocumentId) {
13515
                                        $itemList['document'][] = $itemDocumentId;
13516
                                    }
13517
                                }
13518
                            }
13519
                        }
13520
                    }
13521
                }
13522
            }
13523
13524
            $courseBuilder->build_documents(
13525
                api_get_session_id(),
13526
                $this->get_course_int_id(),
13527
                true,
13528
                $itemList['document']
13529
            );
13530
        }
13531
13532
        if (isset($itemList['quiz'])) {
13533
            $courseBuilder->build_quizzes(
13534
                api_get_session_id(),
13535
                $this->get_course_int_id(),
13536
                true,
13537
                $itemList['quiz']
13538
            );
13539
        }
13540
13541
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13542
13543
        /*if (!empty($itemList['thread'])) {
13544
            $postList = [];
13545
            foreach ($itemList['thread'] as $postId) {
13546
                $post = get_post_information($postId);
13547
                if ($post) {
13548
                    if (!isset($itemList['forum'])) {
13549
                        $itemList['forum'] = [];
13550
                    }
13551
                    $itemList['forum'][] = $post['forum_id'];
13552
                    $postList[] = $postId;
13553
                }
13554
            }
13555
13556
            if (!empty($postList)) {
13557
                $courseBuilder->build_forum_posts(
13558
                    $this->get_course_int_id(),
13559
                    null,
13560
                    null,
13561
                    $postList
13562
                );
13563
            }
13564
        }*/
13565
13566
        if (!empty($itemList['thread'])) {
13567
            $threadList = [];
13568
            $em = Database::getManager();
13569
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13570
            foreach ($itemList['thread'] as $threadId) {
13571
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13572
                $thread = $repo->find($threadId);
13573
                if ($thread) {
13574
                    $itemList['forum'][] = $thread->getForumId();
13575
                    $threadList[] = $thread->getIid();
13576
                }
13577
            }
13578
13579
            if (!empty($threadList)) {
13580
                $courseBuilder->build_forum_topics(
13581
                    api_get_session_id(),
13582
                    $this->get_course_int_id(),
13583
                    null,
13584
                    $threadList
13585
                );
13586
            }
13587
        }
13588
13589
        $forumCategoryList = [];
13590
        if (isset($itemList['forum'])) {
13591
            foreach ($itemList['forum'] as $forumId) {
13592
                $forumInfo = get_forums($forumId);
13593
                $forumCategoryList[] = $forumInfo['forum_category'];
13594
            }
13595
        }
13596
13597
        if (!empty($forumCategoryList)) {
13598
            $courseBuilder->build_forum_category(
13599
                api_get_session_id(),
13600
                $this->get_course_int_id(),
13601
                true,
13602
                $forumCategoryList
13603
            );
13604
        }
13605
13606
        if (!empty($itemList['forum'])) {
13607
            $courseBuilder->build_forums(
13608
                api_get_session_id(),
13609
                $this->get_course_int_id(),
13610
                true,
13611
                $itemList['forum']
13612
            );
13613
        }
13614
13615
        if (isset($itemList['link'])) {
13616
            $courseBuilder->build_links(
13617
                api_get_session_id(),
13618
                $this->get_course_int_id(),
13619
                true,
13620
                $itemList['link']
13621
            );
13622
        }
13623
13624
        if (!empty($itemList['student_publication'])) {
13625
            $courseBuilder->build_works(
13626
                api_get_session_id(),
13627
                $this->get_course_int_id(),
13628
                true,
13629
                $itemList['student_publication']
13630
            );
13631
        }
13632
13633
        $courseBuilder->build_learnpaths(
13634
            api_get_session_id(),
13635
            $this->get_course_int_id(),
13636
            true,
13637
            [$this->get_id()],
13638
            false
13639
        );
13640
13641
        $courseBuilder->restoreDocumentsFromList();
13642
13643
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13644
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13645
        $result = DocumentManager::file_send_for_download(
13646
            $zipPath,
13647
            true,
13648
            $this->get_name().'.zip'
13649
        );
13650
13651
        if ($result) {
13652
            api_not_allowed();
13653
        }
13654
13655
        return true;
13656
    }
13657
13658
    /**
13659
     * Get whether this is a learning path with the accumulated work time or not.
13660
     *
13661
     * @return int
13662
     */
13663
    public function getAccumulateWorkTime()
13664
    {
13665
        return (int) $this->accumulateWorkTime;
13666
    }
13667
13668
    /**
13669
     * Get whether this is a learning path with the accumulated work time or not.
13670
     *
13671
     * @return int
13672
     */
13673
    public function getAccumulateWorkTimeTotalCourse()
13674
    {
13675
        $table = Database::get_course_table(TABLE_LP_MAIN);
13676
        $sql = "SELECT SUM(accumulate_work_time) AS total
13677
                FROM $table
13678
                WHERE c_id = ".$this->course_int_id;
13679
        $result = Database::query($sql);
13680
        $row = Database::fetch_array($result);
13681
13682
        return (int) $row['total'];
13683
    }
13684
13685
    /**
13686
     * Set whether this is a learning path with the accumulated work time or not.
13687
     *
13688
     * @param int $value (0 = false, 1 = true)
13689
     *
13690
     * @return bool
13691
     */
13692
    public function setAccumulateWorkTime($value)
13693
    {
13694
        if (!api_get_configuration_value('lp_minimum_time')) {
13695
            return false;
13696
        }
13697
13698
        $this->accumulateWorkTime = (int) $value;
13699
        $table = Database::get_course_table(TABLE_LP_MAIN);
13700
        $lp_id = $this->get_id();
13701
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13702
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13703
        Database::query($sql);
13704
13705
        return true;
13706
    }
13707
13708
    /**
13709
     * @param int $lpId
13710
     * @param int $courseId
13711
     *
13712
     * @return mixed
13713
     */
13714
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13715
    {
13716
        $lpId = (int) $lpId;
13717
        $courseId = (int) $courseId;
13718
13719
        $table = Database::get_course_table(TABLE_LP_MAIN);
13720
        $sql = "SELECT accumulate_work_time
13721
                FROM $table
13722
                WHERE c_id = $courseId AND id = $lpId";
13723
        $result = Database::query($sql);
13724
        $row = Database::fetch_array($result);
13725
13726
        return $row['accumulate_work_time'];
13727
    }
13728
13729
    /**
13730
     * @param int $courseId
13731
     *
13732
     * @return int
13733
     */
13734
    public static function getAccumulateWorkTimeTotal($courseId)
13735
    {
13736
        $table = Database::get_course_table(TABLE_LP_MAIN);
13737
        $courseId = (int) $courseId;
13738
        $sql = "SELECT SUM(accumulate_work_time) AS total
13739
                FROM $table
13740
                WHERE c_id = $courseId";
13741
        $result = Database::query($sql);
13742
        $row = Database::fetch_array($result);
13743
13744
        return (int) $row['total'];
13745
    }
13746
13747
    /**
13748
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13749
     * and put the images in.
13750
     *
13751
     * @return array
13752
     */
13753
    public static function getIconSelect()
13754
    {
13755
        $theme = api_get_visual_theme();
13756
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13757
        $icons = ['' => get_lang('SelectAnOption')];
13758
13759
        if (is_dir($path)) {
13760
            $finder = new Finder();
13761
            $finder->files()->in($path);
13762
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13763
            /** @var SplFileInfo $file */
13764
            foreach ($finder as $file) {
13765
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13766
                    $icons[$file->getFilename()] = $file->getFilename();
13767
                }
13768
            }
13769
        }
13770
13771
        return $icons;
13772
    }
13773
13774
    /**
13775
     * @param int $lpId
13776
     *
13777
     * @return string
13778
     */
13779
    public static function getSelectedIcon($lpId)
13780
    {
13781
        $extraFieldValue = new ExtraFieldValue('lp');
13782
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13783
        $icon = '';
13784
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13785
            $icon = $lpIcon['value'];
13786
        }
13787
13788
        return $icon;
13789
    }
13790
13791
    /**
13792
     * @param int $lpId
13793
     *
13794
     * @return string
13795
     */
13796
    public static function getSelectedIconHtml($lpId)
13797
    {
13798
        $icon = self::getSelectedIcon($lpId);
13799
13800
        if (empty($icon)) {
13801
            return '';
13802
        }
13803
13804
        $theme = api_get_visual_theme();
13805
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13806
13807
        return Display::img($path);
13808
    }
13809
13810
    /**
13811
     * @param string $value
13812
     *
13813
     * @return string
13814
     */
13815
    public function cleanItemTitle($value)
13816
    {
13817
        $value = Security::remove_XSS(strip_tags($value));
13818
13819
        return $value;
13820
    }
13821
13822
    public function setItemTitle(FormValidator $form)
13823
    {
13824
        if (api_get_configuration_value('save_titles_as_html')) {
13825
            $form->addHtmlEditor(
13826
                'title',
13827
                get_lang('Title'),
13828
                true,
13829
                false,
13830
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13831
            );
13832
        } else {
13833
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13834
            $form->applyFilter('title', 'trim');
13835
            $form->applyFilter('title', 'html_filter');
13836
        }
13837
    }
13838
13839
    /**
13840
     * @return array
13841
     */
13842
    public function getItemsForForm($addParentCondition = false)
13843
    {
13844
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13845
        $course_id = api_get_course_int_id();
13846
13847
        $sql = "SELECT * FROM $tbl_lp_item
13848
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13849
13850
        if ($addParentCondition) {
13851
            $sql .= ' AND parent_item_id = 0 ';
13852
        }
13853
        $sql .= ' ORDER BY display_order ASC';
13854
13855
        $result = Database::query($sql);
13856
        $arrLP = [];
13857
        while ($row = Database::fetch_array($result)) {
13858
            $arrLP[] = [
13859
                'iid' => $row['iid'],
13860
                'id' => $row['iid'],
13861
                'item_type' => $row['item_type'],
13862
                'title' => $this->cleanItemTitle($row['title']),
13863
                'title_raw' => $row['title'],
13864
                'path' => $row['path'],
13865
                'description' => Security::remove_XSS($row['description']),
13866
                'parent_item_id' => $row['parent_item_id'],
13867
                'previous_item_id' => $row['previous_item_id'],
13868
                'next_item_id' => $row['next_item_id'],
13869
                'display_order' => $row['display_order'],
13870
                'max_score' => $row['max_score'],
13871
                'min_score' => $row['min_score'],
13872
                'mastery_score' => $row['mastery_score'],
13873
                'prerequisite' => $row['prerequisite'],
13874
                'max_time_allowed' => $row['max_time_allowed'],
13875
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13876
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13877
            ];
13878
        }
13879
13880
        return $arrLP;
13881
    }
13882
13883
    /**
13884
     * Gets whether this SCORM learning path has been marked to use the score
13885
     * as progress. Takes into account whether the learnpath matches (SCORM
13886
     * content + less than 2 items).
13887
     *
13888
     * @return bool True if the score should be used as progress, false otherwise
13889
     */
13890
    public function getUseScoreAsProgress()
13891
    {
13892
        // If not a SCORM, we don't care about the setting
13893
        if ($this->get_type() != 2) {
13894
            return false;
13895
        }
13896
        // If more than one step in the SCORM, we don't care about the setting
13897
        if ($this->get_total_items_count() > 1) {
13898
            return false;
13899
        }
13900
        $extraFieldValue = new ExtraFieldValue('lp');
13901
        $doUseScore = false;
13902
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
13903
        if (!empty($useScore) && isset($useScore['value'])) {
13904
            $doUseScore = $useScore['value'];
13905
        }
13906
13907
        return $doUseScore;
13908
    }
13909
13910
    /**
13911
     * Get the user identifier (user_id or username
13912
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
13913
     *
13914
     * @return string User ID or username, depending on configuration setting
13915
     */
13916
    public static function getUserIdentifierForExternalServices()
13917
    {
13918
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
13919
            return api_get_user_info(api_get_user_id())['username'];
13920
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
13921
            $extraFieldValue = new ExtraFieldValue('user');
13922
            $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'));
13923
13924
            return $extrafield['value'];
13925
        } else {
13926
            return api_get_user_id();
13927
        }
13928
    }
13929
13930
    /**
13931
     * Save the new order for learning path items.
13932
     *
13933
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
13934
     *
13935
     * @param array $orderList A associative array with item ID as key and parent ID as value.
13936
     * @param int   $courseId
13937
     */
13938
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
13939
    {
13940
        $courseId = $courseId ?: api_get_course_int_id();
13941
        $itemList = new LpItemOrderList();
13942
13943
        foreach ($orderList as $id => $parentId) {
13944
            $item = new LpOrderItem($id, $parentId);
13945
            $itemList->add($item);
13946
        }
13947
13948
        $parents = $itemList->getListOfParents();
13949
13950
        foreach ($parents as $parentId) {
13951
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
13952
            $previous_item_id = 0;
13953
            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...
13954
                $item_id = $sameParentLpItemList->list[$i]->id;
13955
                // display_order
13956
                $display_order = $i + 1;
13957
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
13958
                // previous_item_id
13959
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
13960
                $previous_item_id = $item_id;
13961
                // next_item_id
13962
                $next_item_id = 0;
13963
                if ($i < count($sameParentLpItemList->list) - 1) {
13964
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
13965
                }
13966
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
13967
            }
13968
        }
13969
13970
        $table = Database::get_course_table(TABLE_LP_ITEM);
13971
13972
        foreach ($itemList->list as $item) {
13973
            $params = [];
13974
            $params['display_order'] = $item->display_order;
13975
            $params['previous_item_id'] = $item->previous_item_id;
13976
            $params['next_item_id'] = $item->next_item_id;
13977
            $params['parent_item_id'] = $item->parent_item_id;
13978
13979
            Database::update(
13980
                $table,
13981
                $params,
13982
                [
13983
                    'iid = ? AND c_id = ? ' => [
13984
                        (int) $item->id,
13985
                        (int) $courseId,
13986
                    ],
13987
                ]
13988
            );
13989
        }
13990
    }
13991
13992
    /**
13993
     * Get the depth level of LP item.
13994
     *
13995
     * @param array $items
13996
     * @param int   $currentItemId
13997
     *
13998
     * @return int
13999
     */
14000
    private static function get_level_for_item($items, $currentItemId)
14001
    {
14002
        $parentItemId = 0;
14003
        if (isset($items[$currentItemId])) {
14004
            $parentItemId = $items[$currentItemId]->parent;
14005
        }
14006
14007
        if ($parentItemId == 0) {
14008
            return 0;
14009
        } else {
14010
            return self::get_level_for_item($items, $parentItemId) + 1;
14011
        }
14012
    }
14013
14014
    /**
14015
     * Generate the link for a learnpath category as course tool.
14016
     *
14017
     * @param int $categoryId
14018
     *
14019
     * @return string
14020
     */
14021
    private static function getCategoryLinkForTool($categoryId)
14022
    {
14023
        $categoryId = (int) $categoryId;
14024
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14025
            .http_build_query(
14026
                [
14027
                    'action' => 'view_category',
14028
                    'id' => $categoryId,
14029
                ]
14030
            );
14031
14032
        return $link;
14033
    }
14034
14035
    /**
14036
     * Return the scorm item type object with spaces replaced with _
14037
     * The return result is use to build a css classname like scorm_type_$return.
14038
     *
14039
     * @param $in_type
14040
     *
14041
     * @return mixed
14042
     */
14043
    private static function format_scorm_type_item($in_type)
14044
    {
14045
        return str_replace(' ', '_', $in_type);
14046
    }
14047
14048
    /**
14049
     * Check and obtain the lp final item if exist.
14050
     *
14051
     * @return learnpathItem
14052
     */
14053
    private function getFinalItem()
14054
    {
14055
        if (empty($this->items)) {
14056
            return null;
14057
        }
14058
14059
        foreach ($this->items as $item) {
14060
            if ($item->type !== 'final_item') {
14061
                continue;
14062
            }
14063
14064
            return $item;
14065
        }
14066
    }
14067
14068
    /**
14069
     * Get the LP Final Item Template.
14070
     *
14071
     * @return string
14072
     */
14073
    private function getFinalItemTemplate()
14074
    {
14075
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14076
    }
14077
14078
    /**
14079
     * Get the LP Final Item Url.
14080
     *
14081
     * @return string
14082
     */
14083
    private function getSavedFinalItem()
14084
    {
14085
        $finalItem = $this->getFinalItem();
14086
        $doc = DocumentManager::get_document_data_by_id(
14087
            $finalItem->path,
14088
            $this->cc
14089
        );
14090
        if ($doc && file_exists($doc['absolute_path'])) {
14091
            return file_get_contents($doc['absolute_path']);
14092
        }
14093
14094
        return '';
14095
    }
14096
}
14097