Passed
Push — master ( 49c89f...65060b )
by Julito
10:01
created

learnpath::first()   F

Complexity

Conditions 20
Paths 224

Size

Total Lines 71
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 44
nc 224
nop 0
dl 0
loc 71
rs 3.0333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
5
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
9
use Chamilo\CourseBundle\Entity\CDocument;
10
use Chamilo\CourseBundle\Entity\CItemProperty;
11
use Chamilo\CourseBundle\Entity\CLp;
12
use Chamilo\CourseBundle\Entity\CLpCategory;
13
use Chamilo\CourseBundle\Entity\CLpItem;
14
use Chamilo\CourseBundle\Entity\CLpItemView;
15
use Chamilo\CourseBundle\Entity\CTool;
16
use Chamilo\UserBundle\Entity\User;
17
use ChamiloSession as Session;
18
use Gedmo\Sortable\Entity\Repository\SortableRepository;
19
use Symfony\Component\Filesystem\Filesystem;
20
use Symfony\Component\Finder\Finder;
21
22
/**
23
 * Class learnpath
24
 * This class defines the parent attributes and methods for Chamilo learnpaths
25
 * and SCORM learnpaths. It is used by the scorm class.
26
 *
27
 * @todo decouple class
28
 *
29
 * @package chamilo.learnpath
30
 *
31
 * @author  Yannick Warnier <[email protected]>
32
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
33
 */
34
class learnpath
35
{
36
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
37
38
    public $attempt = 0; // The number for the current ID view.
39
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
40
    public $current; // Id of the current item the user is viewing.
41
    public $current_score; // The score of the current item.
42
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
43
    public $current_time_stop; // The time the user closed this resource.
44
    public $default_status = 'not attempted';
45
    public $encoding = 'UTF-8';
46
    public $error = '';
47
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
48
    public $index; // The index of the active learnpath_item in $ordered_items array.
49
    public $items = [];
50
    public $last; // item_id of last item viewed in the learning path.
51
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
52
    public $license; // Which license this course has been given - not used yet on 20060522.
53
    public $lp_id; // DB iid for this learnpath.
54
    public $lp_view_id; // DB ID for lp_view
55
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
56
    public $message = '';
57
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
58
    public $name; // Learnpath name (they generally have one).
59
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
60
    public $path = ''; // Path inside the scorm directory (if scorm).
61
    public $theme; // The current theme of the learning path.
62
    public $preview_image; // The current image of the learning path.
63
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
64
65
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
66
    public $prevent_reinit = 1;
67
68
    // Describes the mode of progress bar display.
69
    public $seriousgame_mode = 0;
70
    public $progress_bar_mode = '%';
71
72
    // Percentage progress as saved in the db.
73
    public $progress_db = 0;
74
    public $proximity; // Wether the content is distant or local or unknown.
75
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
76
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
77
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
78
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
79
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
80
    public $user_id; //ID of the user that is viewing/using the course
81
    public $update_queue = [];
82
    public $scorm_debug = 0;
83
    public $arrMenu = []; // Array for the menu items.
84
    public $debug = 0; // Logging level.
85
    public $lp_session_id = 0;
86
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
87
    public $prerequisite = 0;
88
    public $use_max_score = 1; // 1 or 0
89
    public $subscribeUsers = 0; // Subscribe users or not
90
    public $created_on = '';
91
    public $modified_on = '';
92
    public $publicated_on = '';
93
    public $expired_on = '';
94
    public $ref = null;
95
    public $course_int_id;
96
    public $course_info = [];
97
    public $categoryId;
98
99
    /**
100
     * Constructor.
101
     * Needs a database handler, a course code and a learnpath id from the database.
102
     * Also builds the list of items into $this->items.
103
     *
104
     * @param string $course  Course code
105
     * @param int    $lp_id   c_lp.iid
106
     * @param int    $user_id
107
     */
108
    public function __construct($course, $lp_id, $user_id)
109
    {
110
        $debug = $this->debug;
111
        $this->encoding = api_get_system_encoding();
112
        if ($debug) {
113
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
114
        }
115
        if (empty($course)) {
116
            $course = api_get_course_id();
117
        }
118
        $course_info = api_get_course_info($course);
119
        if (!empty($course_info)) {
120
            $this->cc = $course_info['code'];
121
            $this->course_info = $course_info;
122
            $course_id = $course_info['real_id'];
123
        } else {
124
            $this->error = 'Course code does not exist in database.';
125
        }
126
127
        $lp_id = (int) $lp_id;
128
        $course_id = (int) $course_id;
129
        $this->set_course_int_id($course_id);
130
        // Check learnpath ID.
131
        if (empty($lp_id) || empty($course_id)) {
132
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
133
        } else {
134
            // TODO: Make it flexible to use any course_code (still using env course code here).
135
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
136
            $sql = "SELECT * FROM $lp_table
137
                    WHERE iid = $lp_id";
138
            if ($debug) {
139
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
140
            }
141
            $res = Database::query($sql);
142
            if (Database::num_rows($res) > 0) {
143
                $this->lp_id = $lp_id;
144
                $row = Database::fetch_array($res);
145
                $this->type = $row['lp_type'];
146
                $this->name = stripslashes($row['name']);
147
                $this->proximity = $row['content_local'];
148
                $this->theme = $row['theme'];
149
                $this->maker = $row['content_maker'];
150
                $this->prevent_reinit = $row['prevent_reinit'];
151
                $this->seriousgame_mode = $row['seriousgame_mode'];
152
                $this->license = $row['content_license'];
153
                $this->scorm_debug = $row['debug'];
154
                $this->js_lib = $row['js_lib'];
155
                $this->path = $row['path'];
156
                $this->preview_image = $row['preview_image'];
157
                $this->author = $row['author'];
158
                $this->hide_toc_frame = $row['hide_toc_frame'];
159
                $this->lp_session_id = $row['session_id'];
160
                $this->use_max_score = $row['use_max_score'];
161
                $this->subscribeUsers = $row['subscribe_users'];
162
                $this->created_on = $row['created_on'];
163
                $this->modified_on = $row['modified_on'];
164
                $this->ref = $row['ref'];
165
                $this->categoryId = $row['category_id'];
166
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
167
168
                if (!empty($row['publicated_on'])) {
169
                    $this->publicated_on = $row['publicated_on'];
170
                }
171
172
                if (!empty($row['expired_on'])) {
173
                    $this->expired_on = $row['expired_on'];
174
                }
175
                if ($this->type == 2) {
176
                    if ($row['force_commit'] == 1) {
177
                        $this->force_commit = true;
178
                    }
179
                }
180
                $this->mode = $row['default_view_mod'];
181
182
                // Check user ID.
183
                if (empty($user_id)) {
184
                    $this->error = 'User ID is empty';
185
                } else {
186
                    $userInfo = api_get_user_info($user_id);
187
                    if (!empty($userInfo)) {
188
                        $this->user_id = $userInfo['user_id'];
189
                    } else {
190
                        $this->error = 'User ID does not exist in database #'.$user_id;
191
                    }
192
                }
193
194
                // End of variables checking.
195
                $session_id = api_get_session_id();
196
                //  Get the session condition for learning paths of the base + session.
197
                $session = api_get_session_condition($session_id);
198
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
199
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
200
201
                // Selecting by view_count descending allows to get the highest view_count first.
202
                $sql = "SELECT * FROM $lp_table
203
                        WHERE 
204
                            c_id = $course_id AND 
205
                            lp_id = $lp_id AND 
206
                            user_id = $user_id 
207
                            $session
208
                        ORDER BY view_count DESC";
209
                $res = Database::query($sql);
210
                if ($debug) {
211
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
212
                }
213
214
                if (Database::num_rows($res) > 0) {
215
                    if ($debug) {
216
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
217
                    }
218
                    $row = Database::fetch_array($res);
219
                    $this->attempt = $row['view_count'];
220
                    $this->lp_view_id = $row['id'];
221
                    $this->last_item_seen = $row['last_item'];
222
                    $this->progress_db = $row['progress'];
223
                    $this->lp_view_session_id = $row['session_id'];
224
                } elseif (!api_is_invitee()) {
225
                    if ($debug) {
226
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
227
                    }
228
                    $this->attempt = 1;
229
                    $params = [
230
                        'c_id' => $course_id,
231
                        'lp_id' => $lp_id,
232
                        'user_id' => $user_id,
233
                        'view_count' => 1,
234
                        'session_id' => $session_id,
235
                        'last_item' => 0,
236
                    ];
237
                    $this->last_item_seen = 0;
238
                    $this->lp_view_session_id = $session_id;
239
                    $this->lp_view_id = Database::insert($lp_table, $params);
240
                    if (!empty($this->lp_view_id)) {
241
                        $sql = "UPDATE $lp_table SET id = iid
242
                                WHERE iid = ".$this->lp_view_id;
243
                        Database::query($sql);
244
                    }
245
                }
246
247
                // Initialise items.
248
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
249
                $sql = "SELECT * FROM $lp_item_table
250
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
251
                        ORDER BY parent_item_id, display_order";
252
                $res = Database::query($sql);
253
254
                if ($debug) {
255
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
256
                    error_log('-- Start while--');
257
                }
258
259
                $lp_item_id_list = [];
260
                while ($row = Database::fetch_array($res)) {
261
                    $lp_item_id_list[] = $row['iid'];
262
                    switch ($this->type) {
263
                        case 3: //aicc
264
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
265
                            if (is_object($oItem)) {
266
                                $my_item_id = $oItem->get_id();
267
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
268
                                $oItem->set_prevent_reinit($this->prevent_reinit);
269
                                // Don't use reference here as the next loop will make the pointed object change.
270
                                $this->items[$my_item_id] = $oItem;
271
                                $this->refs_list[$oItem->ref] = $my_item_id;
272
                                if ($debug) {
273
                                    error_log(
274
                                        'learnpath::__construct() - '.
275
                                        'aicc object with id '.$my_item_id.
276
                                        ' set in items[]',
277
                                        0
278
                                    );
279
                                }
280
                            }
281
                            break;
282
                        case 2:
283
                            $oItem = new scormItem('db', $row['iid'], $course_id);
284
                            if (is_object($oItem)) {
285
                                $my_item_id = $oItem->get_id();
286
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
287
                                $oItem->set_prevent_reinit($this->prevent_reinit);
288
                                // Don't use reference here as the next loop will make the pointed object change.
289
                                $this->items[$my_item_id] = $oItem;
290
                                $this->refs_list[$oItem->ref] = $my_item_id;
291
                                if ($debug) {
292
                                    error_log('object with id '.$my_item_id.' set in items[]');
293
                                }
294
                            }
295
                            break;
296
                        case 1:
297
                        default:
298
                            if ($debug) {
299
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
300
                            }
301
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
302
303
                            if ($debug) {
304
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
305
                            }
306
                            if (is_object($oItem)) {
307
                                $my_item_id = $oItem->get_id();
308
                                // Moved down to when we are sure the item_view exists.
309
                                //$oItem->set_lp_view($this->lp_view_id);
310
                                $oItem->set_prevent_reinit($this->prevent_reinit);
311
                                // Don't use reference here as the next loop will make the pointed object change.
312
                                $this->items[$my_item_id] = $oItem;
313
                                $this->refs_list[$my_item_id] = $my_item_id;
314
                                if ($debug) {
315
                                    error_log(
316
                                        'learnpath::__construct() '.__LINE__.
317
                                        ' - object with id '.$my_item_id.' set in items[]'
318
                                    );
319
                                }
320
                            }
321
                            break;
322
                    }
323
324
                    // Setting the object level with variable $this->items[$i][parent]
325
                    foreach ($this->items as $itemLPObject) {
326
                        $level = self::get_level_for_item(
327
                            $this->items,
328
                            $itemLPObject->db_id
329
                        );
330
                        $itemLPObject->level = $level;
331
                    }
332
333
                    // Setting the view in the item object.
334
                    if (is_object($this->items[$row['iid']])) {
335
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
336
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
337
                            $this->items[$row['iid']]->current_start_time = 0;
338
                            $this->items[$row['iid']]->current_stop_time = 0;
339
                        }
340
                    }
341
                }
342
343
                if ($debug) {
344
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
345
                }
346
347
                if (!empty($lp_item_id_list)) {
348
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
349
                    if (!empty($lp_item_id_list_to_string)) {
350
                        // Get last viewing vars.
351
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
352
                        // This query should only return one or zero result.
353
                        $sql = "SELECT lp_item_id, status
354
                                FROM $itemViewTable
355
                                WHERE
356
                                    c_id = $course_id AND
357
                                    lp_view_id = ".$this->lp_view_id." AND
358
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
359
                                ORDER BY view_count DESC ";
360
361
                        if ($debug) {
362
                            error_log(
363
                                'learnpath::__construct() - Selecting item_views: '.$sql,
364
                                0
365
                            );
366
                        }
367
368
                        $status_list = [];
369
                        $res = Database::query($sql);
370
                        while ($row = Database:: fetch_array($res)) {
371
                            $status_list[$row['lp_item_id']] = $row['status'];
372
                        }
373
374
                        foreach ($lp_item_id_list as $item_id) {
375
                            if (isset($status_list[$item_id])) {
376
                                $status = $status_list[$item_id];
377
                                if (is_object($this->items[$item_id])) {
378
                                    $this->items[$item_id]->set_status($status);
379
                                    if (empty($status)) {
380
                                        $this->items[$item_id]->set_status(
381
                                            $this->default_status
382
                                        );
383
                                    }
384
                                }
385
                            } else {
386
                                if (!api_is_invitee()) {
387
                                    if (is_object($this->items[$item_id])) {
388
                                        $this->items[$item_id]->set_status(
389
                                            $this->default_status
390
                                        );
391
                                    }
392
393
                                    if (!empty($this->lp_view_id)) {
394
                                        // Add that row to the lp_item_view table so that
395
                                        // we have something to show in the stats page.
396
                                        $params = [
397
                                            'c_id' => $course_id,
398
                                            'lp_item_id' => $item_id,
399
                                            'lp_view_id' => $this->lp_view_id,
400
                                            'view_count' => 1,
401
                                            'status' => 'not attempted',
402
                                            'start_time' => time(),
403
                                            'total_time' => 0,
404
                                            'score' => 0,
405
                                        ];
406
                                        $insertId = Database::insert($itemViewTable, $params);
407
408
                                        if ($insertId) {
409
                                            $sql = "UPDATE $itemViewTable SET id = iid
410
                                                    WHERE iid = $insertId";
411
                                            Database::query($sql);
412
                                        }
413
414
                                        $this->items[$item_id]->set_lp_view(
415
                                            $this->lp_view_id,
416
                                            $course_id
417
                                        );
418
                                    }
419
                                }
420
                            }
421
                        }
422
                    }
423
                }
424
425
                $this->ordered_items = self::get_flat_ordered_items_list(
426
                    $this->get_id(),
427
                    0,
428
                    $course_id
429
                );
430
                $this->max_ordered_items = 0;
431
                foreach ($this->ordered_items as $index => $dummy) {
432
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
433
                        $this->max_ordered_items = $index;
434
                    }
435
                }
436
                // TODO: Define the current item better.
437
                $this->first();
438
                if ($debug) {
439
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
440
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
441
                }
442
            } else {
443
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
444
            }
445
        }
446
    }
447
448
    /**
449
     * @return string
450
     */
451
    public function getCourseCode()
452
    {
453
        return $this->cc;
454
    }
455
456
    /**
457
     * @return int
458
     */
459
    public function get_course_int_id()
460
    {
461
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
462
    }
463
464
    /**
465
     * @param $course_id
466
     *
467
     * @return int
468
     */
469
    public function set_course_int_id($course_id)
470
    {
471
        return $this->course_int_id = (int) $course_id;
472
    }
473
474
    /**
475
     * Function rewritten based on old_add_item() from Yannick Warnier.
476
     * Due the fact that users can decide where the item should come, I had to overlook this function and
477
     * I found it better to rewrite it. Old function is still available.
478
     * Added also the possibility to add a description.
479
     *
480
     * @param int    $parent
481
     * @param int    $previous
482
     * @param string $type
483
     * @param int    $id               resource ID (ref)
484
     * @param string $title
485
     * @param string $description
486
     * @param int    $prerequisites
487
     * @param int    $max_time_allowed
488
     * @param int    $userId
489
     *
490
     * @return int
491
     */
492
    public function add_item(
493
        $parent,
494
        $previous,
495
        $type = 'dir',
496
        $id,
497
        $title,
498
        $description,
499
        $prerequisites = 0,
500
        $max_time_allowed = 0,
501
        $userId = 0
502
    ) {
503
        $course_id = $this->course_info['real_id'];
504
        if ($this->debug > 0) {
505
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
506
        }
507
        if (empty($course_id)) {
508
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
509
            $this->course_info = api_get_course_info($this->cc);
510
            $course_id = $this->course_info['real_id'];
511
        }
512
        $userId = empty($userId) ? api_get_user_id() : $userId;
513
        $sessionId = api_get_session_id();
514
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
515
        $_course = $this->course_info;
516
        $parent = (int) $parent;
517
        $previous = (int) $previous;
518
        $id = (int) $id;
519
        $max_time_allowed = htmlentities($max_time_allowed);
520
        if (empty($max_time_allowed)) {
521
            $max_time_allowed = 0;
522
        }
523
        $sql = "SELECT COUNT(iid) AS num
524
                FROM $tbl_lp_item
525
                WHERE
526
                    c_id = $course_id AND
527
                    lp_id = ".$this->get_id()." AND
528
                    parent_item_id = ".$parent;
529
530
        $res_count = Database::query($sql);
531
        $row = Database::fetch_array($res_count);
532
        $num = $row['num'];
533
534
        $tmp_previous = 0;
535
        $display_order = 0;
536
        $next = 0;
537
        if ($num > 0) {
538
            if (empty($previous)) {
539
                $sql = "SELECT iid, next_item_id, display_order
540
                        FROM $tbl_lp_item
541
                        WHERE
542
                            c_id = $course_id AND
543
                            lp_id = ".$this->get_id()." AND
544
                            parent_item_id = $parent AND
545
                            previous_item_id = 0 OR
546
                            previous_item_id = $parent";
547
                $result = Database::query($sql);
548
                $row = Database::fetch_array($result);
549
                if ($row) {
550
                    $next = $row['iid'];
551
                }
552
            } else {
553
                $previous = (int) $previous;
554
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
555
						FROM $tbl_lp_item
556
                        WHERE
557
                            c_id = $course_id AND
558
                            lp_id = ".$this->get_id()." AND
559
                            id = $previous";
560
                $result = Database::query($sql);
561
                $row = Database::fetch_array($result);
562
                if ($row) {
563
                    $tmp_previous = $row['iid'];
564
                    $next = $row['next_item_id'];
565
                    $display_order = $row['display_order'];
566
                }
567
            }
568
        }
569
570
        $id = (int) $id;
571
        $typeCleaned = Database::escape_string($type);
572
        $max_score = 100;
573
        if ($type === 'quiz') {
574
            $sql = 'SELECT SUM(ponderation)
575
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
576
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
577
                    ON
578
                        quiz_question.id = quiz_rel_question.question_id AND
579
                        quiz_question.c_id = quiz_rel_question.c_id
580
                    WHERE
581
                        quiz_rel_question.exercice_id = '.$id." AND
582
                        quiz_question.c_id = $course_id AND
583
                        quiz_rel_question.c_id = $course_id ";
584
            $rsQuiz = Database::query($sql);
585
            $max_score = Database::result($rsQuiz, 0, 0);
586
587
            // Disabling the exercise if we add it inside a LP
588
            $exercise = new Exercise($course_id);
589
            $exercise->read($id);
590
            $exercise->disable();
591
            $exercise->save();
592
        }
593
594
        $params = [
595
            'c_id' => $course_id,
596
            'lp_id' => $this->get_id(),
597
            'item_type' => $typeCleaned,
598
            'ref' => '',
599
            'title' => $title,
600
            'description' => $description,
601
            'path' => $id,
602
            'max_score' => $max_score,
603
            'parent_item_id' => $parent,
604
            'previous_item_id' => $previous,
605
            'next_item_id' => (int) $next,
606
            'display_order' => $display_order + 1,
607
            'prerequisite' => $prerequisites,
608
            'max_time_allowed' => $max_time_allowed,
609
            'min_score' => 0,
610
            'launch_data' => '',
611
        ];
612
613
        if ($prerequisites != 0) {
614
            $params['prerequisite'] = $prerequisites;
615
        }
616
617
        $new_item_id = Database::insert($tbl_lp_item, $params);
618
        if ($new_item_id) {
619
            if ($this->debug > 2) {
620
                error_log('Inserting dir/chapter: '.$new_item_id, 0);
621
            }
622
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
623
            Database::query($sql);
624
625
            if (!empty($next)) {
626
                $sql = "UPDATE $tbl_lp_item
627
                        SET previous_item_id = $new_item_id 
628
                        WHERE c_id = $course_id AND id = $next";
629
                Database::query($sql);
630
            }
631
632
            // Update the item that should be before the new item.
633
            if (!empty($tmp_previous)) {
634
                $sql = "UPDATE $tbl_lp_item
635
                        SET next_item_id = $new_item_id
636
                        WHERE c_id = $course_id AND id = $tmp_previous";
637
                Database::query($sql);
638
            }
639
640
            // Update all the items after the new item.
641
            $sql = "UPDATE $tbl_lp_item
642
                        SET display_order = display_order + 1
643
                    WHERE
644
                        c_id = $course_id AND
645
                        lp_id = ".$this->get_id()." AND
646
                        iid <> $new_item_id AND
647
                        parent_item_id = $parent AND
648
                        display_order > $display_order";
649
            Database::query($sql);
650
651
            // Update the item that should come after the new item.
652
            $sql = "UPDATE $tbl_lp_item
653
                    SET ref = $new_item_id
654
                    WHERE c_id = $course_id AND iid = $new_item_id";
655
            Database::query($sql);
656
657
            // Upload audio.
658
            if (!empty($_FILES['mp3']['name'])) {
659
                // Create the audio folder if it does not exist yet.
660
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
661
                if (!is_dir($filepath.'audio')) {
662
                    mkdir(
663
                        $filepath.'audio',
664
                        api_get_permissions_for_new_directories()
665
                    );
666
                    $audio_id = add_document(
667
                        $_course,
668
                        '/audio',
669
                        'folder',
670
                        0,
671
                        'audio',
672
                        '',
673
                        0,
674
                        true,
675
                        null,
676
                        $sessionId,
677
                        $userId
678
                    );
679
                    api_item_property_update(
680
                        $_course,
681
                        TOOL_DOCUMENT,
682
                        $audio_id,
683
                        'FolderCreated',
684
                        $userId,
685
                        null,
686
                        null,
687
                        null,
688
                        null,
689
                        $sessionId
690
                    );
691
                    api_item_property_update(
692
                        $_course,
693
                        TOOL_DOCUMENT,
694
                        $audio_id,
695
                        'invisible',
696
                        $userId,
697
                        null,
698
                        null,
699
                        null,
700
                        null,
701
                        $sessionId
702
                    );
703
                }
704
705
                $file_path = handle_uploaded_document(
706
                    $_course,
707
                    $_FILES['mp3'],
708
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
709
                    '/audio',
710
                    $userId,
711
                    '',
712
                    '',
713
                    '',
714
                    '',
715
                    false
716
                );
717
718
                // Getting the filename only.
719
                $file_components = explode('/', $file_path);
720
                $file = $file_components[count($file_components) - 1];
721
722
                // Store the mp3 file in the lp_item table.
723
                $sql = "UPDATE $tbl_lp_item SET
724
                          audio = '".Database::escape_string($file)."'
725
                        WHERE iid = '".intval($new_item_id)."'";
726
                Database::query($sql);
727
            }
728
        }
729
730
        return $new_item_id;
731
    }
732
733
    /**
734
     * Static admin function allowing addition of a learnpath to a course.
735
     *
736
     * @param string $courseCode
737
     * @param string $name
738
     * @param string $description
739
     * @param string $learnpath
740
     * @param string $origin
741
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
742
     * @param string $publicated_on
743
     * @param string $expired_on
744
     * @param int    $categoryId
745
     * @param int    $userId
746
     *
747
     * @return int The new learnpath ID on success, 0 on failure
748
     */
749
    public static function add_lp(
750
        $courseCode,
751
        $name,
752
        $description = '',
753
        $learnpath = 'guess',
754
        $origin = 'zip',
755
        $zipname = '',
756
        $publicated_on = '',
757
        $expired_on = '',
758
        $categoryId = 0,
759
        $userId = 0
760
    ) {
761
        global $charset;
762
763
        if (!empty($courseCode)) {
764
            $courseInfo = api_get_course_info($courseCode);
765
            $course_id = $courseInfo['real_id'];
766
        } else {
767
            $course_id = api_get_course_int_id();
768
            $courseInfo = api_get_course_info();
769
        }
770
771
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
772
        // Check course code exists.
773
        // Check lp_name doesn't exist, otherwise append something.
774
        $i = 0;
775
        $name = Database::escape_string($name);
776
        $categoryId = (int) $categoryId;
777
778
        // Session id.
779
        $session_id = api_get_session_id();
780
        $userId = empty($userId) ? api_get_user_id() : $userId;
781
        $check_name = "SELECT * FROM $tbl_lp
782
                       WHERE c_id = $course_id AND name = '$name'";
783
784
        $res_name = Database::query($check_name);
785
786
        if (empty($publicated_on)) {
787
            $publicated_on = null;
788
        } else {
789
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
790
        }
791
792
        if (empty($expired_on)) {
793
            $expired_on = null;
794
        } else {
795
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
796
        }
797
798
        while (Database::num_rows($res_name)) {
799
            // There is already one such name, update the current one a bit.
800
            $i++;
801
            $name = $name.' - '.$i;
802
            $check_name = "SELECT * FROM $tbl_lp 
803
                           WHERE c_id = $course_id AND name = '$name'";
804
            $res_name = Database::query($check_name);
805
        }
806
        // New name does not exist yet; keep it.
807
        // Escape description.
808
        // Kevin: added htmlentities().
809
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
810
        $type = 1;
811
        switch ($learnpath) {
812
            case 'guess':
813
                break;
814
            case 'dokeos':
815
            case 'chamilo':
816
                $type = 1;
817
                break;
818
            case 'aicc':
819
                break;
820
        }
821
822
        switch ($origin) {
823
            case 'zip':
824
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
825
                break;
826
            case 'manual':
827
            default:
828
                $get_max = "SELECT MAX(display_order) 
829
                            FROM $tbl_lp WHERE c_id = $course_id";
830
                $res_max = Database::query($get_max);
831
                if (Database::num_rows($res_max) < 1) {
832
                    $dsp = 1;
833
                } else {
834
                    $row = Database::fetch_array($res_max);
835
                    $dsp = $row[0] + 1;
836
                }
837
838
                $params = [
839
                    'c_id' => $course_id,
840
                    'lp_type' => $type,
841
                    'name' => $name,
842
                    'description' => $description,
843
                    'path' => '',
844
                    'default_view_mod' => 'embedded',
845
                    'default_encoding' => 'UTF-8',
846
                    'display_order' => $dsp,
847
                    'content_maker' => 'Chamilo',
848
                    'content_local' => 'local',
849
                    'js_lib' => '',
850
                    'session_id' => $session_id,
851
                    'created_on' => api_get_utc_datetime(),
852
                    'modified_on' => api_get_utc_datetime(),
853
                    'publicated_on' => $publicated_on,
854
                    'expired_on' => $expired_on,
855
                    'category_id' => $categoryId,
856
                    'force_commit' => 0,
857
                    'content_license' => '',
858
                    'debug' => 0,
859
                    'theme' => '',
860
                    'preview_image' => '',
861
                    'author' => '',
862
                    'prerequisite' => 0,
863
                    'hide_toc_frame' => 0,
864
                    'seriousgame_mode' => 0,
865
                    'autolaunch' => 0,
866
                    'max_attempts' => 0,
867
                    'subscribe_users' => 0,
868
                    'accumulate_scorm_time' => 1,
869
                ];
870
                $id = Database::insert($tbl_lp, $params);
871
872
                if ($id > 0) {
873
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
874
                    Database::query($sql);
875
876
                    // Insert into item_property.
877
                    api_item_property_update(
878
                        $courseInfo,
879
                        TOOL_LEARNPATH,
880
                        $id,
881
                        'LearnpathAdded',
882
                        $userId
883
                    );
884
                    api_set_default_visibility(
885
                        $id,
886
                        TOOL_LEARNPATH,
887
                        0,
888
                        $courseInfo,
889
                        $session_id,
890
                        $userId
891
                    );
892
893
                    return $id;
894
                }
895
                break;
896
        }
897
    }
898
899
    /**
900
     * Auto completes the parents of an item in case it's been completed or passed.
901
     *
902
     * @param int $item Optional ID of the item from which to look for parents
903
     */
904
    public function autocomplete_parents($item)
905
    {
906
        $debug = $this->debug;
907
908
        if ($debug) {
909
            error_log('Learnpath::autocomplete_parents()');
910
        }
911
912
        if (empty($item)) {
913
            $item = $this->current;
914
        }
915
916
        $currentItem = $this->getItem($item);
917
        if ($currentItem) {
918
            $parent_id = $currentItem->get_parent();
919
            $parent = $this->getItem($parent_id);
920
            if ($parent) {
921
                // if $item points to an object and there is a parent.
922
                if ($debug) {
923
                    error_log(
924
                        'Autocompleting parent of item '.$item.' '.
925
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
926
                        0
927
                    );
928
                }
929
930
                // New experiment including failed and browsed in completed status.
931
                //$current_status = $currentItem->get_status();
932
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
933
                // Fixes chapter auto complete
934
                if (true) {
935
                    // If the current item is completed or passes or succeeded.
936
                    $updateParentStatus = true;
937
                    if ($debug) {
938
                        error_log('Status of current item is alright');
939
                    }
940
941
                    foreach ($parent->get_children() as $childItemId) {
942
                        $childItem = $this->getItem($childItemId);
943
944
                        // If children was not set try to get the info
945
                        if (empty($childItem->db_item_view_id)) {
946
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
947
                        }
948
949
                        // Check all his brothers (parent's children) for completion status.
950
                        if ($childItemId != $item) {
951
                            if ($debug) {
952
                                error_log(
953
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
954
                                    0
955
                                );
956
                            }
957
                            // Trying completing parents of failed and browsed items as well.
958
                            if ($childItem->status_is(
959
                                [
960
                                    'completed',
961
                                    'passed',
962
                                    'succeeded',
963
                                    'browsed',
964
                                    'failed',
965
                                ]
966
                            )
967
                            ) {
968
                                // Keep completion status to true.
969
                                continue;
970
                            } else {
971
                                if ($debug > 2) {
972
                                    error_log(
973
                                        '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,
974
                                        0
975
                                    );
976
                                }
977
                                $updateParentStatus = false;
978
                                break;
979
                            }
980
                        }
981
                    }
982
983
                    if ($updateParentStatus) {
984
                        // If all the children were completed:
985
                        $parent->set_status('completed');
986
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
987
                        // Force the status to "completed"
988
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
989
                        $this->update_queue[$parent->get_id()] = 'completed';
990
                        if ($debug) {
991
                            error_log(
992
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
993
                                print_r($this->update_queue, 1),
994
                                0
995
                            );
996
                        }
997
                        // Recursive call.
998
                        $this->autocomplete_parents($parent->get_id());
999
                    }
1000
                }
1001
            } else {
1002
                if ($debug) {
1003
                    error_log("Parent #$parent_id does not exists");
1004
                }
1005
            }
1006
        } else {
1007
            if ($debug) {
1008
                error_log("#$item is an item that doesn't have parents");
1009
            }
1010
        }
1011
    }
1012
1013
    /**
1014
     * Closes the current resource.
1015
     *
1016
     * Stops the timer
1017
     * Saves into the database if required
1018
     * Clears the current resource data from this object
1019
     *
1020
     * @return bool True on success, false on failure
1021
     */
1022
    public function close()
1023
    {
1024
        if ($this->debug > 0) {
1025
            error_log('In learnpath::close()', 0);
1026
        }
1027
        if (empty($this->lp_id)) {
1028
            $this->error = 'Trying to close this learnpath but no ID is set';
1029
1030
            return false;
1031
        }
1032
        $this->current_time_stop = time();
1033
        $this->ordered_items = [];
1034
        $this->index = 0;
1035
        unset($this->lp_id);
1036
        //unset other stuff
1037
        return true;
1038
    }
1039
1040
    /**
1041
     * Static admin function allowing removal of a learnpath.
1042
     *
1043
     * @param array  $courseInfo
1044
     * @param int    $id         Learnpath ID
1045
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1046
     *
1047
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1048
     */
1049
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1050
    {
1051
        $course_id = api_get_course_int_id();
1052
        if (!empty($courseInfo)) {
1053
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1054
        }
1055
1056
        // TODO: Implement a way of getting this to work when the current object is not set.
1057
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1058
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1059
        if (!empty($id) && ($id != $this->lp_id)) {
1060
            return false;
1061
        }
1062
1063
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1064
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1065
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1066
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1067
1068
        // Delete lp item id.
1069
        foreach ($this->items as $lpItemId => $dummy) {
1070
            $sql = "DELETE FROM $lp_item_view
1071
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1072
            Database::query($sql);
1073
        }
1074
1075
        // Proposed by Christophe (nickname: clefevre)
1076
        $sql = "DELETE FROM $lp_item
1077
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1078
        Database::query($sql);
1079
1080
        $sql = "DELETE FROM $lp_view 
1081
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1082
        Database::query($sql);
1083
1084
        self::toggle_publish($this->lp_id, 'i');
1085
1086
        if ($this->type == 2 || $this->type == 3) {
1087
            // This is a scorm learning path, delete the files as well.
1088
            $sql = "SELECT path FROM $lp
1089
                    WHERE iid = ".$this->lp_id;
1090
            $res = Database::query($sql);
1091
            if (Database::num_rows($res) > 0) {
1092
                $row = Database::fetch_array($res);
1093
                $path = $row['path'];
1094
                $sql = "SELECT id FROM $lp
1095
                        WHERE 
1096
                            c_id = $course_id AND
1097
                            path = '$path' AND 
1098
                            iid != ".$this->lp_id;
1099
                $res = Database::query($sql);
1100
                if (Database::num_rows($res) > 0) {
1101
                    // Another learning path uses this directory, so don't delete it.
1102
                    if ($this->debug > 2) {
1103
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1104
                    }
1105
                } else {
1106
                    // No other LP uses that directory, delete it.
1107
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1108
                    // The absolute system path for this course.
1109
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1110
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1111
                        if ($this->debug > 2) {
1112
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1113
                        }
1114
                        // Proposed by Christophe (clefevre).
1115
                        if (strcmp(substr($path, -2), "/.") == 0) {
1116
                            $path = substr($path, 0, -1); // Remove "." at the end.
1117
                        }
1118
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1119
                        rmdirr($course_scorm_dir.$path);
1120
                    }
1121
                }
1122
            }
1123
        }
1124
1125
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1126
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1127
        // Delete tools
1128
        $sql = "DELETE FROM $tbl_tool
1129
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1130
        Database::query($sql);
1131
1132
        $sql = "DELETE FROM $lp 
1133
                WHERE iid = ".$this->lp_id;
1134
        Database::query($sql);
1135
        // Updates the display order of all lps.
1136
        $this->update_display_order();
1137
1138
        api_item_property_update(
1139
            api_get_course_info(),
1140
            TOOL_LEARNPATH,
1141
            $this->lp_id,
1142
            'delete',
1143
            api_get_user_id()
1144
        );
1145
1146
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1147
            api_get_course_id(),
1148
            4,
1149
            $id,
1150
            api_get_session_id()
1151
        );
1152
1153
        if ($link_info !== false) {
1154
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1155
        }
1156
1157
        if (api_get_setting('search_enabled') == 'true') {
1158
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1159
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1160
        }
1161
    }
1162
1163
    /**
1164
     * Removes all the children of one item - dangerous!
1165
     *
1166
     * @param int $id Element ID of which children have to be removed
1167
     *
1168
     * @return int Total number of children removed
1169
     */
1170
    public function delete_children_items($id)
1171
    {
1172
        $course_id = $this->course_info['real_id'];
1173
        if ($this->debug > 0) {
1174
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1175
        }
1176
        $num = 0;
1177
        if (empty($id) || $id != strval(intval($id))) {
1178
            return false;
1179
        }
1180
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1181
        $sql = "SELECT * FROM $lp_item 
1182
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1183
        $res = Database::query($sql);
1184
        while ($row = Database::fetch_array($res)) {
1185
            $num += $this->delete_children_items($row['iid']);
1186
            $sql = "DELETE FROM $lp_item 
1187
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1188
            Database::query($sql);
1189
            $num++;
1190
        }
1191
1192
        return $num;
1193
    }
1194
1195
    /**
1196
     * Removes an item from the current learnpath.
1197
     *
1198
     * @param int $id Elem ID (0 if first)
1199
     *
1200
     * @return int Number of elements moved
1201
     *
1202
     * @todo implement resource removal
1203
     */
1204
    public function delete_item($id)
1205
    {
1206
        $course_id = api_get_course_int_id();
1207
        if ($this->debug > 0) {
1208
            error_log('In learnpath::delete_item()', 0);
1209
        }
1210
        // TODO: Implement the resource removal.
1211
        if (empty($id) || $id != strval(intval($id))) {
1212
            return false;
1213
        }
1214
        // First select item to get previous, next, and display order.
1215
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1216
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1217
        $res_sel = Database::query($sql_sel);
1218
        if (Database::num_rows($res_sel) < 1) {
1219
            return false;
1220
        }
1221
        $row = Database::fetch_array($res_sel);
1222
        $previous = $row['previous_item_id'];
1223
        $next = $row['next_item_id'];
1224
        $display = $row['display_order'];
1225
        $parent = $row['parent_item_id'];
1226
        $lp = $row['lp_id'];
1227
        // Delete children items.
1228
        $num = $this->delete_children_items($id);
1229
        if ($this->debug > 2) {
1230
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1231
        }
1232
        // Now delete the item.
1233
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1234
        if ($this->debug > 2) {
1235
            error_log('Deleting item: '.$sql_del, 0);
1236
        }
1237
        Database::query($sql_del);
1238
        // Now update surrounding items.
1239
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1240
                    WHERE iid = $previous";
1241
        Database::query($sql_upd);
1242
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1243
                    WHERE iid = $next";
1244
        Database::query($sql_upd);
1245
        // Now update all following items with new display order.
1246
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1247
                    WHERE 
1248
                        c_id = $course_id AND 
1249
                        lp_id = $lp AND 
1250
                        parent_item_id = $parent AND 
1251
                        display_order > $display";
1252
        Database::query($sql_all);
1253
1254
        //Removing prerequisites since the item will not longer exist
1255
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1256
                    WHERE c_id = $course_id AND prerequisite = $id";
1257
        Database::query($sql_all);
1258
1259
        // Remove from search engine if enabled.
1260
        if (api_get_setting('search_enabled') === 'true') {
1261
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1262
            $sql = 'SELECT * FROM %s 
1263
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1264
                    LIMIT 1';
1265
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1266
            $res = Database::query($sql);
1267
            if (Database::num_rows($res) > 0) {
1268
                $row2 = Database::fetch_array($res);
1269
                $di = new ChamiloIndexer();
1270
                $di->remove_document($row2['search_did']);
1271
            }
1272
            $sql = 'DELETE FROM %s 
1273
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1274
                    LIMIT 1';
1275
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1276
            Database::query($sql);
1277
        }
1278
    }
1279
1280
    /**
1281
     * Updates an item's content in place.
1282
     *
1283
     * @param int    $id               Element ID
1284
     * @param int    $parent           Parent item ID
1285
     * @param int    $previous         Previous item ID
1286
     * @param string $title            Item title
1287
     * @param string $description      Item description
1288
     * @param string $prerequisites    Prerequisites (optional)
1289
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1290
     * @param int    $max_time_allowed
1291
     * @param string $url
1292
     *
1293
     * @return bool True on success, false on error
1294
     */
1295
    public function edit_item(
1296
        $id,
1297
        $parent,
1298
        $previous,
1299
        $title,
1300
        $description,
1301
        $prerequisites = '0',
1302
        $audio = [],
1303
        $max_time_allowed = 0,
1304
        $url = ''
1305
    ) {
1306
        $course_id = api_get_course_int_id();
1307
        $_course = api_get_course_info();
1308
1309
        if ($this->debug > 0) {
1310
            error_log('In learnpath::edit_item()', 0);
1311
        }
1312
        if (empty($max_time_allowed)) {
1313
            $max_time_allowed = 0;
1314
        }
1315
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1316
            return false;
1317
        }
1318
1319
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1320
        $sql = "SELECT * FROM $tbl_lp_item 
1321
                WHERE iid = $id";
1322
        $res_select = Database::query($sql);
1323
        $row_select = Database::fetch_array($res_select);
1324
        $audio_update_sql = '';
1325
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1326
            // Create the audio folder if it does not exist yet.
1327
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1328
            if (!is_dir($filepath.'audio')) {
1329
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1330
                $audio_id = add_document(
1331
                    $_course,
1332
                    '/audio',
1333
                    'folder',
1334
                    0,
1335
                    'audio'
1336
                );
1337
                api_item_property_update(
1338
                    $_course,
1339
                    TOOL_DOCUMENT,
1340
                    $audio_id,
1341
                    'FolderCreated',
1342
                    api_get_user_id(),
1343
                    null,
1344
                    null,
1345
                    null,
1346
                    null,
1347
                    api_get_session_id()
1348
                );
1349
                api_item_property_update(
1350
                    $_course,
1351
                    TOOL_DOCUMENT,
1352
                    $audio_id,
1353
                    'invisible',
1354
                    api_get_user_id(),
1355
                    null,
1356
                    null,
1357
                    null,
1358
                    null,
1359
                    api_get_session_id()
1360
                );
1361
            }
1362
1363
            // Upload file in documents.
1364
            $pi = pathinfo($audio['name']);
1365
            if ($pi['extension'] == 'mp3') {
1366
                $c_det = api_get_course_info($this->cc);
1367
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1368
                $path = handle_uploaded_document(
1369
                    $c_det,
1370
                    $audio,
1371
                    $bp,
1372
                    '/audio',
1373
                    api_get_user_id(),
1374
                    0,
1375
                    null,
1376
                    0,
1377
                    'rename',
1378
                    false,
1379
                    0
1380
                );
1381
                $path = substr($path, 7);
1382
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1383
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1384
            }
1385
        }
1386
1387
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1388
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1389
1390
        // TODO: htmlspecialchars to be checked for encoding related problems.
1391
        if ($same_parent && $same_previous) {
1392
            // Only update title and description.
1393
            $sql = "UPDATE $tbl_lp_item
1394
                    SET title = '".Database::escape_string($title)."',
1395
                        prerequisite = '".$prerequisites."',
1396
                        description = '".Database::escape_string($description)."'
1397
                        ".$audio_update_sql.",
1398
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1399
                    WHERE iid = $id";
1400
            Database::query($sql);
1401
        } else {
1402
            $old_parent = $row_select['parent_item_id'];
1403
            $old_previous = $row_select['previous_item_id'];
1404
            $old_next = $row_select['next_item_id'];
1405
            $old_order = $row_select['display_order'];
1406
            $old_prerequisite = $row_select['prerequisite'];
1407
            $old_max_time_allowed = $row_select['max_time_allowed'];
1408
1409
            /* BEGIN -- virtually remove the current item id */
1410
            /* for the next and previous item it is like the current item doesn't exist anymore */
1411
            if ($old_previous != 0) {
1412
                // Next
1413
                $sql = "UPDATE $tbl_lp_item
1414
                        SET next_item_id = $old_next
1415
                        WHERE iid = $old_previous";
1416
                Database::query($sql);
1417
            }
1418
1419
            if (!empty($old_next)) {
1420
                // Previous
1421
                $sql = "UPDATE $tbl_lp_item
1422
                        SET previous_item_id = $old_previous
1423
                        WHERE iid = $old_next";
1424
                Database::query($sql);
1425
            }
1426
1427
            // display_order - 1 for every item with a display_order
1428
            // bigger then the display_order of the current item.
1429
            $sql = "UPDATE $tbl_lp_item
1430
                    SET display_order = display_order - 1
1431
                    WHERE
1432
                        c_id = $course_id AND
1433
                        display_order > $old_order AND
1434
                        lp_id = ".$this->lp_id." AND
1435
                        parent_item_id = $old_parent";
1436
            Database::query($sql);
1437
            /* END -- virtually remove the current item id */
1438
1439
            /* BEGIN -- update the current item id to his new location */
1440
            if ($previous == 0) {
1441
                // Select the data of the item that should come after the current item.
1442
                $sql = "SELECT id, display_order
1443
                        FROM $tbl_lp_item
1444
                        WHERE
1445
                            c_id = $course_id AND
1446
                            lp_id = ".$this->lp_id." AND
1447
                            parent_item_id = $parent AND
1448
                            previous_item_id = $previous";
1449
                $res_select_old = Database::query($sql);
1450
                $row_select_old = Database::fetch_array($res_select_old);
1451
1452
                // If the new parent didn't have children before.
1453
                if (Database::num_rows($res_select_old) == 0) {
1454
                    $new_next = 0;
1455
                    $new_order = 1;
1456
                } else {
1457
                    $new_next = $row_select_old['id'];
1458
                    $new_order = $row_select_old['display_order'];
1459
                }
1460
            } else {
1461
                // Select the data of the item that should come before the current item.
1462
                $sql = "SELECT next_item_id, display_order
1463
                        FROM $tbl_lp_item
1464
                        WHERE iid = $previous";
1465
                $res_select_old = Database::query($sql);
1466
                $row_select_old = Database::fetch_array($res_select_old);
1467
                $new_next = $row_select_old['next_item_id'];
1468
                $new_order = $row_select_old['display_order'] + 1;
1469
            }
1470
1471
            // TODO: htmlspecialchars to be checked for encoding related problems.
1472
            // Update the current item with the new data.
1473
            $sql = "UPDATE $tbl_lp_item
1474
                    SET
1475
                        title = '".Database::escape_string($title)."',
1476
                        description = '".Database::escape_string($description)."',
1477
                        parent_item_id = $parent,
1478
                        previous_item_id = $previous,
1479
                        next_item_id = $new_next,
1480
                        display_order = $new_order
1481
                        $audio_update_sql
1482
                    WHERE iid = $id";
1483
            Database::query($sql);
1484
1485
            if ($previous != 0) {
1486
                // Update the previous item's next_item_id.
1487
                $sql = "UPDATE $tbl_lp_item
1488
                        SET next_item_id = $id
1489
                        WHERE iid = $previous";
1490
                Database::query($sql);
1491
            }
1492
1493
            if (!empty($new_next)) {
1494
                // Update the next item's previous_item_id.
1495
                $sql = "UPDATE $tbl_lp_item
1496
                        SET previous_item_id = $id
1497
                        WHERE iid = $new_next";
1498
                Database::query($sql);
1499
            }
1500
1501
            if ($old_prerequisite != $prerequisites) {
1502
                $sql = "UPDATE $tbl_lp_item
1503
                        SET prerequisite = '$prerequisites'
1504
                        WHERE iid = $id";
1505
                Database::query($sql);
1506
            }
1507
1508
            if ($old_max_time_allowed != $max_time_allowed) {
1509
                // update max time allowed
1510
                $sql = "UPDATE $tbl_lp_item
1511
                        SET max_time_allowed = $max_time_allowed
1512
                        WHERE iid = $id";
1513
                Database::query($sql);
1514
            }
1515
1516
            // Update all the items with the same or a bigger display_order than the current item.
1517
            $sql = "UPDATE $tbl_lp_item
1518
                    SET display_order = display_order + 1
1519
                    WHERE
1520
                       c_id = $course_id AND
1521
                       lp_id = ".$this->get_id()." AND
1522
                       iid <> $id AND
1523
                       parent_item_id = $parent AND
1524
                       display_order >= $new_order";
1525
            Database::query($sql);
1526
        }
1527
1528
        if ($row_select['item_type'] == 'link') {
1529
            $link = new Link();
1530
            $linkId = $row_select['path'];
1531
            $link->updateLink($linkId, $url);
1532
        }
1533
    }
1534
1535
    /**
1536
     * Updates an item's prereq in place.
1537
     *
1538
     * @param int    $id              Element ID
1539
     * @param string $prerequisite_id Prerequisite Element ID
1540
     * @param int    $mastery_score   Prerequisite min score
1541
     * @param int    $max_score       Prerequisite max score
1542
     *
1543
     * @return bool True on success, false on error
1544
     */
1545
    public function edit_item_prereq(
1546
        $id,
1547
        $prerequisite_id,
1548
        $mastery_score = 0,
1549
        $max_score = 100
1550
    ) {
1551
        $course_id = api_get_course_int_id();
1552
        if ($this->debug > 0) {
1553
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1554
        }
1555
1556
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1557
            return false;
1558
        }
1559
1560
        $prerequisite_id = (int) $prerequisite_id;
1561
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1562
1563
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1564
            $mastery_score = 0;
1565
        }
1566
1567
        if (!is_numeric($max_score) || $max_score < 0) {
1568
            $max_score = 100;
1569
        }
1570
1571
        /*if ($mastery_score > $max_score) {
1572
            $max_score = $mastery_score;
1573
        }*/
1574
1575
        if (!is_numeric($prerequisite_id)) {
1576
            $prerequisite_id = 'NULL';
1577
        }
1578
1579
        $mastery_score = floatval($mastery_score);
1580
        $max_score = floatval($max_score);
1581
1582
        $sql = " UPDATE $tbl_lp_item
1583
                 SET
1584
                    prerequisite = $prerequisite_id ,
1585
                    prerequisite_min_score = $mastery_score ,
1586
                    prerequisite_max_score = $max_score
1587
                 WHERE iid = $id";
1588
        Database::query($sql);
1589
        // TODO: Update the item object (can be ignored for now because refreshed).
1590
        return true;
1591
    }
1592
1593
    /**
1594
     * Gets all the chapters belonging to the same parent as the item/chapter given
1595
     * Can also be called as abstract method.
1596
     *
1597
     * @param int $id Item ID
1598
     *
1599
     * @return array A list of all the "brother items" (or an empty array on failure)
1600
     */
1601
    public function getSiblingDirectories($id)
1602
    {
1603
        $course_id = api_get_course_int_id();
1604
        if ($this->debug > 0) {
1605
            error_log('In learnpath::getSiblingDirectories()', 0);
1606
        }
1607
1608
        if (empty($id) || $id != strval(intval($id))) {
1609
            return [];
1610
        }
1611
1612
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1613
        $sql_parent = "SELECT * FROM $lp_item
1614
                       WHERE iid = $id AND item_type='dir'";
1615
        $res_parent = Database::query($sql_parent);
1616
        if (Database::num_rows($res_parent) > 0) {
1617
            $row_parent = Database::fetch_array($res_parent);
1618
            $parent = $row_parent['parent_item_id'];
1619
            $sql = "SELECT * FROM $lp_item
1620
                    WHERE
1621
                        parent_item_id = $parent AND
1622
                        iid = $id AND
1623
                        item_type='dir'
1624
                    ORDER BY display_order";
1625
            $res_bros = Database::query($sql);
1626
1627
            $list = [];
1628
            while ($row_bro = Database::fetch_array($res_bros)) {
1629
                $list[] = $row_bro;
1630
            }
1631
1632
            return $list;
1633
        }
1634
1635
        return [];
1636
    }
1637
1638
    /**
1639
     * Gets all the items belonging to the same parent as the item given
1640
     * Can also be called as abstract method.
1641
     *
1642
     * @param int $id Item ID
1643
     *
1644
     * @return array A list of all the "brother items" (or an empty array on failure)
1645
     */
1646
    public function get_brother_items($id)
1647
    {
1648
        $course_id = api_get_course_int_id();
1649
        if ($this->debug > 0) {
1650
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1651
        }
1652
1653
        if (empty($id) || $id != strval(intval($id))) {
1654
            return [];
1655
        }
1656
1657
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1658
        $sql_parent = "SELECT * FROM $lp_item 
1659
                       WHERE iid = $id";
1660
        $res_parent = Database::query($sql_parent);
1661
        if (Database::num_rows($res_parent) > 0) {
1662
            $row_parent = Database::fetch_array($res_parent);
1663
            $parent = $row_parent['parent_item_id'];
1664
            $sql = "SELECT * FROM $lp_item 
1665
                    WHERE c_id = $course_id AND parent_item_id = $parent
1666
                    ORDER BY display_order";
1667
            $res_bros = Database::query($sql);
1668
            $list = [];
1669
            while ($row_bro = Database::fetch_array($res_bros)) {
1670
                $list[] = $row_bro;
1671
            }
1672
1673
            return $list;
1674
        }
1675
1676
        return [];
1677
    }
1678
1679
    /**
1680
     * Get the specific prefix index terms of this learning path.
1681
     *
1682
     * @param string $prefix
1683
     *
1684
     * @return array Array of terms
1685
     */
1686
    public function get_common_index_terms_by_prefix($prefix)
1687
    {
1688
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1689
        $terms = get_specific_field_values_list_by_prefix(
1690
            $prefix,
1691
            $this->cc,
1692
            TOOL_LEARNPATH,
1693
            $this->lp_id
1694
        );
1695
        $prefix_terms = [];
1696
        if (!empty($terms)) {
1697
            foreach ($terms as $term) {
1698
                $prefix_terms[] = $term['value'];
1699
            }
1700
        }
1701
1702
        return $prefix_terms;
1703
    }
1704
1705
    /**
1706
     * Gets the number of items currently completed.
1707
     *
1708
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1709
     *
1710
     * @return int The number of items currently completed
1711
     */
1712
    public function get_complete_items_count($failedStatusException = false)
1713
    {
1714
        if ($this->debug > 0) {
1715
            error_log('In learnpath::get_complete_items_count()', 0);
1716
        }
1717
        $i = 0;
1718
        $completedStatusList = [
1719
            'completed',
1720
            'passed',
1721
            'succeeded',
1722
            'browsed',
1723
        ];
1724
1725
        if (!$failedStatusException) {
1726
            $completedStatusList[] = 'failed';
1727
        }
1728
1729
        foreach ($this->items as $id => $dummy) {
1730
            // Trying failed and browsed considered "progressed" as well.
1731
            if ($this->items[$id]->status_is($completedStatusList) &&
1732
                $this->items[$id]->get_type() != 'dir'
1733
            ) {
1734
                $i++;
1735
            }
1736
        }
1737
1738
        return $i;
1739
    }
1740
1741
    /**
1742
     * Gets the current item ID.
1743
     *
1744
     * @return int The current learnpath item id
1745
     */
1746
    public function get_current_item_id()
1747
    {
1748
        $current = 0;
1749
        if ($this->debug > 0) {
1750
            error_log('In learnpath::get_current_item_id()', 0);
1751
        }
1752
        if (!empty($this->current)) {
1753
            $current = $this->current;
1754
        }
1755
        if ($this->debug > 2) {
1756
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1757
        }
1758
1759
        return $current;
1760
    }
1761
1762
    /**
1763
     * Force to get the first learnpath item id.
1764
     *
1765
     * @return int The current learnpath item id
1766
     */
1767
    public function get_first_item_id()
1768
    {
1769
        $current = 0;
1770
        if (is_array($this->ordered_items)) {
1771
            $current = $this->ordered_items[0];
1772
        }
1773
1774
        return $current;
1775
    }
1776
1777
    /**
1778
     * Gets the total number of items available for viewing in this SCORM.
1779
     *
1780
     * @return int The total number of items
1781
     */
1782
    public function get_total_items_count()
1783
    {
1784
        if ($this->debug > 0) {
1785
            error_log('In learnpath::get_total_items_count()', 0);
1786
        }
1787
1788
        return count($this->items);
1789
    }
1790
1791
    /**
1792
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1793
     *
1794
     * @return int The total no-chapters number of items
1795
     */
1796
    public function getTotalItemsCountWithoutDirs()
1797
    {
1798
        if ($this->debug > 0) {
1799
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1800
        }
1801
        $total = 0;
1802
        $typeListNotToCount = self::getChapterTypes();
1803
        foreach ($this->items as $temp2) {
1804
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1805
                $total++;
1806
            }
1807
        }
1808
1809
        return $total;
1810
    }
1811
1812
    /**
1813
     *  Sets the first element URL.
1814
     */
1815
    public function first()
1816
    {
1817
        if ($this->debug > 0) {
1818
            error_log('In learnpath::first()', 0);
1819
            error_log('$this->last_item_seen '.$this->last_item_seen);
1820
        }
1821
1822
        // Test if the last_item_seen exists and is not a dir.
1823
        if (count($this->ordered_items) == 0) {
1824
            $this->index = 0;
1825
        }
1826
1827
        if (!empty($this->last_item_seen) &&
1828
            !empty($this->items[$this->last_item_seen]) &&
1829
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1830
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1831
            //&& !$this->items[$this->last_item_seen]->is_done()
1832
        ) {
1833
            if ($this->debug > 2) {
1834
                error_log(
1835
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1836
                    $this->items[$this->last_item_seen]->get_type()
1837
                );
1838
            }
1839
            $index = -1;
1840
            foreach ($this->ordered_items as $myindex => $item_id) {
1841
                if ($item_id == $this->last_item_seen) {
1842
                    $index = $myindex;
1843
                    break;
1844
                }
1845
            }
1846
            if ($index == -1) {
1847
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1848
                if ($this->debug > 2) {
1849
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1850
                }
1851
1852
                return false;
1853
            } else {
1854
                $this->last = $this->last_item_seen;
1855
                $this->current = $this->last_item_seen;
1856
                $this->index = $index;
1857
            }
1858
        } else {
1859
            if ($this->debug > 2) {
1860
                error_log('In learnpath::first() - No last item seen', 0);
1861
            }
1862
            $index = 0;
1863
            // Loop through all ordered items and stop at the first item that is
1864
            // not a directory *and* that has not been completed yet.
1865
            while (!empty($this->ordered_items[$index]) &&
1866
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1867
                (
1868
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1869
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1870
                ) && $index < $this->max_ordered_items) {
1871
                $index++;
1872
            }
1873
1874
            $this->last = $this->current;
1875
            // current is
1876
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1877
            $this->index = $index;
1878
            if ($this->debug > 2) {
1879
                error_log('$index '.$index);
1880
                error_log('In learnpath::first() - No last item seen');
1881
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1882
            }
1883
        }
1884
        if ($this->debug > 2) {
1885
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1886
        }
1887
    }
1888
1889
    /**
1890
     * Gets the information about an item in a format usable as JavaScript to update
1891
     * the JS API by just printing this content into the <head> section of the message frame.
1892
     *
1893
     * @param int $item_id
1894
     *
1895
     * @return string
1896
     */
1897
    public function get_js_info($item_id = 0)
1898
    {
1899
        if ($this->debug > 0) {
1900
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1901
        }
1902
1903
        $info = '';
1904
        $item_id = intval($item_id);
1905
1906
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1907
            //if item is defined, return values from DB
1908
            $oItem = $this->items[$item_id];
1909
            $info .= '<script language="javascript">';
1910
            $info .= "top.set_score(".$oItem->get_score().");\n";
1911
            $info .= "top.set_max(".$oItem->get_max().");\n";
1912
            $info .= "top.set_min(".$oItem->get_min().");\n";
1913
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1914
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1915
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1916
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1917
            $info .= "top.set_flag_synchronized();";
1918
            $info .= '</script>';
1919
            if ($this->debug > 2) {
1920
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1921
            }
1922
1923
            return $info;
1924
        } else {
1925
            // If item_id is empty, just update to default SCORM data.
1926
            $info .= '<script language="javascript">';
1927
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1928
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1929
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1930
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1931
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1932
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1933
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1934
            $info .= "top.set_flag_synchronized();";
1935
            $info .= '</script>';
1936
            if ($this->debug > 2) {
1937
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1938
            }
1939
1940
            return $info;
1941
        }
1942
    }
1943
1944
    /**
1945
     * Gets the js library from the database.
1946
     *
1947
     * @return string The name of the javascript library to be used
1948
     */
1949
    public function get_js_lib()
1950
    {
1951
        $lib = '';
1952
        if (!empty($this->js_lib)) {
1953
            $lib = $this->js_lib;
1954
        }
1955
1956
        return $lib;
1957
    }
1958
1959
    /**
1960
     * Gets the learnpath database ID.
1961
     *
1962
     * @return int Learnpath ID in the lp table
1963
     */
1964
    public function get_id()
1965
    {
1966
        if (!empty($this->lp_id)) {
1967
            return $this->lp_id;
1968
        } else {
1969
            return 0;
1970
        }
1971
    }
1972
1973
    /**
1974
     * Gets the last element URL.
1975
     *
1976
     * @return string URL to load into the viewer
1977
     */
1978
    public function get_last()
1979
    {
1980
        if ($this->debug > 0) {
1981
            error_log('In learnpath::get_last()', 0);
1982
        }
1983
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1984
        if (count($this->ordered_items) > 0) {
1985
            $this->index = count($this->ordered_items) - 1;
1986
1987
            return $this->ordered_items[$this->index];
1988
        }
1989
1990
        return false;
1991
    }
1992
1993
    /**
1994
     * Gets the navigation bar for the learnpath display screen.
1995
     *
1996
     * @return string The HTML string to use as a navigation bar
1997
     */
1998
    public function get_navigation_bar($idBar = null, $display = null)
1999
    {
2000
        if ($this->debug > 0) {
2001
            error_log('In learnpath::get_navigation_bar()', 0);
2002
        }
2003
        if (empty($idBar)) {
2004
            $idBar = 'control-top';
2005
        }
2006
        $lpId = $this->lp_id;
2007
        $mycurrentitemid = $this->get_current_item_id();
2008
2009
        $reportingText = get_lang('Reporting');
2010
        $previousText = get_lang('ScormPrevious');
2011
        $nextText = get_lang('ScormNext');
2012
        $fullScreenText = get_lang('ScormExitFullScreen');
2013
2014
        $settings = api_get_configuration_value('lp_view_settings');
2015
        $display = isset($settings['display']) ? $settings['display'] : false;
2016
        $reportingIcon = '
2017
            <a class="icon-toolbar" 
2018
                id="stats_link"
2019
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
2020
                onclick="window.parent.API.save_asset(); return true;" 
2021
                target="content_name" title="'.$reportingText.'">
2022
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2023
            </a>';
2024
2025
        if (!empty($display)) {
2026
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2027
            if ($showReporting == false) {
2028
                $reportingIcon = '';
2029
            }
2030
        }
2031
2032
        $hideArrows = false;
2033
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
2034
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
2035
        }
2036
2037
        $previousIcon = '';
2038
        $nextIcon = '';
2039
        if ($hideArrows === false) {
2040
        $previousIcon = '
2041
            <a class="icon-toolbar" id="scorm-previous" href="#" 
2042
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2043
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2044
            </a>';
2045
2046
        $nextIcon = '
2047
            <a class="icon-toolbar" id="scorm-next" href="#" 
2048
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2049
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2050
            </a>';
2051
        }
2052
2053
        if ($this->mode === 'fullscreen') {
2054
            $navbar = '
2055
                  <span id="'.$idBar.'" class="buttons">
2056
                    '.$reportingIcon.'
2057
                    '.$previousIcon.'                    
2058
                    '.$nextIcon.'
2059
                    <a class="icon-toolbar" id="view-embedded" 
2060
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2061
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2062
                    </a>
2063
                  </span>';
2064
        } else {
2065
            $navbar = '
2066
            <span id="'.$idBar.'" class="buttons text-right">
2067
                '.$reportingIcon.'
2068
                '.$previousIcon.'
2069
                '.$nextIcon.'               
2070
            </span>';
2071
        }
2072
2073
        return $navbar;
2074
    }
2075
2076
    /**
2077
     * Gets the next resource in queue (url).
2078
     *
2079
     * @return string URL to load into the viewer
2080
     */
2081
    public function get_next_index()
2082
    {
2083
        if ($this->debug > 0) {
2084
            error_log('In learnpath::get_next_index()', 0);
2085
        }
2086
        // TODO
2087
        $index = $this->index;
2088
        $index++;
2089
        if ($this->debug > 2) {
2090
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2091
        }
2092
        while (
2093
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2094
            $index < $this->max_ordered_items
2095
        ) {
2096
            $index++;
2097
            if ($index == $this->max_ordered_items) {
2098
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2099
                    return $this->index;
2100
                } else {
2101
                    return $index;
2102
                }
2103
            }
2104
        }
2105
        if (empty($this->ordered_items[$index])) {
2106
            return $this->index;
2107
        }
2108
        if ($this->debug > 2) {
2109
            error_log('index is now '.$index, 0);
2110
        }
2111
2112
        return $index;
2113
    }
2114
2115
    /**
2116
     * Gets item_id for the next element.
2117
     *
2118
     * @return int Next item (DB) ID
2119
     */
2120
    public function get_next_item_id()
2121
    {
2122
        if ($this->debug > 0) {
2123
            error_log('In learnpath::get_next_item_id()', 0);
2124
        }
2125
        $new_index = $this->get_next_index();
2126
        if (!empty($new_index)) {
2127
            if (isset($this->ordered_items[$new_index])) {
2128
                if ($this->debug > 2) {
2129
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2130
                }
2131
2132
                return $this->ordered_items[$new_index];
2133
            }
2134
        }
2135
        if ($this->debug > 2) {
2136
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2137
        }
2138
2139
        return 0;
2140
    }
2141
2142
    /**
2143
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2144
     *
2145
     * Generally, the package provided is in the form of a zip file, so the function
2146
     * has been written to test a zip file. If not a zip, the function will return the
2147
     * default return value: ''
2148
     *
2149
     * @param string $file_path the path to the file
2150
     * @param string $file_name the original name of the file
2151
     *
2152
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2153
     */
2154
    public static function get_package_type($file_path, $file_name)
2155
    {
2156
        // Get name of the zip file without the extension.
2157
        $file_info = pathinfo($file_name);
2158
        $extension = $file_info['extension']; // Extension only.
2159
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2160
                'dll',
2161
                'exe',
2162
            ])) {
2163
            return 'oogie';
2164
        }
2165
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2166
                'dll',
2167
                'exe',
2168
            ])) {
2169
            return 'woogie';
2170
        }
2171
2172
        $zipFile = new PclZip($file_path);
2173
        // Check the zip content (real size and file extension).
2174
        $zipContentArray = $zipFile->listContent();
2175
        $package_type = '';
2176
        $manifest = '';
2177
        $aicc_match_crs = 0;
2178
        $aicc_match_au = 0;
2179
        $aicc_match_des = 0;
2180
        $aicc_match_cst = 0;
2181
2182
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2183
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2184
            foreach ($zipContentArray as $thisContent) {
2185
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2186
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2187
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2188
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2189
                    $package_type = 'scorm';
2190
                    break; // Exit the foreach loop.
2191
                } elseif (
2192
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2193
                    in_array(
2194
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2195
                        ['crs', 'au', 'des', 'cst']
2196
                    )
2197
                ) {
2198
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2199
                    switch ($ext) {
2200
                        case 'crs':
2201
                            $aicc_match_crs = 1;
2202
                            break;
2203
                        case 'au':
2204
                            $aicc_match_au = 1;
2205
                            break;
2206
                        case 'des':
2207
                            $aicc_match_des = 1;
2208
                            break;
2209
                        case 'cst':
2210
                            $aicc_match_cst = 1;
2211
                            break;
2212
                        default:
2213
                            break;
2214
                    }
2215
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2216
                } else {
2217
                    $package_type = '';
2218
                }
2219
            }
2220
        }
2221
2222
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2223
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2224
            $package_type = 'aicc';
2225
        }
2226
2227
        // Try with chamilo course builder
2228
        if (empty($package_type)) {
2229
            $package_type = 'chamilo';
2230
        }
2231
2232
        return $package_type;
2233
    }
2234
2235
    /**
2236
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2237
     *
2238
     * @return string URL to load into the viewer
2239
     */
2240
    public function get_previous_index()
2241
    {
2242
        if ($this->debug > 0) {
2243
            error_log('In learnpath::get_previous_index()', 0);
2244
        }
2245
        $index = $this->index;
2246
        if (isset($this->ordered_items[$index - 1])) {
2247
            $index--;
2248
            while (isset($this->ordered_items[$index]) &&
2249
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2250
            ) {
2251
                $index--;
2252
                if ($index < 0) {
2253
                    return $this->index;
2254
                }
2255
            }
2256
        } else {
2257
            if ($this->debug > 2) {
2258
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2259
            }
2260
            // There is no previous item.
2261
        }
2262
2263
        return $index;
2264
    }
2265
2266
    /**
2267
     * Gets item_id for the next element.
2268
     *
2269
     * @return int Previous item (DB) ID
2270
     */
2271
    public function get_previous_item_id()
2272
    {
2273
        if ($this->debug > 0) {
2274
            error_log('In learnpath::get_previous_item_id()', 0);
2275
        }
2276
        $new_index = $this->get_previous_index();
2277
2278
        return $this->ordered_items[$new_index];
2279
    }
2280
2281
    /**
2282
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2283
     *
2284
     * @param int    $lpItemId
2285
     * @param string $autostart
2286
     *
2287
     * @return string The mediaplayer HTML
2288
     */
2289
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2290
    {
2291
        $course_id = api_get_course_int_id();
2292
        $_course = api_get_course_info();
2293
        if (empty($_course)) {
2294
            return '';
2295
        }
2296
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2297
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2298
        $lpItemId = (int) $lpItemId;
2299
2300
        // Getting all the information about the item.
2301
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2302
                INNER JOIN $tbl_lp_item_view as lp_view
2303
                ON (lpi.iid = lp_view.lp_item_id)
2304
                WHERE
2305
                    lpi.iid = $lpItemId AND
2306
                    lp_view.c_id = $course_id";
2307
        $result = Database::query($sql);
2308
        $row = Database::fetch_assoc($result);
2309
        $output = '';
2310
2311
        if (!empty($row['audio'])) {
2312
            $list = $_SESSION['oLP']->get_toc();
2313
2314
            switch ($row['item_type']) {
2315
                case 'quiz':
2316
                    $type_quiz = false;
2317
2318
                    foreach ($list as $toc) {
2319
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2320
                            $type_quiz = true;
2321
                        }
2322
                    }
2323
2324
                    if ($type_quiz) {
2325
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2326
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2327
                        } else {
2328
                            $autostart_audio = $autostart;
2329
                        }
2330
                    }
2331
                    break;
2332
                case TOOL_READOUT_TEXT:;
2333
                    $autostart_audio = 'false';
2334
                    break;
2335
                default:
2336
                    $autostart_audio = 'true';
2337
            }
2338
2339
            $courseInfo = api_get_course_info();
2340
            $audio = $row['audio'];
2341
2342
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2343
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2344
2345
            if (!file_exists($file)) {
2346
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2347
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2348
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2349
            }
2350
2351
            $player = Display::getMediaPlayer(
2352
                $file,
2353
                [
2354
                    'id' => 'lp_audio_media_player',
2355
                    'url' => $url,
2356
                    'autoplay' => $autostart_audio,
2357
                    'width' => '100%',
2358
                ]
2359
            );
2360
2361
            // The mp3 player.
2362
            $output = '<div id="container">';
2363
            $output .= $player;
2364
            $output .= '</div>';
2365
        }
2366
2367
        return $output;
2368
    }
2369
2370
    /**
2371
     * @param int   $studentId
2372
     * @param int   $prerequisite
2373
     * @param array $courseInfo
2374
     * @param int   $sessionId
2375
     *
2376
     * @return bool
2377
     */
2378
    public static function isBlockedByPrerequisite(
2379
        $studentId,
2380
        $prerequisite,
2381
        $courseInfo,
2382
        $sessionId
2383
    ) {
2384
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2385
        if ($allow) {
2386
            if (api_is_allowed_to_edit() ||
2387
                api_is_platform_admin(true) ||
2388
                api_is_drh() ||
2389
                api_is_coach($sessionId, $courseInfo['real_id'], false)
2390
            ) {
2391
                return false;
2392
            }
2393
        }
2394
2395
        $isBlocked = false;
2396
2397
        if (!empty($prerequisite)) {
2398
            $progress = self::getProgress(
2399
                $prerequisite,
2400
                $studentId,
2401
                $courseInfo['real_id'],
2402
                $sessionId
2403
            );
2404
            if ($progress < 100) {
2405
                $isBlocked = true;
2406
            }
2407
        }
2408
2409
        return $isBlocked;
2410
    }
2411
2412
    /**
2413
     * Checks if the learning path is visible for student after the progress
2414
     * of its prerequisite is completed, considering the time availability and
2415
     * the LP visibility.
2416
     *
2417
     * @param int  $lp_id
2418
     * @param int  $student_id
2419
     * @param null $courseCode
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $courseCode is correct as it would always require null to be passed?
Loading history...
2420
     * @param int  $sessionId
2421
     *
2422
     * @return bool
2423
     */
2424
    public static function is_lp_visible_for_student(
2425
        $lp_id,
2426
        $student_id,
2427
        $courseCode = null,
2428
        $sessionId = 0
2429
    ) {
2430
        $courseInfo = api_get_course_info($courseCode);
2431
        $lp_id = (int) $lp_id;
2432
        $sessionId = (int) $sessionId;
2433
2434
        if (empty($courseInfo)) {
2435
            return false;
2436
        }
2437
2438
        if (empty($sessionId)) {
2439
            $sessionId = api_get_session_id();
2440
        }
2441
2442
        $itemInfo = api_get_item_property_info(
2443
            $courseInfo['real_id'],
2444
            TOOL_LEARNPATH,
2445
            $lp_id,
2446
            $sessionId
2447
        );
2448
2449
        // If the item was deleted.
2450
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2451
            return false;
2452
        }
2453
2454
        // @todo remove this query and load the row info as a parameter
2455
        $table = Database::get_course_table(TABLE_LP_MAIN);
2456
        // Get current prerequisite
2457
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2458
                FROM $table
2459
                WHERE iid = $lp_id";
2460
        $rs = Database::query($sql);
2461
        $now = time();
2462
        if (Database::num_rows($rs) > 0) {
2463
            $row = Database::fetch_array($rs, 'ASSOC');
2464
            $prerequisite = $row['prerequisite'];
2465
            $is_visible = true;
2466
2467
            $isBlocked = self::isBlockedByPrerequisite(
2468
                $student_id,
2469
                $prerequisite,
2470
                $courseInfo,
2471
                $sessionId
2472
            );
2473
2474
            if ($isBlocked) {
2475
                $is_visible = false;
2476
            }
2477
2478
            // Also check the time availability of the LP
2479
            if ($is_visible) {
2480
                // Adding visibility restrictions
2481
                if (!empty($row['publicated_on'])) {
2482
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2483
                        $is_visible = false;
2484
                    }
2485
                }
2486
                // Blocking empty start times see BT#2800
2487
                global $_custom;
2488
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2489
                    $_custom['lps_hidden_when_no_start_date']
2490
                ) {
2491
                    if (empty($row['publicated_on'])) {
2492
                        $is_visible = false;
2493
                    }
2494
                }
2495
2496
                if (!empty($row['expired_on'])) {
2497
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2498
                        $is_visible = false;
2499
                    }
2500
                }
2501
            }
2502
2503
            $subscriptionSettings = self::getSubscriptionSettings();
2504
2505
            // Check if the subscription users/group to a LP is ON
2506
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2507
                $subscriptionSettings['allow_add_users_to_lp'] === true
2508
            ) {
2509
                // Try group
2510
                $is_visible = false;
2511
                // Checking only the user visibility
2512
                $userVisibility = api_get_item_visibility(
2513
                    $courseInfo,
2514
                    'learnpath',
2515
                    $row['id'],
2516
                    $sessionId,
2517
                    $student_id,
2518
                    'LearnpathSubscription'
2519
                );
2520
2521
                if ($userVisibility == 1) {
2522
                    $is_visible = true;
2523
                } else {
2524
                    $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2525
                    if (!empty($userGroups)) {
2526
                        foreach ($userGroups as $groupInfo) {
2527
                            $groupId = $groupInfo['iid'];
2528
                            $userVisibility = api_get_item_visibility(
2529
                                $courseInfo,
2530
                                'learnpath',
2531
                                $row['id'],
2532
                                $sessionId,
2533
                                null,
2534
                                'LearnpathSubscription',
2535
                                $groupId
2536
                            );
2537
2538
                            if ($userVisibility == 1) {
2539
                                $is_visible = true;
2540
                                break;
2541
                            }
2542
                        }
2543
                    }
2544
                }
2545
            }
2546
2547
            return $is_visible;
2548
        }
2549
2550
        return false;
2551
    }
2552
2553
    /**
2554
     * @param int $lpId
2555
     * @param int $userId
2556
     * @param int $courseId
2557
     * @param int $sessionId
2558
     *
2559
     * @return int
2560
     */
2561
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2562
    {
2563
        $lpId = (int) $lpId;
2564
        $userId = (int) $userId;
2565
        $courseId = (int) $courseId;
2566
        $sessionId = (int) $sessionId;
2567
        $progress = 0;
2568
2569
        $sessionCondition = api_get_session_condition($sessionId);
2570
        $table = Database::get_course_table(TABLE_LP_VIEW);
2571
        $sql = "SELECT * FROM $table
2572
                WHERE
2573
                    c_id = $courseId AND
2574
                    lp_id = $lpId AND
2575
                    user_id = $userId $sessionCondition ";
2576
        $res = Database::query($sql);
2577
        if (Database::num_rows($res) > 0) {
2578
            $row = Database:: fetch_array($res);
2579
            $progress = $row['progress'];
2580
        }
2581
2582
        return (int) $progress;
2583
    }
2584
2585
    /**
2586
     * Displays a progress bar
2587
     * completed so far.
2588
     *
2589
     * @param int    $percentage Progress value to display
2590
     * @param string $text_add   Text to display near the progress value
2591
     *
2592
     * @return string HTML string containing the progress bar
2593
     */
2594
    public static function get_progress_bar($percentage = -1, $text_add = '')
2595
    {
2596
        $text = $percentage.$text_add;
2597
        $output = '<div class="progress">
2598
            <div id="progress_bar_value" 
2599
                class="progress-bar progress-bar-success" role="progressbar" 
2600
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2601
            '.$text.'
2602
            </div>
2603
        </div>';
2604
2605
        return $output;
2606
    }
2607
2608
    /**
2609
     * @param string $mode can be '%' or 'abs'
2610
     *                     otherwise this value will be used $this->progress_bar_mode
2611
     *
2612
     * @return string
2613
     */
2614
    public function getProgressBar($mode = null)
2615
    {
2616
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2617
2618
        return self::get_progress_bar($percentage, $text_add);
2619
    }
2620
2621
    /**
2622
     * Gets the progress bar info to display inside the progress bar.
2623
     * Also used by scorm_api.php.
2624
     *
2625
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2626
     *                     we display a number of completed elements per total elements
2627
     * @param int    $add  Additional steps to fake as completed
2628
     *
2629
     * @return array Percentage or number and symbol (% or /xx)
2630
     */
2631
    public function get_progress_bar_text($mode = '', $add = 0)
2632
    {
2633
        if ($this->debug > 0) {
2634
            error_log('In learnpath::get_progress_bar_text()', 0);
2635
        }
2636
        if (empty($mode)) {
2637
            $mode = $this->progress_bar_mode;
2638
        }
2639
        $total_items = $this->getTotalItemsCountWithoutDirs();
2640
        if ($this->debug > 2) {
2641
            error_log('Total items available in this learnpath: '.$total_items, 0);
2642
        }
2643
        $completeItems = $this->get_complete_items_count();
2644
        if ($this->debug > 2) {
2645
            error_log('Items completed so far: '.$completeItems, 0);
2646
        }
2647
        if ($add != 0) {
2648
            $completeItems += $add;
2649
            if ($this->debug > 2) {
2650
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2651
            }
2652
        }
2653
        $text = '';
2654
        if ($completeItems > $total_items) {
2655
            $completeItems = $total_items;
2656
        }
2657
        $percentage = 0;
2658
        if ($mode == '%') {
2659
            if ($total_items > 0) {
2660
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2661
            } else {
2662
                $percentage = 0;
2663
            }
2664
            $percentage = number_format($percentage, 0);
2665
            $text = '%';
2666
        } elseif ($mode == 'abs') {
2667
            $percentage = $completeItems;
2668
            $text = '/'.$total_items;
2669
        }
2670
2671
        return [
2672
            $percentage,
2673
            $text,
2674
        ];
2675
    }
2676
2677
    /**
2678
     * Gets the progress bar mode.
2679
     *
2680
     * @return string The progress bar mode attribute
2681
     */
2682
    public function get_progress_bar_mode()
2683
    {
2684
        if ($this->debug > 0) {
2685
            error_log('In learnpath::get_progress_bar_mode()', 0);
2686
        }
2687
        if (!empty($this->progress_bar_mode)) {
2688
            return $this->progress_bar_mode;
2689
        } else {
2690
            return '%';
2691
        }
2692
    }
2693
2694
    /**
2695
     * Gets the learnpath theme (remote or local).
2696
     *
2697
     * @return string Learnpath theme
2698
     */
2699
    public function get_theme()
2700
    {
2701
        if ($this->debug > 0) {
2702
            error_log('In learnpath::get_theme()', 0);
2703
        }
2704
        if (!empty($this->theme)) {
2705
            return $this->theme;
2706
        } else {
2707
            return '';
2708
        }
2709
    }
2710
2711
    /**
2712
     * Gets the learnpath session id.
2713
     *
2714
     * @return int
2715
     */
2716
    public function get_lp_session_id()
2717
    {
2718
        if ($this->debug > 0) {
2719
            error_log('In learnpath::get_lp_session_id()', 0);
2720
        }
2721
        if (!empty($this->lp_session_id)) {
2722
            return (int) $this->lp_session_id;
2723
        } else {
2724
            return 0;
2725
        }
2726
    }
2727
2728
    /**
2729
     * Gets the learnpath image.
2730
     *
2731
     * @return string Web URL of the LP image
2732
     */
2733
    public function get_preview_image()
2734
    {
2735
        if ($this->debug > 0) {
2736
            error_log('In learnpath::get_preview_image()', 0);
2737
        }
2738
        if (!empty($this->preview_image)) {
2739
            return $this->preview_image;
2740
        } else {
2741
            return '';
2742
        }
2743
    }
2744
2745
    /**
2746
     * @param string $size
2747
     * @param string $path_type
2748
     *
2749
     * @return bool|string
2750
     */
2751
    public function get_preview_image_path($size = null, $path_type = 'web')
2752
    {
2753
        $preview_image = $this->get_preview_image();
2754
        if (isset($preview_image) && !empty($preview_image)) {
2755
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2756
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2757
2758
            if (isset($size)) {
2759
                $info = pathinfo($preview_image);
2760
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2761
2762
                if (file_exists($image_sys_path.$image_custom_size)) {
2763
                    if ($path_type == 'web') {
2764
                        return $image_path.$image_custom_size;
2765
                    } else {
2766
                        return $image_sys_path.$image_custom_size;
2767
                    }
2768
                }
2769
            } else {
2770
                if ($path_type == 'web') {
2771
                    return $image_path.$preview_image;
2772
                } else {
2773
                    return $image_sys_path.$preview_image;
2774
                }
2775
            }
2776
        }
2777
2778
        return false;
2779
    }
2780
2781
    /**
2782
     * Gets the learnpath author.
2783
     *
2784
     * @return string LP's author
2785
     */
2786
    public function get_author()
2787
    {
2788
        if ($this->debug > 0) {
2789
            error_log('In learnpath::get_author()', 0);
2790
        }
2791
        if (!empty($this->author)) {
2792
            return $this->author;
2793
        } else {
2794
            return '';
2795
        }
2796
    }
2797
2798
    /**
2799
     * Gets hide table of contents.
2800
     *
2801
     * @return int
2802
     */
2803
    public function getHideTableOfContents()
2804
    {
2805
        return (int) $this->hide_toc_frame;
2806
    }
2807
2808
    /**
2809
     * Generate a new prerequisites string for a given item. If this item was a sco and
2810
     * its prerequisites were strings (instead of IDs), then transform those strings into
2811
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2812
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2813
     * same rule as the scormExport() method.
2814
     *
2815
     * @param int $item_id Item ID
2816
     *
2817
     * @return string Prerequisites string ready for the export as SCORM
2818
     */
2819
    public function get_scorm_prereq_string($item_id)
2820
    {
2821
        if ($this->debug > 0) {
2822
            error_log('In learnpath::get_scorm_prereq_string()');
2823
        }
2824
        if (!is_object($this->items[$item_id])) {
2825
            return false;
2826
        }
2827
        /** @var learnpathItem $oItem */
2828
        $oItem = $this->items[$item_id];
2829
        $prereq = $oItem->get_prereq_string();
2830
2831
        if (empty($prereq)) {
2832
            return '';
2833
        }
2834
        if (preg_match('/^\d+$/', $prereq) &&
2835
            isset($this->items[$prereq]) &&
2836
            is_object($this->items[$prereq])
2837
        ) {
2838
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2839
            // then simply return it (with the ITEM_ prefix).
2840
            //return 'ITEM_' . $prereq;
2841
            return $this->items[$prereq]->ref;
2842
        } else {
2843
            if (isset($this->refs_list[$prereq])) {
2844
                // It's a simple string item from which the ID can be found in the refs list,
2845
                // so we can transform it directly to an ID for export.
2846
                return $this->items[$this->refs_list[$prereq]]->ref;
2847
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2848
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2849
            } else {
2850
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2851
                // and replace them, one by one, by the internal IDs (chamilo db)
2852
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2853
                // by a space as well.
2854
                $find = [
2855
                    '&',
2856
                    '|',
2857
                    '~',
2858
                    '=',
2859
                    '<>',
2860
                    '{',
2861
                    '}',
2862
                    '*',
2863
                    '(',
2864
                    ')',
2865
                ];
2866
                $replace = [
2867
                    ' ',
2868
                    ' ',
2869
                    ' ',
2870
                    ' ',
2871
                    ' ',
2872
                    ' ',
2873
                    ' ',
2874
                    ' ',
2875
                    ' ',
2876
                    ' ',
2877
                ];
2878
                $prereq_mod = str_replace($find, $replace, $prereq);
2879
                $ids = explode(' ', $prereq_mod);
2880
                foreach ($ids as $id) {
2881
                    $id = trim($id);
2882
                    if (isset($this->refs_list[$id])) {
2883
                        $prereq = preg_replace(
2884
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2885
                            'ITEM_'.$this->refs_list[$id],
2886
                            $prereq
2887
                        );
2888
                    }
2889
                }
2890
2891
                return $prereq;
2892
            }
2893
        }
2894
    }
2895
2896
    /**
2897
     * Returns the XML DOM document's node.
2898
     *
2899
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2900
     * @param string   $id       The identifier to look for
2901
     *
2902
     * @return mixed The reference to the element found with that identifier. False if not found
2903
     */
2904
    public function get_scorm_xml_node(&$children, $id)
2905
    {
2906
        for ($i = 0; $i < $children->length; $i++) {
2907
            $item_temp = $children->item($i);
2908
            if ($item_temp->nodeName == 'item') {
2909
                if ($item_temp->getAttribute('identifier') == $id) {
2910
                    return $item_temp;
2911
                }
2912
            }
2913
            $subchildren = $item_temp->childNodes;
2914
            if ($subchildren && $subchildren->length > 0) {
2915
                $val = $this->get_scorm_xml_node($subchildren, $id);
2916
                if (is_object($val)) {
2917
                    return $val;
2918
                }
2919
            }
2920
        }
2921
2922
        return false;
2923
    }
2924
2925
    /**
2926
     * Gets the status list for all LP's items.
2927
     *
2928
     * @return array Array of [index] => [item ID => current status]
2929
     */
2930
    public function get_items_status_list()
2931
    {
2932
        if ($this->debug > 0) {
2933
            error_log('In learnpath::get_items_status_list()', 0);
2934
        }
2935
        $list = [];
2936
        foreach ($this->ordered_items as $item_id) {
2937
            $list[] = [
2938
                $item_id => $this->items[$item_id]->get_status(),
2939
            ];
2940
        }
2941
2942
        return $list;
2943
    }
2944
2945
    /**
2946
     * Return the number of interactions for the given learnpath Item View ID.
2947
     * This method can be used as static.
2948
     *
2949
     * @param int $lp_iv_id  Item View ID
2950
     * @param int $course_id course id
2951
     *
2952
     * @return int
2953
     */
2954
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2955
    {
2956
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2957
        $lp_iv_id = (int) $lp_iv_id;
2958
        $course_id = (int) $course_id;
2959
2960
        $sql = "SELECT count(*) FROM $table
2961
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2962
        $res = Database::query($sql);
2963
        $num = 0;
2964
        if (Database::num_rows($res)) {
2965
            $row = Database::fetch_array($res);
2966
            $num = $row[0];
2967
        }
2968
2969
        return $num;
2970
    }
2971
2972
    /**
2973
     * Return the interactions as an array for the given lp_iv_id.
2974
     * This method can be used as static.
2975
     *
2976
     * @param int $lp_iv_id Learnpath Item View ID
2977
     *
2978
     * @return array
2979
     *
2980
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2981
     */
2982
    public static function get_iv_interactions_array($lp_iv_id)
2983
    {
2984
        $course_id = api_get_course_int_id();
2985
        $list = [];
2986
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2987
2988
        if (empty($lp_iv_id)) {
2989
            return [];
2990
        }
2991
2992
        $sql = "SELECT * FROM $table
2993
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2994
                ORDER BY order_id ASC";
2995
        $res = Database::query($sql);
2996
        $num = Database::num_rows($res);
2997
        if ($num > 0) {
2998
            $list[] = [
2999
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3000
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
3001
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
3002
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
3003
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
3004
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
3005
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
3006
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
3007
            ];
3008
            while ($row = Database::fetch_array($res)) {
3009
                $list[] = [
3010
                    'order_id' => ($row['order_id'] + 1),
3011
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
3012
                    'type' => $row['interaction_type'],
3013
                    'time' => $row['completion_time'],
3014
                    //'correct_responses' => $row['correct_responses'],
3015
                    'correct_responses' => '', // Hide correct responses from students.
3016
                    'student_response' => $row['student_response'],
3017
                    'result' => $row['result'],
3018
                    'latency' => $row['latency'],
3019
                ];
3020
            }
3021
        }
3022
3023
        return $list;
3024
    }
3025
3026
    /**
3027
     * Return the number of objectives for the given learnpath Item View ID.
3028
     * This method can be used as static.
3029
     *
3030
     * @param int $lp_iv_id  Item View ID
3031
     * @param int $course_id Course ID
3032
     *
3033
     * @return int Number of objectives
3034
     */
3035
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3036
    {
3037
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3038
        $course_id = (int) $course_id;
3039
        $lp_iv_id = (int) $lp_iv_id;
3040
        $sql = "SELECT count(*) FROM $table
3041
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3042
        //@todo seems that this always returns 0
3043
        $res = Database::query($sql);
3044
        $num = 0;
3045
        if (Database::num_rows($res)) {
3046
            $row = Database::fetch_array($res);
3047
            $num = $row[0];
3048
        }
3049
3050
        return $num;
3051
    }
3052
3053
    /**
3054
     * Return the objectives as an array for the given lp_iv_id.
3055
     * This method can be used as static.
3056
     *
3057
     * @param int $lpItemViewId Learnpath Item View ID
3058
     *
3059
     * @return array
3060
     *
3061
     * @todo    Translate labels
3062
     */
3063
    public static function get_iv_objectives_array($lpItemViewId = 0)
3064
    {
3065
        $course_id = api_get_course_int_id();
3066
        $lpItemViewId = (int) $lpItemViewId;
3067
3068
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3069
        $sql = "SELECT * FROM $table
3070
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3071
                ORDER BY order_id ASC";
3072
        $res = Database::query($sql);
3073
        $num = Database::num_rows($res);
3074
        $list = [];
3075
        if ($num > 0) {
3076
            $list[] = [
3077
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3078
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3079
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3080
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3081
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3082
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3083
            ];
3084
            while ($row = Database::fetch_array($res)) {
3085
                $list[] = [
3086
                    'order_id' => ($row['order_id'] + 1),
3087
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3088
                    'score_raw' => $row['score_raw'],
3089
                    'score_max' => $row['score_max'],
3090
                    'score_min' => $row['score_min'],
3091
                    'status' => $row['status'],
3092
                ];
3093
            }
3094
        }
3095
3096
        return $list;
3097
    }
3098
3099
    /**
3100
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3101
     * used by get_html_toc() to be ready to display.
3102
     *
3103
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3104
     */
3105
    public function get_toc()
3106
    {
3107
        if ($this->debug > 0) {
3108
            error_log('learnpath::get_toc()', 0);
3109
        }
3110
        $toc = [];
3111
        foreach ($this->ordered_items as $item_id) {
3112
            if ($this->debug > 2) {
3113
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3114
            }
3115
            // TODO: Change this link generation and use new function instead.
3116
            $toc[] = [
3117
                'id' => $item_id,
3118
                'title' => $this->items[$item_id]->get_title(),
3119
                'status' => $this->items[$item_id]->get_status(),
3120
                'level' => $this->items[$item_id]->get_level(),
3121
                'type' => $this->items[$item_id]->get_type(),
3122
                'description' => $this->items[$item_id]->get_description(),
3123
                'path' => $this->items[$item_id]->get_path(),
3124
                'parent' => $this->items[$item_id]->get_parent(),
3125
            ];
3126
        }
3127
        if ($this->debug > 2) {
3128
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3129
        }
3130
3131
        return $toc;
3132
    }
3133
3134
    /**
3135
     * Generate and return the table of contents for this learnpath. The JS
3136
     * table returned is used inside of scorm_api.php.
3137
     *
3138
     * @param string $varname
3139
     *
3140
     * @return string A JS array variable construction
3141
     */
3142
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3143
    {
3144
        if ($this->debug > 0) {
3145
            error_log('In learnpath::get_items_details_as_js()', 0);
3146
        }
3147
        $toc = $varname.' = new Array();';
3148
        foreach ($this->ordered_items as $item_id) {
3149
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3150
        }
3151
        if ($this->debug > 2) {
3152
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
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 ($this->debug > 0) {
3169
            error_log('In learnpath::get_type()', 0);
3170
        }
3171
        if (!empty($this->type) && (!$get_name)) {
3172
            $res = $this->type;
3173
        }
3174
        if ($this->debug > 2) {
3175
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3176
        }
3177
3178
        return $res;
3179
    }
3180
3181
    /**
3182
     * Gets the learning path type as static method.
3183
     *
3184
     * @param int $lp_id
3185
     *
3186
     * @return mixed Type ID or name, depending on the parameter
3187
     */
3188
    public static function get_type_static($lp_id = 0)
3189
    {
3190
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3191
        $lp_id = (int) $lp_id;
3192
        $sql = "SELECT lp_type FROM $tbl_lp
3193
                WHERE iid = $lp_id";
3194
        $res = Database::query($sql);
3195
        if ($res === false) {
3196
            return null;
3197
        }
3198
        if (Database::num_rows($res) <= 0) {
3199
            return null;
3200
        }
3201
        $row = Database::fetch_array($res);
3202
3203
        return $row['lp_type'];
3204
    }
3205
3206
    /**
3207
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3208
     * This method can be used as abstract and is recursive.
3209
     *
3210
     * @param int $lp        Learnpath ID
3211
     * @param int $parent    Parent ID of the items to look for
3212
     * @param int $course_id
3213
     *
3214
     * @return array Ordered list of item IDs (empty array on error)
3215
     */
3216
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3217
    {
3218
        if (empty($course_id)) {
3219
            $course_id = api_get_course_int_id();
3220
        } else {
3221
            $course_id = (int) $course_id;
3222
        }
3223
        $list = [];
3224
3225
        if (empty($lp)) {
3226
            return $list;
3227
        }
3228
3229
        $lp = (int) $lp;
3230
        $parent = (int) $parent;
3231
3232
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3233
        $sql = "SELECT iid FROM $tbl_lp_item
3234
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3235
                ORDER BY display_order";
3236
3237
        $res = Database::query($sql);
3238
        while ($row = Database::fetch_array($res)) {
3239
            $sublist = self::get_flat_ordered_items_list(
3240
                $lp,
3241
                $row['iid'],
3242
                $course_id
3243
            );
3244
            $list[] = $row['iid'];
3245
            foreach ($sublist as $item) {
3246
                $list[] = $item;
3247
            }
3248
        }
3249
3250
        return $list;
3251
    }
3252
3253
    /**
3254
     * @return array
3255
     */
3256
    public static function getChapterTypes()
3257
    {
3258
        return [
3259
            'dir',
3260
        ];
3261
    }
3262
3263
    /**
3264
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3265
     *
3266
     * @param $tree
3267
     *
3268
     * @return array HTML TOC ready to display
3269
     */
3270
    public function getParentToc($tree)
3271
    {
3272
        if ($this->debug > 0) {
3273
            error_log('In learnpath::get_html_toc()', 0);
3274
        }
3275
        if (empty($tree)) {
3276
            $tree = $this->get_toc();
3277
        }
3278
        $dirTypes = self::getChapterTypes();
3279
        $myCurrentId = $this->get_current_item_id();
3280
        $listParent = [];
3281
        $listChildren = [];
3282
        $listNotParent = [];
3283
        $list = [];
3284
        foreach ($tree as $subtree) {
3285
            if (in_array($subtree['type'], $dirTypes)) {
3286
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3287
                $subtree['children'] = $listChildren;
3288
                if (!empty($subtree['children'])) {
3289
                    foreach ($subtree['children'] as $subItem) {
3290
                        if ($subItem['id'] == $this->current) {
3291
                            $subtree['parent_current'] = 'in';
3292
                            $subtree['current'] = 'on';
3293
                        }
3294
                    }
3295
                }
3296
                $listParent[] = $subtree;
3297
            }
3298
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3299
                $classStatus = [
3300
                    'not attempted' => 'scorm_not_attempted',
3301
                    'incomplete' => 'scorm_not_attempted',
3302
                    'failed' => 'scorm_failed',
3303
                    'completed' => 'scorm_completed',
3304
                    'passed' => 'scorm_completed',
3305
                    'succeeded' => 'scorm_completed',
3306
                    'browsed' => 'scorm_completed',
3307
                ];
3308
3309
                if (isset($classStatus[$subtree['status']])) {
3310
                    $cssStatus = $classStatus[$subtree['status']];
3311
                }
3312
3313
                $title = Security::remove_XSS($subtree['title']);
3314
                unset($subtree['title']);
3315
3316
                if (empty($title)) {
3317
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3318
                }
3319
                $classStyle = null;
3320
                if ($subtree['id'] == $this->current) {
3321
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3322
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3323
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3324
                }
3325
                $subtree['title'] = $title;
3326
                $subtree['class'] = $classStyle.' '.$cssStatus;
3327
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3328
                $subtree['current_id'] = $myCurrentId;
3329
                $listNotParent[] = $subtree;
3330
            }
3331
        }
3332
3333
        $list['are_parents'] = $listParent;
3334
        $list['not_parents'] = $listNotParent;
3335
3336
        return $list;
3337
    }
3338
3339
    /**
3340
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3341
     *
3342
     * @param array $tree
3343
     * @param int   $id
3344
     * @param bool  $parent
3345
     *
3346
     * @return array HTML TOC ready to display
3347
     */
3348
    public function getChildrenToc($tree, $id, $parent = true)
3349
    {
3350
        if ($this->debug > 0) {
3351
            error_log('In learnpath::get_html_toc()', 0);
3352
        }
3353
        if (empty($tree)) {
3354
            $tree = $this->get_toc();
3355
        }
3356
3357
        $dirTypes = self::getChapterTypes();
3358
        $mycurrentitemid = $this->get_current_item_id();
3359
        $list = [];
3360
        $classStatus = [
3361
            'not attempted' => 'scorm_not_attempted',
3362
            'incomplete' => 'scorm_not_attempted',
3363
            'failed' => 'scorm_failed',
3364
            'completed' => 'scorm_completed',
3365
            'passed' => 'scorm_completed',
3366
            'succeeded' => 'scorm_completed',
3367
            'browsed' => 'scorm_completed',
3368
        ];
3369
3370
        foreach ($tree as $subtree) {
3371
            $subtree['tree'] = null;
3372
3373
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3374
                if ($subtree['id'] == $this->current) {
3375
                    $subtree['current'] = 'active';
3376
                } else {
3377
                    $subtree['current'] = null;
3378
                }
3379
                if (isset($classStatus[$subtree['status']])) {
3380
                    $cssStatus = $classStatus[$subtree['status']];
3381
                }
3382
3383
                $title = Security::remove_XSS($subtree['title']);
3384
                unset($subtree['title']);
3385
                if (empty($title)) {
3386
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3387
                }
3388
3389
                $classStyle = null;
3390
                if ($subtree['id'] == $this->current) {
3391
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3392
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3393
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3394
                }
3395
3396
                if (in_array($subtree['type'], $dirTypes)) {
3397
                    $subtree['title'] = stripslashes($title);
3398
                } else {
3399
                    $subtree['title'] = $title;
3400
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3401
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3402
                    $subtree['current_id'] = $mycurrentitemid;
3403
                }
3404
                $list[] = $subtree;
3405
            }
3406
        }
3407
3408
        return $list;
3409
    }
3410
3411
    /**
3412
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3413
     *
3414
     * @param array $toc_list
3415
     *
3416
     * @return array HTML TOC ready to display
3417
     */
3418
    public function getListArrayToc($toc_list = [])
3419
    {
3420
        if ($this->debug > 0) {
3421
            error_log('In learnpath::get_html_toc()', 0);
3422
        }
3423
        if (empty($toc_list)) {
3424
            $toc_list = $this->get_toc();
3425
        }
3426
        // Temporary variables.
3427
        $mycurrentitemid = $this->get_current_item_id();
3428
        $list = [];
3429
        $arrayList = [];
3430
        $classStatus = [
3431
            'not attempted' => 'scorm_not_attempted',
3432
            'incomplete' => 'scorm_not_attempted',
3433
            'failed' => 'scorm_failed',
3434
            'completed' => 'scorm_completed',
3435
            'passed' => 'scorm_completed',
3436
            'succeeded' => 'scorm_completed',
3437
            'browsed' => 'scorm_completed',
3438
        ];
3439
3440
        foreach ($toc_list as $item) {
3441
            $list['id'] = $item['id'];
3442
            $list['status'] = $item['status'];
3443
            $cssStatus = null;
3444
3445
            if (isset($classStatus[$item['status']])) {
3446
                $cssStatus = $classStatus[$item['status']];
3447
            }
3448
3449
            $classStyle = ' ';
3450
            $dirTypes = self::getChapterTypes();
3451
3452
            if (in_array($item['type'], $dirTypes)) {
3453
                $classStyle = 'scorm_item_section ';
3454
            }
3455
            if ($item['id'] == $this->current) {
3456
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3457
            } elseif (!in_array($item['type'], $dirTypes)) {
3458
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3459
            }
3460
            $title = $item['title'];
3461
            if (empty($title)) {
3462
                $title = self::rl_get_resource_name(
3463
                    api_get_course_id(),
3464
                    $this->get_id(),
3465
                    $item['id']
3466
                );
3467
            }
3468
            $title = Security::remove_XSS($item['title']);
3469
3470
            if (empty($item['description'])) {
3471
                $list['description'] = $title;
3472
            } else {
3473
                $list['description'] = $item['description'];
3474
            }
3475
3476
            $list['class'] = $classStyle.' '.$cssStatus;
3477
            $list['level'] = $item['level'];
3478
            $list['type'] = $item['type'];
3479
3480
            if (in_array($item['type'], $dirTypes)) {
3481
                $list['css_level'] = 'level_'.$item['level'];
3482
            } else {
3483
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3484
            }
3485
3486
            if (in_array($item['type'], $dirTypes)) {
3487
                $list['title'] = stripslashes($title);
3488
            } else {
3489
                $list['title'] = stripslashes($title);
3490
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3491
                $list['current_id'] = $mycurrentitemid;
3492
            }
3493
            $arrayList[] = $list;
3494
        }
3495
3496
        return $arrayList;
3497
    }
3498
3499
    /**
3500
     * Returns an HTML-formatted string ready to display with teacher buttons
3501
     * in LP view menu.
3502
     *
3503
     * @return string HTML TOC ready to display
3504
     */
3505
    public function get_teacher_toc_buttons()
3506
    {
3507
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3508
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3509
        $html = '';
3510
        if ($isAllow && $hideIcons == false) {
3511
            if ($this->get_lp_session_id() == api_get_session_id()) {
3512
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3513
                $html .= '<div class="btn-group">';
3514
                $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'>".
3515
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3516
                $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'>".
3517
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3518
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3519
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3520
                $html .= '</div>';
3521
                $html .= '</div>';
3522
            }
3523
        }
3524
3525
        return $html;
3526
    }
3527
3528
    /**
3529
     * Gets the learnpath maker name - generally the editor's name.
3530
     *
3531
     * @return string Learnpath maker name
3532
     */
3533
    public function get_maker()
3534
    {
3535
        if ($this->debug > 0) {
3536
            error_log('In learnpath::get_maker()', 0);
3537
        }
3538
        if (!empty($this->maker)) {
3539
            return $this->maker;
3540
        } else {
3541
            return '';
3542
        }
3543
    }
3544
3545
    /**
3546
     * Gets the learnpath name/title.
3547
     *
3548
     * @return string Learnpath name/title
3549
     */
3550
    public function get_name()
3551
    {
3552
        if ($this->debug > 0) {
3553
            error_log('In learnpath::get_name()', 0);
3554
        }
3555
        if (!empty($this->name)) {
3556
            return $this->name;
3557
        } else {
3558
            return 'N/A';
3559
        }
3560
    }
3561
3562
    /**
3563
     * Gets a link to the resource from the present location, depending on item ID.
3564
     *
3565
     * @param string $type         Type of link expected
3566
     * @param int    $item_id      Learnpath item ID
3567
     * @param bool   $provided_toc
3568
     *
3569
     * @return string $provided_toc Link to the lp_item resource
3570
     */
3571
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3572
    {
3573
        $course_id = $this->get_course_int_id();
3574
        if ($this->debug > 0) {
3575
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3576
        }
3577
        if (empty($item_id)) {
3578
            if ($this->debug > 2) {
3579
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3580
                error_log('using current: '.$this->get_current_item_id(), 0);
3581
            }
3582
            $item_id = $this->get_current_item_id();
3583
3584
            if (empty($item_id)) {
3585
                if ($this->debug > 2) {
3586
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3587
                }
3588
                //still empty, this means there was no item_id given and we are not in an object context or
3589
                //the object property is empty, return empty link
3590
                $this->first();
3591
3592
                return '';
3593
            }
3594
        }
3595
3596
        $file = '';
3597
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3598
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3599
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3600
        $item_id = (int) $item_id;
3601
3602
        $sql = "SELECT
3603
                    l.lp_type as ltype,
3604
                    l.path as lpath,
3605
                    li.item_type as litype,
3606
                    li.path as lipath,
3607
                    li.parameters as liparams
3608
        		FROM $lp_table l
3609
                INNER JOIN $lp_item_table li
3610
                ON (li.lp_id = l.iid)
3611
        		WHERE 
3612
        		    li.iid = $item_id 
3613
        		";
3614
        if ($this->debug > 2) {
3615
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3616
        }
3617
        $res = Database::query($sql);
3618
        if (Database::num_rows($res) > 0) {
3619
            $row = Database::fetch_array($res);
3620
            $lp_type = $row['ltype'];
3621
            $lp_path = $row['lpath'];
3622
            $lp_item_type = $row['litype'];
3623
            $lp_item_path = $row['lipath'];
3624
            $lp_item_params = $row['liparams'];
3625
3626
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3627
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3628
            }
3629
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3630
            if ($type === 'http') {
3631
                //web path
3632
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3633
            } else {
3634
                $course_path = $sys_course_path; //system path
3635
            }
3636
3637
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3638
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3639
            if (in_array(
3640
                $lp_item_type,
3641
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3642
            )
3643
            ) {
3644
                $lp_type = 1;
3645
            }
3646
3647
            if ($this->debug > 2) {
3648
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3649
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3650
            }
3651
3652
            // Now go through the specific cases to get the end of the path
3653
            // @todo Use constants instead of int values.
3654
            switch ($lp_type) {
3655
                case 1:
3656
                    $file = self::rl_get_resource_link_for_learnpath(
3657
                        $course_id,
3658
                        $this->get_id(),
3659
                        $item_id,
3660
                        $this->get_view_id()
3661
                    );
3662
3663
                    if ($this->debug > 0) {
3664
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3665
                    }
3666
3667
                    switch ($lp_item_type) {
3668
                        case 'dir':
3669
                            $file = 'lp_content.php?type=dir';
3670
                            break;
3671
                        case 'link':
3672
                            if (Link::is_youtube_link($file)) {
3673
                                $src = Link::get_youtube_video_id($file);
3674
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3675
                            } elseif (Link::isVimeoLink($file)) {
3676
                                $src = Link::getVimeoLinkId($file);
3677
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3678
                            } else {
3679
                                // If the current site is HTTPS and the link is
3680
                                // HTTP, browsers will refuse opening the link
3681
                                $urlId = api_get_current_access_url_id();
3682
                                $url = api_get_access_url($urlId, false);
3683
                                $protocol = substr($url['url'], 0, 5);
3684
                                if ($protocol === 'https') {
3685
                                    $linkProtocol = substr($file, 0, 5);
3686
                                    if ($linkProtocol === 'http:') {
3687
                                        //this is the special intervention case
3688
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3689
                                    }
3690
                                }
3691
                            }
3692
                            break;
3693
                        case 'quiz':
3694
                            // Check how much attempts of a exercise exits in lp
3695
                            $lp_item_id = $this->get_current_item_id();
3696
                            $lp_view_id = $this->get_view_id();
3697
3698
                            $prevent_reinit = null;
3699
                            if (isset($this->items[$this->current])) {
3700
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3701
                            }
3702
3703
                            if (empty($provided_toc)) {
3704
                                if ($this->debug > 0) {
3705
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3706
                                }
3707
                                $list = $this->get_toc();
3708
                            } else {
3709
                                if ($this->debug > 0) {
3710
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3711
                                }
3712
                                $list = $provided_toc;
3713
                            }
3714
3715
                            $type_quiz = false;
3716
3717
                            foreach ($list as $toc) {
3718
                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
3719
                                    $type_quiz = true;
3720
                                }
3721
                            }
3722
3723
                            if ($type_quiz) {
3724
                                $lp_item_id = intval($lp_item_id);
3725
                                $lp_view_id = intval($lp_view_id);
3726
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3727
                                        WHERE
3728
                                            c_id = $course_id AND
3729
                                            lp_item_id='".$lp_item_id."' AND
3730
                                            lp_view_id ='".$lp_view_id."' AND
3731
                                            status='completed'";
3732
                                $result = Database::query($sql);
3733
                                $row_count = Database:: fetch_row($result);
3734
                                $count_item_view = (int) $row_count[0];
3735
                                $not_multiple_attempt = 0;
3736
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3737
                                    $not_multiple_attempt = 1;
3738
                                }
3739
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3740
                            }
3741
                            break;
3742
                    }
3743
3744
                    $tmp_array = explode('/', $file);
3745
                    $document_name = $tmp_array[count($tmp_array) - 1];
3746
                    if (strpos($document_name, '_DELETED_')) {
3747
                        $file = 'blank.php?error=document_deleted';
3748
                    }
3749
3750
                    break;
3751
                case 2:
3752
                    if ($this->debug > 2) {
3753
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3754
                    }
3755
3756
                    if ($lp_item_type != 'dir') {
3757
                        // Quite complex here:
3758
                        // We want to make sure 'http://' (and similar) links can
3759
                        // be loaded as is (withouth the Chamilo path in front) but
3760
                        // some contents use this form: resource.htm?resource=http://blablabla
3761
                        // which means we have to find a protocol at the path's start, otherwise
3762
                        // it should not be considered as an external URL.
3763
                        // if ($this->prerequisites_match($item_id)) {
3764
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3765
                            if ($this->debug > 2) {
3766
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3767
                            }
3768
                            // Distant url, return as is.
3769
                            $file = $lp_item_path;
3770
                        } else {
3771
                            if ($this->debug > 2) {
3772
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3773
                            }
3774
                            // Prevent getting untranslatable urls.
3775
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3776
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3777
                            // Prepare the path.
3778
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3779
                            // TODO: Fix this for urls with protocol header.
3780
                            $file = str_replace('//', '/', $file);
3781
                            $file = str_replace(':/', '://', $file);
3782
                            if (substr($lp_path, -1) == '/') {
3783
                                $lp_path = substr($lp_path, 0, -1);
3784
                            }
3785
3786
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3787
                                // if file not found.
3788
                                $decoded = html_entity_decode($lp_item_path);
3789
                                list($decoded) = explode('?', $decoded);
3790
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3791
                                    $file = self::rl_get_resource_link_for_learnpath(
3792
                                        $course_id,
3793
                                        $this->get_id(),
3794
                                        $item_id,
3795
                                        $this->get_view_id()
3796
                                    );
3797
                                    if (empty($file)) {
3798
                                        $file = 'blank.php?error=document_not_found';
3799
                                    } else {
3800
                                        $tmp_array = explode('/', $file);
3801
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3802
                                        if (strpos($document_name, '_DELETED_')) {
3803
                                            $file = 'blank.php?error=document_deleted';
3804
                                        } else {
3805
                                            $file = 'blank.php?error=document_not_found';
3806
                                        }
3807
                                    }
3808
                                } else {
3809
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3810
                                }
3811
                            }
3812
                        }
3813
3814
                        // We want to use parameters if they were defined in the imsmanifest
3815
                        if (strpos($file, 'blank.php') === false) {
3816
                            $lp_item_params = ltrim($lp_item_params, '?');
3817
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3818
                        }
3819
                    } else {
3820
                        $file = 'lp_content.php?type=dir';
3821
                    }
3822
                    break;
3823
                case 3:
3824
                    if ($this->debug > 2) {
3825
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3826
                    }
3827
                    // Formatting AICC HACP append URL.
3828
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3829
                    if (!empty($lp_item_params)) {
3830
                        $aicc_append .= $lp_item_params.'&';
3831
                    }
3832
                    if ($lp_item_type != 'dir') {
3833
                        // Quite complex here:
3834
                        // We want to make sure 'http://' (and similar) links can
3835
                        // be loaded as is (withouth the Chamilo path in front) but
3836
                        // some contents use this form: resource.htm?resource=http://blablabla
3837
                        // which means we have to find a protocol at the path's start, otherwise
3838
                        // it should not be considered as an external URL.
3839
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3840
                            if ($this->debug > 2) {
3841
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3842
                            }
3843
                            // Distant url, return as is.
3844
                            $file = $lp_item_path;
3845
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3846
                            /*
3847
                            if (stristr($file,'<servername>') !== false) {
3848
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3849
                            }
3850
                            */
3851
                            if (stripos($file, '<servername>') !== false) {
3852
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3853
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3854
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3855
                            }
3856
3857
                            $file .= $aicc_append;
3858
                        } else {
3859
                            if ($this->debug > 2) {
3860
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3861
                            }
3862
                            // Prevent getting untranslatable urls.
3863
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3864
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3865
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3866
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3867
                            // TODO: Fix this for urls with protocol header.
3868
                            $file = str_replace('//', '/', $file);
3869
                            $file = str_replace(':/', '://', $file);
3870
                            $file .= $aicc_append;
3871
                        }
3872
                    } else {
3873
                        $file = 'lp_content.php?type=dir';
3874
                    }
3875
                    break;
3876
                case 4:
3877
                    break;
3878
                default:
3879
                    break;
3880
            }
3881
            // Replace &amp; by & because &amp; will break URL with params
3882
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3883
        }
3884
        if ($this->debug > 2) {
3885
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3886
        }
3887
3888
        return $file;
3889
    }
3890
3891
    /**
3892
     * Gets the latest usable view or generate a new one.
3893
     *
3894
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3895
     *
3896
     * @return int DB lp_view id
3897
     */
3898
    public function get_view($attempt_num = 0)
3899
    {
3900
        if ($this->debug > 0) {
3901
            error_log('In learnpath::get_view()', 0);
3902
        }
3903
        $search = '';
3904
        // Use $attempt_num to enable multi-views management (disabled so far).
3905
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3906
            $search = 'AND view_count = '.$attempt_num;
3907
        }
3908
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3909
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3910
3911
        $course_id = api_get_course_int_id();
3912
        $sessionId = api_get_session_id();
3913
3914
        $sql = "SELECT iid, view_count FROM $lp_view_table
3915
        		WHERE
3916
        		    c_id = $course_id AND
3917
        		    lp_id = ".$this->get_id()." AND
3918
        		    user_id = ".$this->get_user_id()." AND
3919
        		    session_id = $sessionId
3920
        		    $search
3921
                ORDER BY view_count DESC";
3922
        $res = Database::query($sql);
3923
        if (Database::num_rows($res) > 0) {
3924
            $row = Database::fetch_array($res);
3925
            $this->lp_view_id = $row['iid'];
3926
        } elseif (!api_is_invitee()) {
3927
            // There is no database record, create one.
3928
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3929
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3930
            Database::query($sql);
3931
            $id = Database::insert_id();
3932
            $this->lp_view_id = $id;
3933
3934
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3935
            Database::query($sql);
3936
        }
3937
3938
        return $this->lp_view_id;
3939
    }
3940
3941
    /**
3942
     * Gets the current view id.
3943
     *
3944
     * @return int View ID (from lp_view)
3945
     */
3946
    public function get_view_id()
3947
    {
3948
        if ($this->debug > 0) {
3949
            error_log('In learnpath::get_view_id()', 0);
3950
        }
3951
        if (!empty($this->lp_view_id)) {
3952
            return $this->lp_view_id;
3953
        } else {
3954
            return 0;
3955
        }
3956
    }
3957
3958
    /**
3959
     * Gets the update queue.
3960
     *
3961
     * @return array Array containing IDs of items to be updated by JavaScript
3962
     */
3963
    public function get_update_queue()
3964
    {
3965
        if ($this->debug > 0) {
3966
            error_log('In learnpath::get_update_queue()', 0);
3967
        }
3968
3969
        return $this->update_queue;
3970
    }
3971
3972
    /**
3973
     * Gets the user ID.
3974
     *
3975
     * @return int User ID
3976
     */
3977
    public function get_user_id()
3978
    {
3979
        if ($this->debug > 0) {
3980
            error_log('In learnpath::get_user_id()', 0);
3981
        }
3982
        if (!empty($this->user_id)) {
3983
            return $this->user_id;
3984
        } else {
3985
            return false;
3986
        }
3987
    }
3988
3989
    /**
3990
     * Checks if any of the items has an audio element attached.
3991
     *
3992
     * @return bool True or false
3993
     */
3994
    public function has_audio()
3995
    {
3996
        if ($this->debug > 1) {
3997
            error_log('In learnpath::has_audio()', 0);
3998
        }
3999
        $has = false;
4000
        foreach ($this->items as $i => $item) {
4001
            if (!empty($this->items[$i]->audio)) {
4002
                $has = true;
4003
                break;
4004
            }
4005
        }
4006
4007
        return $has;
4008
    }
4009
4010
    /**
4011
     * Moves an item up and down at its level.
4012
     *
4013
     * @param int    $id        Item to move up and down
4014
     * @param string $direction Direction 'up' or 'down'
4015
     *
4016
     * @return bool|int
4017
     */
4018
    public function move_item($id, $direction)
4019
    {
4020
        $course_id = api_get_course_int_id();
4021
        if ($this->debug > 0) {
4022
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
4023
        }
4024
        if (empty($id) || empty($direction)) {
4025
            return false;
4026
        }
4027
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4028
        $sql_sel = "SELECT *
4029
                    FROM $tbl_lp_item
4030
                    WHERE  
4031
                        iid = $id
4032
                    ";
4033
        $res_sel = Database::query($sql_sel);
4034
        // Check if elem exists.
4035
        if (Database::num_rows($res_sel) < 1) {
4036
            return false;
4037
        }
4038
        // Gather data.
4039
        $row = Database::fetch_array($res_sel);
4040
        $previous = $row['previous_item_id'];
4041
        $next = $row['next_item_id'];
4042
        $display = $row['display_order'];
4043
        $parent = $row['parent_item_id'];
4044
        $lp = $row['lp_id'];
4045
        // Update the item (switch with previous/next one).
4046
        switch ($direction) {
4047
            case 'up':
4048
                if ($this->debug > 2) {
4049
                    error_log('Movement up detected', 0);
4050
                }
4051
                if ($display > 1) {
4052
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4053
                                 WHERE iid = $previous";
4054
4055
                    if ($this->debug > 2) {
4056
                        error_log('Selecting previous: '.$sql_sel2, 0);
4057
                    }
4058
                    $res_sel2 = Database::query($sql_sel2);
4059
                    if (Database::num_rows($res_sel2) < 1) {
4060
                        $previous_previous = 0;
4061
                    }
4062
                    // Gather data.
4063
                    $row2 = Database::fetch_array($res_sel2);
4064
                    $previous_previous = $row2['previous_item_id'];
4065
                    // Update previous_previous item (switch "next" with current).
4066
                    if ($previous_previous != 0) {
4067
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4068
                                        next_item_id = $id
4069
                                    WHERE iid = $previous_previous";
4070
                        if ($this->debug > 2) {
4071
                            error_log($sql_upd2, 0);
4072
                        }
4073
                        Database::query($sql_upd2);
4074
                    }
4075
                    // Update previous item (switch with current).
4076
                    if ($previous != 0) {
4077
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4078
                                    next_item_id = $next,
4079
                                    previous_item_id = $id,
4080
                                    display_order = display_order +1
4081
                                    WHERE iid = $previous";
4082
                        if ($this->debug > 2) {
4083
                            error_log($sql_upd2, 0);
4084
                        }
4085
                        Database::query($sql_upd2);
4086
                    }
4087
4088
                    // Update current item (switch with previous).
4089
                    if ($id != 0) {
4090
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4091
                                        next_item_id = $previous,
4092
                                        previous_item_id = $previous_previous,
4093
                                        display_order = display_order-1
4094
                                    WHERE c_id = ".$course_id." AND id = $id";
4095
                        if ($this->debug > 2) {
4096
                            error_log($sql_upd2, 0);
4097
                        }
4098
                        Database::query($sql_upd2);
4099
                    }
4100
                    // Update next item (new previous item).
4101
                    if (!empty($next)) {
4102
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4103
                                     WHERE iid = $next";
4104
                        if ($this->debug > 2) {
4105
                            error_log($sql_upd2, 0);
4106
                        }
4107
                        Database::query($sql_upd2);
4108
                    }
4109
                    $display = $display - 1;
4110
                }
4111
                break;
4112
            case 'down':
4113
                if ($this->debug > 2) {
4114
                    error_log('Movement down detected', 0);
4115
                }
4116
                if ($next != 0) {
4117
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4118
                                 WHERE iid = $next";
4119
                    if ($this->debug > 2) {
4120
                        error_log('Selecting next: '.$sql_sel2, 0);
4121
                    }
4122
                    $res_sel2 = Database::query($sql_sel2);
4123
                    if (Database::num_rows($res_sel2) < 1) {
4124
                        $next_next = 0;
4125
                    }
4126
                    // Gather data.
4127
                    $row2 = Database::fetch_array($res_sel2);
4128
                    $next_next = $row2['next_item_id'];
4129
                    // Update previous item (switch with current).
4130
                    if ($previous != 0) {
4131
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4132
                                     SET next_item_id = $next
4133
                                     WHERE iid = $previous";
4134
                        Database::query($sql_upd2);
4135
                    }
4136
                    // Update current item (switch with previous).
4137
                    if ($id != 0) {
4138
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4139
                                     previous_item_id = $next, 
4140
                                     next_item_id = $next_next, 
4141
                                     display_order = display_order + 1
4142
                                     WHERE iid = $id";
4143
                        Database::query($sql_upd2);
4144
                    }
4145
4146
                    // Update next item (new previous item).
4147
                    if ($next != 0) {
4148
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4149
                                     previous_item_id = $previous, 
4150
                                     next_item_id = $id, 
4151
                                     display_order = display_order-1
4152
                                     WHERE iid = $next";
4153
                        Database::query($sql_upd2);
4154
                    }
4155
4156
                    // Update next_next item (switch "previous" with current).
4157
                    if ($next_next != 0) {
4158
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4159
                                     previous_item_id = $id
4160
                                     WHERE iid = $next_next";
4161
                        Database::query($sql_upd2);
4162
                    }
4163
                    $display = $display + 1;
4164
                }
4165
                break;
4166
            default:
4167
                return false;
4168
        }
4169
4170
        return $display;
4171
    }
4172
4173
    /**
4174
     * Move a LP up (display_order).
4175
     *
4176
     * @param int $lp_id      Learnpath ID
4177
     * @param int $categoryId Category ID
4178
     *
4179
     * @return bool
4180
     */
4181
    public static function move_up($lp_id, $categoryId = 0)
4182
    {
4183
        $courseId = api_get_course_int_id();
4184
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4185
4186
        $categoryCondition = '';
4187
        if (!empty($categoryId)) {
4188
            $categoryId = (int) $categoryId;
4189
            $categoryCondition = " AND category_id = $categoryId";
4190
        }
4191
        $sql = "SELECT * FROM $lp_table
4192
                WHERE c_id = $courseId
4193
                $categoryCondition
4194
                ORDER BY display_order";
4195
        $res = Database::query($sql);
4196
        if ($res === false) {
4197
            return false;
4198
        }
4199
4200
        $lps = [];
4201
        $lp_order = [];
4202
        $num = Database::num_rows($res);
4203
        // First check the order is correct, globally (might be wrong because
4204
        // of versions < 1.8.4)
4205
        if ($num > 0) {
4206
            $i = 1;
4207
            while ($row = Database::fetch_array($res)) {
4208
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4209
                    $sql = "UPDATE $lp_table SET display_order = $i
4210
                            WHERE iid = ".$row['iid'];
4211
                    Database::query($sql);
4212
                }
4213
                $row['display_order'] = $i;
4214
                $lps[$row['iid']] = $row;
4215
                $lp_order[$i] = $row['iid'];
4216
                $i++;
4217
            }
4218
        }
4219
        if ($num > 1) { // If there's only one element, no need to sort.
4220
            $order = $lps[$lp_id]['display_order'];
4221
            if ($order > 1) { // If it's the first element, no need to move up.
4222
                $sql = "UPDATE $lp_table SET display_order = $order
4223
                        WHERE iid = ".$lp_order[$order - 1];
4224
                Database::query($sql);
4225
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4226
                        WHERE iid = $lp_id";
4227
                Database::query($sql);
4228
            }
4229
        }
4230
4231
        return true;
4232
    }
4233
4234
    /**
4235
     * Move a learnpath down (display_order).
4236
     *
4237
     * @param int $lp_id      Learnpath ID
4238
     * @param int $categoryId Category ID
4239
     *
4240
     * @return bool
4241
     */
4242
    public static function move_down($lp_id, $categoryId = 0)
4243
    {
4244
        $courseId = api_get_course_int_id();
4245
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4246
4247
        $categoryCondition = '';
4248
        if (!empty($categoryId)) {
4249
            $categoryId = (int) $categoryId;
4250
            $categoryCondition = " AND category_id = $categoryId";
4251
        }
4252
4253
        $sql = "SELECT * FROM $lp_table
4254
                WHERE c_id = $courseId
4255
                $categoryCondition
4256
                ORDER BY display_order";
4257
        $res = Database::query($sql);
4258
        if ($res === false) {
4259
            return false;
4260
        }
4261
        $lps = [];
4262
        $lp_order = [];
4263
        $num = Database::num_rows($res);
4264
        $max = 0;
4265
        // First check the order is correct, globally (might be wrong because
4266
        // of versions < 1.8.4).
4267
        if ($num > 0) {
4268
            $i = 1;
4269
            while ($row = Database::fetch_array($res)) {
4270
                $max = $i;
4271
                if ($row['display_order'] != $i) {
4272
                    // If we find a gap in the order, we need to fix it.
4273
                    $sql = "UPDATE $lp_table SET display_order = $i
4274
                              WHERE iid = ".$row['iid'];
4275
                    Database::query($sql);
4276
                }
4277
                $row['display_order'] = $i;
4278
                $lps[$row['iid']] = $row;
4279
                $lp_order[$i] = $row['iid'];
4280
                $i++;
4281
            }
4282
        }
4283
        if ($num > 1) { // If there's only one element, no need to sort.
4284
            $order = $lps[$lp_id]['display_order'];
4285
            if ($order < $max) { // If it's the first element, no need to move up.
4286
                $sql = "UPDATE $lp_table SET display_order = $order
4287
                        WHERE iid = ".$lp_order[$order + 1];
4288
                Database::query($sql);
4289
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4290
                        WHERE iid = $lp_id";
4291
                Database::query($sql);
4292
            }
4293
        }
4294
4295
        return true;
4296
    }
4297
4298
    /**
4299
     * Updates learnpath attributes to point to the next element
4300
     * The last part is similar to set_current_item but processing the other way around.
4301
     */
4302
    public function next()
4303
    {
4304
        if ($this->debug > 0) {
4305
            error_log('In learnpath::next()', 0);
4306
        }
4307
        $this->last = $this->get_current_item_id();
4308
        $this->items[$this->last]->save(
4309
            false,
4310
            $this->prerequisites_match($this->last)
4311
        );
4312
        $this->autocomplete_parents($this->last);
4313
        $new_index = $this->get_next_index();
4314
        if ($this->debug > 2) {
4315
            error_log('New index: '.$new_index, 0);
4316
        }
4317
        $this->index = $new_index;
4318
        if ($this->debug > 2) {
4319
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4320
        }
4321
        $this->current = $this->ordered_items[$new_index];
4322
        if ($this->debug > 2) {
4323
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4324
        }
4325
    }
4326
4327
    /**
4328
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4329
     * class, this might be redefined to allow several behaviours depending on the document type.
4330
     *
4331
     * @param int $id Resource ID
4332
     */
4333
    public function open($id)
4334
    {
4335
        if ($this->debug > 0) {
4336
            error_log('In learnpath::open()', 0);
4337
        }
4338
        // TODO:
4339
        // set the current resource attribute to this resource
4340
        // switch on element type (redefine in child class?)
4341
        // set status for this item to "opened"
4342
        // start timer
4343
        // initialise score
4344
        $this->index = 0; //or = the last item seen (see $this->last)
4345
    }
4346
4347
    /**
4348
     * Check that all prerequisites are fulfilled. Returns true and an
4349
     * empty string on success, returns false
4350
     * and the prerequisite string on error.
4351
     * This function is based on the rules for aicc_script language as
4352
     * described in the SCORM 1.2 CAM documentation page 108.
4353
     *
4354
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4355
     *
4356
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4357
     *              string otherwise
4358
     */
4359
    public function prerequisites_match($itemId = null)
4360
    {
4361
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4362
        if ($allow) {
4363
            if (api_is_allowed_to_edit() ||
4364
                api_is_platform_admin(true) ||
4365
                api_is_drh() ||
4366
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4367
            ) {
4368
                return true;
4369
            }
4370
        }
4371
4372
        $debug = $this->debug;
4373
        if ($debug > 0) {
4374
            error_log('In learnpath::prerequisites_match()', 0);
4375
        }
4376
4377
        if (empty($itemId)) {
4378
            $itemId = $this->current;
4379
        }
4380
4381
        $currentItem = $this->getItem($itemId);
4382
4383
        if ($currentItem) {
4384
            if ($this->type == 2) {
4385
                // Getting prereq from scorm
4386
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4387
            } else {
4388
                $prereq_string = $currentItem->get_prereq_string();
4389
            }
4390
4391
            if (empty($prereq_string)) {
4392
                if ($debug > 0) {
4393
                    error_log('Found prereq_string is empty return true');
4394
                }
4395
4396
                return true;
4397
            }
4398
            // Clean spaces.
4399
            $prereq_string = str_replace(' ', '', $prereq_string);
4400
            if ($debug > 0) {
4401
                error_log('Found prereq_string: '.$prereq_string, 0);
4402
            }
4403
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4404
            $result = $currentItem->parse_prereq(
4405
                $prereq_string,
4406
                $this->items,
4407
                $this->refs_list,
4408
                $this->get_user_id()
4409
            );
4410
4411
            if ($result === false) {
4412
                $this->set_error_msg($currentItem->prereq_alert);
4413
            }
4414
        } else {
4415
            $result = true;
4416
            if ($debug > 1) {
4417
                error_log('$this->items['.$itemId.'] was not an object', 0);
4418
            }
4419
        }
4420
4421
        if ($debug > 1) {
4422
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4423
        }
4424
4425
        return $result;
4426
    }
4427
4428
    /**
4429
     * Updates learnpath attributes to point to the previous element
4430
     * The last part is similar to set_current_item but processing the other way around.
4431
     */
4432
    public function previous()
4433
    {
4434
        if ($this->debug > 0) {
4435
            error_log('In learnpath::previous()', 0);
4436
        }
4437
        $this->last = $this->get_current_item_id();
4438
        $this->items[$this->last]->save(
4439
            false,
4440
            $this->prerequisites_match($this->last)
4441
        );
4442
        $this->autocomplete_parents($this->last);
4443
        $new_index = $this->get_previous_index();
4444
        $this->index = $new_index;
4445
        $this->current = $this->ordered_items[$new_index];
4446
    }
4447
4448
    /**
4449
     * Publishes a learnpath. This basically means show or hide the learnpath
4450
     * to normal users.
4451
     * Can be used as abstract.
4452
     *
4453
     * @param int $lp_id          Learnpath ID
4454
     * @param int $set_visibility New visibility
4455
     *
4456
     * @return bool
4457
     */
4458
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4459
    {
4460
        $action = 'visible';
4461
        if ($set_visibility != 1) {
4462
            $action = 'invisible';
4463
            self::toggle_publish($lp_id, 'i');
4464
        }
4465
4466
        return api_item_property_update(
4467
            api_get_course_info(),
4468
            TOOL_LEARNPATH,
4469
            $lp_id,
4470
            $action,
4471
            api_get_user_id()
4472
        );
4473
    }
4474
4475
    /**
4476
     * Publishes a learnpath category.
4477
     * This basically means show or hide the learnpath category to normal users.
4478
     *
4479
     * @param int $id
4480
     * @param int $visibility
4481
     *
4482
     * @throws \Doctrine\ORM\NonUniqueResultException
4483
     * @throws \Doctrine\ORM\ORMException
4484
     * @throws \Doctrine\ORM\OptimisticLockException
4485
     * @throws \Doctrine\ORM\TransactionRequiredException
4486
     *
4487
     * @return bool
4488
     */
4489
    public static function toggleCategoryVisibility($id, $visibility = 1)
4490
    {
4491
        $action = 'visible';
4492
4493
        if ($visibility != 1) {
4494
            $action = 'invisible';
4495
            $list = new LearnpathList(
4496
                api_get_user_id(),
4497
                null,
4498
                null,
4499
                null,
4500
                false,
4501
                $id
4502
            );
4503
4504
            $lpList = $list->get_flat_list();
4505
            foreach ($lpList as $lp) {
4506
                self::toggle_visibility($lp['iid'], 0);
4507
            }
4508
4509
            self::toggleCategoryPublish($id, 0);
4510
        }
4511
4512
        return api_item_property_update(
4513
            api_get_course_info(),
4514
            TOOL_LEARNPATH_CATEGORY,
4515
            $id,
4516
            $action,
4517
            api_get_user_id()
4518
        );
4519
    }
4520
4521
    /**
4522
     * Publishes a learnpath. This basically means show or hide the learnpath
4523
     * on the course homepage
4524
     * Can be used as abstract.
4525
     *
4526
     * @param int    $lp_id          Learnpath id
4527
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4528
     *
4529
     * @return bool
4530
     */
4531
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4532
    {
4533
        $course_id = api_get_course_int_id();
4534
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4535
        $lp_id = (int) $lp_id;
4536
        $sql = "SELECT * FROM $tbl_lp
4537
                WHERE iid = $lp_id";
4538
        $result = Database::query($sql);
4539
        if (Database::num_rows($result)) {
4540
            $row = Database::fetch_array($result);
4541
            $name = Database::escape_string($row['name']);
4542
            if ($set_visibility == 'i') {
4543
                $v = 0;
4544
            }
4545
            if ($set_visibility == 'v') {
4546
                $v = 1;
4547
            }
4548
4549
            $session_id = api_get_session_id();
4550
            $session_condition = api_get_session_condition($session_id);
4551
4552
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4553
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4554
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4555
4556
            $sql = "SELECT * FROM $tbl_tool
4557
                    WHERE
4558
                        c_id = $course_id AND
4559
                        (link = '$link' OR link = '$oldLink') AND
4560
                        image = 'scormbuilder.gif' AND
4561
                        (
4562
                            link LIKE '$link%' OR
4563
                            link LIKE '$oldLink%'
4564
                        )
4565
                        $session_condition
4566
                    ";
4567
4568
            $result = Database::query($sql);
4569
            $num = Database::num_rows($result);
4570
            if ($set_visibility == 'i' && $num > 0) {
4571
                $sql = "DELETE FROM $tbl_tool
4572
                        WHERE 
4573
                            c_id = $course_id AND 
4574
                            (link = '$link' OR link = '$oldLink') AND 
4575
                            image='scormbuilder.gif' 
4576
                            $session_condition";
4577
                Database::query($sql);
4578
            } elseif ($set_visibility == 'v' && $num == 0) {
4579
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4580
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4581
                Database::query($sql);
4582
                $insertId = Database::insert_id();
4583
                if ($insertId) {
4584
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4585
                    Database::query($sql);
4586
                }
4587
            } elseif ($set_visibility == 'v' && $num > 0) {
4588
                $sql = "UPDATE $tbl_tool SET
4589
                            c_id = $course_id,
4590
                            name = '$name',
4591
                            link = '$link',
4592
                            image = 'scormbuilder.gif',
4593
                            visibility = '$v',
4594
                            admin = '0',
4595
                            address = 'pastillegris.gif',
4596
                            added_tool = 0,
4597
                            session_id = $session_id
4598
                        WHERE
4599
                            c_id = ".$course_id." AND
4600
                            (link = '$link' OR link = '$oldLink') AND 
4601
                            image='scormbuilder.gif' 
4602
                            $session_condition
4603
                        ";
4604
                Database::query($sql);
4605
            } else {
4606
                // Parameter and database incompatible, do nothing, exit.
4607
                return false;
4608
            }
4609
        } else {
4610
            return false;
4611
        }
4612
    }
4613
4614
    /**
4615
     * Publishes a learnpath.
4616
     * Show or hide the learnpath category on the course homepage.
4617
     *
4618
     * @param int $id
4619
     * @param int $setVisibility
4620
     *
4621
     * @throws \Doctrine\ORM\NonUniqueResultException
4622
     * @throws \Doctrine\ORM\ORMException
4623
     * @throws \Doctrine\ORM\OptimisticLockException
4624
     * @throws \Doctrine\ORM\TransactionRequiredException
4625
     *
4626
     * @return bool
4627
     */
4628
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4629
    {
4630
        $courseId = api_get_course_int_id();
4631
        $sessionId = api_get_session_id();
4632
        $sessionCondition = api_get_session_condition(
4633
            $sessionId,
4634
            true,
4635
            false,
4636
            't.sessionId'
4637
        );
4638
4639
        $em = Database::getManager();
4640
4641
        /** @var CLpCategory $category */
4642
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4643
4644
        if (!$category) {
4645
            return false;
4646
        }
4647
4648
        if (empty($courseId)) {
4649
            return false;
4650
        }
4651
4652
        $link = self::getCategoryLinkForTool($id);
4653
4654
        /** @var CTool $tool */
4655
        $tool = $em->createQuery("
4656
                SELECT t FROM ChamiloCourseBundle:CTool t
4657
                WHERE
4658
                    t.course = :course AND
4659
                    t.link = :link1 AND
4660
                    t.image = 'lp_category.gif' AND
4661
                    t.link LIKE :link2
4662
                    $sessionCondition
4663
            ")
4664
            ->setParameters([
4665
                'course' => $courseId,
4666
                'link1' => $link,
4667
                'link2' => "$link%",
4668
            ])
4669
            ->getOneOrNullResult();
4670
4671
        if ($setVisibility == 0 && $tool) {
4672
            $em->remove($tool);
4673
            $em->flush();
4674
4675
            return true;
4676
        }
4677
4678
        if ($setVisibility == 1 && !$tool) {
4679
            $tool = new CTool();
4680
            $tool
4681
                ->setCategory('authoring')
4682
                ->setCourse(api_get_course_entity($courseId))
4683
                ->setName(strip_tags($category->getName()))
4684
                ->setLink($link)
4685
                ->setImage('lp_category.gif')
4686
                ->setVisibility(1)
4687
                ->setAdmin(0)
4688
                ->setAddress('pastillegris.gif')
4689
                ->setAddedTool(0)
4690
                ->setSessionId($sessionId)
4691
                ->setTarget('_self');
4692
4693
            $em->persist($tool);
4694
            $em->flush();
4695
4696
            $tool->setId($tool->getIid());
4697
4698
            $em->persist($tool);
4699
            $em->flush();
4700
4701
            return true;
4702
        }
4703
4704
        if ($setVisibility == 1 && $tool) {
4705
            $tool
4706
                ->setName(strip_tags($category->getName()))
4707
                ->setVisibility(1);
4708
4709
            $em->persist($tool);
4710
            $em->flush();
4711
4712
            return true;
4713
        }
4714
4715
        return false;
4716
    }
4717
4718
    /**
4719
     * Check if the learnpath category is visible for a user.
4720
     *
4721
     * @param CLpCategory $category
4722
     * @param User        $user
4723
     *
4724
     * @return bool
4725
     */
4726
    public static function categoryIsVisibleForStudent(
4727
        CLpCategory $category,
4728
        User $user
4729
    ) {
4730
        $subscriptionSettings = self::getSubscriptionSettings();
4731
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4732
            return true;
4733
        }
4734
4735
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4736
4737
        if ($isAllowedToEdit) {
4738
            return true;
4739
        }
4740
4741
        if (empty($category)) {
4742
            return false;
4743
        }
4744
4745
        $users = $category->getUsers();
4746
4747
        if (empty($users) || !$users->count()) {
4748
            return true;
4749
        }
4750
4751
        if ($category->hasUserAdded($user)) {
4752
            return true;
4753
        }
4754
4755
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4756
        if (!empty($groups)) {
4757
            $em = Database::getManager();
4758
4759
            /** @var ItemPropertyRepository $itemRepo */
4760
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4761
4762
            /** @var CourseRepository $courseRepo */
4763
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4764
            $sessionId = api_get_session_id();
4765
            $session = null;
4766
            if (!empty($sessionId)) {
4767
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4768
            }
4769
4770
            $course = $courseRepo->find(api_get_course_int_id());
4771
4772
            // Subscribed groups to a LP
4773
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4774
                TOOL_LEARNPATH_CATEGORY,
4775
                $category->getId(),
4776
                $course,
4777
                $session
4778
            );
4779
4780
            if (!empty($subscribedGroupsInLp)) {
4781
                $groups = array_column($groups, 'iid');
4782
                /** @var CItemProperty $item */
4783
                foreach ($subscribedGroupsInLp as $item) {
4784
                    if ($item->getGroup() &&
4785
                        in_array($item->getGroup()->getId(), $groups)
4786
                    ) {
4787
                        return true;
4788
                    }
4789
                }
4790
            }
4791
        }
4792
4793
        return false;
4794
    }
4795
4796
    /**
4797
     * Check if a learnpath category is published as course tool.
4798
     *
4799
     * @param CLpCategory $category
4800
     * @param int         $courseId
4801
     *
4802
     * @return bool
4803
     */
4804
    public static function categoryIsPublished(
4805
        CLpCategory $category,
4806
        $courseId
4807
    ) {
4808
        $link = self::getCategoryLinkForTool($category->getId());
4809
        $em = Database::getManager();
4810
4811
        $tools = $em
4812
            ->createQuery("
4813
                SELECT t FROM ChamiloCourseBundle:CTool t
4814
                WHERE t.course = :course AND 
4815
                    t.name = :name AND
4816
                    t.image = 'lp_category.gif' AND
4817
                    t.link LIKE :link
4818
            ")
4819
            ->setParameters([
4820
                'course' => $courseId,
4821
                'name' => strip_tags($category->getName()),
4822
                'link' => "$link%",
4823
            ])
4824
            ->getResult();
4825
4826
        /** @var CTool $tool */
4827
        $tool = current($tools);
4828
4829
        return $tool ? $tool->getVisibility() : false;
4830
    }
4831
4832
    /**
4833
     * Restart the whole learnpath. Return the URL of the first element.
4834
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4835
     * To use a similar method  statically, use the create_new_attempt() method.
4836
     *
4837
     * @return bool
4838
     */
4839
    public function restart()
4840
    {
4841
        if ($this->debug > 0) {
4842
            error_log('In learnpath::restart()', 0);
4843
        }
4844
        // TODO
4845
        // Call autosave method to save the current progress.
4846
        //$this->index = 0;
4847
        if (api_is_invitee()) {
4848
            return false;
4849
        }
4850
        $session_id = api_get_session_id();
4851
        $course_id = api_get_course_int_id();
4852
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4853
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4854
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4855
        if ($this->debug > 2) {
4856
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4857
        }
4858
        Database::query($sql);
4859
        $view_id = Database::insert_id();
4860
4861
        if ($view_id) {
4862
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4863
            Database::query($sql);
4864
            $this->lp_view_id = $view_id;
4865
            $this->attempt = $this->attempt + 1;
4866
        } else {
4867
            $this->error = 'Could not insert into item_view table...';
4868
4869
            return false;
4870
        }
4871
        $this->autocomplete_parents($this->current);
4872
        foreach ($this->items as $index => $dummy) {
4873
            $this->items[$index]->restart();
4874
            $this->items[$index]->set_lp_view($this->lp_view_id);
4875
        }
4876
        $this->first();
4877
4878
        return true;
4879
    }
4880
4881
    /**
4882
     * Saves the current item.
4883
     *
4884
     * @return bool
4885
     */
4886
    public function save_current()
4887
    {
4888
        $debug = $this->debug;
4889
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4890
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4891
        if ($debug) {
4892
            error_log('save_current() saving item '.$this->current, 0);
4893
            error_log(''.print_r($this->items, true), 0);
4894
        }
4895
        if (isset($this->items[$this->current]) &&
4896
            is_object($this->items[$this->current])
4897
        ) {
4898
            if ($debug) {
4899
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4900
            }
4901
4902
            $res = $this->items[$this->current]->save(
4903
                false,
4904
                $this->prerequisites_match($this->current)
4905
            );
4906
            $this->autocomplete_parents($this->current);
4907
            $status = $this->items[$this->current]->get_status();
4908
            $this->update_queue[$this->current] = $status;
4909
4910
            if ($debug) {
4911
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4912
            }
4913
4914
            return $res;
4915
        }
4916
4917
        return false;
4918
    }
4919
4920
    /**
4921
     * Saves the given item.
4922
     *
4923
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4924
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4925
     *
4926
     * @return bool
4927
     */
4928
    public function save_item($item_id = null, $from_outside = true)
4929
    {
4930
        $debug = $this->debug;
4931
        if ($debug) {
4932
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4933
        }
4934
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4935
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4936
        if (empty($item_id)) {
4937
            $item_id = (int) $_REQUEST['id'];
4938
        }
4939
4940
        if (empty($item_id)) {
4941
            $item_id = $this->get_current_item_id();
4942
        }
4943
        if (isset($this->items[$item_id]) &&
4944
            is_object($this->items[$item_id])
4945
        ) {
4946
            if ($debug) {
4947
                error_log('Object exists');
4948
            }
4949
4950
            // Saving the item.
4951
            $res = $this->items[$item_id]->save(
4952
                $from_outside,
4953
                $this->prerequisites_match($item_id)
4954
            );
4955
4956
            if ($debug) {
4957
                error_log('update_queue before:');
4958
                error_log(print_r($this->update_queue, 1));
4959
            }
4960
            $this->autocomplete_parents($item_id);
4961
4962
            $status = $this->items[$item_id]->get_status();
4963
            $this->update_queue[$item_id] = $status;
4964
4965
            if ($debug) {
4966
                error_log('get_status(): '.$status);
4967
                error_log('update_queue after:');
4968
                error_log(print_r($this->update_queue, 1));
4969
            }
4970
4971
            return $res;
4972
        }
4973
4974
        return false;
4975
    }
4976
4977
    /**
4978
     * Saves the last item seen's ID only in case.
4979
     */
4980
    public function save_last()
4981
    {
4982
        $course_id = api_get_course_int_id();
4983
        $debug = $this->debug;
4984
        if ($debug) {
4985
            error_log('In learnpath::save_last()', 0);
4986
        }
4987
        $session_condition = api_get_session_condition(
4988
            api_get_session_id(),
4989
            true,
4990
            false
4991
        );
4992
        $table = Database::get_course_table(TABLE_LP_VIEW);
4993
4994
        if (isset($this->current) && !api_is_invitee()) {
4995
            if ($debug) {
4996
                error_log('Saving current item ('.$this->current.') for later review', 0);
4997
            }
4998
            $sql = "UPDATE $table SET
4999
                        last_item = ".intval($this->get_current_item_id())."
5000
                    WHERE
5001
                        c_id = $course_id AND
5002
                        lp_id = ".$this->get_id()." AND
5003
                        user_id = ".$this->get_user_id()." ".$session_condition;
5004
5005
            if ($debug) {
5006
                error_log('Saving last item seen : '.$sql, 0);
5007
            }
5008
            Database::query($sql);
5009
        }
5010
5011
        if (!api_is_invitee()) {
5012
            // Save progress.
5013
            list($progress) = $this->get_progress_bar_text('%');
5014
            if ($progress >= 0 && $progress <= 100) {
5015
                $progress = (int) $progress;
5016
                $sql = "UPDATE $table SET
5017
                            progress = $progress
5018
                        WHERE
5019
                            c_id = $course_id AND
5020
                            lp_id = ".$this->get_id()." AND
5021
                            user_id = ".$this->get_user_id()." ".$session_condition;
5022
                // Ignore errors as some tables might not have the progress field just yet.
5023
                Database::query($sql);
5024
                if ($debug) {
5025
                    error_log($sql);
5026
                }
5027
                $this->progress_db = $progress;
5028
            }
5029
        }
5030
    }
5031
5032
    /**
5033
     * Sets the current item ID (checks if valid and authorized first).
5034
     *
5035
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5036
     */
5037
    public function set_current_item($item_id = null)
5038
    {
5039
        $debug = $this->debug;
5040
        if ($debug) {
5041
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5042
        }
5043
        if (empty($item_id)) {
5044
            if ($debug) {
5045
                error_log('No new current item given, ignore...', 0);
5046
            }
5047
            // Do nothing.
5048
        } else {
5049
            if ($debug) {
5050
                error_log('New current item given is '.$item_id.'...', 0);
5051
            }
5052
            if (is_numeric($item_id)) {
5053
                $item_id = (int) $item_id;
5054
                // TODO: Check in database here.
5055
                $this->last = $this->current;
5056
                $this->current = $item_id;
5057
                // TODO: Update $this->index as well.
5058
                foreach ($this->ordered_items as $index => $item) {
5059
                    if ($item == $this->current) {
5060
                        $this->index = $index;
5061
                        break;
5062
                    }
5063
                }
5064
                if ($debug) {
5065
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5066
                }
5067
            } else {
5068
                if ($debug) {
5069
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5070
                }
5071
            }
5072
        }
5073
    }
5074
5075
    /**
5076
     * Sets the encoding.
5077
     *
5078
     * @param string $enc New encoding
5079
     *
5080
     * @return bool
5081
     *
5082
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5083
     */
5084
    public function set_encoding($enc = 'UTF-8')
5085
    {
5086
        if ($this->debug > 0) {
5087
            error_log('In learnpath::set_encoding()', 0);
5088
        }
5089
5090
        $enc = api_refine_encoding_id($enc);
5091
        if (empty($enc)) {
5092
            $enc = api_get_system_encoding();
5093
        }
5094
        if (api_is_encoding_supported($enc)) {
5095
            $lp = $this->get_id();
5096
            if ($lp != 0) {
5097
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5098
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5099
                        WHERE iid = ".$lp;
5100
                $res = Database::query($sql);
5101
5102
                return $res;
5103
            }
5104
        }
5105
5106
        return false;
5107
    }
5108
5109
    /**
5110
     * Sets the JS lib setting in the database directly.
5111
     * This is the JavaScript library file this lp needs to load on startup.
5112
     *
5113
     * @param string $lib Proximity setting
5114
     *
5115
     * @return bool True on update success. False otherwise.
5116
     */
5117
    public function set_jslib($lib = '')
5118
    {
5119
        if ($this->debug > 0) {
5120
            error_log('In learnpath::set_jslib()', 0);
5121
        }
5122
        $lp = $this->get_id();
5123
5124
        if ($lp != 0) {
5125
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5126
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5127
                    WHERE iid = $lp";
5128
            $res = Database::query($sql);
5129
5130
            return $res;
5131
        } else {
5132
            return false;
5133
        }
5134
    }
5135
5136
    /**
5137
     * Sets the name of the LP maker (publisher) (and save).
5138
     *
5139
     * @param string $name Optional string giving the new content_maker of this learnpath
5140
     *
5141
     * @return bool True
5142
     */
5143
    public function set_maker($name = '')
5144
    {
5145
        if ($this->debug > 0) {
5146
            error_log('In learnpath::set_maker()', 0);
5147
        }
5148
        if (empty($name)) {
5149
            return false;
5150
        }
5151
        $this->maker = $name;
5152
        $table = Database::get_course_table(TABLE_LP_MAIN);
5153
        $lp_id = $this->get_id();
5154
        $sql = "UPDATE $table SET
5155
                content_maker = '".Database::escape_string($this->maker)."'
5156
                WHERE iid = $lp_id";
5157
        if ($this->debug > 2) {
5158
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5159
        }
5160
        Database::query($sql);
5161
5162
        return true;
5163
    }
5164
5165
    /**
5166
     * Sets the name of the current learnpath (and save).
5167
     *
5168
     * @param string $name Optional string giving the new name of this learnpath
5169
     *
5170
     * @return bool True/False
5171
     */
5172
    public function set_name($name = null)
5173
    {
5174
        if ($this->debug > 0) {
5175
            error_log('In learnpath::set_name()', 0);
5176
        }
5177
        if (empty($name)) {
5178
            return false;
5179
        }
5180
        $this->name = Database::escape_string($name);
5181
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5182
        $lp_id = $this->get_id();
5183
        $course_id = $this->course_info['real_id'];
5184
        $sql = "UPDATE $lp_table SET
5185
                name = '".Database::escape_string($this->name)."'
5186
                WHERE iid = $lp_id";
5187
        if ($this->debug > 2) {
5188
            error_log('lp updated with new name : '.$this->name, 0);
5189
        }
5190
        $result = Database::query($sql);
5191
        // If the lp is visible on the homepage, change his name there.
5192
        if (Database::affected_rows($result)) {
5193
            $session_id = api_get_session_id();
5194
            $session_condition = api_get_session_condition($session_id);
5195
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5196
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5197
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5198
            	    WHERE
5199
            	        c_id = $course_id AND
5200
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5201
            Database::query($sql);
5202
5203
            return true;
5204
        } else {
5205
            return false;
5206
        }
5207
    }
5208
5209
    /**
5210
     * Set index specified prefix terms for all items in this path.
5211
     *
5212
     * @param string $terms_string Comma-separated list of terms
5213
     * @param string $prefix       Xapian term prefix
5214
     *
5215
     * @return bool False on error, true otherwise
5216
     */
5217
    public function set_terms_by_prefix($terms_string, $prefix)
5218
    {
5219
        $course_id = api_get_course_int_id();
5220
        if (api_get_setting('search_enabled') !== 'true') {
5221
            return false;
5222
        }
5223
5224
        if (!extension_loaded('xapian')) {
5225
            return false;
5226
        }
5227
5228
        $terms_string = trim($terms_string);
5229
        $terms = explode(',', $terms_string);
5230
        array_walk($terms, 'trim_value');
5231
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5232
5233
        // Don't do anything if no change, verify only at DB, not the search engine.
5234
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5235
            return false;
5236
        }
5237
5238
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5239
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5240
5241
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5242
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5243
        $lp_id = (int) $_POST['lp_id'];
5244
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5245
        $result = Database::query($sql);
5246
        $di = new ChamiloIndexer();
5247
5248
        while ($lp_item = Database::fetch_array($result)) {
5249
            // Get search_did.
5250
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5251
            $sql = 'SELECT * FROM %s
5252
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5253
                    LIMIT 1';
5254
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5255
5256
            //echo $sql; echo '<br>';
5257
            $res = Database::query($sql);
5258
            if (Database::num_rows($res) > 0) {
5259
                $se_ref = Database::fetch_array($res);
5260
                // Compare terms.
5261
                $doc = $di->get_document($se_ref['search_did']);
5262
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5263
                $xterms = [];
5264
                foreach ($xapian_terms as $xapian_term) {
5265
                    $xterms[] = substr($xapian_term['name'], 1);
5266
                }
5267
5268
                $dterms = $terms;
5269
                $missing_terms = array_diff($dterms, $xterms);
5270
                $deprecated_terms = array_diff($xterms, $dterms);
5271
5272
                // Save it to search engine.
5273
                foreach ($missing_terms as $term) {
5274
                    $doc->add_term($prefix.$term, 1);
5275
                }
5276
                foreach ($deprecated_terms as $term) {
5277
                    $doc->remove_term($prefix.$term);
5278
                }
5279
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5280
                $di->getDb()->flush();
5281
            }
5282
        }
5283
5284
        return true;
5285
    }
5286
5287
    /**
5288
     * Sets the theme of the LP (local/remote) (and save).
5289
     *
5290
     * @param string $name Optional string giving the new theme of this learnpath
5291
     *
5292
     * @return bool Returns true if theme name is not empty
5293
     */
5294
    public function set_theme($name = '')
5295
    {
5296
        if ($this->debug > 0) {
5297
            error_log('In learnpath::set_theme()', 0);
5298
        }
5299
        $this->theme = $name;
5300
        $table = Database::get_course_table(TABLE_LP_MAIN);
5301
        $lp_id = $this->get_id();
5302
        $sql = "UPDATE $table 
5303
                SET theme = '".Database::escape_string($this->theme)."'
5304
                WHERE iid = $lp_id";
5305
        if ($this->debug > 2) {
5306
            error_log('lp updated with new theme : '.$this->theme, 0);
5307
        }
5308
        Database::query($sql);
5309
5310
        return true;
5311
    }
5312
5313
    /**
5314
     * Sets the image of an LP (and save).
5315
     *
5316
     * @param string $name Optional string giving the new image of this learnpath
5317
     *
5318
     * @return bool Returns true if theme name is not empty
5319
     */
5320
    public function set_preview_image($name = '')
5321
    {
5322
        if ($this->debug > 0) {
5323
            error_log('In learnpath::set_preview_image()', 0);
5324
        }
5325
5326
        $this->preview_image = $name;
5327
        $table = Database::get_course_table(TABLE_LP_MAIN);
5328
        $lp_id = $this->get_id();
5329
        $sql = "UPDATE $table SET
5330
                preview_image = '".Database::escape_string($this->preview_image)."'
5331
                WHERE iid = $lp_id";
5332
        if ($this->debug > 2) {
5333
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5334
        }
5335
        Database::query($sql);
5336
5337
        return true;
5338
    }
5339
5340
    /**
5341
     * Sets the author of a LP (and save).
5342
     *
5343
     * @param string $name Optional string giving the new author of this learnpath
5344
     *
5345
     * @return bool Returns true if author's name is not empty
5346
     */
5347
    public function set_author($name = '')
5348
    {
5349
        if ($this->debug > 0) {
5350
            error_log('In learnpath::set_author()', 0);
5351
        }
5352
        $this->author = $name;
5353
        $table = Database::get_course_table(TABLE_LP_MAIN);
5354
        $lp_id = $this->get_id();
5355
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5356
                WHERE iid = $lp_id";
5357
        if ($this->debug > 2) {
5358
            error_log('lp updated with new preview author : '.$this->author, 0);
5359
        }
5360
        Database::query($sql);
5361
5362
        return true;
5363
    }
5364
5365
    /**
5366
     * Sets the hide_toc_frame parameter of a LP (and save).
5367
     *
5368
     * @param int $hide 1 if frame is hidden 0 then else
5369
     *
5370
     * @return bool Returns true if author's name is not empty
5371
     */
5372
    public function set_hide_toc_frame($hide)
5373
    {
5374
        if ($this->debug > 0) {
5375
            error_log('In learnpath::set_hide_toc_frame()', 0);
5376
        }
5377
        if (intval($hide) == $hide) {
5378
            $this->hide_toc_frame = $hide;
5379
            $table = Database::get_course_table(TABLE_LP_MAIN);
5380
            $lp_id = $this->get_id();
5381
            $sql = "UPDATE $table SET
5382
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5383
                    WHERE iid = $lp_id";
5384
            if ($this->debug > 2) {
5385
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5386
            }
5387
            Database::query($sql);
5388
5389
            return true;
5390
        } else {
5391
            return false;
5392
        }
5393
    }
5394
5395
    /**
5396
     * Sets the prerequisite of a LP (and save).
5397
     *
5398
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5399
     *
5400
     * @return bool returns true if prerequisite is not empty
5401
     */
5402
    public function set_prerequisite($prerequisite)
5403
    {
5404
        if ($this->debug > 0) {
5405
            error_log('In learnpath::set_prerequisite()', 0);
5406
        }
5407
        $this->prerequisite = (int) $prerequisite;
5408
        $table = Database::get_course_table(TABLE_LP_MAIN);
5409
        $lp_id = $this->get_id();
5410
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5411
                WHERE iid = $lp_id";
5412
        if ($this->debug > 2) {
5413
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5414
        }
5415
        Database::query($sql);
5416
5417
        return true;
5418
    }
5419
5420
    /**
5421
     * Sets the location/proximity of the LP (local/remote) (and save).
5422
     *
5423
     * @param string $name Optional string giving the new location of this learnpath
5424
     *
5425
     * @return bool True on success / False on error
5426
     */
5427
    public function set_proximity($name = '')
5428
    {
5429
        if ($this->debug > 0) {
5430
            error_log('In learnpath::set_proximity()', 0);
5431
        }
5432
        if (empty($name)) {
5433
            return false;
5434
        }
5435
5436
        $this->proximity = $name;
5437
        $table = Database::get_course_table(TABLE_LP_MAIN);
5438
        $lp_id = $this->get_id();
5439
        $sql = "UPDATE $table SET
5440
                    content_local = '".Database::escape_string($name)."'
5441
                WHERE iid = $lp_id";
5442
        if ($this->debug > 2) {
5443
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5444
        }
5445
        Database::query($sql);
5446
5447
        return true;
5448
    }
5449
5450
    /**
5451
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5452
     *
5453
     * @param int $id DB ID of the item
5454
     */
5455
    public function set_previous_item($id)
5456
    {
5457
        if ($this->debug > 0) {
5458
            error_log('In learnpath::set_previous_item()', 0);
5459
        }
5460
        $this->last = $id;
5461
    }
5462
5463
    /**
5464
     * Sets use_max_score.
5465
     *
5466
     * @param int $use_max_score Optional string giving the new location of this learnpath
5467
     *
5468
     * @return bool True on success / False on error
5469
     */
5470
    public function set_use_max_score($use_max_score = 1)
5471
    {
5472
        if ($this->debug > 0) {
5473
            error_log('In learnpath::set_use_max_score()', 0);
5474
        }
5475
        $use_max_score = (int) $use_max_score;
5476
        $this->use_max_score = $use_max_score;
5477
        $table = Database::get_course_table(TABLE_LP_MAIN);
5478
        $lp_id = $this->get_id();
5479
        $sql = "UPDATE $table SET
5480
                    use_max_score = '".$this->use_max_score."'
5481
                WHERE iid = $lp_id";
5482
5483
        if ($this->debug > 2) {
5484
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5485
        }
5486
        Database::query($sql);
5487
5488
        return true;
5489
    }
5490
5491
    /**
5492
     * Sets and saves the expired_on date.
5493
     *
5494
     * @param string $expired_on Optional string giving the new author of this learnpath
5495
     *
5496
     * @throws \Doctrine\ORM\OptimisticLockException
5497
     *
5498
     * @return bool Returns true if author's name is not empty
5499
     */
5500
    public function set_expired_on($expired_on)
5501
    {
5502
        if ($this->debug > 0) {
5503
            error_log('In learnpath::set_expired_on()', 0);
5504
        }
5505
5506
        $em = Database::getManager();
5507
        /** @var CLp $lp */
5508
        $lp = $em
5509
            ->getRepository('ChamiloCourseBundle:CLp')
5510
            ->findOneBy(
5511
                [
5512
                    'iid' => $this->get_id(),
5513
                ]
5514
            );
5515
5516
        if (!$lp) {
5517
            return false;
5518
        }
5519
5520
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5521
5522
        $lp->setExpiredOn($this->expired_on);
5523
        $em->persist($lp);
5524
        $em->flush();
5525
5526
        if ($this->debug > 2) {
5527
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5528
        }
5529
5530
        return true;
5531
    }
5532
5533
    /**
5534
     * Sets and saves the publicated_on date.
5535
     *
5536
     * @param string $publicated_on Optional string giving the new author of this learnpath
5537
     *
5538
     * @throws \Doctrine\ORM\OptimisticLockException
5539
     *
5540
     * @return bool Returns true if author's name is not empty
5541
     */
5542
    public function set_publicated_on($publicated_on)
5543
    {
5544
        if ($this->debug > 0) {
5545
            error_log('In learnpath::set_expired_on()', 0);
5546
        }
5547
5548
        $em = Database::getManager();
5549
        /** @var CLp $lp */
5550
        $lp = $em
5551
            ->getRepository('ChamiloCourseBundle:CLp')
5552
            ->findOneBy(
5553
                [
5554
                    'iid' => $this->get_id(),
5555
                ]
5556
            );
5557
5558
        if (!$lp) {
5559
            return false;
5560
        }
5561
5562
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5563
        $lp->setPublicatedOn($this->publicated_on);
5564
        $em->persist($lp);
5565
        $em->flush();
5566
5567
        if ($this->debug > 2) {
5568
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5569
        }
5570
5571
        return true;
5572
    }
5573
5574
    /**
5575
     * Sets and saves the expired_on date.
5576
     *
5577
     * @return bool Returns true if author's name is not empty
5578
     */
5579
    public function set_modified_on()
5580
    {
5581
        if ($this->debug > 0) {
5582
            error_log('In learnpath::set_expired_on()', 0);
5583
        }
5584
        $this->modified_on = api_get_utc_datetime();
5585
        $table = Database::get_course_table(TABLE_LP_MAIN);
5586
        $lp_id = $this->get_id();
5587
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5588
                WHERE iid = $lp_id";
5589
        if ($this->debug > 2) {
5590
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5591
        }
5592
        Database::query($sql);
5593
5594
        return true;
5595
    }
5596
5597
    /**
5598
     * Sets the object's error message.
5599
     *
5600
     * @param string $error Error message. If empty, reinits the error string
5601
     */
5602
    public function set_error_msg($error = '')
5603
    {
5604
        if ($this->debug > 0) {
5605
            error_log('In learnpath::set_error_msg()', 0);
5606
        }
5607
        if (empty($error)) {
5608
            $this->error = '';
5609
        } else {
5610
            $this->error .= $error;
5611
        }
5612
    }
5613
5614
    /**
5615
     * Launches the current item if not 'sco'
5616
     * (starts timer and make sure there is a record ready in the DB).
5617
     *
5618
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5619
     *
5620
     * @return bool
5621
     */
5622
    public function start_current_item($allow_new_attempt = false)
5623
    {
5624
        $debug = $this->debug;
5625
        if ($debug) {
5626
            error_log('In learnpath::start_current_item()');
5627
            error_log('current: '.$this->current);
5628
        }
5629
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5630
            $type = $this->get_type();
5631
            $item_type = $this->items[$this->current]->get_type();
5632
            if (($type == 2 && $item_type != 'sco') ||
5633
                ($type == 3 && $item_type != 'au') ||
5634
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5635
            ) {
5636
                if ($debug) {
5637
                    error_log('item type: '.$item_type);
5638
                    error_log('lp type: '.$type);
5639
                }
5640
                $this->items[$this->current]->open($allow_new_attempt);
5641
                $this->autocomplete_parents($this->current);
5642
                $prereq_check = $this->prerequisites_match($this->current);
5643
                if ($debug) {
5644
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5645
                }
5646
                $this->items[$this->current]->save(false, $prereq_check);
5647
            }
5648
            // If sco, then it is supposed to have been updated by some other call.
5649
            if ($item_type == 'sco') {
5650
                $this->items[$this->current]->restart();
5651
            }
5652
        }
5653
        if ($debug) {
5654
            error_log('lp_view_session_id');
5655
            error_log($this->lp_view_session_id);
5656
            error_log('api session id');
5657
            error_log(api_get_session_id());
5658
            error_log('End of learnpath::start_current_item()');
5659
        }
5660
5661
        return true;
5662
    }
5663
5664
    /**
5665
     * Stops the processing and counters for the old item (as held in $this->last).
5666
     *
5667
     * @return bool True/False
5668
     */
5669
    public function stop_previous_item()
5670
    {
5671
        $debug = $this->debug;
5672
        if ($debug) {
5673
            error_log('In learnpath::stop_previous_item()', 0);
5674
        }
5675
5676
        if ($this->last != 0 && $this->last != $this->current &&
5677
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5678
        ) {
5679
            if ($debug) {
5680
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5681
            }
5682
            switch ($this->get_type()) {
5683
                case '3':
5684
                    if ($this->items[$this->last]->get_type() != 'au') {
5685
                        if ($debug) {
5686
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5687
                        }
5688
                        $this->items[$this->last]->close();
5689
                    } else {
5690
                        if ($debug) {
5691
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5692
                        }
5693
                    }
5694
                    break;
5695
                case '2':
5696
                    if ($this->items[$this->last]->get_type() != 'sco') {
5697
                        if ($debug) {
5698
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5699
                        }
5700
                        $this->items[$this->last]->close();
5701
                    } else {
5702
                        if ($debug) {
5703
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5704
                        }
5705
                    }
5706
                    break;
5707
                case '1':
5708
                default:
5709
                    if ($debug) {
5710
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5711
                    }
5712
                    $this->items[$this->last]->close();
5713
                    break;
5714
            }
5715
        } else {
5716
            if ($debug) {
5717
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5718
            }
5719
5720
            return false;
5721
        }
5722
5723
        return true;
5724
    }
5725
5726
    /**
5727
     * Updates the default view mode from fullscreen to embedded and inversely.
5728
     *
5729
     * @return string The current default view mode ('fullscreen' or 'embedded')
5730
     */
5731
    public function update_default_view_mode()
5732
    {
5733
        if ($this->debug > 0) {
5734
            error_log('In learnpath::update_default_view_mode()', 0);
5735
        }
5736
        $table = Database::get_course_table(TABLE_LP_MAIN);
5737
        $sql = "SELECT * FROM $table
5738
                WHERE iid = ".$this->get_id();
5739
        $res = Database::query($sql);
5740
        if (Database::num_rows($res) > 0) {
5741
            $row = Database::fetch_array($res);
5742
            $default_view_mode = $row['default_view_mod'];
5743
            $view_mode = $default_view_mode;
5744
            switch ($default_view_mode) {
5745
                case 'fullscreen': // default with popup
5746
                    $view_mode = 'embedded';
5747
                    break;
5748
                case 'embedded': // default view with left menu
5749
                    $view_mode = 'embedframe';
5750
                    break;
5751
                case 'embedframe': //folded menu
5752
                    $view_mode = 'impress';
5753
                    break;
5754
                case 'impress':
5755
                    $view_mode = 'fullscreen';
5756
                    break;
5757
            }
5758
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5759
                    WHERE iid = ".$this->get_id();
5760
            Database::query($sql);
5761
            $this->mode = $view_mode;
5762
5763
            return $view_mode;
5764
        } else {
5765
            if ($this->debug > 2) {
5766
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5767
            }
5768
        }
5769
5770
        return -1;
5771
    }
5772
5773
    /**
5774
     * Updates the default behaviour about auto-commiting SCORM updates.
5775
     *
5776
     * @return bool True if auto-commit has been set to 'on', false otherwise
5777
     */
5778
    public function update_default_scorm_commit()
5779
    {
5780
        if ($this->debug > 0) {
5781
            error_log('In learnpath::update_default_scorm_commit()', 0);
5782
        }
5783
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5784
        $sql = "SELECT * FROM $lp_table
5785
                WHERE iid = ".$this->get_id();
5786
        $res = Database::query($sql);
5787
        if (Database::num_rows($res) > 0) {
5788
            $row = Database::fetch_array($res);
5789
            $force = $row['force_commit'];
5790
            if ($force == 1) {
5791
                $force = 0;
5792
                $force_return = false;
5793
            } elseif ($force == 0) {
5794
                $force = 1;
5795
                $force_return = true;
5796
            }
5797
            $sql = "UPDATE $lp_table SET force_commit = $force
5798
                    WHERE iid = ".$this->get_id();
5799
            Database::query($sql);
5800
            $this->force_commit = $force_return;
5801
5802
            return $force_return;
5803
        } else {
5804
            if ($this->debug > 2) {
5805
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5806
            }
5807
        }
5808
5809
        return -1;
5810
    }
5811
5812
    /**
5813
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5814
     *
5815
     * @return bool True on success, false on failure
5816
     */
5817
    public function update_display_order()
5818
    {
5819
        $course_id = api_get_course_int_id();
5820
        $table = Database::get_course_table(TABLE_LP_MAIN);
5821
        $sql = "SELECT * FROM $table 
5822
                WHERE c_id = $course_id 
5823
                ORDER BY display_order";
5824
        $res = Database::query($sql);
5825
        if ($res === false) {
5826
            return false;
5827
        }
5828
5829
        $num = Database::num_rows($res);
5830
        // First check the order is correct, globally (might be wrong because
5831
        // of versions < 1.8.4).
5832
        if ($num > 0) {
5833
            $i = 1;
5834
            while ($row = Database::fetch_array($res)) {
5835
                if ($row['display_order'] != $i) {
5836
                    // If we find a gap in the order, we need to fix it.
5837
                    $sql = "UPDATE $table SET display_order = $i
5838
                            WHERE iid = ".$row['iid'];
5839
                    Database::query($sql);
5840
                }
5841
                $i++;
5842
            }
5843
        }
5844
5845
        return true;
5846
    }
5847
5848
    /**
5849
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5850
     *
5851
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5852
     */
5853
    public function update_reinit()
5854
    {
5855
        if ($this->debug > 0) {
5856
            error_log('In learnpath::update_reinit()', 0);
5857
        }
5858
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5859
        $sql = "SELECT * FROM $lp_table
5860
                WHERE iid = ".$this->get_id();
5861
        $res = Database::query($sql);
5862
        if (Database::num_rows($res) > 0) {
5863
            $row = Database::fetch_array($res);
5864
            $force = $row['prevent_reinit'];
5865
            if ($force == 1) {
5866
                $force = 0;
5867
            } elseif ($force == 0) {
5868
                $force = 1;
5869
            }
5870
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5871
                    WHERE iid = ".$this->get_id();
5872
            Database::query($sql);
5873
            $this->prevent_reinit = $force;
5874
5875
            return $force;
5876
        } else {
5877
            if ($this->debug > 2) {
5878
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5879
            }
5880
        }
5881
5882
        return -1;
5883
    }
5884
5885
    /**
5886
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5887
     *
5888
     * @return string 'single', 'multi' or 'seriousgame'
5889
     *
5890
     * @author ndiechburg <[email protected]>
5891
     */
5892
    public function get_attempt_mode()
5893
    {
5894
        //Set default value for seriousgame_mode
5895
        if (!isset($this->seriousgame_mode)) {
5896
            $this->seriousgame_mode = 0;
5897
        }
5898
        // Set default value for prevent_reinit
5899
        if (!isset($this->prevent_reinit)) {
5900
            $this->prevent_reinit = 1;
5901
        }
5902
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5903
            return 'seriousgame';
5904
        }
5905
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5906
            return 'single';
5907
        }
5908
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5909
            return 'multiple';
5910
        }
5911
5912
        return 'single';
5913
    }
5914
5915
    /**
5916
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5917
     *
5918
     * @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...
5919
     *
5920
     * @return bool
5921
     *
5922
     * @author ndiechburg <[email protected]>
5923
     */
5924
    public function set_attempt_mode($mode)
5925
    {
5926
        switch ($mode) {
5927
            case 'seriousgame':
5928
                $sg_mode = 1;
5929
                $prevent_reinit = 1;
5930
                break;
5931
            case 'single':
5932
                $sg_mode = 0;
5933
                $prevent_reinit = 1;
5934
                break;
5935
            case 'multiple':
5936
                $sg_mode = 0;
5937
                $prevent_reinit = 0;
5938
                break;
5939
            default:
5940
                $sg_mode = 0;
5941
                $prevent_reinit = 0;
5942
                break;
5943
        }
5944
        $this->prevent_reinit = $prevent_reinit;
5945
        $this->seriousgame_mode = $sg_mode;
5946
        $table = Database::get_course_table(TABLE_LP_MAIN);
5947
        $sql = "UPDATE $table SET
5948
                prevent_reinit = $prevent_reinit ,
5949
                seriousgame_mode = $sg_mode
5950
                WHERE iid = ".$this->get_id();
5951
        $res = Database::query($sql);
5952
        if ($res) {
5953
            return true;
5954
        } else {
5955
            return false;
5956
        }
5957
    }
5958
5959
    /**
5960
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5961
     *
5962
     * @author ndiechburg <[email protected]>
5963
     */
5964
    public function switch_attempt_mode()
5965
    {
5966
        if ($this->debug > 0) {
5967
            error_log('In learnpath::switch_attempt_mode()', 0);
5968
        }
5969
        $mode = $this->get_attempt_mode();
5970
        switch ($mode) {
5971
            case 'single':
5972
                $next_mode = 'multiple';
5973
                break;
5974
            case 'multiple':
5975
                $next_mode = 'seriousgame';
5976
                break;
5977
            case 'seriousgame':
5978
                $next_mode = 'single';
5979
                break;
5980
            default:
5981
                $next_mode = 'single';
5982
                break;
5983
        }
5984
        $this->set_attempt_mode($next_mode);
5985
    }
5986
5987
    /**
5988
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5989
     * but possibility to do again a completed item.
5990
     *
5991
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5992
     *
5993
     * @author ndiechburg <[email protected]>
5994
     */
5995
    public function set_seriousgame_mode()
5996
    {
5997
        if ($this->debug > 0) {
5998
            error_log('In learnpath::set_seriousgame_mode()', 0);
5999
        }
6000
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6001
        $sql = "SELECT * FROM $lp_table 
6002
                WHERE iid = ".$this->get_id();
6003
        $res = Database::query($sql);
6004
        if (Database::num_rows($res) > 0) {
6005
            $row = Database::fetch_array($res);
6006
            $force = $row['seriousgame_mode'];
6007
            if ($force == 1) {
6008
                $force = 0;
6009
            } elseif ($force == 0) {
6010
                $force = 1;
6011
            }
6012
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
6013
			        WHERE iid = ".$this->get_id();
6014
            Database::query($sql);
6015
            $this->seriousgame_mode = $force;
6016
6017
            return $force;
6018
        } else {
6019
            if ($this->debug > 2) {
6020
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
6021
            }
6022
        }
6023
6024
        return -1;
6025
    }
6026
6027
    /**
6028
     * Updates the "scorm_debug" value that shows or hide the debug window.
6029
     *
6030
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6031
     */
6032
    public function update_scorm_debug()
6033
    {
6034
        if ($this->debug > 0) {
6035
            error_log('In learnpath::update_scorm_debug()', 0);
6036
        }
6037
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6038
        $sql = "SELECT * FROM $lp_table
6039
                WHERE iid = ".$this->get_id();
6040
        $res = Database::query($sql);
6041
        if (Database::num_rows($res) > 0) {
6042
            $row = Database::fetch_array($res);
6043
            $force = $row['debug'];
6044
            if ($force == 1) {
6045
                $force = 0;
6046
            } elseif ($force == 0) {
6047
                $force = 1;
6048
            }
6049
            $sql = "UPDATE $lp_table SET debug = $force
6050
                    WHERE iid = ".$this->get_id();
6051
            Database::query($sql);
6052
            $this->scorm_debug = $force;
6053
6054
            return $force;
6055
        } else {
6056
            if ($this->debug > 2) {
6057
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6058
            }
6059
        }
6060
6061
        return -1;
6062
    }
6063
6064
    /**
6065
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6066
     *
6067
     * @author Kevin Van Den Haute
6068
     *
6069
     * @param  array
6070
     */
6071
    public function tree_array($array)
6072
    {
6073
        if ($this->debug > 1) {
6074
            error_log('In learnpath::tree_array()', 0);
6075
        }
6076
        $array = $this->sort_tree_array($array);
6077
        $this->create_tree_array($array);
6078
    }
6079
6080
    /**
6081
     * Creates an array with the elements of the learning path tree in it.
6082
     *
6083
     * @author Kevin Van Den Haute
6084
     *
6085
     * @param array $array
6086
     * @param int   $parent
6087
     * @param int   $depth
6088
     * @param array $tmp
6089
     */
6090
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6091
    {
6092
        if ($this->debug > 1) {
6093
            error_log('In learnpath::create_tree_array())', 0);
6094
        }
6095
6096
        if (is_array($array)) {
6097
            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...
6098
                if ($array[$i]['parent_item_id'] == $parent) {
6099
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6100
                        $tmp[] = $array[$i]['parent_item_id'];
6101
                        $depth++;
6102
                    }
6103
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6104
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6105
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6106
6107
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6108
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6109
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6110
                    $this->arrMenu[] = [
6111
                        'id' => $array[$i]['id'],
6112
                        'ref' => $ref,
6113
                        'item_type' => $array[$i]['item_type'],
6114
                        'title' => $array[$i]['title'],
6115
                        'path' => $path,
6116
                        'description' => $array[$i]['description'],
6117
                        'parent_item_id' => $array[$i]['parent_item_id'],
6118
                        'previous_item_id' => $array[$i]['previous_item_id'],
6119
                        'next_item_id' => $array[$i]['next_item_id'],
6120
                        'min_score' => $array[$i]['min_score'],
6121
                        'max_score' => $array[$i]['max_score'],
6122
                        'mastery_score' => $array[$i]['mastery_score'],
6123
                        'display_order' => $array[$i]['display_order'],
6124
                        'prerequisite' => $preq,
6125
                        'depth' => $depth,
6126
                        'audio' => $audio,
6127
                        'prerequisite_min_score' => $prerequisiteMinScore,
6128
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6129
                    ];
6130
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6131
                }
6132
            }
6133
        }
6134
    }
6135
6136
    /**
6137
     * Sorts a multi dimensional array by parent id and display order.
6138
     *
6139
     * @author Kevin Van Den Haute
6140
     *
6141
     * @param array $array (array with al the learning path items in it)
6142
     *
6143
     * @return array
6144
     */
6145
    public function sort_tree_array($array)
6146
    {
6147
        foreach ($array as $key => $row) {
6148
            $parent[$key] = $row['parent_item_id'];
6149
            $position[$key] = $row['display_order'];
6150
        }
6151
6152
        if (count($array) > 0) {
6153
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6154
        }
6155
6156
        return $array;
6157
    }
6158
6159
    /**
6160
     * Function that creates a html list of learning path items so that we can add audio files to them.
6161
     *
6162
     * @author Kevin Van Den Haute
6163
     *
6164
     * @return string
6165
     */
6166
    public function overview()
6167
    {
6168
        if ($this->debug > 0) {
6169
            error_log('In learnpath::overview()', 0);
6170
        }
6171
6172
        $return = '';
6173
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6174
6175
        // we need to start a form when we want to update all the mp3 files
6176
        if ($update_audio == 'true') {
6177
            $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">';
6178
        }
6179
        $return .= '<div id="message"></div>';
6180
        if (count($this->items) == 0) {
6181
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6182
        } else {
6183
            $return_audio = '<table class="data_table">';
6184
            $return_audio .= '<tr>';
6185
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6186
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6187
            $return_audio .= '</tr>';
6188
6189
            if ($update_audio != 'true') {
6190
                $return .= '<div class="col-md-12">';
6191
                $return .= self::return_new_tree($update_audio);
6192
                $return .= '</div>';
6193
                $return .= Display::div(
6194
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6195
                    ['style' => 'float:left; margin-top:15px;width:100%']
6196
                );
6197
            } else {
6198
                $return_audio .= self::return_new_tree($update_audio);
6199
                $return .= $return_audio.'</table>';
6200
            }
6201
6202
            // We need to close the form when we are updating the mp3 files.
6203
            if ($update_audio == 'true') {
6204
                $return .= '<div class="footer-audio">';
6205
                $return .= Display::button(
6206
                    'save_audio',
6207
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6208
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6209
                );
6210
                $return .= '</div>';
6211
            }
6212
        }
6213
6214
        // We need to close the form when we are updating the mp3 files.
6215
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6216
            $return .= '</form>';
6217
        }
6218
6219
        return $return;
6220
    }
6221
6222
    /**
6223
     * @param string $update_audio
6224
     *
6225
     * @return array
6226
     */
6227
    public function processBuildMenuElements($update_audio = 'false')
6228
    {
6229
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6230
        $course_id = api_get_course_int_id();
6231
        $table = Database::get_course_table(TABLE_LP_ITEM);
6232
6233
        $sql = "SELECT * FROM $table
6234
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6235
6236
        $result = Database::query($sql);
6237
        $arrLP = [];
6238
        while ($row = Database::fetch_array($result)) {
6239
            $arrLP[] = [
6240
                'id' => $row['iid'],
6241
                'item_type' => $row['item_type'],
6242
                'title' => Security::remove_XSS($row['title']),
6243
                'path' => $row['path'],
6244
                'description' => Security::remove_XSS($row['description']),
6245
                'parent_item_id' => $row['parent_item_id'],
6246
                'previous_item_id' => $row['previous_item_id'],
6247
                'next_item_id' => $row['next_item_id'],
6248
                'max_score' => $row['max_score'],
6249
                'min_score' => $row['min_score'],
6250
                'mastery_score' => $row['mastery_score'],
6251
                'prerequisite' => $row['prerequisite'],
6252
                'display_order' => $row['display_order'],
6253
                'audio' => $row['audio'],
6254
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6255
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6256
            ];
6257
        }
6258
6259
        $this->tree_array($arrLP);
6260
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6261
        unset($this->arrMenu);
6262
        $default_data = null;
6263
        $default_content = null;
6264
        $elements = [];
6265
        $return_audio = null;
6266
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6267
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6268
6269
        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...
6270
            $title = $arrLP[$i]['title'];
6271
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6272
6273
            // Link for the documents
6274
            if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6275
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6276
                $title_cut = Display::url(
6277
                    $title_cut,
6278
                    $url,
6279
                    [
6280
                        'class' => 'ajax moved',
6281
                        'data-title' => $title,
6282
                        'title' => $title,
6283
                    ]
6284
                );
6285
            }
6286
6287
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6288
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6289
                Session::write('pathItem', $arrLP[$i]['path']);
6290
            }
6291
6292
            if (($i % 2) == 0) {
6293
                $oddClass = 'row_odd';
6294
            } else {
6295
                $oddClass = 'row_even';
6296
            }
6297
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6298
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6299
6300
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6301
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6302
            } else {
6303
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6304
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6305
                } else {
6306
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6307
                        $icon = Display::return_icon('certificate.png');
6308
                    } else {
6309
                        $icon = Display::return_icon('folder_document.gif');
6310
                    }
6311
                }
6312
            }
6313
6314
            // The audio column.
6315
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6316
            $audio = '';
6317
            if (!$update_audio || $update_audio != 'true') {
6318
                if (empty($arrLP[$i]['audio'])) {
6319
                    $audio .= '';
6320
                }
6321
            } else {
6322
                $types = self::getChapterTypes();
6323
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6324
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6325
                    if (!empty($arrLP[$i]['audio'])) {
6326
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6327
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6328
                    }
6329
                }
6330
            }
6331
6332
            $return_audio .= Display::span($icon.' '.$title).
6333
                Display::tag(
6334
                    'td',
6335
                    $audio,
6336
                    ['style' => '']
6337
                );
6338
            $return_audio .= '</td>';
6339
            $move_icon = '';
6340
            $move_item_icon = '';
6341
            $edit_icon = '';
6342
            $delete_icon = '';
6343
            $audio_icon = '';
6344
            $prerequisities_icon = '';
6345
            $forumIcon = '';
6346
            $previewIcon = '';
6347
            $pluginCalendarIcon = '';
6348
6349
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6350
            $plugin = null;
6351
            if ($pluginCalendar) {
6352
                $plugin = LearningCalendarPlugin::create();
6353
            }
6354
6355
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6356
6357
            if ($is_allowed_to_edit) {
6358
                if (!$update_audio || $update_audio != 'true') {
6359
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6360
                        $move_icon .= '<a class="moved" href="#">';
6361
                        $move_icon .= Display::return_icon(
6362
                            'move_everywhere.png',
6363
                            get_lang('Move'),
6364
                            [],
6365
                            ICON_SIZE_TINY
6366
                        );
6367
                        $move_icon .= '</a>';
6368
                    }
6369
                }
6370
6371
                // No edit for this item types
6372
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6373
                    if ($arrLP[$i]['item_type'] != 'dir') {
6374
                        $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">';
6375
                        $edit_icon .= Display::return_icon(
6376
                            'edit.png',
6377
                            get_lang('LearnpathEditModule'),
6378
                            [],
6379
                            ICON_SIZE_TINY
6380
                        );
6381
                        $edit_icon .= '</a>';
6382
6383
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6384
                            $forumThread = null;
6385
                            if (isset($this->items[$arrLP[$i]['id']])) {
6386
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6387
                                    $this->course_int_id,
6388
                                    $this->lp_session_id
6389
                                );
6390
                            }
6391
                            if ($forumThread) {
6392
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6393
                                        'action' => 'dissociate_forum',
6394
                                        'id' => $arrLP[$i]['id'],
6395
                                        'lp_id' => $this->lp_id,
6396
                                    ]);
6397
                                $forumIcon = Display::url(
6398
                                    Display::return_icon(
6399
                                        'forum.png',
6400
                                        get_lang('DissociateForumToLPItem'),
6401
                                        [],
6402
                                        ICON_SIZE_TINY
6403
                                    ),
6404
                                    $forumIconUrl,
6405
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6406
                                );
6407
                            } else {
6408
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6409
                                        'action' => 'create_forum',
6410
                                        'id' => $arrLP[$i]['id'],
6411
                                        'lp_id' => $this->lp_id,
6412
                                    ]);
6413
                                $forumIcon = Display::url(
6414
                                    Display::return_icon(
6415
                                        'forum.png',
6416
                                        get_lang('AssociateForumToLPItem'),
6417
                                        [],
6418
                                        ICON_SIZE_TINY
6419
                                    ),
6420
                                    $forumIconUrl,
6421
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6422
                                );
6423
                            }
6424
                        }
6425
                    } else {
6426
                        $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">';
6427
                        $edit_icon .= Display::return_icon(
6428
                            'edit.png',
6429
                            get_lang('LearnpathEditModule'),
6430
                            [],
6431
                            ICON_SIZE_TINY
6432
                        );
6433
                        $edit_icon .= '</a>';
6434
                    }
6435
                } else {
6436
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6437
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6438
                        $edit_icon .= Display::return_icon(
6439
                            'edit.png',
6440
                            get_lang('Edit'),
6441
                            [],
6442
                            ICON_SIZE_TINY
6443
                        );
6444
                        $edit_icon .= '</a>';
6445
                    }
6446
                }
6447
6448
                if ($pluginCalendar) {
6449
                    $pluginLink = $pluginUrl.
6450
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6451
6452
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6453
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6454
                    if ($itemInfo && $itemInfo['value'] == 1) {
6455
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6456
                    }
6457
                    $pluginCalendarIcon = Display::url(
6458
                        $iconCalendar,
6459
                        $pluginLink,
6460
                        ['class' => 'btn btn-default']
6461
                    );
6462
                }
6463
6464
                $delete_icon .= ' <a 
6465
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6466
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6467
                    class="btn btn-default">';
6468
                $delete_icon .= Display::return_icon(
6469
                    'delete.png',
6470
                    get_lang('LearnpathDeleteModule'),
6471
                    [],
6472
                    ICON_SIZE_TINY
6473
                );
6474
                $delete_icon .= '</a>';
6475
6476
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6477
                $previewImage = Display::return_icon(
6478
                    'preview_view.png',
6479
                    get_lang('Preview'),
6480
                    [],
6481
                    ICON_SIZE_TINY
6482
                );
6483
6484
                switch ($arrLP[$i]['item_type']) {
6485
                    case TOOL_DOCUMENT:
6486
                    case TOOL_LP_FINAL_ITEM:
6487
                    case TOOL_READOUT_TEXT:
6488
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6489
                        $previewIcon = Display::url(
6490
                            $previewImage,
6491
                            $urlPreviewLink,
6492
                            [
6493
                                'target' => '_blank',
6494
                                'class' => 'btn btn-default',
6495
                                'data-title' => $arrLP[$i]['title'],
6496
                                'title' => $arrLP[$i]['title'],
6497
                            ]
6498
                        );
6499
                        break;
6500
                    case TOOL_THREAD:
6501
                    case TOOL_FORUM:
6502
                    case TOOL_QUIZ:
6503
                    case TOOL_STUDENTPUBLICATION:
6504
                    case TOOL_LP_FINAL_ITEM:
6505
                    case TOOL_LINK:
6506
                        //$target = '';
6507
                        //$class = 'btn btn-default ajax';
6508
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6509
                        $class = 'btn btn-default';
6510
                        $target = '_blank';
6511
                        //}
6512
6513
                        $link = self::rl_get_resource_link_for_learnpath(
6514
                            $this->course_int_id,
6515
                            $this->lp_id,
6516
                            $arrLP[$i]['id'],
6517
                            0
6518
                        );
6519
                        $previewIcon = Display::url(
6520
                            $previewImage,
6521
                            $link,
6522
                            [
6523
                                'class' => $class,
6524
                                'data-title' => $arrLP[$i]['title'],
6525
                                'title' => $arrLP[$i]['title'],
6526
                                'target' => $target,
6527
                            ]
6528
                        );
6529
                        break;
6530
                    default:
6531
                        $previewIcon = Display::url(
6532
                            $previewImage,
6533
                            $url.'&action=view_item',
6534
                            ['class' => 'btn btn-default', 'target' => '_blank']
6535
                        );
6536
                        break;
6537
                }
6538
6539
                if ($arrLP[$i]['item_type'] != 'dir') {
6540
                    $prerequisities_icon = Display::url(
6541
                        Display::return_icon(
6542
                            'accept.png',
6543
                            get_lang('LearnpathPrerequisites'),
6544
                            [],
6545
                            ICON_SIZE_TINY
6546
                        ),
6547
                        $url.'&action=edit_item_prereq',
6548
                        ['class' => 'btn btn-default']
6549
                    );
6550
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6551
                        $move_item_icon = Display::url(
6552
                            Display::return_icon(
6553
                                'move.png',
6554
                                get_lang('Move'),
6555
                                [],
6556
                                ICON_SIZE_TINY
6557
                            ),
6558
                            $url.'&action=move_item',
6559
                            ['class' => 'btn btn-default']
6560
                        );
6561
                    }
6562
                    $audio_icon = Display::url(
6563
                        Display::return_icon(
6564
                            'audio.png',
6565
                            get_lang('UplUpload'),
6566
                            [],
6567
                            ICON_SIZE_TINY
6568
                        ),
6569
                        $url.'&action=add_audio',
6570
                        ['class' => 'btn btn-default']
6571
                    );
6572
                }
6573
            }
6574
            if ($update_audio != 'true') {
6575
                $row = $move_icon.' '.$icon.
6576
                    Display::span($title_cut).
6577
                    Display::tag(
6578
                        'div',
6579
                        "<div class=\"btn-group btn-group-xs\">
6580
                                    $previewIcon 
6581
                                    $audio 
6582
                                    $edit_icon 
6583
                                    $pluginCalendarIcon
6584
                                    $forumIcon 
6585
                                    $prerequisities_icon 
6586
                                    $move_item_icon 
6587
                                    $audio_icon 
6588
                                    $delete_icon
6589
                                </div>",
6590
                        ['class' => 'btn-toolbar button_actions']
6591
                    );
6592
            } else {
6593
                $row =
6594
                    Display::span($title.$icon).
6595
                    Display::span($audio, ['class' => 'button_actions']);
6596
            }
6597
6598
            $parent_id = $arrLP[$i]['parent_item_id'];
6599
            $default_data[$arrLP[$i]['id']] = $row;
6600
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6601
6602
            if (empty($parent_id)) {
6603
                $elements[$arrLP[$i]['id']]['data'] = $row;
6604
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6605
            } else {
6606
                $parent_arrays = [];
6607
                if ($arrLP[$i]['depth'] > 1) {
6608
                    //Getting list of parents
6609
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6610
                        foreach ($arrLP as $item) {
6611
                            if ($item['id'] == $parent_id) {
6612
                                if ($item['parent_item_id'] == 0) {
6613
                                    $parent_id = $item['id'];
6614
                                    break;
6615
                                } else {
6616
                                    $parent_id = $item['parent_item_id'];
6617
                                    if (empty($parent_arrays)) {
6618
                                        $parent_arrays[] = intval($item['id']);
6619
                                    }
6620
                                    $parent_arrays[] = $parent_id;
6621
                                    break;
6622
                                }
6623
                            }
6624
                        }
6625
                    }
6626
                }
6627
6628
                if (!empty($parent_arrays)) {
6629
                    $parent_arrays = array_reverse($parent_arrays);
6630
                    $val = '$elements';
6631
                    $x = 0;
6632
                    foreach ($parent_arrays as $item) {
6633
                        if ($x != count($parent_arrays) - 1) {
6634
                            $val .= '["'.$item.'"]["children"]';
6635
                        } else {
6636
                            $val .= '["'.$item.'"]["children"]';
6637
                        }
6638
                        $x++;
6639
                    }
6640
                    $val .= "";
6641
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6642
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6643
                } else {
6644
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6645
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6646
                }
6647
            }
6648
        }
6649
6650
        return [
6651
            'elements' => $elements,
6652
            'default_data' => $default_data,
6653
            'default_content' => $default_content,
6654
            'return_audio' => $return_audio,
6655
        ];
6656
    }
6657
6658
    /**
6659
     * @param string $updateAudio true/false strings
6660
     *
6661
     * @return string
6662
     */
6663
    public function returnLpItemList($updateAudio)
6664
    {
6665
        $result = $this->processBuildMenuElements($updateAudio);
6666
6667
        $html = self::print_recursive(
6668
            $result['elements'],
6669
            $result['default_data'],
6670
            $result['default_content']
6671
        );
6672
6673
        if (!empty($html)) {
6674
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6675
        }
6676
6677
        return $html;
6678
    }
6679
6680
    /**
6681
     * @param string $update_audio
6682
     * @param bool   $drop_element_here
6683
     *
6684
     * @return string
6685
     */
6686
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6687
    {
6688
        $return = '';
6689
        $result = $this->processBuildMenuElements($update_audio);
6690
6691
        $list = '<ul id="lp_item_list">';
6692
        $tree = $this->print_recursive(
6693
            $result['elements'],
6694
            $result['default_data'],
6695
            $result['default_content']
6696
        );
6697
6698
        if (!empty($tree)) {
6699
            $list .= $tree;
6700
        } else {
6701
            if ($drop_element_here) {
6702
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6703
            }
6704
        }
6705
        $list .= '</ul>';
6706
6707
        $return .= Display::panelCollapse(
6708
            $this->name,
6709
            $list,
6710
            'scorm-list',
6711
            null,
6712
            'scorm-list-accordion',
6713
            'scorm-list-collapse'
6714
        );
6715
6716
        if ($update_audio === 'true') {
6717
            $return = $result['return_audio'];
6718
        }
6719
6720
        return $return;
6721
    }
6722
6723
    /**
6724
     * @param array $elements
6725
     * @param array $default_data
6726
     * @param array $default_content
6727
     *
6728
     * @return string
6729
     */
6730
    public function print_recursive($elements, $default_data, $default_content)
6731
    {
6732
        $return = '';
6733
        foreach ($elements as $key => $item) {
6734
            if (isset($item['load_data']) || empty($item['data'])) {
6735
                $item['data'] = $default_data[$item['load_data']];
6736
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6737
            }
6738
            $sub_list = '';
6739
            if (isset($item['type']) && $item['type'] == 'dir') {
6740
                // empty value
6741
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6742
            }
6743
            if (empty($item['children'])) {
6744
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6745
                $active = null;
6746
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6747
                    $active = 'active';
6748
                }
6749
                $return .= Display::tag(
6750
                    'li',
6751
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6752
                    ['id' => $key, 'class' => 'record li_container']
6753
                );
6754
            } else {
6755
                // Sections
6756
                $data = '';
6757
                if (isset($item['children'])) {
6758
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6759
                }
6760
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6761
                $return .= Display::tag(
6762
                    'li',
6763
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6764
                    ['id' => $key, 'class' => 'record li_container']
6765
                );
6766
            }
6767
        }
6768
6769
        return $return;
6770
    }
6771
6772
    /**
6773
     * This function builds the action menu.
6774
     *
6775
     * @param bool $returnContent          Optional
6776
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6777
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6778
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6779
     *
6780
     * @return string
6781
     */
6782
    public function build_action_menu(
6783
        $returnContent = false,
6784
        $showRequirementButtons = true,
6785
        $isConfigPage = false,
6786
        $allowExpand = true
6787
    ) {
6788
        $actionsLeft = '';
6789
        $actionsRight = '';
6790
        $actionsLeft .= Display::url(
6791
            Display::return_icon(
6792
                'preview_view.png',
6793
                get_lang('Preview'),
6794
                '',
6795
                ICON_SIZE_MEDIUM
6796
            ),
6797
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6798
                'action' => 'view',
6799
                'lp_id' => $this->lp_id,
6800
                'isStudentView' => 'true',
6801
            ])
6802
        );
6803
6804
        $actionsLeft .= Display::url(
6805
            Display::return_icon(
6806
                'upload_audio.png',
6807
                get_lang('UpdateAllAudioFragments'),
6808
                '',
6809
                ICON_SIZE_MEDIUM
6810
            ),
6811
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6812
                'action' => 'admin_view',
6813
                'lp_id' => $this->lp_id,
6814
                'updateaudio' => 'true',
6815
            ])
6816
        );
6817
6818
        if (!$isConfigPage) {
6819
            $actionsLeft .= Display::url(
6820
                Display::return_icon(
6821
                    'settings.png',
6822
                    get_lang('CourseSettings'),
6823
                    '',
6824
                    ICON_SIZE_MEDIUM
6825
                ),
6826
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6827
                    'action' => 'edit',
6828
                    'lp_id' => $this->lp_id,
6829
                ])
6830
            );
6831
        } else {
6832
            $actionsLeft .= Display::url(
6833
                Display::return_icon(
6834
                    'edit.png',
6835
                    get_lang('Edit'),
6836
                    '',
6837
                    ICON_SIZE_MEDIUM
6838
                ),
6839
                'lp_controller.php?'.http_build_query([
6840
                    'action' => 'build',
6841
                    'lp_id' => $this->lp_id,
6842
                ]).'&'.api_get_cidreq()
6843
            );
6844
        }
6845
6846
        if ($allowExpand) {
6847
            $actionsLeft .= Display::url(
6848
                Display::return_icon(
6849
                    'expand.png',
6850
                    get_lang('Expand'),
6851
                    ['id' => 'expand'],
6852
                    ICON_SIZE_MEDIUM
6853
                ).
6854
                Display::return_icon(
6855
                    'contract.png',
6856
                    get_lang('Collapse'),
6857
                    ['id' => 'contract', 'class' => 'hide'],
6858
                    ICON_SIZE_MEDIUM
6859
                ),
6860
                '#',
6861
                ['role' => 'button', 'id' => 'hide_bar_template']
6862
            );
6863
        }
6864
6865
        if ($showRequirementButtons) {
6866
            $buttons = [
6867
                [
6868
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6869
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6870
                        'action' => 'set_previous_step_as_prerequisite',
6871
                        'lp_id' => $this->lp_id,
6872
                    ]),
6873
                ],
6874
                [
6875
                    'title' => get_lang('ClearAllPrerequisites'),
6876
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6877
                        'action' => 'clear_prerequisites',
6878
                        'lp_id' => $this->lp_id,
6879
                    ]),
6880
                ],
6881
            ];
6882
            $actionsRight = Display::groupButtonWithDropDown(
6883
                get_lang('PrerequisitesOptions'),
6884
                $buttons,
6885
                true
6886
            );
6887
        }
6888
6889
        $toolbar = Display::toolbarAction(
6890
            'actions-lp-controller',
6891
            [$actionsLeft, $actionsRight]
6892
        );
6893
6894
        if ($returnContent) {
6895
            return $toolbar;
6896
        }
6897
6898
        echo $toolbar;
6899
    }
6900
6901
    /**
6902
     * Creates the default learning path folder.
6903
     *
6904
     * @param array $course
6905
     * @param int   $creatorId
6906
     *
6907
     * @return bool
6908
     */
6909
    public static function generate_learning_path_folder($course, $creatorId = 0)
6910
    {
6911
        // Creating learning_path folder
6912
        $dir = '/learning_path';
6913
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6914
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6915
6916
        $folder = false;
6917
        if (!is_dir($filepath.'/'.$dir)) {
6918
            $folderData = create_unexisting_directory(
6919
                $course,
6920
                $creatorId,
6921
                0,
6922
                null,
6923
                0,
6924
                $filepath,
6925
                $dir,
6926
                get_lang('LearningPaths'),
6927
                0
6928
            );
6929
            if (!empty($folderData)) {
6930
                $folder = true;
6931
            }
6932
        } else {
6933
            $folder = true;
6934
        }
6935
6936
        return $folder;
6937
    }
6938
6939
    /**
6940
     * @param array  $course
6941
     * @param string $lp_name
6942
     * @param int    $creatorId
6943
     *
6944
     * @return array
6945
     */
6946
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6947
    {
6948
        $filepath = '';
6949
        $dir = '/learning_path/';
6950
6951
        if (empty($lp_name)) {
6952
            $lp_name = $this->name;
6953
        }
6954
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6955
6956
        $folder = self::generate_learning_path_folder($course, $creatorId);
6957
6958
        // Limits title size
6959
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6960
        $dir = $dir.$title;
6961
6962
        // Creating LP folder
6963
        $documentId = null;
6964
        if ($folder) {
6965
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6966
            if (!is_dir($filepath.'/'.$dir)) {
6967
                $folderData = create_unexisting_directory(
6968
                    $course,
6969
                    $creatorId,
6970
                    0,
6971
                    0,
6972
                    0,
6973
                    $filepath,
6974
                    $dir,
6975
                    $lp_name
6976
                );
6977
                if (!empty($folderData)) {
6978
                    $folder = true;
6979
                }
6980
6981
                $documentId = $folderData['id'];
6982
            } else {
6983
                $folder = true;
6984
            }
6985
            $dir = $dir.'/';
6986
            if ($folder) {
6987
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6988
            }
6989
        }
6990
6991
        if (empty($documentId)) {
6992
            $dir = api_remove_trailing_slash($dir);
6993
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6994
        }
6995
6996
        $array = [
6997
            'dir' => $dir,
6998
            'filepath' => $filepath,
6999
            'folder' => $folder,
7000
            'id' => $documentId,
7001
        ];
7002
7003
        return $array;
7004
    }
7005
7006
    /**
7007
     * Create a new document //still needs some finetuning.
7008
     *
7009
     * @param array  $courseInfo
7010
     * @param string $content
7011
     * @param string $title
7012
     * @param string $extension
7013
     * @param int    $parentId
7014
     * @param int    $creatorId  creator id
7015
     *
7016
     * @return int
7017
     */
7018
    public function create_document(
7019
        $courseInfo,
7020
        $content = '',
7021
        $title = '',
7022
        $extension = 'html',
7023
        $parentId = 0,
7024
        $creatorId = 0
7025
    ) {
7026
        if (!empty($courseInfo)) {
7027
            $course_id = $courseInfo['real_id'];
7028
        } else {
7029
            $course_id = api_get_course_int_id();
7030
        }
7031
7032
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7033
        $sessionId = api_get_session_id();
7034
7035
        // Generates folder
7036
        $result = $this->generate_lp_folder($courseInfo);
7037
        $dir = $result['dir'];
7038
7039
        if (empty($parentId) || $parentId == '/') {
7040
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7041
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7042
7043
            if ($parentId === '/') {
7044
                $dir = '/';
7045
            }
7046
7047
            // Please, do not modify this dirname formatting.
7048
            if (strstr($dir, '..')) {
7049
                $dir = '/';
7050
            }
7051
7052
            if (!empty($dir[0]) && $dir[0] == '.') {
7053
                $dir = substr($dir, 1);
7054
            }
7055
            if (!empty($dir[0]) && $dir[0] != '/') {
7056
                $dir = '/'.$dir;
7057
            }
7058
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7059
                $dir .= '/';
7060
            }
7061
        } else {
7062
            $parentInfo = DocumentManager::get_document_data_by_id(
7063
                $parentId,
7064
                $courseInfo['code']
7065
            );
7066
            if (!empty($parentInfo)) {
7067
                $dir = $parentInfo['path'].'/';
7068
            }
7069
        }
7070
7071
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7072
        if (!is_dir($filepath)) {
7073
            $dir = '/';
7074
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7075
        }
7076
7077
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7078
        // is already escaped twice when it gets here.
7079
7080
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7081
        if (!empty($title)) {
7082
            $title = api_replace_dangerous_char(stripslashes($title));
7083
        } else {
7084
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7085
        }
7086
7087
        $title = disable_dangerous_file($title);
7088
        $filename = $title;
7089
        $content = !empty($content) ? $content : $_POST['content_lp'];
7090
        $tmp_filename = $filename;
7091
7092
        $i = 0;
7093
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7094
            $tmp_filename = $filename.'_'.++$i;
7095
        }
7096
7097
        $filename = $tmp_filename.'.'.$extension;
7098
        if ($extension == 'html') {
7099
            $content = stripslashes($content);
7100
            $content = str_replace(
7101
                api_get_path(WEB_COURSE_PATH),
7102
                api_get_path(REL_PATH).'courses/',
7103
                $content
7104
            );
7105
7106
            // Change the path of mp3 to absolute.
7107
7108
            // The first regexp deals with :// urls.
7109
            $content = preg_replace(
7110
                "|(flashvars=\"file=)([^:/]+)/|",
7111
                "$1".api_get_path(
7112
                    REL_COURSE_PATH
7113
                ).$courseInfo['path'].'/document/',
7114
                $content
7115
            );
7116
            // The second regexp deals with audio/ urls.
7117
            $content = preg_replace(
7118
                "|(flashvars=\"file=)([^/]+)/|",
7119
                "$1".api_get_path(
7120
                    REL_COURSE_PATH
7121
                ).$courseInfo['path'].'/document/$2/',
7122
                $content
7123
            );
7124
            // For flv player: To prevent edition problem with firefox,
7125
            // we have to use a strange tip (don't blame me please).
7126
            $content = str_replace(
7127
                '</body>',
7128
                '<style type="text/css">body{}</style></body>',
7129
                $content
7130
            );
7131
        }
7132
7133
        if (!file_exists($filepath.$filename)) {
7134
            if ($fp = @fopen($filepath.$filename, 'w')) {
7135
                fputs($fp, $content);
7136
                fclose($fp);
7137
7138
                $file_size = filesize($filepath.$filename);
7139
                $save_file_path = $dir.$filename;
7140
7141
                $document_id = add_document(
7142
                    $courseInfo,
7143
                    $save_file_path,
7144
                    'file',
7145
                    $file_size,
7146
                    $tmp_filename,
7147
                    '',
7148
                    0, //readonly
7149
                    true,
7150
                    null,
7151
                    $sessionId,
7152
                    $creatorId
7153
                );
7154
7155
                if ($document_id) {
7156
                    api_item_property_update(
7157
                        $courseInfo,
7158
                        TOOL_DOCUMENT,
7159
                        $document_id,
7160
                        'DocumentAdded',
7161
                        $creatorId,
7162
                        null,
7163
                        null,
7164
                        null,
7165
                        null,
7166
                        $sessionId
7167
                    );
7168
7169
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7170
                    $new_title = $originalTitle;
7171
7172
                    if ($new_comment || $new_title) {
7173
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7174
                        $ct = '';
7175
                        if ($new_comment) {
7176
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7177
                        }
7178
                        if ($new_title) {
7179
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7180
                        }
7181
7182
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7183
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7184
                        Database::query($sql);
7185
                    }
7186
                }
7187
7188
                return $document_id;
7189
            }
7190
        }
7191
    }
7192
7193
    /**
7194
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7195
     *
7196
     * @param array $_course array
7197
     */
7198
    public function edit_document($_course)
7199
    {
7200
        $course_id = api_get_course_int_id();
7201
        $urlAppend = api_get_configuration_value('url_append');
7202
        // Please, do not modify this dirname formatting.
7203
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7204
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7205
7206
        if (strstr($dir, '..')) {
7207
            $dir = '/';
7208
        }
7209
7210
        if (isset($dir[0]) && $dir[0] == '.') {
7211
            $dir = substr($dir, 1);
7212
        }
7213
7214
        if (isset($dir[0]) && $dir[0] != '/') {
7215
            $dir = '/'.$dir;
7216
        }
7217
7218
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7219
            $dir .= '/';
7220
        }
7221
7222
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7223
        if (!is_dir($filepath)) {
7224
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7225
        }
7226
7227
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7228
7229
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7230
            $document_id = (int) $_POST['path'];
7231
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7232
            if (empty($documentInfo)) {
7233
                // Try with iid
7234
                $table = Database::get_course_table(TABLE_DOCUMENT);
7235
                $sql = "SELECT id, path FROM $table 
7236
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7237
                $res_doc = Database::query($sql);
7238
                $row = Database::fetch_array($res_doc);
7239
                if ($row) {
7240
                    $document_id = $row['id'];
7241
                    $documentPath = $row['path'];
7242
                }
7243
            } else {
7244
                $documentPath = $documentInfo['path'];
7245
            }
7246
7247
            $content = stripslashes($_POST['content_lp']);
7248
            $file = $filepath.$documentPath;
7249
7250
            if (!file_exists($file)) {
7251
                return false;
7252
            }
7253
7254
            if ($fp = @fopen($file, 'w')) {
7255
                $content = str_replace(
7256
                    api_get_path(WEB_COURSE_PATH),
7257
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7258
                    $content
7259
                );
7260
                // Change the path of mp3 to absolute.
7261
                // The first regexp deals with :// urls.
7262
                $content = preg_replace(
7263
                    "|(flashvars=\"file=)([^:/]+)/|",
7264
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7265
                    $content
7266
                );
7267
                // The second regexp deals with audio/ urls.
7268
                $content = preg_replace(
7269
                    "|(flashvars=\"file=)([^:/]+)/|",
7270
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7271
                    $content
7272
                );
7273
                fputs($fp, $content);
7274
                fclose($fp);
7275
7276
                $sql = "UPDATE $table_doc SET
7277
                            title='".Database::escape_string($_POST['title'])."'
7278
                        WHERE c_id = $course_id AND id = ".$document_id;
7279
                Database::query($sql);
7280
            }
7281
        }
7282
    }
7283
7284
    /**
7285
     * Displays the selected item, with a panel for manipulating the item.
7286
     *
7287
     * @param int    $item_id
7288
     * @param string $msg
7289
     * @param bool   $show_actions
7290
     *
7291
     * @return string
7292
     */
7293
    public function display_item($item_id, $msg = null, $show_actions = true)
7294
    {
7295
        $course_id = api_get_course_int_id();
7296
        $return = '';
7297
        if (is_numeric($item_id)) {
7298
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7299
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7300
                    WHERE lp.iid = ".intval($item_id);
7301
            $result = Database::query($sql);
7302
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7303
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7304
7305
                // Prevents wrong parent selection for document, see Bug#1251.
7306
                if ($row['item_type'] != 'dir') {
7307
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7308
                }
7309
7310
                if ($show_actions) {
7311
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7312
                }
7313
                $return .= '<div style="padding:10px;">';
7314
7315
                if ($msg != '') {
7316
                    $return .= $msg;
7317
                }
7318
7319
                $return .= '<h3>'.$row['title'].'</h3>';
7320
7321
                switch ($row['item_type']) {
7322
                    case TOOL_THREAD:
7323
                        $link = $this->rl_get_resource_link_for_learnpath(
7324
                            $course_id,
7325
                            $row['lp_id'],
7326
                            $item_id,
7327
                            0
7328
                        );
7329
                        $return .= Display::url(
7330
                            get_lang('GoToThread'),
7331
                            $link,
7332
                            ['class' => 'btn btn-primary']
7333
                        );
7334
                        break;
7335
                    case TOOL_FORUM:
7336
                        $return .= Display::url(
7337
                            get_lang('GoToForum'),
7338
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7339
                            ['class' => 'btn btn-primary']
7340
                        );
7341
                        break;
7342
                    case TOOL_QUIZ:
7343
                        if (!empty($row['path'])) {
7344
                            $exercise = new Exercise();
7345
                            $exercise->read($row['path']);
7346
                            $return .= $exercise->description.'<br />';
7347
                            $return .= Display::url(
7348
                                get_lang('GoToExercise'),
7349
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7350
                                ['class' => 'btn btn-primary']
7351
                            );
7352
                        }
7353
                        break;
7354
                    case TOOL_LP_FINAL_ITEM:
7355
                        $return .= $this->getSavedFinalItem();
7356
                        break;
7357
                    case TOOL_DOCUMENT:
7358
                    case TOOL_READOUT_TEXT:
7359
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7360
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
7361
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
7362
                        $result = Database::query($sql_doc);
7363
                        $path_file = Database::result($result, 0, 0);
7364
                        $path_parts = pathinfo($path_file);
7365
                        // TODO: Correct the following naive comparisons.
7366
                        if (in_array($path_parts['extension'], [
7367
                            'html',
7368
                            'txt',
7369
                            'png',
7370
                            'jpg',
7371
                            'JPG',
7372
                            'jpeg',
7373
                            'JPEG',
7374
                            'gif',
7375
                            'swf',
7376
                            'pdf',
7377
                            'htm',
7378
                        ])) {
7379
                            $return .= $this->display_document($row['path'], true, true);
7380
                        }
7381
                        break;
7382
                    case TOOL_HOTPOTATOES:
7383
                        $return .= $this->display_document($row['path'], false, true);
7384
                        break;
7385
                }
7386
                $return .= '</div>';
7387
            }
7388
        }
7389
7390
        return $return;
7391
    }
7392
7393
    /**
7394
     * Shows the needed forms for editing a specific item.
7395
     *
7396
     * @param int $item_id
7397
     *
7398
     * @throws Exception
7399
     * @throws HTML_QuickForm_Error
7400
     *
7401
     * @return string
7402
     */
7403
    public function display_edit_item($item_id)
7404
    {
7405
        $course_id = api_get_course_int_id();
7406
        $return = '';
7407
        $item_id = (int) $item_id;
7408
7409
        if (empty($item_id)) {
7410
            return '';
7411
        }
7412
7413
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7414
        $sql = "SELECT * FROM $tbl_lp_item
7415
                WHERE iid = ".$item_id;
7416
        $res = Database::query($sql);
7417
        $row = Database::fetch_array($res);
7418
        switch ($row['item_type']) {
7419
            case 'dir':
7420
            case 'asset':
7421
            case 'sco':
7422
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7423
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7424
                    $return .= $this->display_item_form(
7425
                        $row['item_type'],
7426
                        get_lang('EditCurrentChapter').' :',
7427
                        'edit',
7428
                        $item_id,
7429
                        $row
7430
                    );
7431
                } else {
7432
                    $return .= $this->display_item_form(
7433
                        $row['item_type'],
7434
                        get_lang('EditCurrentChapter').' :',
7435
                        'edit_item',
7436
                        $item_id,
7437
                        $row
7438
                    );
7439
                }
7440
                break;
7441
            case TOOL_DOCUMENT:
7442
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7443
                $sql = "SELECT lp.*, doc.path as dir
7444
                        FROM $tbl_lp_item as lp
7445
                        LEFT JOIN $tbl_doc as doc
7446
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7447
                        WHERE
7448
                            doc.c_id = $course_id AND
7449
                            lp.iid = ".$item_id;
7450
                $res_step = Database::query($sql);
7451
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7452
                $return .= $this->display_manipulate(
7453
                    $item_id,
7454
                    $row['item_type']
7455
                );
7456
                $return .= $this->display_document_form(
7457
                    'edit',
7458
                    $item_id,
7459
                    $row_step
7460
                );
7461
                break;
7462
            case TOOL_READOUT_TEXT:
7463
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7464
                $sql = "SELECT lp.*, doc.path as dir
7465
                        FROM $tbl_lp_item as lp
7466
                        LEFT JOIN $tbl_doc as doc
7467
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7468
                        WHERE
7469
                            doc.c_id = $course_id AND
7470
                            lp.iid = ".$item_id;
7471
                $res_step = Database::query($sql);
7472
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7473
                $return .= $this->display_manipulate(
7474
                    $item_id,
7475
                    $row['item_type']
7476
                );
7477
                $return .= $this->displayFrmReadOutText(
7478
                    'edit',
7479
                    $item_id,
7480
                    $row_step
7481
                );
7482
                break;
7483
            case TOOL_LINK:
7484
                $link_id = (string) $row['path'];
7485
                if (ctype_digit($link_id)) {
7486
                    $tbl_link = Database::get_course_table(TABLE_LINK);
7487
                    $sql_select = 'SELECT url FROM '.$tbl_link.'
7488
                                   WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7489
                    $res_link = Database::query($sql_select);
7490
                    $row_link = Database::fetch_array($res_link);
7491
                    if (is_array($row_link)) {
7492
                        $row['url'] = $row_link['url'];
7493
                    }
7494
                }
7495
                $return .= $this->display_manipulate(
7496
                    $item_id,
7497
                    $row['item_type']
7498
                );
7499
                $return .= $this->display_link_form('edit', $item_id, $row);
7500
                break;
7501
            case TOOL_LP_FINAL_ITEM:
7502
                Session::write('finalItem', true);
7503
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7504
                $sql = "SELECT lp.*, doc.path as dir
7505
                        FROM $tbl_lp_item as lp
7506
                        LEFT JOIN $tbl_doc as doc
7507
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7508
                        WHERE
7509
                            doc.c_id = $course_id AND
7510
                            lp.iid = ".$item_id;
7511
                $res_step = Database::query($sql);
7512
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7513
                $return .= $this->display_manipulate(
7514
                    $item_id,
7515
                    $row['item_type']
7516
                );
7517
                $return .= $this->display_document_form(
7518
                    'edit',
7519
                    $item_id,
7520
                    $row_step
7521
                );
7522
                break;
7523
            case TOOL_QUIZ:
7524
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7525
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7526
                break;
7527
            case TOOL_HOTPOTATOES:
7528
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7529
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7530
                break;
7531
            case TOOL_STUDENTPUBLICATION:
7532
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7533
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7534
                break;
7535
            case TOOL_FORUM:
7536
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7537
                $return .= $this->display_forum_form('edit', $item_id, $row);
7538
                break;
7539
            case TOOL_THREAD:
7540
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7541
                $return .= $this->display_thread_form('edit', $item_id, $row);
7542
                break;
7543
        }
7544
7545
        return $return;
7546
    }
7547
7548
    /**
7549
     * Function that displays a list with al the resources that
7550
     * could be added to the learning path.
7551
     *
7552
     * @throws Exception
7553
     * @throws HTML_QuickForm_Error
7554
     *
7555
     * @return bool
7556
     */
7557
    public function display_resources()
7558
    {
7559
        $course_code = api_get_course_id();
7560
7561
        // Get all the docs.
7562
        $documents = $this->get_documents(true);
7563
7564
        // Get all the exercises.
7565
        $exercises = $this->get_exercises();
7566
7567
        // Get all the links.
7568
        $links = $this->get_links();
7569
7570
        // Get all the student publications.
7571
        $works = $this->get_student_publications();
7572
7573
        // Get all the forums.
7574
        $forums = $this->get_forums(null, $course_code);
7575
7576
        // Get the final item form (see BT#11048) .
7577
        $finish = $this->getFinalItemForm();
7578
7579
        $headers = [
7580
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7581
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7582
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7583
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7584
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7585
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7586
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7587
        ];
7588
7589
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7590
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7591
        echo Display::tabs(
7592
            $headers,
7593
            [
7594
                $documents,
7595
                $exercises,
7596
                $links,
7597
                $works,
7598
                $forums,
7599
                $dir,
7600
                $finish,
7601
            ],
7602
            'resource_tab'
7603
        );
7604
7605
        return true;
7606
    }
7607
7608
    /**
7609
     * Returns the extension of a document.
7610
     *
7611
     * @param string $filename
7612
     *
7613
     * @return string Extension (part after the last dot)
7614
     */
7615
    public function get_extension($filename)
7616
    {
7617
        $explode = explode('.', $filename);
7618
7619
        return $explode[count($explode) - 1];
7620
    }
7621
7622
    /**
7623
     * Displays a document by id.
7624
     *
7625
     * @param int  $id
7626
     * @param bool $show_title
7627
     * @param bool $iframe
7628
     * @param bool $edit_link
7629
     *
7630
     * @return string
7631
     */
7632
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7633
    {
7634
        $_course = api_get_course_info();
7635
        $course_id = api_get_course_int_id();
7636
        $id = (int) $id;
7637
        $return = '';
7638
        $table = Database::get_course_table(TABLE_DOCUMENT);
7639
        $sql_doc = "SELECT * FROM $table
7640
                    WHERE c_id = $course_id AND iid = $id";
7641
        $res_doc = Database::query($sql_doc);
7642
        $row_doc = Database::fetch_array($res_doc);
7643
7644
        // TODO: Add a path filter.
7645
        if ($iframe) {
7646
            $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>';
7647
        } else {
7648
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7649
        }
7650
7651
        return $return;
7652
    }
7653
7654
    /**
7655
     * Return HTML form to add/edit a quiz.
7656
     *
7657
     * @param string $action     Action (add/edit)
7658
     * @param int    $id         Item ID if already exists
7659
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7660
     *
7661
     * @throws Exception
7662
     *
7663
     * @return string HTML form
7664
     */
7665
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7666
    {
7667
        $course_id = api_get_course_int_id();
7668
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7669
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7670
7671
        if ($id != 0 && is_array($extra_info)) {
7672
            $item_title = $extra_info['title'];
7673
            $item_description = $extra_info['description'];
7674
        } elseif (is_numeric($extra_info)) {
7675
            $sql = "SELECT title, description
7676
                    FROM $tbl_quiz
7677
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7678
7679
            $result = Database::query($sql);
7680
            $row = Database::fetch_array($result);
7681
            $item_title = $row['title'];
7682
            $item_description = $row['description'];
7683
        } else {
7684
            $item_title = '';
7685
            $item_description = '';
7686
        }
7687
        $item_title = Security::remove_XSS($item_title);
7688
        $item_description = Security::remove_XSS($item_description);
7689
7690
        if ($id != 0 && is_array($extra_info)) {
7691
            $parent = $extra_info['parent_item_id'];
7692
        } else {
7693
            $parent = 0;
7694
        }
7695
7696
        $sql = "SELECT * FROM $tbl_lp_item 
7697
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7698
7699
        $result = Database::query($sql);
7700
        $arrLP = [];
7701
        while ($row = Database::fetch_array($result)) {
7702
            $arrLP[] = [
7703
                'id' => $row['iid'],
7704
                'item_type' => $row['item_type'],
7705
                'title' => $row['title'],
7706
                'path' => $row['path'],
7707
                'description' => $row['description'],
7708
                'parent_item_id' => $row['parent_item_id'],
7709
                'previous_item_id' => $row['previous_item_id'],
7710
                'next_item_id' => $row['next_item_id'],
7711
                'display_order' => $row['display_order'],
7712
                'max_score' => $row['max_score'],
7713
                'min_score' => $row['min_score'],
7714
                'mastery_score' => $row['mastery_score'],
7715
                'prerequisite' => $row['prerequisite'],
7716
                'max_time_allowed' => $row['max_time_allowed'],
7717
            ];
7718
        }
7719
7720
        $this->tree_array($arrLP);
7721
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7722
        unset($this->arrMenu);
7723
7724
        $form = new FormValidator(
7725
            'quiz_form',
7726
            'POST',
7727
            $this->getCurrentBuildingModeURL()
7728
        );
7729
        $defaults = [];
7730
7731
        if ($action == 'add') {
7732
            $legend = get_lang('CreateTheExercise');
7733
        } elseif ($action == 'move') {
7734
            $legend = get_lang('MoveTheCurrentExercise');
7735
        } else {
7736
            $legend = get_lang('EditCurrentExecice');
7737
        }
7738
7739
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7740
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7741
        }
7742
7743
        $form->addHeader($legend);
7744
7745
        if ($action != 'move') {
7746
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7747
            $defaults['title'] = $item_title;
7748
        }
7749
7750
        // Select for Parent item, root or chapter
7751
        $selectParent = $form->addSelect(
7752
            'parent',
7753
            get_lang('Parent'),
7754
            [],
7755
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7756
        );
7757
        $selectParent->addOption($this->name, 0);
7758
7759
        $arrHide = [
7760
            $id,
7761
        ];
7762
        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...
7763
            if ($action != 'add') {
7764
                if (
7765
                    ($arrLP[$i]['item_type'] == 'dir') &&
7766
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7767
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7768
                ) {
7769
                    $selectParent->addOption(
7770
                        $arrLP[$i]['title'],
7771
                        $arrLP[$i]['id'],
7772
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7773
                    );
7774
7775
                    if ($parent == $arrLP[$i]['id']) {
7776
                        $selectParent->setSelected($arrLP[$i]['id']);
7777
                    }
7778
                } else {
7779
                    $arrHide[] = $arrLP[$i]['id'];
7780
                }
7781
            } else {
7782
                if ($arrLP[$i]['item_type'] == 'dir') {
7783
                    $selectParent->addOption(
7784
                        $arrLP[$i]['title'],
7785
                        $arrLP[$i]['id'],
7786
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7787
                    );
7788
7789
                    if ($parent == $arrLP[$i]['id']) {
7790
                        $selectParent->setSelected($arrLP[$i]['id']);
7791
                    }
7792
                }
7793
            }
7794
        }
7795
        if (is_array($arrLP)) {
7796
            reset($arrLP);
7797
        }
7798
7799
        $selectPrevious = $form->addSelect(
7800
            'previous',
7801
            get_lang('Position'),
7802
            [],
7803
            ['id' => 'previous']
7804
        );
7805
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7806
7807
        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...
7808
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7809
                $arrLP[$i]['id'] != $id
7810
            ) {
7811
                $selectPrevious->addOption(
7812
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7813
                    $arrLP[$i]['id']
7814
                );
7815
7816
                if (is_array($extra_info)) {
7817
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7818
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7819
                    }
7820
                } elseif ($action == 'add') {
7821
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7822
                }
7823
            }
7824
        }
7825
7826
        if ($action != 'move') {
7827
            $arrHide = [];
7828
            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...
7829
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7830
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7831
                }
7832
            }
7833
        }
7834
7835
        if ($action == 'add') {
7836
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7837
        } else {
7838
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7839
        }
7840
7841
        if ($action == 'move') {
7842
            $form->addHidden('title', $item_title);
7843
            $form->addHidden('description', $item_description);
7844
        }
7845
7846
        if (is_numeric($extra_info)) {
7847
            $form->addHidden('path', $extra_info);
7848
        } elseif (is_array($extra_info)) {
7849
            $form->addHidden('path', $extra_info['path']);
7850
        }
7851
7852
        $form->addHidden('type', TOOL_QUIZ);
7853
        $form->addHidden('post_time', time());
7854
        $form->setDefaults($defaults);
7855
7856
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7857
    }
7858
7859
    /**
7860
     * Addition of Hotpotatoes tests.
7861
     *
7862
     * @param string $action
7863
     * @param int    $id         Internal ID of the item
7864
     * @param string $extra_info
7865
     *
7866
     * @return string HTML structure to display the hotpotatoes addition formular
7867
     */
7868
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7869
    {
7870
        $course_id = api_get_course_int_id();
7871
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7872
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7873
7874
        if ($id != 0 && is_array($extra_info)) {
7875
            $item_title = stripslashes($extra_info['title']);
7876
            $item_description = stripslashes($extra_info['description']);
7877
        } elseif (is_numeric($extra_info)) {
7878
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7879
7880
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7881
                    WHERE
7882
                        c_id = ".$course_id." AND
7883
                        path LIKE '".$uploadPath."/%/%htm%' AND
7884
                        iid = ".(int) $extra_info."
7885
                    ORDER BY iid ASC";
7886
7887
            $res_hot = Database::query($sql);
7888
            $row = Database::fetch_array($res_hot);
7889
7890
            $item_title = $row['title'];
7891
            $item_description = $row['description'];
7892
7893
            if (!empty($row['comment'])) {
7894
                $item_title = $row['comment'];
7895
            }
7896
        } else {
7897
            $item_title = '';
7898
            $item_description = '';
7899
        }
7900
7901
        if ($id != 0 && is_array($extra_info)) {
7902
            $parent = $extra_info['parent_item_id'];
7903
        } else {
7904
            $parent = 0;
7905
        }
7906
7907
        $sql = "SELECT * FROM $tbl_lp_item
7908
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7909
        $result = Database::query($sql);
7910
        $arrLP = [];
7911
        while ($row = Database::fetch_array($result)) {
7912
            $arrLP[] = [
7913
                'id' => $row['id'],
7914
                'item_type' => $row['item_type'],
7915
                'title' => $row['title'],
7916
                'path' => $row['path'],
7917
                'description' => $row['description'],
7918
                'parent_item_id' => $row['parent_item_id'],
7919
                'previous_item_id' => $row['previous_item_id'],
7920
                'next_item_id' => $row['next_item_id'],
7921
                'display_order' => $row['display_order'],
7922
                'max_score' => $row['max_score'],
7923
                'min_score' => $row['min_score'],
7924
                'mastery_score' => $row['mastery_score'],
7925
                'prerequisite' => $row['prerequisite'],
7926
                'max_time_allowed' => $row['max_time_allowed'],
7927
            ];
7928
        }
7929
7930
        $legend = '<legend>';
7931
        if ($action == 'add') {
7932
            $legend .= get_lang('CreateTheExercise');
7933
        } elseif ($action == 'move') {
7934
            $legend .= get_lang('MoveTheCurrentExercise');
7935
        } else {
7936
            $legend .= get_lang('EditCurrentExecice');
7937
        }
7938
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7939
            $legend .= Display:: return_message(
7940
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7941
            );
7942
        }
7943
        $legend .= '</legend>';
7944
7945
        $return = '<form method="POST">';
7946
        $return .= $legend;
7947
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7948
        $return .= '<tr>';
7949
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7950
        $return .= '<td class="input">';
7951
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7952
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7953
        $arrHide = [
7954
            $id,
7955
        ];
7956
7957
        if (count($arrLP) > 0) {
7958
            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...
7959
                if ($action != 'add') {
7960
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7961
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7962
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7963
                    ) {
7964
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7965
                    } else {
7966
                        $arrHide[] = $arrLP[$i]['id'];
7967
                    }
7968
                } else {
7969
                    if ($arrLP[$i]['item_type'] == 'dir') {
7970
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7971
                    }
7972
                }
7973
            }
7974
            reset($arrLP);
7975
        }
7976
7977
        $return .= '</select>';
7978
        $return .= '</td>';
7979
        $return .= '</tr>';
7980
        $return .= '<tr>';
7981
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7982
        $return .= '<td class="input">';
7983
        $return .= '<select id="previous" name="previous" size="1">';
7984
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7985
7986
        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...
7987
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7988
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7989
                    $selected = 'selected="selected" ';
7990
                } elseif ($action == 'add') {
7991
                    $selected = 'selected="selected" ';
7992
                } else {
7993
                    $selected = '';
7994
                }
7995
7996
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7997
            }
7998
        }
7999
8000
        $return .= '</select>';
8001
        $return .= '</td>';
8002
        $return .= '</tr>';
8003
8004
        if ($action != 'move') {
8005
            $return .= '<tr>';
8006
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
8007
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
8008
            $return .= '</tr>';
8009
            $id_prerequisite = 0;
8010
            if (is_array($arrLP) && count($arrLP) > 0) {
8011
                foreach ($arrLP as $key => $value) {
8012
                    if ($value['id'] == $id) {
8013
                        $id_prerequisite = $value['prerequisite'];
8014
                        break;
8015
                    }
8016
                }
8017
8018
                $arrHide = [];
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]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8021
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8022
                    }
8023
                }
8024
            }
8025
        }
8026
8027
        $return .= '<tr>';
8028
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8029
            get_lang('SaveHotpotatoes').'</button></td>';
8030
        $return .= '</tr>';
8031
        $return .= '</table>';
8032
8033
        if ($action == 'move') {
8034
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8035
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8036
        }
8037
8038
        if (is_numeric($extra_info)) {
8039
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8040
        } elseif (is_array($extra_info)) {
8041
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8042
        }
8043
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8044
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8045
        $return .= '</form>';
8046
8047
        return $return;
8048
    }
8049
8050
    /**
8051
     * Return the form to display the forum edit/add option.
8052
     *
8053
     * @param string $action
8054
     * @param int    $id         ID of the lp_item if already exists
8055
     * @param string $extra_info
8056
     *
8057
     * @throws Exception
8058
     *
8059
     * @return string HTML form
8060
     */
8061
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
8062
    {
8063
        $course_id = api_get_course_int_id();
8064
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8065
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8066
8067
        if ($id != 0 && is_array($extra_info)) {
8068
            $item_title = stripslashes($extra_info['title']);
8069
        } elseif (is_numeric($extra_info)) {
8070
            $sql = "SELECT forum_title as title, forum_comment as comment
8071
                    FROM ".$tbl_forum."
8072
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
8073
8074
            $result = Database::query($sql);
8075
            $row = Database::fetch_array($result);
8076
8077
            $item_title = $row['title'];
8078
            $item_description = $row['comment'];
8079
        } else {
8080
            $item_title = '';
8081
            $item_description = '';
8082
        }
8083
8084
        if ($id != 0 && is_array($extra_info)) {
8085
            $parent = $extra_info['parent_item_id'];
8086
        } else {
8087
            $parent = 0;
8088
        }
8089
8090
        $sql = "SELECT * FROM $tbl_lp_item
8091
                WHERE
8092
                    c_id = $course_id AND
8093
                    lp_id = ".$this->lp_id;
8094
        $result = Database::query($sql);
8095
        $arrLP = [];
8096
        while ($row = Database::fetch_array($result)) {
8097
            $arrLP[] = [
8098
                'id' => $row['iid'],
8099
                'item_type' => $row['item_type'],
8100
                'title' => $row['title'],
8101
                'path' => $row['path'],
8102
                'description' => $row['description'],
8103
                'parent_item_id' => $row['parent_item_id'],
8104
                'previous_item_id' => $row['previous_item_id'],
8105
                'next_item_id' => $row['next_item_id'],
8106
                'display_order' => $row['display_order'],
8107
                'max_score' => $row['max_score'],
8108
                'min_score' => $row['min_score'],
8109
                'mastery_score' => $row['mastery_score'],
8110
                'prerequisite' => $row['prerequisite'],
8111
            ];
8112
        }
8113
8114
        $this->tree_array($arrLP);
8115
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8116
        unset($this->arrMenu);
8117
8118
        if ($action == 'add') {
8119
            $legend = get_lang('CreateTheForum');
8120
        } elseif ($action == 'move') {
8121
            $legend = get_lang('MoveTheCurrentForum');
8122
        } else {
8123
            $legend = get_lang('EditCurrentForum');
8124
        }
8125
8126
        $form = new FormValidator(
8127
            'forum_form',
8128
            'POST',
8129
            $this->getCurrentBuildingModeURL()
8130
        );
8131
        $defaults = [];
8132
8133
        $form->addHeader($legend);
8134
8135
        if ($action != 'move') {
8136
            $form->addText(
8137
                'title',
8138
                get_lang('Title'),
8139
                true,
8140
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8141
            );
8142
            $defaults['title'] = $item_title;
8143
        }
8144
8145
        $selectParent = $form->addSelect(
8146
            'parent',
8147
            get_lang('Parent'),
8148
            [],
8149
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8150
        );
8151
        $selectParent->addOption($this->name, 0);
8152
        $arrHide = [
8153
            $id,
8154
        ];
8155
        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...
8156
            if ($action != 'add') {
8157
                if ($arrLP[$i]['item_type'] == 'dir' &&
8158
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8159
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8160
                ) {
8161
                    $selectParent->addOption(
8162
                        $arrLP[$i]['title'],
8163
                        $arrLP[$i]['id'],
8164
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8165
                    );
8166
8167
                    if ($parent == $arrLP[$i]['id']) {
8168
                        $selectParent->setSelected($arrLP[$i]['id']);
8169
                    }
8170
                } else {
8171
                    $arrHide[] = $arrLP[$i]['id'];
8172
                }
8173
            } else {
8174
                if ($arrLP[$i]['item_type'] == 'dir') {
8175
                    $selectParent->addOption(
8176
                        $arrLP[$i]['title'],
8177
                        $arrLP[$i]['id'],
8178
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8179
                    );
8180
8181
                    if ($parent == $arrLP[$i]['id']) {
8182
                        $selectParent->setSelected($arrLP[$i]['id']);
8183
                    }
8184
                }
8185
            }
8186
        }
8187
8188
        if (is_array($arrLP)) {
8189
            reset($arrLP);
8190
        }
8191
8192
        $selectPrevious = $form->addSelect(
8193
            'previous',
8194
            get_lang('Position'),
8195
            [],
8196
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8197
        );
8198
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8199
8200
        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...
8201
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8202
                $arrLP[$i]['id'] != $id
8203
            ) {
8204
                $selectPrevious->addOption(
8205
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8206
                    $arrLP[$i]['id']
8207
                );
8208
8209
                if (isset($extra_info['previous_item_id']) &&
8210
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8211
                ) {
8212
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8213
                } elseif ($action == 'add') {
8214
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8215
                }
8216
            }
8217
        }
8218
8219
        if ($action != 'move') {
8220
            $id_prerequisite = 0;
8221
            if (is_array($arrLP)) {
8222
                foreach ($arrLP as $key => $value) {
8223
                    if ($value['id'] == $id) {
8224
                        $id_prerequisite = $value['prerequisite'];
8225
                        break;
8226
                    }
8227
                }
8228
            }
8229
8230
            $arrHide = [];
8231
            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...
8232
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8233
                    if (isset($extra_info['previous_item_id']) &&
8234
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8235
                    ) {
8236
                        $s_selected_position = $arrLP[$i]['id'];
8237
                    } elseif ($action == 'add') {
8238
                        $s_selected_position = 0;
8239
                    }
8240
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8241
                }
8242
            }
8243
        }
8244
8245
        if ($action == 'add') {
8246
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8247
        } else {
8248
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8249
        }
8250
8251
        if ($action == 'move') {
8252
            $form->addHidden('title', $item_title);
8253
            $form->addHidden('description', $item_description);
8254
        }
8255
8256
        if (is_numeric($extra_info)) {
8257
            $form->addHidden('path', $extra_info);
8258
        } elseif (is_array($extra_info)) {
8259
            $form->addHidden('path', $extra_info['path']);
8260
        }
8261
        $form->addHidden('type', TOOL_FORUM);
8262
        $form->addHidden('post_time', time());
8263
        $form->setDefaults($defaults);
8264
8265
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8266
    }
8267
8268
    /**
8269
     * Return HTML form to add/edit forum threads.
8270
     *
8271
     * @param string $action
8272
     * @param int    $id         Item ID if already exists in learning path
8273
     * @param string $extra_info
8274
     *
8275
     * @throws Exception
8276
     *
8277
     * @return string HTML form
8278
     */
8279
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8280
    {
8281
        $course_id = api_get_course_int_id();
8282
        if (empty($course_id)) {
8283
            return null;
8284
        }
8285
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8286
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8287
8288
        if ($id != 0 && is_array($extra_info)) {
8289
            $item_title = stripslashes($extra_info['title']);
8290
        } elseif (is_numeric($extra_info)) {
8291
            $sql = "SELECT thread_title as title FROM $tbl_forum
8292
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8293
8294
            $result = Database::query($sql);
8295
            $row = Database::fetch_array($result);
8296
8297
            $item_title = $row['title'];
8298
            $item_description = '';
8299
        } else {
8300
            $item_title = '';
8301
            $item_description = '';
8302
        }
8303
8304
        if ($id != 0 && is_array($extra_info)) {
8305
            $parent = $extra_info['parent_item_id'];
8306
        } else {
8307
            $parent = 0;
8308
        }
8309
8310
        $sql = "SELECT * FROM $tbl_lp_item
8311
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8312
        $result = Database::query($sql);
8313
8314
        $arrLP = [];
8315
        while ($row = Database::fetch_array($result)) {
8316
            $arrLP[] = [
8317
                'id' => $row['iid'],
8318
                'item_type' => $row['item_type'],
8319
                'title' => $row['title'],
8320
                'path' => $row['path'],
8321
                'description' => $row['description'],
8322
                'parent_item_id' => $row['parent_item_id'],
8323
                'previous_item_id' => $row['previous_item_id'],
8324
                'next_item_id' => $row['next_item_id'],
8325
                'display_order' => $row['display_order'],
8326
                'max_score' => $row['max_score'],
8327
                'min_score' => $row['min_score'],
8328
                'mastery_score' => $row['mastery_score'],
8329
                'prerequisite' => $row['prerequisite'],
8330
            ];
8331
        }
8332
8333
        $this->tree_array($arrLP);
8334
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8335
        unset($this->arrMenu);
8336
8337
        $form = new FormValidator(
8338
            'thread_form',
8339
            'POST',
8340
            $this->getCurrentBuildingModeURL()
8341
        );
8342
        $defaults = [];
8343
8344
        if ($action == 'add') {
8345
            $legend = get_lang('CreateTheForum');
8346
        } elseif ($action == 'move') {
8347
            $legend = get_lang('MoveTheCurrentForum');
8348
        } else {
8349
            $legend = get_lang('EditCurrentForum');
8350
        }
8351
8352
        $form->addHeader($legend);
8353
        $selectParent = $form->addSelect(
8354
            'parent',
8355
            get_lang('Parent'),
8356
            [],
8357
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8358
        );
8359
        $selectParent->addOption($this->name, 0);
8360
8361
        $arrHide = [
8362
            $id,
8363
        ];
8364
8365
        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...
8366
            if ($action != 'add') {
8367
                if (
8368
                    ($arrLP[$i]['item_type'] == 'dir') &&
8369
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8370
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8371
                ) {
8372
                    $selectParent->addOption(
8373
                        $arrLP[$i]['title'],
8374
                        $arrLP[$i]['id'],
8375
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8376
                    );
8377
8378
                    if ($parent == $arrLP[$i]['id']) {
8379
                        $selectParent->setSelected($arrLP[$i]['id']);
8380
                    }
8381
                } else {
8382
                    $arrHide[] = $arrLP[$i]['id'];
8383
                }
8384
            } else {
8385
                if ($arrLP[$i]['item_type'] == 'dir') {
8386
                    $selectParent->addOption(
8387
                        $arrLP[$i]['title'],
8388
                        $arrLP[$i]['id'],
8389
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8390
                    );
8391
8392
                    if ($parent == $arrLP[$i]['id']) {
8393
                        $selectParent->setSelected($arrLP[$i]['id']);
8394
                    }
8395
                }
8396
            }
8397
        }
8398
8399
        if ($arrLP != null) {
8400
            reset($arrLP);
8401
        }
8402
8403
        $selectPrevious = $form->addSelect(
8404
            'previous',
8405
            get_lang('Position'),
8406
            [],
8407
            ['id' => 'previous']
8408
        );
8409
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8410
8411
        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...
8412
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8413
                $selectPrevious->addOption(
8414
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8415
                    $arrLP[$i]['id']
8416
                );
8417
8418
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8419
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8420
                } elseif ($action == 'add') {
8421
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8422
                }
8423
            }
8424
        }
8425
8426
        if ($action != 'move') {
8427
            $form->addText(
8428
                'title',
8429
                get_lang('Title'),
8430
                true,
8431
                ['id' => 'idTitle']
8432
            );
8433
            $defaults['title'] = $item_title;
8434
8435
            $id_prerequisite = 0;
8436
            if ($arrLP != null) {
8437
                foreach ($arrLP as $key => $value) {
8438
                    if ($value['id'] == $id) {
8439
                        $id_prerequisite = $value['prerequisite'];
8440
                        break;
8441
                    }
8442
                }
8443
            }
8444
8445
            $arrHide = [];
8446
            $s_selected_position = 0;
8447
            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...
8448
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8449
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8450
                        $s_selected_position = $arrLP[$i]['id'];
8451
                    } elseif ($action == 'add') {
8452
                        $s_selected_position = 0;
8453
                    }
8454
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8455
                }
8456
            }
8457
8458
            $selectPrerequisites = $form->addSelect(
8459
                'prerequisites',
8460
                get_lang('LearnpathPrerequisites'),
8461
                [],
8462
                ['id' => 'prerequisites']
8463
            );
8464
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8465
8466
            foreach ($arrHide as $key => $value) {
8467
                $selectPrerequisites->addOption($value['value'], $key);
8468
8469
                if ($key == $s_selected_position && $action == 'add') {
8470
                    $selectPrerequisites->setSelected($key);
8471
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8472
                    $selectPrerequisites->setSelected($key);
8473
                }
8474
            }
8475
        }
8476
8477
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8478
8479
        if ($action == 'move') {
8480
            $form->addHidden('title', $item_title);
8481
            $form->addHidden('description', $item_description);
8482
        }
8483
8484
        if (is_numeric($extra_info)) {
8485
            $form->addHidden('path', $extra_info);
8486
        } elseif (is_array($extra_info)) {
8487
            $form->addHidden('path', $extra_info['path']);
8488
        }
8489
8490
        $form->addHidden('type', TOOL_THREAD);
8491
        $form->addHidden('post_time', time());
8492
        $form->setDefaults($defaults);
8493
8494
        return $form->returnForm();
8495
    }
8496
8497
    /**
8498
     * Return the HTML form to display an item (generally a dir item).
8499
     *
8500
     * @param string $item_type
8501
     * @param string $title
8502
     * @param string $action
8503
     * @param int    $id
8504
     * @param string $extra_info
8505
     *
8506
     * @throws Exception
8507
     * @throws HTML_QuickForm_Error
8508
     *
8509
     * @return string HTML form
8510
     */
8511
    public function display_item_form(
8512
        $item_type,
8513
        $title = '',
8514
        $action = 'add_item',
8515
        $id = 0,
8516
        $extra_info = 'new'
8517
    ) {
8518
        $_course = api_get_course_info();
8519
8520
        global $charset;
8521
8522
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8523
        $item_title = '';
8524
        $item_description = '';
8525
        $item_path_fck = '';
8526
8527
        if ($id != 0 && is_array($extra_info)) {
8528
            $item_title = $extra_info['title'];
8529
            $item_description = $extra_info['description'];
8530
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8531
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8532
        }
8533
        $parent = 0;
8534
        if ($id != 0 && is_array($extra_info)) {
8535
            $parent = $extra_info['parent_item_id'];
8536
        }
8537
8538
        $id = (int) $id;
8539
        $sql = "SELECT * FROM $tbl_lp_item
8540
                WHERE
8541
                    lp_id = ".$this->lp_id." AND
8542
                    iid != $id";
8543
8544
        if ($item_type == 'dir') {
8545
            $sql .= " AND parent_item_id = 0";
8546
        }
8547
8548
        $result = Database::query($sql);
8549
        $arrLP = [];
8550
        while ($row = Database::fetch_array($result)) {
8551
            $arrLP[] = [
8552
                'id' => $row['iid'],
8553
                'item_type' => $row['item_type'],
8554
                'title' => $row['title'],
8555
                'path' => $row['path'],
8556
                'description' => $row['description'],
8557
                'parent_item_id' => $row['parent_item_id'],
8558
                'previous_item_id' => $row['previous_item_id'],
8559
                'next_item_id' => $row['next_item_id'],
8560
                'max_score' => $row['max_score'],
8561
                'min_score' => $row['min_score'],
8562
                'mastery_score' => $row['mastery_score'],
8563
                'prerequisite' => $row['prerequisite'],
8564
                'display_order' => $row['display_order'],
8565
            ];
8566
        }
8567
8568
        $this->tree_array($arrLP);
8569
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8570
        unset($this->arrMenu);
8571
8572
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8573
8574
        $form = new FormValidator('form', 'POST', $url);
8575
        $defaults['title'] = api_html_entity_decode(
8576
            $item_title,
8577
            ENT_QUOTES,
8578
            $charset
8579
        );
8580
        $defaults['description'] = $item_description;
8581
8582
        $form->addHeader($title);
8583
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8584
        $arrHide[0]['padding'] = 20;
8585
        $charset = api_get_system_encoding();
8586
        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...
8587
            if ($action != 'add') {
8588
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8589
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8590
                ) {
8591
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8592
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8593
                    if ($parent == $arrLP[$i]['id']) {
8594
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8595
                    }
8596
                }
8597
            } else {
8598
                if ($arrLP[$i]['item_type'] == 'dir') {
8599
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8600
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8601
                    if ($parent == $arrLP[$i]['id']) {
8602
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8603
                    }
8604
                }
8605
            }
8606
        }
8607
8608
        if ($action != 'move') {
8609
            $form->addElement('text', 'title', get_lang('Title'));
8610
            $form->applyFilter('title', 'html_filter');
8611
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8612
        } else {
8613
            $form->addElement('hidden', 'title');
8614
        }
8615
8616
        $parentSelect = $form->addElement(
8617
            'select',
8618
            'parent',
8619
            get_lang('Parent'),
8620
            '',
8621
            [
8622
                'id' => 'idParent',
8623
                'onchange' => "javascript: load_cbo(this.value);",
8624
            ]
8625
        );
8626
8627
        foreach ($arrHide as $key => $value) {
8628
            $parentSelect->addOption(
8629
                $value['value'],
8630
                $key,
8631
                'style="padding-left:'.$value['padding'].'px;"'
8632
            );
8633
            $lastPosition = $key;
8634
        }
8635
8636
        if (!empty($s_selected_parent)) {
8637
            $parentSelect->setSelected($s_selected_parent);
8638
        }
8639
8640
        if (is_array($arrLP)) {
8641
            reset($arrLP);
8642
        }
8643
        $arrHide = [];
8644
        // POSITION
8645
        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...
8646
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8647
                //this is the same!
8648
                if (isset($extra_info['previous_item_id']) &&
8649
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8650
                ) {
8651
                    $s_selected_position = $arrLP[$i]['id'];
8652
                } elseif ($action == 'add') {
8653
                    $s_selected_position = $arrLP[$i]['id'];
8654
                }
8655
8656
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8657
            }
8658
        }
8659
8660
        $position = $form->addElement(
8661
            'select',
8662
            'previous',
8663
            get_lang('Position'),
8664
            '',
8665
            ['id' => 'previous']
8666
        );
8667
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8668
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8669
8670
        $lastPosition = null;
8671
        foreach ($arrHide as $key => $value) {
8672
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8673
            $lastPosition = $key;
8674
        }
8675
8676
        if (!empty($s_selected_position)) {
8677
            $position->setSelected($s_selected_position);
8678
        }
8679
8680
        // When new chapter add at the end
8681
        if ($action == 'add_item') {
8682
            $position->setSelected($lastPosition);
8683
        }
8684
8685
        if (is_array($arrLP)) {
8686
            reset($arrLP);
8687
        }
8688
8689
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8690
8691
        //fix in order to use the tab
8692
        if ($item_type == 'dir') {
8693
            $form->addElement('hidden', 'type', 'dir');
8694
        }
8695
8696
        $extension = null;
8697
        if (!empty($item_path)) {
8698
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8699
        }
8700
8701
        //assets can't be modified
8702
        //$item_type == 'asset' ||
8703
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8704
            if ($item_type == 'sco') {
8705
                $form->addElement(
8706
                    'html',
8707
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8708
                );
8709
            }
8710
            $renderer = $form->defaultRenderer();
8711
            $renderer->setElementTemplate(
8712
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8713
                'content_lp'
8714
            );
8715
8716
            $relative_prefix = '';
8717
8718
            $editor_config = [
8719
                'ToolbarSet' => 'LearningPathDocuments',
8720
                'Width' => '100%',
8721
                'Height' => '500',
8722
                'FullPage' => true,
8723
                'CreateDocumentDir' => $relative_prefix,
8724
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8725
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8726
            ];
8727
8728
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8729
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8730
            $defaults['content_lp'] = file_get_contents($content_path);
8731
        }
8732
8733
        if (!empty($id)) {
8734
            $form->addHidden('id', $id);
8735
        }
8736
8737
        $form->addElement('hidden', 'type', $item_type);
8738
        $form->addElement('hidden', 'post_time', time());
8739
        $form->setDefaults($defaults);
8740
8741
        return $form->returnForm();
8742
    }
8743
8744
    /**
8745
     * @return string
8746
     */
8747
    public function getCurrentBuildingModeURL()
8748
    {
8749
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8750
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8751
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8752
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8753
8754
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8755
8756
        return $currentUrl;
8757
    }
8758
8759
    /**
8760
     * Returns the form to update or create a document.
8761
     *
8762
     * @param string $action     (add/edit)
8763
     * @param int    $id         ID of the lp_item (if already exists)
8764
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8765
     *
8766
     * @throws Exception
8767
     * @throws HTML_QuickForm_Error
8768
     *
8769
     * @return string HTML form
8770
     */
8771
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8772
    {
8773
        $course_id = api_get_course_int_id();
8774
        $_course = api_get_course_info();
8775
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8776
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8777
8778
        $no_display_edit_textarea = false;
8779
        $item_description = '';
8780
        //If action==edit document
8781
        //We don't display the document form if it's not an editable document (html or txt file)
8782
        if ($action == 'edit') {
8783
            if (is_array($extra_info)) {
8784
                $path_parts = pathinfo($extra_info['dir']);
8785
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8786
                    $no_display_edit_textarea = true;
8787
                }
8788
            }
8789
        }
8790
        $no_display_add = false;
8791
8792
        // If action==add an existing document
8793
        // We don't display the document form if it's not an editable document (html or txt file).
8794
        if ($action == 'add') {
8795
            if (is_numeric($extra_info)) {
8796
                $extra_info = (int) $extra_info;
8797
                $sql_doc = "SELECT path FROM $tbl_doc 
8798
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8799
                $result = Database::query($sql_doc);
8800
                $path_file = Database::result($result, 0, 0);
8801
                $path_parts = pathinfo($path_file);
8802
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8803
                    $no_display_add = true;
8804
                }
8805
            }
8806
        }
8807
        if ($id != 0 && is_array($extra_info)) {
8808
            $item_title = stripslashes($extra_info['title']);
8809
            $item_description = stripslashes($extra_info['description']);
8810
            if (empty($item_title)) {
8811
                $path_parts = pathinfo($extra_info['path']);
8812
                $item_title = stripslashes($path_parts['filename']);
8813
            }
8814
        } elseif (is_numeric($extra_info)) {
8815
            $sql = "SELECT path, title FROM $tbl_doc
8816
                    WHERE
8817
                        c_id = ".$course_id." AND
8818
                        iid = ".intval($extra_info);
8819
            $result = Database::query($sql);
8820
            $row = Database::fetch_array($result);
8821
            $item_title = $row['title'];
8822
            $item_title = str_replace('_', ' ', $item_title);
8823
            if (empty($item_title)) {
8824
                $path_parts = pathinfo($row['path']);
8825
                $item_title = stripslashes($path_parts['filename']);
8826
            }
8827
        } else {
8828
            $item_title = '';
8829
            $item_description = '';
8830
        }
8831
        $return = '<legend>';
8832
        $parent = 0;
8833
        if ($id != 0 && is_array($extra_info)) {
8834
            $parent = $extra_info['parent_item_id'];
8835
        }
8836
8837
        $sql = "SELECT * FROM $tbl_lp_item
8838
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8839
        $result = Database::query($sql);
8840
        $arrLP = [];
8841
8842
        while ($row = Database::fetch_array($result)) {
8843
            $arrLP[] = [
8844
                'id' => $row['iid'],
8845
                'item_type' => $row['item_type'],
8846
                'title' => $row['title'],
8847
                'path' => $row['path'],
8848
                'description' => $row['description'],
8849
                'parent_item_id' => $row['parent_item_id'],
8850
                'previous_item_id' => $row['previous_item_id'],
8851
                'next_item_id' => $row['next_item_id'],
8852
                'display_order' => $row['display_order'],
8853
                'max_score' => $row['max_score'],
8854
                'min_score' => $row['min_score'],
8855
                'mastery_score' => $row['mastery_score'],
8856
                'prerequisite' => $row['prerequisite'],
8857
            ];
8858
        }
8859
8860
        $this->tree_array($arrLP);
8861
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8862
        unset($this->arrMenu);
8863
8864
        if ($action == 'add') {
8865
            $return .= get_lang('CreateTheDocument');
8866
        } elseif ($action == 'move') {
8867
            $return .= get_lang('MoveTheCurrentDocument');
8868
        } else {
8869
            $return .= get_lang('EditTheCurrentDocument');
8870
        }
8871
        $return .= '</legend>';
8872
8873
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8874
            $return .= Display::return_message(
8875
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8876
                false
8877
            );
8878
        }
8879
        $form = new FormValidator(
8880
            'form',
8881
            'POST',
8882
            $this->getCurrentBuildingModeURL(),
8883
            '',
8884
            ['enctype' => 'multipart/form-data']
8885
        );
8886
        $defaults['title'] = Security::remove_XSS($item_title);
8887
        if (empty($item_title)) {
8888
            $defaults['title'] = Security::remove_XSS($item_title);
8889
        }
8890
        $defaults['description'] = $item_description;
8891
        $form->addElement('html', $return);
8892
8893
        if ($action != 'move') {
8894
            $data = $this->generate_lp_folder($_course);
8895
            if ($action != 'edit') {
8896
                $folders = DocumentManager::get_all_document_folders(
8897
                    $_course,
8898
                    0,
8899
                    true
8900
                );
8901
                DocumentManager::build_directory_selector(
8902
                    $folders,
8903
                    '',
8904
                    [],
8905
                    true,
8906
                    $form,
8907
                    'directory_parent_id'
8908
                );
8909
            }
8910
8911
            if (isset($data['id'])) {
8912
                $defaults['directory_parent_id'] = $data['id'];
8913
            }
8914
8915
            $form->addElement(
8916
                'text',
8917
                'title',
8918
                get_lang('Title'),
8919
                ['id' => 'idTitle', 'class' => 'col-md-4']
8920
            );
8921
            $form->applyFilter('title', 'html_filter');
8922
        }
8923
8924
        $arrHide[0]['value'] = $this->name;
8925
        $arrHide[0]['padding'] = 20;
8926
8927
        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...
8928
            if ($action != 'add') {
8929
                if ($arrLP[$i]['item_type'] == 'dir' &&
8930
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8931
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8932
                ) {
8933
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8934
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8935
                }
8936
            } else {
8937
                if ($arrLP[$i]['item_type'] == 'dir') {
8938
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8939
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8940
                }
8941
            }
8942
        }
8943
8944
        $parentSelect = $form->addSelect(
8945
            'parent',
8946
            get_lang('Parent'),
8947
            [],
8948
            [
8949
                'id' => 'idParent',
8950
                'onchange' => 'javascript: load_cbo(this.value);',
8951
            ]
8952
        );
8953
8954
        $my_count = 0;
8955
        foreach ($arrHide as $key => $value) {
8956
            if ($my_count != 0) {
8957
                // The LP name is also the first section and is not in the same charset like the other sections.
8958
                $value['value'] = Security::remove_XSS($value['value']);
8959
                $parentSelect->addOption(
8960
                    $value['value'],
8961
                    $key,
8962
                    'style="padding-left:'.$value['padding'].'px;"'
8963
                );
8964
            } else {
8965
                $value['value'] = Security::remove_XSS($value['value']);
8966
                $parentSelect->addOption(
8967
                    $value['value'],
8968
                    $key,
8969
                    'style="padding-left:'.$value['padding'].'px;"'
8970
                );
8971
            }
8972
            $my_count++;
8973
        }
8974
8975
        if (!empty($id)) {
8976
            $parentSelect->setSelected($parent);
8977
        } else {
8978
            $parent_item_id = Session::read('parent_item_id', 0);
8979
            $parentSelect->setSelected($parent_item_id);
8980
        }
8981
8982
        if (is_array($arrLP)) {
8983
            reset($arrLP);
8984
        }
8985
8986
        $arrHide = [];
8987
        // POSITION
8988
        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...
8989
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8990
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8991
            ) {
8992
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8993
            }
8994
        }
8995
8996
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8997
8998
        $position = $form->addSelect(
8999
            'previous',
9000
            get_lang('Position'),
9001
            [],
9002
            ['id' => 'previous']
9003
        );
9004
9005
        $position->addOption(get_lang('FirstPosition'), 0);
9006
        foreach ($arrHide as $key => $value) {
9007
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9008
            $position->addOption(
9009
                $value['value'],
9010
                $key,
9011
                'style="padding-left:'.$padding.'px;"'
9012
            );
9013
        }
9014
9015
        $position->setSelected($selectedPosition);
9016
9017
        if (is_array($arrLP)) {
9018
            reset($arrLP);
9019
        }
9020
9021
        if ($action != 'move') {
9022
            $arrHide = [];
9023
            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...
9024
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9025
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9026
                ) {
9027
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9028
                }
9029
            }
9030
9031
            if (!$no_display_add) {
9032
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9033
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9034
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
9035
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
9036
                ) {
9037
                    if (isset($_POST['content'])) {
9038
                        $content = stripslashes($_POST['content']);
9039
                    } elseif (is_array($extra_info)) {
9040
                        //If it's an html document or a text file
9041
                        if (!$no_display_edit_textarea) {
9042
                            $content = $this->display_document(
9043
                                $extra_info['path'],
9044
                                false,
9045
                                false
9046
                            );
9047
                        }
9048
                    } elseif (is_numeric($extra_info)) {
9049
                        $content = $this->display_document(
9050
                            $extra_info,
9051
                            false,
9052
                            false
9053
                        );
9054
                    } else {
9055
                        $content = '';
9056
                    }
9057
9058
                    if (!$no_display_edit_textarea) {
9059
                        // We need to calculate here some specific settings for the online editor.
9060
                        // The calculated settings work for documents in the Documents tool
9061
                        // (on the root or in subfolders).
9062
                        // For documents in native scorm packages it is unclear whether the
9063
                        // online editor should be activated or not.
9064
9065
                        // A new document, it is in the root of the repository.
9066
                        $relative_path = '';
9067
                        $relative_prefix = '';
9068
                        if (is_array($extra_info) && $extra_info != 'new') {
9069
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9070
                            $relative_path = explode('/', $extra_info['dir']);
9071
                            $cnt = count($relative_path) - 2;
9072
                            if ($cnt < 0) {
9073
                                $cnt = 0;
9074
                            }
9075
                            $relative_prefix = str_repeat('../', $cnt);
9076
                            $relative_path = array_slice($relative_path, 1, $cnt);
9077
                            $relative_path = implode('/', $relative_path);
9078
                            if (strlen($relative_path) > 0) {
9079
                                $relative_path = $relative_path.'/';
9080
                            }
9081
                        } else {
9082
                            $result = $this->generate_lp_folder($_course);
9083
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9084
                            $relative_prefix = '../../';
9085
                        }
9086
9087
                        $editor_config = [
9088
                            'ToolbarSet' => 'LearningPathDocuments',
9089
                            'Width' => '100%',
9090
                            'Height' => '500',
9091
                            'FullPage' => true,
9092
                            'CreateDocumentDir' => $relative_prefix,
9093
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9094
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9095
                        ];
9096
9097
                        if ($_GET['action'] == 'add_item') {
9098
                            $class = 'add';
9099
                            $text = get_lang('LPCreateDocument');
9100
                        } else {
9101
                            if ($_GET['action'] == 'edit_item') {
9102
                                $class = 'save';
9103
                                $text = get_lang('SaveDocument');
9104
                            }
9105
                        }
9106
9107
                        $form->addButtonSave($text, 'submit_button');
9108
                        $renderer = $form->defaultRenderer();
9109
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9110
                        $form->addElement('html', '<div class="editor-lp">');
9111
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9112
                        $form->addElement('html', '</div>');
9113
                        $defaults['content_lp'] = $content;
9114
                    }
9115
                } elseif (is_numeric($extra_info)) {
9116
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9117
9118
                    $return = $this->display_document($extra_info, true, true, true);
9119
                    $form->addElement('html', $return);
9120
                }
9121
            }
9122
        }
9123
        if (isset($extra_info['item_type']) &&
9124
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9125
        ) {
9126
            $parentSelect->freeze();
9127
            $position->freeze();
9128
        }
9129
9130
        if ($action == 'move') {
9131
            $form->addElement('hidden', 'title', $item_title);
9132
            $form->addElement('hidden', 'description', $item_description);
9133
        }
9134
        if (is_numeric($extra_info)) {
9135
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9136
            $form->addElement('hidden', 'path', $extra_info);
9137
        } elseif (is_array($extra_info)) {
9138
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9139
            $form->addElement('hidden', 'path', $extra_info['path']);
9140
        }
9141
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9142
        $form->addElement('hidden', 'post_time', time());
9143
        $form->setDefaults($defaults);
9144
9145
        return $form->returnForm();
9146
    }
9147
9148
    /**
9149
     * Returns the form to update or create a read-out text.
9150
     *
9151
     * @param string $action     "add" or "edit"
9152
     * @param int    $id         ID of the lp_item (if already exists)
9153
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9154
     *
9155
     * @throws Exception
9156
     * @throws HTML_QuickForm_Error
9157
     *
9158
     * @return string HTML form
9159
     */
9160
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9161
    {
9162
        $course_id = api_get_course_int_id();
9163
        $_course = api_get_course_info();
9164
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9165
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9166
9167
        $no_display_edit_textarea = false;
9168
        $item_description = '';
9169
        //If action==edit document
9170
        //We don't display the document form if it's not an editable document (html or txt file)
9171
        if ($action == 'edit') {
9172
            if (is_array($extra_info)) {
9173
                $path_parts = pathinfo($extra_info['dir']);
9174
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9175
                    $no_display_edit_textarea = true;
9176
                }
9177
            }
9178
        }
9179
        $no_display_add = false;
9180
9181
        if ($id != 0 && is_array($extra_info)) {
9182
            $item_title = stripslashes($extra_info['title']);
9183
            $item_description = stripslashes($extra_info['description']);
9184
            $item_terms = stripslashes($extra_info['terms']);
9185
            if (empty($item_title)) {
9186
                $path_parts = pathinfo($extra_info['path']);
9187
                $item_title = stripslashes($path_parts['filename']);
9188
            }
9189
        } elseif (is_numeric($extra_info)) {
9190
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9191
            $result = Database::query($sql);
9192
            $row = Database::fetch_array($result);
9193
            $item_title = $row['title'];
9194
            $item_title = str_replace('_', ' ', $item_title);
9195
            if (empty($item_title)) {
9196
                $path_parts = pathinfo($row['path']);
9197
                $item_title = stripslashes($path_parts['filename']);
9198
            }
9199
        } else {
9200
            $item_title = '';
9201
            $item_description = '';
9202
        }
9203
9204
        if ($id != 0 && is_array($extra_info)) {
9205
            $parent = $extra_info['parent_item_id'];
9206
        } else {
9207
            $parent = 0;
9208
        }
9209
9210
        $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9211
        $result = Database::query($sql);
9212
        $arrLP = [];
9213
9214
        while ($row = Database::fetch_array($result)) {
9215
            $arrLP[] = [
9216
                'id' => $row['iid'],
9217
                'item_type' => $row['item_type'],
9218
                'title' => $row['title'],
9219
                'path' => $row['path'],
9220
                'description' => $row['description'],
9221
                'parent_item_id' => $row['parent_item_id'],
9222
                'previous_item_id' => $row['previous_item_id'],
9223
                'next_item_id' => $row['next_item_id'],
9224
                'display_order' => $row['display_order'],
9225
                'max_score' => $row['max_score'],
9226
                'min_score' => $row['min_score'],
9227
                'mastery_score' => $row['mastery_score'],
9228
                'prerequisite' => $row['prerequisite'],
9229
            ];
9230
        }
9231
9232
        $this->tree_array($arrLP);
9233
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9234
        unset($this->arrMenu);
9235
9236
        if ($action == 'add') {
9237
            $formHeader = get_lang('CreateTheDocument');
9238
        } else {
9239
            $formHeader = get_lang('EditTheCurrentDocument');
9240
        }
9241
9242
        if ('edit' === $action) {
9243
            $urlAudioIcon = Display::url(
9244
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9245
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9246
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9247
            );
9248
        } else {
9249
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9250
        }
9251
9252
        $form = new FormValidator(
9253
            'frm_add_reading',
9254
            'POST',
9255
            $this->getCurrentBuildingModeURL(),
9256
            '',
9257
            ['enctype' => 'multipart/form-data']
9258
        );
9259
        $form->addHeader($formHeader);
9260
        $form->addHtml(
9261
            Display::return_message(
9262
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9263
                'normal',
9264
                false
9265
            )
9266
        );
9267
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9268
        $defaults['description'] = $item_description;
9269
9270
        $data = $this->generate_lp_folder($_course);
9271
9272
        if ($action != 'edit') {
9273
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9274
            DocumentManager::build_directory_selector(
9275
                $folders,
9276
                '',
9277
                [],
9278
                true,
9279
                $form,
9280
                'directory_parent_id'
9281
            );
9282
        }
9283
9284
        if (isset($data['id'])) {
9285
            $defaults['directory_parent_id'] = $data['id'];
9286
        }
9287
9288
        $form->addElement(
9289
            'text',
9290
            'title',
9291
            get_lang('Title')
9292
        );
9293
        $form->applyFilter('title', 'trim');
9294
        $form->applyFilter('title', 'html_filter');
9295
9296
        $arrHide[0]['value'] = $this->name;
9297
        $arrHide[0]['padding'] = 20;
9298
9299
        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...
9300
            if ($action != 'add') {
9301
                if ($arrLP[$i]['item_type'] == 'dir' &&
9302
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9303
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9304
                ) {
9305
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9306
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9307
                }
9308
            } else {
9309
                if ($arrLP[$i]['item_type'] == 'dir') {
9310
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9311
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9312
                }
9313
            }
9314
        }
9315
9316
        $parent_select = $form->addSelect(
9317
            'parent',
9318
            get_lang('Parent'),
9319
            [],
9320
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9321
        );
9322
9323
        $my_count = 0;
9324
        foreach ($arrHide as $key => $value) {
9325
            if ($my_count != 0) {
9326
                // The LP name is also the first section and is not in the same charset like the other sections.
9327
                $value['value'] = Security::remove_XSS($value['value']);
9328
                $parent_select->addOption(
9329
                    $value['value'],
9330
                    $key,
9331
                    'style="padding-left:'.$value['padding'].'px;"'
9332
                );
9333
            } else {
9334
                $value['value'] = Security::remove_XSS($value['value']);
9335
                $parent_select->addOption(
9336
                    $value['value'],
9337
                    $key,
9338
                    'style="padding-left:'.$value['padding'].'px;"'
9339
                );
9340
            }
9341
            $my_count++;
9342
        }
9343
9344
        if (!empty($id)) {
9345
            $parent_select->setSelected($parent);
9346
        } else {
9347
            $parent_item_id = Session::read('parent_item_id', 0);
9348
            $parent_select->setSelected($parent_item_id);
9349
        }
9350
9351
        if (is_array($arrLP)) {
9352
            reset($arrLP);
9353
        }
9354
9355
        $arrHide = [];
9356
        $s_selected_position = null;
9357
9358
        // POSITION
9359
        $lastPosition = null;
9360
9361
        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...
9362
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9363
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9364
            ) {
9365
                if ((isset($extra_info['previous_item_id']) &&
9366
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9367
                ) {
9368
                    $s_selected_position = $arrLP[$i]['id'];
9369
                }
9370
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9371
            }
9372
            $lastPosition = $arrLP[$i]['id'];
9373
        }
9374
9375
        if (empty($s_selected_position)) {
9376
            $s_selected_position = $lastPosition;
9377
        }
9378
9379
        $position = $form->addSelect(
9380
            'previous',
9381
            get_lang('Position'),
9382
            []
9383
        );
9384
        $position->addOption(get_lang('FirstPosition'), 0);
9385
9386
        foreach ($arrHide as $key => $value) {
9387
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9388
            $position->addOption(
9389
                $value['value'],
9390
                $key,
9391
                'style="padding-left:'.$padding.'px;"'
9392
            );
9393
        }
9394
        $position->setSelected($s_selected_position);
9395
9396
        if (is_array($arrLP)) {
9397
            reset($arrLP);
9398
        }
9399
9400
        $arrHide = [];
9401
9402
        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...
9403
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9404
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9405
            ) {
9406
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9407
            }
9408
        }
9409
9410
        if (!$no_display_add) {
9411
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9412
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9413
9414
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9415
                if (!$no_display_edit_textarea) {
9416
                    $content = '';
9417
9418
                    if (isset($_POST['content'])) {
9419
                        $content = stripslashes($_POST['content']);
9420
                    } elseif (is_array($extra_info)) {
9421
                        $content = $this->display_document($extra_info['path'], false, false);
9422
                    } elseif (is_numeric($extra_info)) {
9423
                        $content = $this->display_document($extra_info, false, false);
9424
                    }
9425
9426
                    // A new document, it is in the root of the repository.
9427
                    if (is_array($extra_info) && $extra_info != 'new') {
9428
                    } else {
9429
                        $this->generate_lp_folder($_course);
9430
                    }
9431
9432
                    if ($_GET['action'] == 'add_item') {
9433
                        $text = get_lang('LPCreateDocument');
9434
                    } else {
9435
                        $text = get_lang('SaveDocument');
9436
                    }
9437
9438
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9439
                    $form
9440
                        ->defaultRenderer()
9441
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9442
                    $form->addButtonSave($text, 'submit_button');
9443
                    $defaults['content_lp'] = $content;
9444
                }
9445
            } elseif (is_numeric($extra_info)) {
9446
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9447
9448
                $return = $this->display_document($extra_info, true, true, true);
9449
                $form->addElement('html', $return);
9450
            }
9451
        }
9452
9453
        if (is_numeric($extra_info)) {
9454
            $form->addElement('hidden', 'path', $extra_info);
9455
        } elseif (is_array($extra_info)) {
9456
            $form->addElement('hidden', 'path', $extra_info['path']);
9457
        }
9458
9459
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9460
        $form->addElement('hidden', 'post_time', time());
9461
        $form->setDefaults($defaults);
9462
9463
        return $form->returnForm();
9464
    }
9465
9466
    /**
9467
     * @param array  $courseInfo
9468
     * @param string $content
9469
     * @param string $title
9470
     * @param int    $parentId
9471
     *
9472
     * @throws \Doctrine\ORM\ORMException
9473
     * @throws \Doctrine\ORM\OptimisticLockException
9474
     * @throws \Doctrine\ORM\TransactionRequiredException
9475
     *
9476
     * @return int
9477
     */
9478
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9479
    {
9480
        $creatorId = api_get_user_id();
9481
        $sessionId = api_get_session_id();
9482
9483
        // Generates folder
9484
        $result = $this->generate_lp_folder($courseInfo);
9485
        $dir = $result['dir'];
9486
9487
        if (empty($parentId) || $parentId == '/') {
9488
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9489
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9490
9491
            if ($parentId === '/') {
9492
                $dir = '/';
9493
            }
9494
9495
            // Please, do not modify this dirname formatting.
9496
            if (strstr($dir, '..')) {
9497
                $dir = '/';
9498
            }
9499
9500
            if (!empty($dir[0]) && $dir[0] == '.') {
9501
                $dir = substr($dir, 1);
9502
            }
9503
            if (!empty($dir[0]) && $dir[0] != '/') {
9504
                $dir = '/'.$dir;
9505
            }
9506
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9507
                $dir .= '/';
9508
            }
9509
        } else {
9510
            $parentInfo = DocumentManager::get_document_data_by_id(
9511
                $parentId,
9512
                $courseInfo['code']
9513
            );
9514
            if (!empty($parentInfo)) {
9515
                $dir = $parentInfo['path'].'/';
9516
            }
9517
        }
9518
9519
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9520
9521
        if (!is_dir($filepath)) {
9522
            $dir = '/';
9523
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9524
        }
9525
9526
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9527
9528
        if (!empty($title)) {
9529
            $title = api_replace_dangerous_char(stripslashes($title));
9530
        } else {
9531
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9532
        }
9533
9534
        $title = disable_dangerous_file($title);
9535
        $filename = $title;
9536
        $content = !empty($content) ? $content : $_POST['content_lp'];
9537
        $tmpFileName = $filename;
9538
9539
        $i = 0;
9540
        while (file_exists($filepath.$tmpFileName.'.html')) {
9541
            $tmpFileName = $filename.'_'.++$i;
9542
        }
9543
9544
        $filename = $tmpFileName.'.html';
9545
        $content = stripslashes($content);
9546
9547
        if (file_exists($filepath.$filename)) {
9548
            return 0;
9549
        }
9550
9551
        $putContent = file_put_contents($filepath.$filename, $content);
9552
9553
        if ($putContent === false) {
9554
            return 0;
9555
        }
9556
9557
        $fileSize = filesize($filepath.$filename);
9558
        $saveFilePath = $dir.$filename;
9559
9560
        $documentId = add_document(
9561
            $courseInfo,
9562
            $saveFilePath,
9563
            'file',
9564
            $fileSize,
9565
            $tmpFileName,
9566
            '',
9567
            0, //readonly
9568
            true,
9569
            null,
9570
            $sessionId,
9571
            $creatorId
9572
        );
9573
9574
        if (!$documentId) {
9575
            return 0;
9576
        }
9577
9578
        api_item_property_update(
9579
            $courseInfo,
9580
            TOOL_DOCUMENT,
9581
            $documentId,
9582
            'DocumentAdded',
9583
            $creatorId,
9584
            null,
9585
            null,
9586
            null,
9587
            null,
9588
            $sessionId
9589
        );
9590
9591
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9592
        $newTitle = $originalTitle;
9593
9594
        if ($newComment || $newTitle) {
9595
            $em = Database::getManager();
9596
9597
            /** @var CDocument $doc */
9598
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9599
9600
            if ($newComment) {
9601
                $doc->setComment($newComment);
9602
            }
9603
9604
            if ($newTitle) {
9605
                $doc->setTitle($newTitle);
9606
            }
9607
9608
            $em->persist($doc);
9609
            $em->flush();
9610
        }
9611
9612
        return $documentId;
9613
    }
9614
9615
    /**
9616
     * Return HTML form to add/edit a link item.
9617
     *
9618
     * @param string $action     (add/edit)
9619
     * @param int    $id         Item ID if exists
9620
     * @param mixed  $extra_info
9621
     *
9622
     * @throws Exception
9623
     * @throws HTML_QuickForm_Error
9624
     *
9625
     * @return string HTML form
9626
     */
9627
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9628
    {
9629
        $course_id = api_get_course_int_id();
9630
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9631
        $tbl_link = Database::get_course_table(TABLE_LINK);
9632
9633
        if ($id != 0 && is_array($extra_info)) {
9634
            $item_title = stripslashes($extra_info['title']);
9635
            $item_description = stripslashes($extra_info['description']);
9636
            $item_url = stripslashes($extra_info['url']);
9637
        } elseif (is_numeric($extra_info)) {
9638
            $extra_info = intval($extra_info);
9639
            $sql = "SELECT title, description, url FROM ".$tbl_link."
9640
                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
9641
            $result = Database::query($sql);
9642
            $row = Database::fetch_array($result);
9643
            $item_title = $row['title'];
9644
            $item_description = $row['description'];
9645
            $item_url = $row['url'];
9646
        } else {
9647
            $item_title = '';
9648
            $item_description = '';
9649
            $item_url = '';
9650
        }
9651
9652
        $form = new FormValidator(
9653
            'edit_link',
9654
            'POST',
9655
            $this->getCurrentBuildingModeURL()
9656
        );
9657
        $defaults = [];
9658
        if ($id != 0 && is_array($extra_info)) {
9659
            $parent = $extra_info['parent_item_id'];
9660
        } else {
9661
            $parent = 0;
9662
        }
9663
9664
        $sql = "SELECT * FROM $tbl_lp_item
9665
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9666
        $result = Database::query($sql);
9667
        $arrLP = [];
9668
9669
        while ($row = Database::fetch_array($result)) {
9670
            $arrLP[] = [
9671
                'id' => $row['id'],
9672
                'item_type' => $row['item_type'],
9673
                'title' => $row['title'],
9674
                'path' => $row['path'],
9675
                'description' => $row['description'],
9676
                'parent_item_id' => $row['parent_item_id'],
9677
                'previous_item_id' => $row['previous_item_id'],
9678
                'next_item_id' => $row['next_item_id'],
9679
                'display_order' => $row['display_order'],
9680
                'max_score' => $row['max_score'],
9681
                'min_score' => $row['min_score'],
9682
                'mastery_score' => $row['mastery_score'],
9683
                'prerequisite' => $row['prerequisite'],
9684
            ];
9685
        }
9686
9687
        $this->tree_array($arrLP);
9688
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9689
        unset($this->arrMenu);
9690
9691
        if ($action == 'add') {
9692
            $legend = get_lang('CreateTheLink');
9693
        } elseif ($action == 'move') {
9694
            $legend = get_lang('MoveCurrentLink');
9695
        } else {
9696
            $legend = get_lang('EditCurrentLink');
9697
        }
9698
9699
        $form->addHeader($legend);
9700
9701
        if ($action != 'move') {
9702
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9703
            $defaults['title'] = $item_title;
9704
        }
9705
9706
        $selectParent = $form->addSelect(
9707
            'parent',
9708
            get_lang('Parent'),
9709
            [],
9710
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9711
        );
9712
        $selectParent->addOption($this->name, 0);
9713
        $arrHide = [
9714
            $id,
9715
        ];
9716
9717
        $parent_item_id = Session::read('parent_item_id', 0);
9718
9719
        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...
9720
            if ($action != 'add') {
9721
                if (
9722
                    ($arrLP[$i]['item_type'] == 'dir') &&
9723
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9724
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9725
                ) {
9726
                    $selectParent->addOption(
9727
                        $arrLP[$i]['title'],
9728
                        $arrLP[$i]['id'],
9729
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9730
                    );
9731
9732
                    if ($parent == $arrLP[$i]['id']) {
9733
                        $selectParent->setSelected($arrLP[$i]['id']);
9734
                    }
9735
                } else {
9736
                    $arrHide[] = $arrLP[$i]['id'];
9737
                }
9738
            } else {
9739
                if ($arrLP[$i]['item_type'] == 'dir') {
9740
                    $selectParent->addOption(
9741
                        $arrLP[$i]['title'],
9742
                        $arrLP[$i]['id'],
9743
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9744
                    );
9745
9746
                    if ($parent_item_id == $arrLP[$i]['id']) {
9747
                        $selectParent->setSelected($arrLP[$i]['id']);
9748
                    }
9749
                }
9750
            }
9751
        }
9752
9753
        if (is_array($arrLP)) {
9754
            reset($arrLP);
9755
        }
9756
9757
        $selectPrevious = $form->addSelect(
9758
            'previous',
9759
            get_lang('Position'),
9760
            [],
9761
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9762
        );
9763
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9764
9765
        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...
9766
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9767
                $selectPrevious->addOption(
9768
                    $arrLP[$i]['title'],
9769
                    $arrLP[$i]['id']
9770
                );
9771
9772
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9773
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9774
                } elseif ($action == 'add') {
9775
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9776
                }
9777
            }
9778
        }
9779
9780
        if ($action != 'move') {
9781
            $urlAttributes = ['class' => 'learnpath_item_form'];
9782
9783
            if (is_numeric($extra_info)) {
9784
                $urlAttributes['disabled'] = 'disabled';
9785
            }
9786
9787
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9788
            $defaults['url'] = $item_url;
9789
            $arrHide = [];
9790
            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...
9791
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9792
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9793
                }
9794
            }
9795
        }
9796
9797
        if ($action == 'add') {
9798
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9799
        } else {
9800
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9801
        }
9802
9803
        if ($action == 'move') {
9804
            $form->addHidden('title', $item_title);
9805
            $form->addHidden('description', $item_description);
9806
        }
9807
9808
        if (is_numeric($extra_info)) {
9809
            $form->addHidden('path', $extra_info);
9810
        } elseif (is_array($extra_info)) {
9811
            $form->addHidden('path', $extra_info['path']);
9812
        }
9813
        $form->addHidden('type', TOOL_LINK);
9814
        $form->addHidden('post_time', time());
9815
        $form->setDefaults($defaults);
9816
9817
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9818
    }
9819
9820
    /**
9821
     * Return HTML form to add/edit a student publication (work).
9822
     *
9823
     * @param string $action
9824
     * @param int    $id         Item ID if already exists
9825
     * @param string $extra_info
9826
     *
9827
     * @throws Exception
9828
     *
9829
     * @return string HTML form
9830
     */
9831
    public function display_student_publication_form(
9832
        $action = 'add',
9833
        $id = 0,
9834
        $extra_info = ''
9835
    ) {
9836
        $course_id = api_get_course_int_id();
9837
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9838
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9839
9840
        $item_title = get_lang('Student_publication');
9841
        if ($id != 0 && is_array($extra_info)) {
9842
            $item_title = stripslashes($extra_info['title']);
9843
            $item_description = stripslashes($extra_info['description']);
9844
        } elseif (is_numeric($extra_info)) {
9845
            $extra_info = (int) $extra_info;
9846
            $sql = "SELECT title, description
9847
                    FROM $tbl_publication
9848
                    WHERE c_id = $course_id AND id = ".$extra_info;
9849
9850
            $result = Database::query($sql);
9851
            $row = Database::fetch_array($result);
9852
            if ($row) {
9853
                $item_title = $row['title'];
9854
            }
9855
        }
9856
9857
        $parent = 0;
9858
        if ($id != 0 && is_array($extra_info)) {
9859
            $parent = $extra_info['parent_item_id'];
9860
        }
9861
9862
        $sql = "SELECT * FROM $tbl_lp_item 
9863
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9864
        $result = Database::query($sql);
9865
        $arrLP = [];
9866
9867
        while ($row = Database::fetch_array($result)) {
9868
            $arrLP[] = [
9869
                'id' => $row['iid'],
9870
                'item_type' => $row['item_type'],
9871
                'title' => $row['title'],
9872
                'path' => $row['path'],
9873
                'description' => $row['description'],
9874
                'parent_item_id' => $row['parent_item_id'],
9875
                'previous_item_id' => $row['previous_item_id'],
9876
                'next_item_id' => $row['next_item_id'],
9877
                'display_order' => $row['display_order'],
9878
                'max_score' => $row['max_score'],
9879
                'min_score' => $row['min_score'],
9880
                'mastery_score' => $row['mastery_score'],
9881
                'prerequisite' => $row['prerequisite'],
9882
            ];
9883
        }
9884
9885
        $this->tree_array($arrLP);
9886
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9887
        unset($this->arrMenu);
9888
9889
        $form = new FormValidator('frm_student_publication', 'post', '#');
9890
9891
        if ($action == 'add') {
9892
            $form->addHeader(get_lang('Student_publication'));
9893
        } elseif ($action == 'move') {
9894
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9895
        } else {
9896
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9897
        }
9898
9899
        if ($action != 'move') {
9900
            $form->addText(
9901
                'title',
9902
                get_lang('Title'),
9903
                true,
9904
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9905
            );
9906
        }
9907
9908
        $parentSelect = $form->addSelect(
9909
            'parent',
9910
            get_lang('Parent'),
9911
            ['0' => $this->name],
9912
            [
9913
                'onchange' => 'javascript: load_cbo(this.value);',
9914
                'class' => 'learnpath_item_form',
9915
                'id' => 'idParent',
9916
            ]
9917
        );
9918
9919
        $arrHide = [$id];
9920
        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...
9921
            if ($action != 'add') {
9922
                if (
9923
                    ($arrLP[$i]['item_type'] == 'dir') &&
9924
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9925
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9926
                ) {
9927
                    $parentSelect->addOption(
9928
                        $arrLP[$i]['title'],
9929
                        $arrLP[$i]['id'],
9930
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9931
                    );
9932
9933
                    if ($parent == $arrLP[$i]['id']) {
9934
                        $parentSelect->setSelected($arrLP[$i]['id']);
9935
                    }
9936
                } else {
9937
                    $arrHide[] = $arrLP[$i]['id'];
9938
                }
9939
            } else {
9940
                if ($arrLP[$i]['item_type'] == 'dir') {
9941
                    $parentSelect->addOption(
9942
                        $arrLP[$i]['title'],
9943
                        $arrLP[$i]['id'],
9944
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9945
                    );
9946
9947
                    if ($parent == $arrLP[$i]['id']) {
9948
                        $parentSelect->setSelected($arrLP[$i]['id']);
9949
                    }
9950
                }
9951
            }
9952
        }
9953
9954
        if (is_array($arrLP)) {
9955
            reset($arrLP);
9956
        }
9957
9958
        $previousSelect = $form->addSelect(
9959
            'previous',
9960
            get_lang('Position'),
9961
            ['0' => get_lang('FirstPosition')],
9962
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9963
        );
9964
9965
        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...
9966
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9967
                $previousSelect->addOption(
9968
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9969
                    $arrLP[$i]['id']
9970
                );
9971
9972
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9973
                    $previousSelect->setSelected($arrLP[$i]['id']);
9974
                } elseif ($action == 'add') {
9975
                    $previousSelect->setSelected($arrLP[$i]['id']);
9976
                }
9977
            }
9978
        }
9979
9980
        if ($action == 'add') {
9981
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9982
        } else {
9983
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9984
        }
9985
9986
        if ($action == 'move') {
9987
            $form->addHidden('title', $item_title);
9988
            $form->addHidden('description', $item_description);
9989
        }
9990
9991
        if (is_numeric($extra_info)) {
9992
            $form->addHidden('path', $extra_info);
9993
        } elseif (is_array($extra_info)) {
9994
            $form->addHidden('path', $extra_info['path']);
9995
        }
9996
9997
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9998
        $form->addHidden('post_time', time());
9999
        $form->setDefaults(['title' => $item_title]);
10000
10001
        $return = '<div class="sectioncomment">';
10002
        $return .= $form->returnForm();
10003
        $return .= '</div>';
10004
10005
        return $return;
10006
    }
10007
10008
    /**
10009
     * Displays the menu for manipulating a step.
10010
     *
10011
     * @param id     $item_id
10012
     * @param string $item_type
10013
     *
10014
     * @return string
10015
     */
10016
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
10017
    {
10018
        $_course = api_get_course_info();
10019
        $course_code = api_get_course_id();
10020
        $return = '<div class="actions">';
10021
        switch ($item_type) {
10022
            case 'dir':
10023
                // Commented the message cause should not show it.
10024
                //$lang = get_lang('TitleManipulateChapter');
10025
                break;
10026
            case TOOL_LP_FINAL_ITEM:
10027
            case TOOL_DOCUMENT:
10028
                // Commented the message cause should not show it.
10029
                //$lang = get_lang('TitleManipulateDocument');
10030
                break;
10031
            case TOOL_LINK:
10032
            case 'link':
10033
                // Commented the message cause should not show it.
10034
                //$lang = get_lang('TitleManipulateLink');
10035
                break;
10036
            case TOOL_QUIZ:
10037
                // Commented the message cause should not show it.
10038
                //$lang = get_lang('TitleManipulateQuiz');
10039
                break;
10040
            case TOOL_STUDENTPUBLICATION:
10041
                // Commented the message cause should not show it.
10042
                //$lang = get_lang('TitleManipulateStudentPublication');
10043
                break;
10044
        }
10045
10046
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10047
        $item_id = (int) $item_id;
10048
        $sql = "SELECT * FROM $tbl_lp_item 
10049
                WHERE iid = ".$item_id;
10050
        $result = Database::query($sql);
10051
        $row = Database::fetch_assoc($result);
10052
10053
        $audio_player = null;
10054
        // We display an audio player if needed.
10055
        if (!empty($row['audio'])) {
10056
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
10057
10058
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
10059
                .'<audio src="'.$webAudioPath.'" controls>'
10060
                .'</div><br>';
10061
        }
10062
10063
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
10064
10065
        if ($item_type != TOOL_LP_FINAL_ITEM) {
10066
            $return .= Display::url(
10067
                Display::return_icon(
10068
                    'edit.png',
10069
                    get_lang('Edit'),
10070
                    [],
10071
                    ICON_SIZE_SMALL
10072
                ),
10073
                $url.'&action=edit_item&path_item='.$row['path']
10074
            );
10075
10076
            $return .= Display::url(
10077
                Display::return_icon(
10078
                    'move.png',
10079
                    get_lang('Move'),
10080
                    [],
10081
                    ICON_SIZE_SMALL
10082
                ),
10083
                $url.'&action=move_item'
10084
            );
10085
        }
10086
10087
        // Commented for now as prerequisites cannot be added to chapters.
10088
        if ($item_type != 'dir') {
10089
            $return .= Display::url(
10090
                Display::return_icon(
10091
                    'accept.png',
10092
                    get_lang('LearnpathPrerequisites'),
10093
                    [],
10094
                    ICON_SIZE_SMALL
10095
                ),
10096
                $url.'&action=edit_item_prereq'
10097
            );
10098
        }
10099
        $return .= Display::url(
10100
            Display::return_icon(
10101
                'delete.png',
10102
                get_lang('Delete'),
10103
                [],
10104
                ICON_SIZE_SMALL
10105
            ),
10106
            $url.'&action=delete_item'
10107
        );
10108
10109
        if ($item_type == TOOL_HOTPOTATOES) {
10110
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10111
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
10112
        }
10113
10114
        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
10115
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10116
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
10117
        }
10118
10119
        $return .= '</div>';
10120
10121
        if (!empty($audio_player)) {
10122
            $return .= $audio_player;
10123
        }
10124
10125
        return $return;
10126
    }
10127
10128
    /**
10129
     * Creates the javascript needed for filling up the checkboxes without page reload.
10130
     *
10131
     * @return string
10132
     */
10133
    public function get_js_dropdown_array()
10134
    {
10135
        $course_id = api_get_course_int_id();
10136
        $return = 'var child_name = new Array();'."\n";
10137
        $return .= 'var child_value = new Array();'."\n\n";
10138
        $return .= 'child_name[0] = new Array();'."\n";
10139
        $return .= 'child_value[0] = new Array();'."\n\n";
10140
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10141
        $sql = "SELECT * FROM ".$tbl_lp_item."
10142
                WHERE 
10143
                    c_id = $course_id AND 
10144
                    lp_id = ".$this->lp_id." AND 
10145
                    parent_item_id = 0
10146
                ORDER BY display_order ASC";
10147
        $res_zero = Database::query($sql);
10148
        $i = 0;
10149
10150
        while ($row_zero = Database::fetch_array($res_zero)) {
10151
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10152
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10153
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10154
                }
10155
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10156
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10157
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10158
            }
10159
        }
10160
        $return .= "\n";
10161
        $sql = "SELECT * FROM $tbl_lp_item
10162
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10163
        $res = Database::query($sql);
10164
        while ($row = Database::fetch_array($res)) {
10165
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10166
                           WHERE
10167
                                c_id = ".$course_id." AND
10168
                                parent_item_id = ".$row['iid']."
10169
                           ORDER BY display_order ASC";
10170
            $res_parent = Database::query($sql_parent);
10171
            $i = 0;
10172
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10173
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10174
10175
            while ($row_parent = Database::fetch_array($res_parent)) {
10176
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
10177
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10178
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10179
            }
10180
            $return .= "\n";
10181
        }
10182
10183
        $return .= "
10184
            function load_cbo(id) {
10185
                if (!id) {
10186
                    return false;
10187
                }
10188
            
10189
                var cbo = document.getElementById('previous');
10190
                for(var i = cbo.length - 1; i > 0; i--) {
10191
                    cbo.options[i] = null;
10192
                }
10193
            
10194
                var k=0;
10195
                for(var i = 1; i <= child_name[id].length; i++){
10196
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10197
                    option.style.paddingLeft = '40px';
10198
                    cbo.options[i] = option;
10199
                    k = i;
10200
                }
10201
            
10202
                cbo.options[k].selected = true;
10203
                $('#previous').selectpicker('refresh');
10204
            }";
10205
10206
        return $return;
10207
    }
10208
10209
    /**
10210
     * Display the form to allow moving an item.
10211
     *
10212
     * @param int $item_id Item ID
10213
     *
10214
     * @throws Exception
10215
     * @throws HTML_QuickForm_Error
10216
     *
10217
     * @return string HTML form
10218
     */
10219
    public function display_move_item($item_id)
10220
    {
10221
        $return = '';
10222
        if (is_numeric($item_id)) {
10223
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10224
10225
            $sql = "SELECT * FROM $tbl_lp_item
10226
                    WHERE iid = $item_id";
10227
            $res = Database::query($sql);
10228
            $row = Database::fetch_array($res);
10229
10230
            switch ($row['item_type']) {
10231
                case 'dir':
10232
                case 'asset':
10233
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10234
                    $return .= $this->display_item_form(
10235
                        $row['item_type'],
10236
                        get_lang('MoveCurrentChapter'),
10237
                        'move',
10238
                        $item_id,
10239
                        $row
10240
                    );
10241
                    break;
10242
                case TOOL_DOCUMENT:
10243
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10244
                    $return .= $this->display_document_form('move', $item_id, $row);
10245
                    break;
10246
                case TOOL_LINK:
10247
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10248
                    $return .= $this->display_link_form('move', $item_id, $row);
10249
                    break;
10250
                case TOOL_HOTPOTATOES:
10251
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10252
                    $return .= $this->display_link_form('move', $item_id, $row);
10253
                    break;
10254
                case TOOL_QUIZ:
10255
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10256
                    $return .= $this->display_quiz_form('move', $item_id, $row);
10257
                    break;
10258
                case TOOL_STUDENTPUBLICATION:
10259
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10260
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
10261
                    break;
10262
                case TOOL_FORUM:
10263
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10264
                    $return .= $this->display_forum_form('move', $item_id, $row);
10265
                    break;
10266
                case TOOL_THREAD:
10267
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10268
                    $return .= $this->display_forum_form('move', $item_id, $row);
10269
                    break;
10270
            }
10271
        }
10272
10273
        return $return;
10274
    }
10275
10276
    /**
10277
     * Return HTML form to allow prerequisites selection.
10278
     *
10279
     * @todo use FormValidator
10280
     *
10281
     * @param int Item ID
10282
     *
10283
     * @return string HTML form
10284
     */
10285
    public function display_item_prerequisites_form($item_id = 0)
10286
    {
10287
        $course_id = api_get_course_int_id();
10288
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10289
        $item_id = intval($item_id);
10290
        /* Current prerequisite */
10291
        $sql = "SELECT * FROM $tbl_lp_item
10292
                WHERE iid = $item_id";
10293
        $result = Database::query($sql);
10294
        $row = Database::fetch_array($result);
10295
        $prerequisiteId = $row['prerequisite'];
10296
        $return = '<legend>';
10297
        $return .= get_lang('AddEditPrerequisites');
10298
        $return .= '</legend>';
10299
        $return .= '<form method="POST">';
10300
        $return .= '<div class="table-responsive">';
10301
        $return .= '<table class="table table-hover">';
10302
        $return .= '<thead>';
10303
        $return .= '<tr>';
10304
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10305
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10306
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10307
        $return .= '</tr>';
10308
        $return .= '</thead>';
10309
10310
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10311
        $return .= '<tbody>';
10312
        $return .= '<tr>';
10313
        $return .= '<td colspan="3">';
10314
        $return .= '<div class="radio learnpath"><label for="idNone">';
10315
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10316
        $return .= get_lang('None').'</label>';
10317
        $return .= '</div>';
10318
        $return .= '</tr>';
10319
10320
        $sql = "SELECT * FROM $tbl_lp_item
10321
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10322
        $result = Database::query($sql);
10323
        $arrLP = [];
10324
10325
        $selectedMinScore = [];
10326
        $selectedMaxScore = [];
10327
        while ($row = Database::fetch_array($result)) {
10328
            if ($row['id'] == $item_id) {
10329
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10330
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10331
            }
10332
            $arrLP[] = [
10333
                'id' => $row['iid'],
10334
                'item_type' => $row['item_type'],
10335
                'title' => $row['title'],
10336
                'ref' => $row['ref'],
10337
                'description' => $row['description'],
10338
                'parent_item_id' => $row['parent_item_id'],
10339
                'previous_item_id' => $row['previous_item_id'],
10340
                'next_item_id' => $row['next_item_id'],
10341
                'max_score' => $row['max_score'],
10342
                'min_score' => $row['min_score'],
10343
                'mastery_score' => $row['mastery_score'],
10344
                'prerequisite' => $row['prerequisite'],
10345
                'display_order' => $row['display_order'],
10346
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10347
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10348
            ];
10349
        }
10350
10351
        $this->tree_array($arrLP);
10352
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10353
        unset($this->arrMenu);
10354
10355
        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...
10356
            $item = $arrLP[$i];
10357
10358
            if ($item['id'] == $item_id) {
10359
                break;
10360
            }
10361
10362
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10363
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10364
10365
            $return .= '<tr>';
10366
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10367
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10368
            $return .= '<label for="id'.$item['id'].'">';
10369
            $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').($item['item_type'] == 'dir' ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
10370
10371
            $icon_name = str_replace(' ', '', $item['item_type']);
10372
10373
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10374
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10375
            } else {
10376
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10377
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10378
                } else {
10379
                    $return .= Display::return_icon('folder_document.png');
10380
                }
10381
            }
10382
10383
            $return .= $item['title'].'</label>';
10384
            $return .= '</div>';
10385
            $return .= '</td>';
10386
10387
            if ($item['item_type'] == TOOL_QUIZ) {
10388
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10389
                $lpItemObj = new LpItem($course_id, $item['id']);
10390
                $exercise = new Exercise($course_id);
10391
                $exercise->read($lpItemObj->path);
10392
                $lpItemObj->max_score = $exercise->get_max_score();
10393
                $lpItemObj->update();
10394
                $item['max_score'] = $lpItemObj->max_score;
10395
10396
                $return .= '<td>';
10397
                $return .= '<input 
10398
                    class="form-control" 
10399
                    size="4" maxlength="3" 
10400
                    name="min_'.$item['id'].'" 
10401
                    type="number" 
10402
                    min="0" 
10403
                    step="1" 
10404
                    max="'.$item['max_score'].'" 
10405
                    value="'.$selectedMinScoreValue.'" 
10406
                />';
10407
                $return .= '</td>';
10408
                $return .= '<td>';
10409
                $return .= '<input 
10410
                    class="form-control" 
10411
                    size="4" 
10412
                    maxlength="3" 
10413
                    name="max_'.$item['id'].'" 
10414
                    type="number" 
10415
                    min="0" 
10416
                    step="1" 
10417
                    max="'.$item['max_score'].'" 
10418
                    value="'.$selectedMaxScoreValue.'" 
10419
                />';
10420
                $return .= '</td>';
10421
            }
10422
10423
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10424
                $return .= '<td>';
10425
                $return .= '<input 
10426
                    size="4" 
10427
                    maxlength="3" 
10428
                    name="min_'.$item['id'].'" 
10429
                    type="number" 
10430
                    min="0" 
10431
                    step="1" 
10432
                    max="'.$item['max_score'].'" 
10433
                    value="'.$selectedMinScoreValue.'" 
10434
                />';
10435
                $return .= '</td>';
10436
                $return .= '<td>';
10437
                $return .= '<input 
10438
                    size="4" 
10439
                    maxlength="3" 
10440
                    name="max_'.$item['id'].'" 
10441
                    type="number" 
10442
                    min="0" 
10443
                    step="1" 
10444
                    max="'.$item['max_score'].'" 
10445
                    value="'.$selectedMaxScoreValue.'" 
10446
                />';
10447
                $return .= '</td>';
10448
            }
10449
            $return .= '</tr>';
10450
        }
10451
        $return .= '<tr>';
10452
        $return .= '</tr>';
10453
        $return .= '</tbody>';
10454
        $return .= '</table>';
10455
        $return .= '</div>';
10456
        $return .= '<div class="form-group">';
10457
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10458
            get_lang('ModifyPrerequisites').'</button>';
10459
        $return .= '</form>';
10460
10461
        return $return;
10462
    }
10463
10464
    /**
10465
     * Return HTML list to allow prerequisites selection for lp.
10466
     *
10467
     * @return string HTML form
10468
     */
10469
    public function display_lp_prerequisites_list()
10470
    {
10471
        $course_id = api_get_course_int_id();
10472
        $lp_id = $this->lp_id;
10473
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10474
10475
        // get current prerequisite
10476
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10477
        $result = Database::query($sql);
10478
        $row = Database::fetch_array($result);
10479
        $prerequisiteId = $row['prerequisite'];
10480
        $session_id = api_get_session_id();
10481
        $session_condition = api_get_session_condition($session_id, true, true);
10482
        $sql = "SELECT * FROM $tbl_lp
10483
                WHERE c_id = $course_id $session_condition
10484
                ORDER BY display_order ";
10485
        $rs = Database::query($sql);
10486
        $return = '';
10487
        $return .= '<select name="prerequisites" class="form-control">';
10488
        $return .= '<option value="0">'.get_lang('None').'</option>';
10489
        if (Database::num_rows($rs) > 0) {
10490
            while ($row = Database::fetch_array($rs)) {
10491
                if ($row['id'] == $lp_id) {
10492
                    continue;
10493
                }
10494
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10495
            }
10496
        }
10497
        $return .= '</select>';
10498
10499
        return $return;
10500
    }
10501
10502
    /**
10503
     * Creates a list with all the documents in it.
10504
     *
10505
     * @param bool $showInvisibleFiles
10506
     *
10507
     * @throws Exception
10508
     * @throws HTML_QuickForm_Error
10509
     *
10510
     * @return string
10511
     */
10512
    public function get_documents($showInvisibleFiles = false)
10513
    {
10514
        $course_info = api_get_course_info();
10515
        $sessionId = api_get_session_id();
10516
        $documentTree = DocumentManager::get_document_preview(
10517
            $course_info,
10518
            $this->lp_id,
10519
            null,
10520
            $sessionId,
10521
            true,
10522
            null,
10523
            null,
10524
            $showInvisibleFiles,
10525
            true
10526
        );
10527
10528
        $headers = [
10529
            get_lang('Files'),
10530
            get_lang('CreateTheDocument'),
10531
            get_lang('CreateReadOutText'),
10532
            get_lang('Upload'),
10533
        ];
10534
10535
        $form = new FormValidator(
10536
            'form_upload',
10537
            'POST',
10538
            $this->getCurrentBuildingModeURL(),
10539
            '',
10540
            ['enctype' => 'multipart/form-data']
10541
        );
10542
10543
        $folders = DocumentManager::get_all_document_folders(
10544
            api_get_course_info(),
10545
            0,
10546
            true
10547
        );
10548
10549
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10550
10551
        DocumentManager::build_directory_selector(
10552
            $folders,
10553
            $lpPathInfo['id'],
10554
            [],
10555
            true,
10556
            $form,
10557
            'directory_parent_id'
10558
        );
10559
10560
        $group = [
10561
            $form->createElement(
10562
                'radio',
10563
                'if_exists',
10564
                get_lang("UplWhatIfFileExists"),
10565
                get_lang('UplDoNothing'),
10566
                'nothing'
10567
            ),
10568
            $form->createElement(
10569
                'radio',
10570
                'if_exists',
10571
                null,
10572
                get_lang('UplOverwriteLong'),
10573
                'overwrite'
10574
            ),
10575
            $form->createElement(
10576
                'radio',
10577
                'if_exists',
10578
                null,
10579
                get_lang('UplRenameLong'),
10580
                'rename'
10581
            ),
10582
        ];
10583
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10584
10585
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10586
        $defaultFileExistsOption = 'rename';
10587
        if (!empty($fileExistsOption)) {
10588
            $defaultFileExistsOption = $fileExistsOption;
10589
        }
10590
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10591
10592
        // Check box options
10593
        $form->addElement(
10594
            'checkbox',
10595
            'unzip',
10596
            get_lang('Options'),
10597
            get_lang('Uncompress')
10598
        );
10599
10600
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10601
        $form->addMultipleUpload($url);
10602
        $new = $this->display_document_form('add', 0);
10603
        $frmReadOutText = $this->displayFrmReadOutText('add');
10604
        $tabs = Display::tabs(
10605
            $headers,
10606
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10607
            'subtab'
10608
        );
10609
10610
        return $tabs;
10611
    }
10612
10613
    /**
10614
     * Creates a list with all the exercises (quiz) in it.
10615
     *
10616
     * @return string
10617
     */
10618
    public function get_exercises()
10619
    {
10620
        $course_id = api_get_course_int_id();
10621
        $session_id = api_get_session_id();
10622
        $userInfo = api_get_user_info();
10623
10624
        // New for hotpotatoes.
10625
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10626
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10627
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10628
        $condition_session = api_get_session_condition($session_id, true, true);
10629
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10630
10631
        $activeCondition = ' active <> -1 ';
10632
        if ($setting) {
10633
            $activeCondition = ' active = 1 ';
10634
        }
10635
10636
        $sql_quiz = "SELECT * FROM $tbl_quiz
10637
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10638
                     ORDER BY title ASC";
10639
10640
        $sql_hot = "SELECT * FROM $tbl_doc
10641
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10642
                     ORDER BY id ASC";
10643
10644
        $res_quiz = Database::query($sql_quiz);
10645
        $res_hot = Database::query($sql_hot);
10646
10647
        $return = '<ul class="lp_resource">';
10648
        $return .= '<li class="lp_resource_element">';
10649
        $return .= Display::return_icon('new_exercice.png');
10650
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10651
            get_lang('NewExercise').'</a>';
10652
        $return .= '</li>';
10653
10654
        $previewIcon = Display::return_icon(
10655
            'preview_view.png',
10656
            get_lang('Preview')
10657
        );
10658
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10659
10660
        // Display hotpotatoes
10661
        while ($row_hot = Database::fetch_array($res_hot)) {
10662
            $link = Display::url(
10663
                $previewIcon,
10664
                $exerciseUrl.'&file='.$row_hot['path'],
10665
                ['target' => '_blank']
10666
            );
10667
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10668
            $return .= '<a class="moved" href="#">';
10669
            $return .= Display::return_icon(
10670
                'move_everywhere.png',
10671
                get_lang('Move'),
10672
                [],
10673
                ICON_SIZE_TINY
10674
            );
10675
            $return .= '</a> ';
10676
            $return .= Display::return_icon('hotpotatoes_s.png');
10677
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10678
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10679
            $return .= '</li>';
10680
        }
10681
10682
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10683
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10684
            $title = strip_tags(
10685
                api_html_entity_decode($row_quiz['title'])
10686
            );
10687
10688
            $link = Display::url(
10689
                $previewIcon,
10690
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10691
                ['target' => '_blank']
10692
            );
10693
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10694
            $return .= '<a class="moved" href="#">';
10695
            $return .= Display::return_icon(
10696
                'move_everywhere.png',
10697
                get_lang('Move'),
10698
                [],
10699
                ICON_SIZE_TINY
10700
            );
10701
            $return .= '</a> ';
10702
            $return .= Display::return_icon(
10703
                'quiz.png',
10704
                '',
10705
                [],
10706
                ICON_SIZE_TINY
10707
            );
10708
            $sessionStar = api_get_session_image(
10709
                $row_quiz['session_id'],
10710
                $userInfo['status']
10711
            );
10712
            $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id.'">'.
10713
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
10714
                '</a>';
10715
10716
            $return .= '</li>';
10717
        }
10718
10719
        $return .= '</ul>';
10720
10721
        return $return;
10722
    }
10723
10724
    /**
10725
     * Creates a list with all the links in it.
10726
     *
10727
     * @return string
10728
     */
10729
    public function get_links()
10730
    {
10731
        $selfUrl = api_get_self();
10732
        $courseIdReq = api_get_cidreq();
10733
        $course = api_get_course_info();
10734
        $userInfo = api_get_user_info();
10735
10736
        $course_id = $course['real_id'];
10737
        $tbl_link = Database::get_course_table(TABLE_LINK);
10738
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10739
        $moveEverywhereIcon = Display::return_icon(
10740
            'move_everywhere.png',
10741
            get_lang('Move'),
10742
            [],
10743
            ICON_SIZE_TINY
10744
        );
10745
10746
        $session_id = api_get_session_id();
10747
        $condition_session = api_get_session_condition(
10748
            $session_id,
10749
            true,
10750
            true,
10751
            "link.session_id"
10752
        );
10753
10754
        $sql = "SELECT 
10755
                    link.id as link_id,
10756
                    link.title as link_title,
10757
                    link.session_id as link_session_id,
10758
                    link.category_id as category_id,
10759
                    link_category.category_title as category_title
10760
                FROM $tbl_link as link
10761
                LEFT JOIN $linkCategoryTable as link_category
10762
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10763
                WHERE link.c_id = ".$course_id." $condition_session
10764
                ORDER BY link_category.category_title ASC, link.title ASC";
10765
        $result = Database::query($sql);
10766
        $categorizedLinks = [];
10767
        $categories = [];
10768
10769
        while ($link = Database::fetch_array($result)) {
10770
            if (!$link['category_id']) {
10771
                $link['category_title'] = get_lang('Uncategorized');
10772
            }
10773
            $categories[$link['category_id']] = $link['category_title'];
10774
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10775
        }
10776
10777
        $linksHtmlCode =
10778
            '<script>
10779
            function toggle_tool(tool, id) {
10780
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10781
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10782
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10783
                } else {
10784
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10785
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10786
                }
10787
            }
10788
        </script>
10789
10790
        <ul class="lp_resource">
10791
            <li class="lp_resource_element">
10792
                '.Display::return_icon('linksnew.gif').'
10793
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10794
                get_lang('LinkAdd').'
10795
                </a>
10796
            </li>';
10797
10798
        foreach ($categorizedLinks as $categoryId => $links) {
10799
            $linkNodes = null;
10800
            foreach ($links as $key => $linkInfo) {
10801
                $title = $linkInfo['link_title'];
10802
                $linkSessionId = $linkInfo['link_session_id'];
10803
10804
                $link = Display::url(
10805
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10806
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10807
                    ['target' => '_blank']
10808
                );
10809
10810
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10811
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10812
                    $linkNodes .=
10813
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10814
                        <a class="moved" href="#">'.
10815
                            $moveEverywhereIcon.
10816
                        '</a>
10817
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10818
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10819
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10820
                        Security::remove_XSS($title).$sessionStar.$link.
10821
                        '</a>
10822
                    </li>';
10823
                }
10824
            }
10825
            $linksHtmlCode .=
10826
                '<li>
10827
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10828
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10829
                    align="absbottom" />
10830
                </a>
10831
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10832
            </li>
10833
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10834
        }
10835
        $linksHtmlCode .= '</ul>';
10836
10837
        return $linksHtmlCode;
10838
    }
10839
10840
    /**
10841
     * Creates a list with all the student publications in it.
10842
     *
10843
     * @return string
10844
     */
10845
    public function get_student_publications()
10846
    {
10847
        $return = '<ul class="lp_resource">';
10848
        $return .= '<li class="lp_resource_element">';
10849
        $return .= Display::return_icon('works_new.gif');
10850
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10851
            get_lang('AddAssignmentPage').'</a>';
10852
        $return .= '</li>';
10853
10854
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10855
        $works = getWorkListTeacher(0, 100, null, null, null);
10856
        if (!empty($works)) {
10857
            foreach ($works as $work) {
10858
                $link = Display::url(
10859
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10860
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10861
                    ['target' => '_blank']
10862
                );
10863
10864
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10865
                $return .= '<a class="moved" href="#">';
10866
                $return .= Display::return_icon(
10867
                    'move_everywhere.png',
10868
                    get_lang('Move'),
10869
                    [],
10870
                    ICON_SIZE_TINY
10871
                );
10872
                $return .= '</a> ';
10873
10874
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10875
                $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.'">'.
10876
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10877
                </a>';
10878
10879
                $return .= '</li>';
10880
            }
10881
        }
10882
10883
        $return .= '</ul>';
10884
10885
        return $return;
10886
    }
10887
10888
    /**
10889
     * Creates a list with all the forums in it.
10890
     *
10891
     * @return string
10892
     */
10893
    public function get_forums()
10894
    {
10895
        require_once '../forum/forumfunction.inc.php';
10896
        require_once '../forum/forumconfig.inc.php';
10897
10898
        $forumCategories = get_forum_categories();
10899
        $forumsInNoCategory = get_forums_in_category(0);
10900
        if (!empty($forumsInNoCategory)) {
10901
            $forumCategories = array_merge(
10902
                $forumCategories,
10903
                [
10904
                    [
10905
                        'cat_id' => 0,
10906
                        'session_id' => 0,
10907
                        'visibility' => 1,
10908
                        'cat_comment' => null,
10909
                    ],
10910
                ]
10911
            );
10912
        }
10913
10914
        $forumList = get_forums();
10915
        $a_forums = [];
10916
        foreach ($forumCategories as $forumCategory) {
10917
            // The forums in this category.
10918
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10919
            if (!empty($forumsInCategory)) {
10920
                foreach ($forumList as $forum) {
10921
                    if (isset($forum['forum_category']) &&
10922
                        $forum['forum_category'] == $forumCategory['cat_id']
10923
                    ) {
10924
                        $a_forums[] = $forum;
10925
                    }
10926
                }
10927
            }
10928
        }
10929
10930
        $return = '<ul class="lp_resource">';
10931
10932
        // First add link
10933
        $return .= '<li class="lp_resource_element">';
10934
        $return .= Display::return_icon('new_forum.png');
10935
        $return .= Display::url(
10936
            get_lang('CreateANewForum'),
10937
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10938
                'action' => 'add',
10939
                'content' => 'forum',
10940
                'lp_id' => $this->lp_id,
10941
            ]),
10942
            ['title' => get_lang('CreateANewForum')]
10943
        );
10944
        $return .= '</li>';
10945
10946
        $return .= '<script>
10947
            function toggle_forum(forum_id) {
10948
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10949
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10950
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10951
                } else {
10952
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10953
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10954
                }
10955
            }
10956
        </script>';
10957
10958
        foreach ($a_forums as $forum) {
10959
            if (!empty($forum['forum_id'])) {
10960
                $link = Display::url(
10961
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10962
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10963
                    ['target' => '_blank']
10964
                );
10965
10966
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10967
                $return .= '<a class="moved" href="#">';
10968
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10969
                $return .= ' </a>';
10970
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10971
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10972
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10973
                            </a>
10974
                            <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">'.
10975
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10976
10977
                $return .= '</li>';
10978
10979
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10980
                $a_threads = get_threads($forum['forum_id']);
10981
                if (is_array($a_threads)) {
10982
                    foreach ($a_threads as $thread) {
10983
                        $link = Display::url(
10984
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10985
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10986
                            ['target' => '_blank']
10987
                        );
10988
10989
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10990
                        $return .= '&nbsp;<a class="moved" href="#">';
10991
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10992
                        $return .= ' </a>';
10993
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10994
                        $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.'">'.
10995
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10996
                        $return .= '</li>';
10997
                    }
10998
                }
10999
                $return .= '</div>';
11000
            }
11001
        }
11002
        $return .= '</ul>';
11003
11004
        return $return;
11005
    }
11006
11007
    /**
11008
     * // TODO: The output encoding should be equal to the system encoding.
11009
     *
11010
     * Exports the learning path as a SCORM package. This is the main function that
11011
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
11012
     * whole thing and returns the zip.
11013
     *
11014
     * This method needs to be called in PHP5, as it will fail with non-adequate
11015
     * XML package (like the ones for PHP4), and it is *not* a static method, so
11016
     * you need to call it on a learnpath object.
11017
     *
11018
     * @TODO The method might be redefined later on in the scorm class itself to avoid
11019
     * creating a SCORM structure if there is one already. However, if the initial SCORM
11020
     * path has been modified, it should use the generic method here below.
11021
     *
11022
     * @return string Returns the zip package string, or null if error
11023
     */
11024
    public function scormExport()
11025
    {
11026
        api_set_more_memory_and_time_limits();
11027
11028
        $_course = api_get_course_info();
11029
        $course_id = $_course['real_id'];
11030
        // Create the zip handler (this will remain available throughout the method).
11031
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11032
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11033
        $temp_dir_short = uniqid('scorm_export', true);
11034
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11035
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11036
        $zip_folder = new PclZip($temp_zip_file);
11037
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11038
        $root_path = $main_path = api_get_path(SYS_PATH);
11039
        $files_cleanup = [];
11040
11041
        // Place to temporarily stash the zip file.
11042
        // create the temp dir if it doesn't exist
11043
        // or do a cleanup before creating the zip file.
11044
        if (!is_dir($temp_zip_dir)) {
11045
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11046
        } else {
11047
            // Cleanup: Check the temp dir for old files and delete them.
11048
            $handle = opendir($temp_zip_dir);
11049
            while (false !== ($file = readdir($handle))) {
11050
                if ($file != '.' && $file != '..') {
11051
                    unlink("$temp_zip_dir/$file");
11052
                }
11053
            }
11054
            closedir($handle);
11055
        }
11056
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11057
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11058
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11059
        ) {
11060
            // Remove the possible . at the end of the path.
11061
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11062
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11063
            mkdir(
11064
                $dest_path_to_scorm_folder,
11065
                api_get_permissions_for_new_directories(),
11066
                true
11067
            );
11068
            copyr(
11069
                $current_course_path.'/scorm/'.$this->path,
11070
                $dest_path_to_scorm_folder,
11071
                ['imsmanifest'],
11072
                $zip_files
11073
            );
11074
        }
11075
11076
        // Build a dummy imsmanifest structure.
11077
        // Do not add to the zip yet (we still need it).
11078
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11079
        // Aggregation Model official document, section "2.3 Content Packaging".
11080
        // We are going to build a UTF-8 encoded manifest.
11081
        // Later we will recode it to the desired (and supported) encoding.
11082
        $xmldoc = new DOMDocument('1.0');
11083
        $root = $xmldoc->createElement('manifest');
11084
        $root->setAttribute('identifier', 'SingleCourseManifest');
11085
        $root->setAttribute('version', '1.1');
11086
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11087
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11088
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11089
        $root->setAttribute(
11090
            'xsi:schemaLocation',
11091
            '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'
11092
        );
11093
        // Build mandatory sub-root container elements.
11094
        $metadata = $xmldoc->createElement('metadata');
11095
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11096
        $metadata->appendChild($md_schema);
11097
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11098
        $metadata->appendChild($md_schemaversion);
11099
        $root->appendChild($metadata);
11100
11101
        $organizations = $xmldoc->createElement('organizations');
11102
        $resources = $xmldoc->createElement('resources');
11103
11104
        // Build the only organization we will use in building our learnpaths.
11105
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11106
        $organization = $xmldoc->createElement('organization');
11107
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11108
        // To set the title of the SCORM entity (=organization), we take the name given
11109
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11110
        // learning path charset) as it is the encoding that defines how it is stored
11111
        // in the database. Then we convert it to HTML entities again as the "&" character
11112
        // alone is not authorized in XML (must be &amp;).
11113
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11114
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11115
        $organization->appendChild($org_title);
11116
        $folder_name = 'document';
11117
11118
        // Removes the learning_path/scorm_folder path when exporting see #4841
11119
        $path_to_remove = '';
11120
        $path_to_replace = '';
11121
        $result = $this->generate_lp_folder($_course);
11122
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11123
            $path_to_remove = 'document'.$result['dir'];
11124
            $path_to_replace = $folder_name.'/';
11125
        }
11126
11127
        // Fixes chamilo scorm exports
11128
        if ($this->ref === 'chamilo_scorm_export') {
11129
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11130
        }
11131
11132
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11133
        $link_updates = [];
11134
        $links_to_create = [];
11135
        foreach ($this->ordered_items as $index => $itemId) {
11136
            /** @var learnpathItem $item */
11137
            $item = $this->items[$itemId];
11138
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11139
                // Get included documents from this item.
11140
                if ($item->type === 'sco') {
11141
                    $inc_docs = $item->get_resources_from_source(
11142
                        null,
11143
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11144
                    );
11145
                } else {
11146
                    $inc_docs = $item->get_resources_from_source();
11147
                }
11148
11149
                // Give a child element <item> to the <organization> element.
11150
                $my_item_id = $item->get_id();
11151
                $my_item = $xmldoc->createElement('item');
11152
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11153
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11154
                $my_item->setAttribute('isvisible', 'true');
11155
                // Give a child element <title> to the <item> element.
11156
                $my_title = $xmldoc->createElement(
11157
                    'title',
11158
                    htmlspecialchars(
11159
                        api_utf8_encode($item->get_title()),
11160
                        ENT_QUOTES,
11161
                        'UTF-8'
11162
                    )
11163
                );
11164
                $my_item->appendChild($my_title);
11165
                // Give a child element <adlcp:prerequisites> to the <item> element.
11166
                $my_prereqs = $xmldoc->createElement(
11167
                    'adlcp:prerequisites',
11168
                    $this->get_scorm_prereq_string($my_item_id)
11169
                );
11170
                $my_prereqs->setAttribute('type', 'aicc_script');
11171
                $my_item->appendChild($my_prereqs);
11172
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11173
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11174
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11175
                //$xmldoc->createElement('adlcp:timelimitaction','');
11176
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11177
                //$xmldoc->createElement('adlcp:datafromlms','');
11178
                // Give a child element <adlcp:masteryscore> to the <item> element.
11179
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11180
                $my_item->appendChild($my_masteryscore);
11181
11182
                // Attach this item to the organization element or hits parent if there is one.
11183
                if (!empty($item->parent) && $item->parent != 0) {
11184
                    $children = $organization->childNodes;
11185
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11186
                    if (is_object($possible_parent)) {
11187
                        $possible_parent->appendChild($my_item);
11188
                    } else {
11189
                        if ($this->debug > 0) {
11190
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11191
                        }
11192
                    }
11193
                } else {
11194
                    if ($this->debug > 0) {
11195
                        error_log('No parent');
11196
                    }
11197
                    $organization->appendChild($my_item);
11198
                }
11199
11200
                // Get the path of the file(s) from the course directory root.
11201
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11202
                $my_xml_file_path = $my_file_path;
11203
                if (!empty($path_to_remove)) {
11204
                    // From docs
11205
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11206
11207
                    // From quiz
11208
                    if ($this->ref === 'chamilo_scorm_export') {
11209
                        $path_to_remove = 'scorm/'.$this->path.'/';
11210
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11211
                    }
11212
                }
11213
11214
                $my_sub_dir = dirname($my_file_path);
11215
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11216
                $my_xml_sub_dir = $my_sub_dir;
11217
                // Give a <resource> child to the <resources> element
11218
                $my_resource = $xmldoc->createElement('resource');
11219
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11220
                $my_resource->setAttribute('type', 'webcontent');
11221
                $my_resource->setAttribute('href', $my_xml_file_path);
11222
                // adlcp:scormtype can be either 'sco' or 'asset'.
11223
                if ($item->type === 'sco') {
11224
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11225
                } else {
11226
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11227
                }
11228
                // xml:base is the base directory to find the files declared in this resource.
11229
                $my_resource->setAttribute('xml:base', '');
11230
                // Give a <file> child to the <resource> element.
11231
                $my_file = $xmldoc->createElement('file');
11232
                $my_file->setAttribute('href', $my_xml_file_path);
11233
                $my_resource->appendChild($my_file);
11234
11235
                // Dependency to other files - not yet supported.
11236
                $i = 1;
11237
                if ($inc_docs) {
11238
                    foreach ($inc_docs as $doc_info) {
11239
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11240
                            continue;
11241
                        }
11242
                        $my_dep = $xmldoc->createElement('resource');
11243
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11244
                        $my_dep->setAttribute('identifier', $res_id);
11245
                        $my_dep->setAttribute('type', 'webcontent');
11246
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11247
                        $my_dep_file = $xmldoc->createElement('file');
11248
                        // Check type of URL.
11249
                        if ($doc_info[1] == 'remote') {
11250
                            // Remote file. Save url as is.
11251
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11252
                            $my_dep->setAttribute('xml:base', '');
11253
                        } elseif ($doc_info[1] === 'local') {
11254
                            switch ($doc_info[2]) {
11255
                                case 'url':
11256
                                    // Local URL - save path as url for now, don't zip file.
11257
                                    $abs_path = api_get_path(SYS_PATH).
11258
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11259
                                    $current_dir = dirname($abs_path);
11260
                                    $current_dir = str_replace('\\', '/', $current_dir);
11261
                                    $file_path = realpath($abs_path);
11262
                                    $file_path = str_replace('\\', '/', $file_path);
11263
                                    $my_dep_file->setAttribute('href', $file_path);
11264
                                    $my_dep->setAttribute('xml:base', '');
11265
                                    if (strstr($file_path, $main_path) !== false) {
11266
                                        // The calculated real path is really inside Chamilo's root path.
11267
                                        // Reduce file path to what's under the DocumentRoot.
11268
                                        $replace = $file_path;
11269
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11270
                                        $destinationFile = $file_path;
11271
11272
                                        if (strstr($file_path, 'upload/users') !== false) {
11273
                                            $pos = strpos($file_path, 'my_files/');
11274
                                            if ($pos !== false) {
11275
                                                $onlyDirectory = str_replace(
11276
                                                    'upload/users/',
11277
                                                    '',
11278
                                                    substr($file_path, $pos, strlen($file_path))
11279
                                                );
11280
                                            }
11281
                                            $replace = $onlyDirectory;
11282
                                            $destinationFile = $replace;
11283
                                        }
11284
                                        $zip_files_abs[] = $file_path;
11285
                                        $link_updates[$my_file_path][] = [
11286
                                            'orig' => $doc_info[0],
11287
                                            'dest' => $destinationFile,
11288
                                            'replace' => $replace,
11289
                                        ];
11290
                                        $my_dep_file->setAttribute('href', $file_path);
11291
                                        $my_dep->setAttribute('xml:base', '');
11292
                                    } elseif (empty($file_path)) {
11293
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11294
                                        $file_path = str_replace('//', '/', $file_path);
11295
                                        if (file_exists($file_path)) {
11296
                                            // We get the relative path.
11297
                                            $file_path = substr($file_path, strlen($current_dir));
11298
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11299
                                            $link_updates[$my_file_path][] = [
11300
                                                'orig' => $doc_info[0],
11301
                                                'dest' => $file_path,
11302
                                            ];
11303
                                            $my_dep_file->setAttribute('href', $file_path);
11304
                                            $my_dep->setAttribute('xml:base', '');
11305
                                        }
11306
                                    }
11307
                                    break;
11308
                                case 'abs':
11309
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11310
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11311
                                    $my_dep->setAttribute('xml:base', '');
11312
11313
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11314
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11315
                                    $abs_img_path_without_subdir = $doc_info[0];
11316
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11317
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11318
                                    if ($pos === 0) {
11319
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11320
                                    }
11321
11322
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11323
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11324
11325
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11326
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11327
                                    // Check if the current document is in that path.
11328
                                    if (strstr($file_path, $cur_path) !== false) {
11329
                                        $destinationFile = substr($file_path, strlen($cur_path));
11330
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11331
11332
                                        $fileToTest = $cur_path.$my_file_path;
11333
                                        if (!empty($path_to_remove)) {
11334
                                            $fileToTest = str_replace(
11335
                                                $path_to_remove.'/',
11336
                                                $path_to_replace,
11337
                                                $cur_path.$my_file_path
11338
                                            );
11339
                                        }
11340
11341
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11342
11343
                                        // Put the current document in the zip (this array is the array
11344
                                        // that will manage documents already in the course folder - relative).
11345
                                        $zip_files[] = $filePathNoCoursePart;
11346
                                        // Update the links to the current document in the
11347
                                        // containing document (make them relative).
11348
                                        $link_updates[$my_file_path][] = [
11349
                                            'orig' => $doc_info[0],
11350
                                            'dest' => $destinationFile,
11351
                                            'replace' => $relative_path,
11352
                                        ];
11353
11354
                                        $my_dep_file->setAttribute('href', $file_path);
11355
                                        $my_dep->setAttribute('xml:base', '');
11356
                                    } elseif (strstr($file_path, $main_path) !== false) {
11357
                                        // The calculated real path is really inside Chamilo's root path.
11358
                                        // Reduce file path to what's under the DocumentRoot.
11359
                                        $file_path = substr($file_path, strlen($root_path));
11360
                                        $zip_files_abs[] = $file_path;
11361
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11362
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11363
                                        $my_dep->setAttribute('xml:base', '');
11364
                                    } elseif (empty($file_path)) {
11365
                                        // Probably this is an image inside "/main" directory
11366
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11367
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11368
11369
                                        if (file_exists($file_path)) {
11370
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11371
                                                // We get the relative path.
11372
                                                $pos = strpos($file_path, 'main/default_course_document/');
11373
                                                if ($pos !== false) {
11374
                                                    $onlyDirectory = str_replace(
11375
                                                        'main/default_course_document/',
11376
                                                        '',
11377
                                                        substr($file_path, $pos, strlen($file_path))
11378
                                                    );
11379
                                                }
11380
11381
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11382
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11383
                                                $zip_files_abs[] = $fileAbs;
11384
                                                $link_updates[$my_file_path][] = [
11385
                                                    'orig' => $doc_info[0],
11386
                                                    'dest' => $destinationFile,
11387
                                                ];
11388
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11389
                                                $my_dep->setAttribute('xml:base', '');
11390
                                            }
11391
                                        }
11392
                                    }
11393
                                    break;
11394
                                case 'rel':
11395
                                    // Path relative to the current document.
11396
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11397
                                    if (substr($doc_info[0], 0, 2) === '..') {
11398
                                        // Relative path going up.
11399
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11400
                                        $current_dir = str_replace('\\', '/', $current_dir);
11401
                                        $file_path = realpath($current_dir.$doc_info[0]);
11402
                                        $file_path = str_replace('\\', '/', $file_path);
11403
                                        if (strstr($file_path, $main_path) !== false) {
11404
                                            // The calculated real path is really inside Chamilo's root path.
11405
                                            // Reduce file path to what's under the DocumentRoot.
11406
                                            $file_path = substr($file_path, strlen($root_path));
11407
                                            $zip_files_abs[] = $file_path;
11408
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11409
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11410
                                            $my_dep->setAttribute('xml:base', '');
11411
                                        }
11412
                                    } else {
11413
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11414
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11415
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11416
                                    }
11417
                                    break;
11418
                                default:
11419
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11420
                                    $my_dep->setAttribute('xml:base', '');
11421
                                    break;
11422
                            }
11423
                        }
11424
                        $my_dep->appendChild($my_dep_file);
11425
                        $resources->appendChild($my_dep);
11426
                        $dependency = $xmldoc->createElement('dependency');
11427
                        $dependency->setAttribute('identifierref', $res_id);
11428
                        $my_resource->appendChild($dependency);
11429
                        $i++;
11430
                    }
11431
                }
11432
                $resources->appendChild($my_resource);
11433
                $zip_files[] = $my_file_path;
11434
            } else {
11435
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11436
                switch ($item->type) {
11437
                    case TOOL_LINK:
11438
                        $my_item = $xmldoc->createElement('item');
11439
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11440
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11441
                        $my_item->setAttribute('isvisible', 'true');
11442
                        // Give a child element <title> to the <item> element.
11443
                        $my_title = $xmldoc->createElement(
11444
                            'title',
11445
                            htmlspecialchars(
11446
                                api_utf8_encode($item->get_title()),
11447
                                ENT_QUOTES,
11448
                                'UTF-8'
11449
                            )
11450
                        );
11451
                        $my_item->appendChild($my_title);
11452
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11453
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11454
                        $my_prereqs->setAttribute('type', 'aicc_script');
11455
                        $my_item->appendChild($my_prereqs);
11456
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11457
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11458
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11459
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11460
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11461
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11462
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11463
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11464
                        $my_item->appendChild($my_masteryscore);
11465
11466
                        // Attach this item to the organization element or its parent if there is one.
11467
                        if (!empty($item->parent) && $item->parent != 0) {
11468
                            $children = $organization->childNodes;
11469
                            for ($i = 0; $i < $children->length; $i++) {
11470
                                $item_temp = $children->item($i);
11471
                                if ($item_temp->nodeName == 'item') {
11472
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11473
                                        $item_temp->appendChild($my_item);
11474
                                    }
11475
                                }
11476
                            }
11477
                        } else {
11478
                            $organization->appendChild($my_item);
11479
                        }
11480
11481
                        $my_file_path = 'link_'.$item->get_id().'.html';
11482
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11483
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11484
                        $rs = Database::query($sql);
11485
                        if ($link = Database::fetch_array($rs)) {
11486
                            $url = $link['url'];
11487
                            $title = stripslashes($link['title']);
11488
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11489
                            $my_xml_file_path = $my_file_path;
11490
                            $my_sub_dir = dirname($my_file_path);
11491
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11492
                            $my_xml_sub_dir = $my_sub_dir;
11493
                            // Give a <resource> child to the <resources> element.
11494
                            $my_resource = $xmldoc->createElement('resource');
11495
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11496
                            $my_resource->setAttribute('type', 'webcontent');
11497
                            $my_resource->setAttribute('href', $my_xml_file_path);
11498
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11499
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11500
                            // xml:base is the base directory to find the files declared in this resource.
11501
                            $my_resource->setAttribute('xml:base', '');
11502
                            // give a <file> child to the <resource> element.
11503
                            $my_file = $xmldoc->createElement('file');
11504
                            $my_file->setAttribute('href', $my_xml_file_path);
11505
                            $my_resource->appendChild($my_file);
11506
                            $resources->appendChild($my_resource);
11507
                        }
11508
                        break;
11509
                    case TOOL_QUIZ:
11510
                        $exe_id = $item->path;
11511
                        // Should be using ref when everything will be cleaned up in this regard.
11512
                        $exe = new Exercise();
11513
                        $exe->read($exe_id);
11514
                        $my_item = $xmldoc->createElement('item');
11515
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11516
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11517
                        $my_item->setAttribute('isvisible', 'true');
11518
                        // Give a child element <title> to the <item> element.
11519
                        $my_title = $xmldoc->createElement(
11520
                            'title',
11521
                            htmlspecialchars(
11522
                                api_utf8_encode($item->get_title()),
11523
                                ENT_QUOTES,
11524
                                'UTF-8'
11525
                            )
11526
                        );
11527
                        $my_item->appendChild($my_title);
11528
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11529
                        $my_item->appendChild($my_max_score);
11530
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11531
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11532
                        $my_prereqs->setAttribute('type', 'aicc_script');
11533
                        $my_item->appendChild($my_prereqs);
11534
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11535
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11536
                        $my_item->appendChild($my_masteryscore);
11537
11538
                        // Attach this item to the organization element or hits parent if there is one.
11539
                        if (!empty($item->parent) && $item->parent != 0) {
11540
                            $children = $organization->childNodes;
11541
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11542
                            if ($possible_parent) {
11543
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11544
                                    $possible_parent->appendChild($my_item);
11545
                                }
11546
                            }
11547
                        } else {
11548
                            $organization->appendChild($my_item);
11549
                        }
11550
11551
                        // Get the path of the file(s) from the course directory root
11552
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11553
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11554
                        // Write the contents of the exported exercise into a (big) html file
11555
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11556
                        $scormExercise = new ScormExercise($exe, true);
11557
                        $contents = $scormExercise->export();
11558
11559
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11560
                        $res = file_put_contents($tmp_file_path, $contents);
11561
                        if ($res === false) {
11562
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11563
                        }
11564
                        $files_cleanup[] = $tmp_file_path;
11565
                        $my_xml_file_path = $my_file_path;
11566
                        $my_sub_dir = dirname($my_file_path);
11567
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11568
                        $my_xml_sub_dir = $my_sub_dir;
11569
                        // Give a <resource> child to the <resources> element.
11570
                        $my_resource = $xmldoc->createElement('resource');
11571
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11572
                        $my_resource->setAttribute('type', 'webcontent');
11573
                        $my_resource->setAttribute('href', $my_xml_file_path);
11574
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11575
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11576
                        // xml:base is the base directory to find the files declared in this resource.
11577
                        $my_resource->setAttribute('xml:base', '');
11578
                        // Give a <file> child to the <resource> element.
11579
                        $my_file = $xmldoc->createElement('file');
11580
                        $my_file->setAttribute('href', $my_xml_file_path);
11581
                        $my_resource->appendChild($my_file);
11582
11583
                        // Get included docs.
11584
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11585
11586
                        // Dependency to other files - not yet supported.
11587
                        $i = 1;
11588
                        foreach ($inc_docs as $doc_info) {
11589
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11590
                                continue;
11591
                            }
11592
                            $my_dep = $xmldoc->createElement('resource');
11593
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11594
                            $my_dep->setAttribute('identifier', $res_id);
11595
                            $my_dep->setAttribute('type', 'webcontent');
11596
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11597
                            $my_dep_file = $xmldoc->createElement('file');
11598
                            // Check type of URL.
11599
                            if ($doc_info[1] == 'remote') {
11600
                                // Remote file. Save url as is.
11601
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11602
                                $my_dep->setAttribute('xml:base', '');
11603
                            } elseif ($doc_info[1] == 'local') {
11604
                                switch ($doc_info[2]) {
11605
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11606
                                        // Save file but as local file (retrieve from URL).
11607
                                        $abs_path = api_get_path(SYS_PATH).
11608
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11609
                                        $current_dir = dirname($abs_path);
11610
                                        $current_dir = str_replace('\\', '/', $current_dir);
11611
                                        $file_path = realpath($abs_path);
11612
                                        $file_path = str_replace('\\', '/', $file_path);
11613
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11614
                                        $my_dep->setAttribute('xml:base', '');
11615
                                        if (strstr($file_path, $main_path) !== false) {
11616
                                            // The calculated real path is really inside the chamilo root path.
11617
                                            // Reduce file path to what's under the DocumentRoot.
11618
                                            $file_path = substr($file_path, strlen($root_path));
11619
                                            $zip_files_abs[] = $file_path;
11620
                                            $link_updates[$my_file_path][] = [
11621
                                                'orig' => $doc_info[0],
11622
                                                'dest' => 'document/'.$file_path,
11623
                                            ];
11624
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11625
                                            $my_dep->setAttribute('xml:base', '');
11626
                                        } elseif (empty($file_path)) {
11627
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11628
                                            $file_path = str_replace('//', '/', $file_path);
11629
                                            if (file_exists($file_path)) {
11630
                                                $file_path = substr($file_path, strlen($current_dir));
11631
                                                // We get the relative path.
11632
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11633
                                                $link_updates[$my_file_path][] = [
11634
                                                    'orig' => $doc_info[0],
11635
                                                    'dest' => 'document/'.$file_path,
11636
                                                ];
11637
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11638
                                                $my_dep->setAttribute('xml:base', '');
11639
                                            }
11640
                                        }
11641
                                        break;
11642
                                    case 'abs':
11643
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11644
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11645
                                        $current_dir = str_replace('\\', '/', $current_dir);
11646
                                        $file_path = realpath($doc_info[0]);
11647
                                        $file_path = str_replace('\\', '/', $file_path);
11648
                                        $my_dep_file->setAttribute('href', $file_path);
11649
                                        $my_dep->setAttribute('xml:base', '');
11650
11651
                                        if (strstr($file_path, $main_path) !== false) {
11652
                                            // The calculated real path is really inside the chamilo root path.
11653
                                            // Reduce file path to what's under the DocumentRoot.
11654
                                            $file_path = substr($file_path, strlen($root_path));
11655
                                            $zip_files_abs[] = $file_path;
11656
                                            $link_updates[$my_file_path][] = [
11657
                                                'orig' => $doc_info[0],
11658
                                                'dest' => $file_path,
11659
                                            ];
11660
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11661
                                            $my_dep->setAttribute('xml:base', '');
11662
                                        } elseif (empty($file_path)) {
11663
                                            $docSysPartPath = str_replace(
11664
                                                api_get_path(REL_COURSE_PATH),
11665
                                                '',
11666
                                                $doc_info[0]
11667
                                            );
11668
11669
                                            $docSysPartPathNoCourseCode = str_replace(
11670
                                                $_course['directory'].'/',
11671
                                                '',
11672
                                                $docSysPartPath
11673
                                            );
11674
11675
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11676
                                            if (file_exists($docSysPath)) {
11677
                                                $file_path = $docSysPartPathNoCourseCode;
11678
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11679
                                                $link_updates[$my_file_path][] = [
11680
                                                    'orig' => $doc_info[0],
11681
                                                    'dest' => $file_path,
11682
                                                ];
11683
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11684
                                                $my_dep->setAttribute('xml:base', '');
11685
                                            }
11686
                                        }
11687
                                        break;
11688
                                    case 'rel':
11689
                                        // Path relative to the current document. Save xml:base as current document's
11690
                                        // directory and save file in zip as subdir.file_path
11691
                                        if (substr($doc_info[0], 0, 2) === '..') {
11692
                                            // Relative path going up.
11693
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11694
                                            $current_dir = str_replace('\\', '/', $current_dir);
11695
                                            $file_path = realpath($current_dir.$doc_info[0]);
11696
                                            $file_path = str_replace('\\', '/', $file_path);
11697
                                            //error_log($file_path.' <-> '.$main_path, 0);
11698
                                            if (strstr($file_path, $main_path) !== false) {
11699
                                                // The calculated real path is really inside Chamilo's root path.
11700
                                                // Reduce file path to what's under the DocumentRoot.
11701
11702
                                                $file_path = substr($file_path, strlen($root_path));
11703
                                                $file_path_dest = $file_path;
11704
11705
                                                // File path is courses/CHAMILO/document/....
11706
                                                $info_file_path = explode('/', $file_path);
11707
                                                if ($info_file_path[0] == 'courses') {
11708
                                                    // Add character "/" in file path.
11709
                                                    $file_path_dest = 'document/'.$file_path;
11710
                                                }
11711
                                                $zip_files_abs[] = $file_path;
11712
11713
                                                $link_updates[$my_file_path][] = [
11714
                                                    'orig' => $doc_info[0],
11715
                                                    'dest' => $file_path_dest,
11716
                                                ];
11717
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11718
                                                $my_dep->setAttribute('xml:base', '');
11719
                                            }
11720
                                        } else {
11721
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11722
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11723
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11724
                                        }
11725
                                        break;
11726
                                    default:
11727
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11728
                                        $my_dep->setAttribute('xml:base', '');
11729
                                        break;
11730
                                }
11731
                            }
11732
                            $my_dep->appendChild($my_dep_file);
11733
                            $resources->appendChild($my_dep);
11734
                            $dependency = $xmldoc->createElement('dependency');
11735
                            $dependency->setAttribute('identifierref', $res_id);
11736
                            $my_resource->appendChild($dependency);
11737
                            $i++;
11738
                        }
11739
                        $resources->appendChild($my_resource);
11740
                        $zip_files[] = $my_file_path;
11741
                        break;
11742
                    default:
11743
                        // Get the path of the file(s) from the course directory root
11744
                        $my_file_path = 'non_exportable.html';
11745
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11746
                        $my_xml_file_path = $my_file_path;
11747
                        $my_sub_dir = dirname($my_file_path);
11748
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11749
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11750
                        $my_xml_sub_dir = $my_sub_dir;
11751
                        // Give a <resource> child to the <resources> element.
11752
                        $my_resource = $xmldoc->createElement('resource');
11753
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11754
                        $my_resource->setAttribute('type', 'webcontent');
11755
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11756
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11757
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11758
                        // xml:base is the base directory to find the files declared in this resource.
11759
                        $my_resource->setAttribute('xml:base', '');
11760
                        // Give a <file> child to the <resource> element.
11761
                        $my_file = $xmldoc->createElement('file');
11762
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11763
                        $my_resource->appendChild($my_file);
11764
                        $resources->appendChild($my_resource);
11765
                        break;
11766
                }
11767
            }
11768
        }
11769
        $organizations->appendChild($organization);
11770
        $root->appendChild($organizations);
11771
        $root->appendChild($resources);
11772
        $xmldoc->appendChild($root);
11773
11774
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11775
11776
        // then add the file to the zip, then destroy the file (this is done automatically).
11777
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11778
        foreach ($zip_files as $file_path) {
11779
            if (empty($file_path)) {
11780
                continue;
11781
            }
11782
11783
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11784
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11785
11786
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11787
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11788
            }
11789
11790
            $this->create_path($dest_file);
11791
            @copy($filePath, $dest_file);
11792
11793
            // Check if the file needs a link update.
11794
            if (in_array($file_path, array_keys($link_updates))) {
11795
                $string = file_get_contents($dest_file);
11796
                unlink($dest_file);
11797
                foreach ($link_updates[$file_path] as $old_new) {
11798
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11799
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11800
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11801
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11802
                    if (substr($old_new['dest'], -3) === 'flv' &&
11803
                        substr($old_new['dest'], 0, 5) === 'main/'
11804
                    ) {
11805
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11806
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11807
                        substr($old_new['dest'], 0, 6) === 'video/'
11808
                    ) {
11809
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11810
                    }
11811
11812
                    // Fix to avoid problems with default_course_document
11813
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11814
                        $newDestination = $old_new['dest'];
11815
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11816
                            $newDestination = $old_new['replace'];
11817
                        }
11818
                    } else {
11819
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11820
                    }
11821
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11822
11823
                    // Add files inside the HTMLs
11824
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11825
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11826
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11827
                        copy(
11828
                            $sys_course_path.$new_path,
11829
                            $destinationFile
11830
                        );
11831
                    }
11832
                }
11833
                file_put_contents($dest_file, $string);
11834
            }
11835
11836
            if (file_exists($filePath) && $copyAll) {
11837
                $extension = $this->get_extension($filePath);
11838
                if (in_array($extension, ['html', 'html'])) {
11839
                    $containerOrigin = dirname($filePath);
11840
                    $containerDestination = dirname($dest_file);
11841
11842
                    $finder = new Finder();
11843
                    $finder->files()->in($containerOrigin)
11844
                        ->notName('*_DELETED_*')
11845
                        ->exclude('share_folder')
11846
                        ->exclude('chat_files')
11847
                        ->exclude('certificates')
11848
                    ;
11849
11850
                    if (is_dir($containerOrigin) &&
11851
                        is_dir($containerDestination)
11852
                    ) {
11853
                        $fs = new Filesystem();
11854
                        $fs->mirror(
11855
                            $containerOrigin,
11856
                            $containerDestination,
11857
                            $finder
11858
                        );
11859
                    }
11860
                }
11861
            }
11862
        }
11863
11864
        foreach ($zip_files_abs as $file_path) {
11865
            if (empty($file_path)) {
11866
                continue;
11867
            }
11868
11869
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11870
                continue;
11871
            }
11872
11873
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11874
            if (strstr($file_path, 'upload/users') !== false) {
11875
                $pos = strpos($file_path, 'my_files/');
11876
                if ($pos !== false) {
11877
                    $onlyDirectory = str_replace(
11878
                        'upload/users/',
11879
                        '',
11880
                        substr($file_path, $pos, strlen($file_path))
11881
                    );
11882
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11883
                }
11884
            }
11885
11886
            if (strstr($file_path, 'default_course_document/') !== false) {
11887
                $replace = str_replace('/main', '', $file_path);
11888
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11889
            }
11890
11891
            if (empty($dest_file)) {
11892
                continue;
11893
            }
11894
11895
            $this->create_path($dest_file);
11896
            copy($main_path.$file_path, $dest_file);
11897
            // Check if the file needs a link update.
11898
            if (in_array($file_path, array_keys($link_updates))) {
11899
                $string = file_get_contents($dest_file);
11900
                unlink($dest_file);
11901
                foreach ($link_updates[$file_path] as $old_new) {
11902
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11903
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11904
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11905
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11906
                    if (substr($old_new['dest'], -3) == 'flv' &&
11907
                        substr($old_new['dest'], 0, 5) == 'main/'
11908
                    ) {
11909
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11910
                    }
11911
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11912
                }
11913
                file_put_contents($dest_file, $string);
11914
            }
11915
        }
11916
11917
        if (is_array($links_to_create)) {
11918
            foreach ($links_to_create as $file => $link) {
11919
                $content = '<!DOCTYPE html><head>
11920
                            <meta charset="'.api_get_language_isocode().'" />
11921
                            <title>'.$link['title'].'</title>
11922
                            </head>
11923
                            <body dir="'.api_get_text_direction().'">
11924
                            <div style="text-align:center">
11925
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11926
                            </body>
11927
                            </html>';
11928
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11929
            }
11930
        }
11931
11932
        // Add non exportable message explanation.
11933
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11934
        $file_content = '<!DOCTYPE html><head>
11935
                        <meta charset="'.api_get_language_isocode().'" />
11936
                        <title>'.$lang_not_exportable.'</title>
11937
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11938
                        </head>
11939
                        <body dir="'.api_get_text_direction().'">';
11940
        $file_content .=
11941
            <<<EOD
11942
                    <style>
11943
            .error-message {
11944
                font-family: arial, verdana, helvetica, sans-serif;
11945
                border-width: 1px;
11946
                border-style: solid;
11947
                left: 50%;
11948
                margin: 10px auto;
11949
                min-height: 30px;
11950
                padding: 5px;
11951
                right: 50%;
11952
                width: 500px;
11953
                background-color: #FFD1D1;
11954
                border-color: #FF0000;
11955
                color: #000;
11956
            }
11957
        </style>
11958
    <body>
11959
        <div class="error-message">
11960
            $lang_not_exportable
11961
        </div>
11962
    </body>
11963
</html>
11964
EOD;
11965
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11966
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11967
        }
11968
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11969
11970
        // Add the extra files that go along with a SCORM package.
11971
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11972
11973
        $fs = new Filesystem();
11974
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11975
11976
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11977
        $manifest = @$xmldoc->saveXML();
11978
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11979
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11980
        $zip_folder->add(
11981
            $archivePath.'/'.$temp_dir_short,
11982
            PCLZIP_OPT_REMOVE_PATH,
11983
            $archivePath.'/'.$temp_dir_short.'/'
11984
        );
11985
11986
        // Clean possible temporary files.
11987
        foreach ($files_cleanup as $file) {
11988
            $res = unlink($file);
11989
            if ($res === false) {
11990
                error_log(
11991
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11992
                    0
11993
                );
11994
            }
11995
        }
11996
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11997
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11998
    }
11999
12000
    /**
12001
     * @param int $lp_id
12002
     *
12003
     * @return bool
12004
     */
12005
    public function scorm_export_to_pdf($lp_id)
12006
    {
12007
        $lp_id = (int) $lp_id;
12008
        $files_to_export = [];
12009
        $course_data = api_get_course_info($this->cc);
12010
        if (!empty($course_data)) {
12011
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
12012
            $list = self::get_flat_ordered_items_list($lp_id);
12013
            if (!empty($list)) {
12014
                foreach ($list as $item_id) {
12015
                    $item = $this->items[$item_id];
12016
                    switch ($item->type) {
12017
                        case 'document':
12018
                            //Getting documents from a LP with chamilo documents
12019
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
12020
                            // Try loading document from the base course.
12021
                            if (empty($file_data) && !empty($sessionId)) {
12022
                                $file_data = DocumentManager::get_document_data_by_id(
12023
                                    $item->path,
12024
                                    $this->cc,
12025
                                    false,
12026
                                    0
12027
                                );
12028
                            }
12029
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12030
                            if (file_exists($file_path)) {
12031
                                $files_to_export[] = [
12032
                                    'title' => $item->get_title(),
12033
                                    'path' => $file_path,
12034
                                ];
12035
                            }
12036
                            break;
12037
                        case 'asset': //commes from a scorm package generated by chamilo
12038
                        case 'sco':
12039
                            $file_path = $scorm_path.'/'.$item->path;
12040
                            if (file_exists($file_path)) {
12041
                                $files_to_export[] = [
12042
                                    'title' => $item->get_title(),
12043
                                    'path' => $file_path,
12044
                                ];
12045
                            }
12046
                            break;
12047
                        case 'dir':
12048
                            $files_to_export[] = [
12049
                                'title' => $item->get_title(),
12050
                                'path' => null,
12051
                            ];
12052
                            break;
12053
                    }
12054
                }
12055
            }
12056
            $pdf = new PDF();
12057
            $result = $pdf->html_to_pdf(
12058
                $files_to_export,
12059
                $this->name,
12060
                $this->cc,
12061
                true
12062
            );
12063
12064
            return $result;
12065
        }
12066
12067
        return false;
12068
    }
12069
12070
    /**
12071
     * Temp function to be moved in main_api or the best place around for this.
12072
     * Creates a file path if it doesn't exist.
12073
     *
12074
     * @param string $path
12075
     */
12076
    public function create_path($path)
12077
    {
12078
        $path_bits = explode('/', dirname($path));
12079
12080
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12081
        $path_built = IS_WINDOWS_OS ? '' : '/';
12082
        foreach ($path_bits as $bit) {
12083
            if (!empty($bit)) {
12084
                $new_path = $path_built.$bit;
12085
                if (is_dir($new_path)) {
12086
                    $path_built = $new_path.'/';
12087
                } else {
12088
                    mkdir($new_path, api_get_permissions_for_new_directories());
12089
                    $path_built = $new_path.'/';
12090
                }
12091
            }
12092
        }
12093
    }
12094
12095
    /**
12096
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12097
     *
12098
     * @return bool The results of the unlink function, or false if there was no image to start with
12099
     */
12100
    public function delete_lp_image()
12101
    {
12102
        $img = $this->get_preview_image();
12103
        if ($img != '') {
12104
            $del_file = $this->get_preview_image_path(null, 'sys');
12105
            if (isset($del_file) && file_exists($del_file)) {
12106
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12107
                if (file_exists($del_file_2)) {
12108
                    unlink($del_file_2);
12109
                }
12110
                $this->set_preview_image('');
12111
12112
                return @unlink($del_file);
12113
            }
12114
        }
12115
12116
        return false;
12117
    }
12118
12119
    /**
12120
     * Uploads an author image to the upload/learning_path/images directory.
12121
     *
12122
     * @param array    The image array, coming from the $_FILES superglobal
12123
     *
12124
     * @return bool True on success, false on error
12125
     */
12126
    public function upload_image($image_array)
12127
    {
12128
        if (!empty($image_array['name'])) {
12129
            $upload_ok = process_uploaded_file($image_array);
12130
            $has_attachment = true;
12131
        }
12132
12133
        if ($upload_ok && $has_attachment) {
12134
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12135
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12136
            $updir = $sys_course_path.$courseDir;
12137
            // Try to add an extension to the file if it hasn't one.
12138
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12139
12140
            if (filter_extension($new_file_name)) {
12141
                $file_extension = explode('.', $image_array['name']);
12142
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
12143
                $filename = uniqid('');
12144
                $new_file_name = $filename.'.'.$file_extension;
12145
                $new_path = $updir.'/'.$new_file_name;
12146
12147
                // Resize the image.
12148
                $temp = new Image($image_array['tmp_name']);
12149
                $temp->resize(104);
12150
                $result = $temp->send_image($new_path);
12151
12152
                // Storing the image filename.
12153
                if ($result) {
12154
                    $this->set_preview_image($new_file_name);
12155
12156
                    //Resize to 64px to use on course homepage
12157
                    $temp->resize(64);
12158
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12159
12160
                    return true;
12161
                }
12162
            }
12163
        }
12164
12165
        return false;
12166
    }
12167
12168
    /**
12169
     * @param int    $lp_id
12170
     * @param string $status
12171
     */
12172
    public function set_autolaunch($lp_id, $status)
12173
    {
12174
        $course_id = api_get_course_int_id();
12175
        $lp_id = intval($lp_id);
12176
        $status = intval($status);
12177
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12178
12179
        // Setting everything to autolaunch = 0
12180
        $attributes['autolaunch'] = 0;
12181
        $where = [
12182
            'session_id = ? AND c_id = ? ' => [
12183
                api_get_session_id(),
12184
                $course_id,
12185
            ],
12186
        ];
12187
        Database::update($lp_table, $attributes, $where);
12188
        if ($status == 1) {
12189
            //Setting my lp_id to autolaunch = 1
12190
            $attributes['autolaunch'] = 1;
12191
            $where = [
12192
                'iid = ? AND session_id = ? AND c_id = ?' => [
12193
                    $lp_id,
12194
                    api_get_session_id(),
12195
                    $course_id,
12196
                ],
12197
            ];
12198
            Database::update($lp_table, $attributes, $where);
12199
        }
12200
    }
12201
12202
    /**
12203
     * Gets previous_item_id for the next element of the lp_item table.
12204
     *
12205
     * @author Isaac flores paz
12206
     *
12207
     * @return int Previous item ID
12208
     */
12209
    public function select_previous_item_id()
12210
    {
12211
        $course_id = api_get_course_int_id();
12212
        if ($this->debug > 0) {
12213
            error_log('In learnpath::select_previous_item_id()', 0);
12214
        }
12215
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12216
12217
        // Get the max order of the items
12218
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12219
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12220
        $rs_max_order = Database::query($sql);
12221
        $row_max_order = Database::fetch_object($rs_max_order);
12222
        $max_order = $row_max_order->display_order;
12223
        // Get the previous item ID
12224
        $sql = "SELECT iid as previous FROM $table_lp_item
12225
                WHERE 
12226
                    c_id = $course_id AND 
12227
                    lp_id = ".$this->lp_id." AND 
12228
                    display_order = '$max_order' ";
12229
        $rs_max = Database::query($sql);
12230
        $row_max = Database::fetch_object($rs_max);
12231
12232
        // Return the previous item ID
12233
        return $row_max->previous;
12234
    }
12235
12236
    /**
12237
     * Copies an LP.
12238
     */
12239
    public function copy()
12240
    {
12241
        // Course builder
12242
        $cb = new CourseBuilder();
12243
12244
        //Setting tools that will be copied
12245
        $cb->set_tools_to_build(['learnpaths']);
12246
12247
        //Setting elements that will be copied
12248
        $cb->set_tools_specific_id_list(
12249
            ['learnpaths' => [$this->lp_id]]
12250
        );
12251
12252
        $course = $cb->build();
12253
12254
        //Course restorer
12255
        $course_restorer = new CourseRestorer($course);
12256
        $course_restorer->set_add_text_in_items(true);
12257
        $course_restorer->set_tool_copy_settings(
12258
            ['learnpaths' => ['reset_dates' => true]]
12259
        );
12260
        $course_restorer->restore(
12261
            api_get_course_id(),
12262
            api_get_session_id(),
12263
            false,
12264
            false
12265
        );
12266
    }
12267
12268
    /**
12269
     * Verify document size.
12270
     *
12271
     * @param string $s
12272
     *
12273
     * @return bool
12274
     */
12275
    public static function verify_document_size($s)
12276
    {
12277
        $post_max = ini_get('post_max_size');
12278
        if (substr($post_max, -1, 1) == 'M') {
12279
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12280
        } elseif (substr($post_max, -1, 1) == 'G') {
12281
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12282
        }
12283
        $upl_max = ini_get('upload_max_filesize');
12284
        if (substr($upl_max, -1, 1) == 'M') {
12285
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12286
        } elseif (substr($upl_max, -1, 1) == 'G') {
12287
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12288
        }
12289
        $documents_total_space = DocumentManager::documents_total_space();
12290
        $course_max_space = DocumentManager::get_course_quota();
12291
        $total_size = filesize($s) + $documents_total_space;
12292
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12293
            return true;
12294
        } else {
12295
            return false;
12296
        }
12297
    }
12298
12299
    /**
12300
     * Clear LP prerequisites.
12301
     */
12302
    public function clear_prerequisites()
12303
    {
12304
        $course_id = $this->get_course_int_id();
12305
        if ($this->debug > 0) {
12306
            error_log('In learnpath::clear_prerequisites()', 0);
12307
        }
12308
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12309
        $lp_id = $this->get_id();
12310
        //Cleaning prerequisites
12311
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12312
                WHERE c_id = $course_id AND lp_id = $lp_id";
12313
        Database::query($sql);
12314
12315
        //Cleaning mastery score for exercises
12316
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12317
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12318
        Database::query($sql);
12319
    }
12320
12321
    public function set_previous_step_as_prerequisite_for_all_items()
12322
    {
12323
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12324
        $course_id = $this->get_course_int_id();
12325
        $lp_id = $this->get_id();
12326
12327
        if (!empty($this->items)) {
12328
            $previous_item_id = null;
12329
            $previous_item_max = 0;
12330
            $previous_item_type = null;
12331
            $last_item_not_dir = null;
12332
            $last_item_not_dir_type = null;
12333
            $last_item_not_dir_max = null;
12334
12335
            foreach ($this->ordered_items as $itemId) {
12336
                $item = $this->getItem($itemId);
12337
                // if there was a previous item... (otherwise jump to set it)
12338
                if (!empty($previous_item_id)) {
12339
                    $current_item_id = $item->get_id(); //save current id
12340
                    if ($item->get_type() != 'dir') {
12341
                        // Current item is not a folder, so it qualifies to get a prerequisites
12342
                        if ($last_item_not_dir_type == 'quiz') {
12343
                            // if previous is quiz, mark its max score as default score to be achieved
12344
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12345
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12346
                            Database::query($sql);
12347
                        }
12348
                        // now simply update the prerequisite to set it to the last non-chapter item
12349
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12350
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12351
                        Database::query($sql);
12352
                        // record item as 'non-chapter' reference
12353
                        $last_item_not_dir = $item->get_id();
12354
                        $last_item_not_dir_type = $item->get_type();
12355
                        $last_item_not_dir_max = $item->get_max();
12356
                    }
12357
                } else {
12358
                    if ($item->get_type() != 'dir') {
12359
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12360
                        $last_item_not_dir = $item->get_id();
12361
                        $last_item_not_dir_type = $item->get_type();
12362
                        $last_item_not_dir_max = $item->get_max();
12363
                    }
12364
                }
12365
                // Saving the item as "previous item" for the next loop
12366
                $previous_item_id = $item->get_id();
12367
                $previous_item_max = $item->get_max();
12368
                $previous_item_type = $item->get_type();
12369
            }
12370
        }
12371
    }
12372
12373
    /**
12374
     * @param array $params
12375
     *
12376
     * @throws \Doctrine\ORM\OptimisticLockException
12377
     *
12378
     * @return int
12379
     */
12380
    public static function createCategory($params)
12381
    {
12382
        $em = Database::getManager();
12383
        $item = new CLpCategory();
12384
        $item->setName($params['name']);
12385
        $item->setCId($params['c_id']);
12386
        $em->persist($item);
12387
        $em->flush();
12388
12389
        api_item_property_update(
12390
            api_get_course_info(),
12391
            TOOL_LEARNPATH_CATEGORY,
12392
            $item->getId(),
12393
            'visible',
12394
            api_get_user_id()
12395
        );
12396
12397
        return $item->getId();
12398
    }
12399
12400
    /**
12401
     * @param array $params
12402
     *
12403
     * @throws \Doctrine\ORM\ORMException
12404
     * @throws \Doctrine\ORM\OptimisticLockException
12405
     * @throws \Doctrine\ORM\TransactionRequiredException
12406
     */
12407
    public static function updateCategory($params)
12408
    {
12409
        $em = Database::getManager();
12410
        /** @var CLpCategory $item */
12411
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12412
        if ($item) {
12413
            $item->setName($params['name']);
12414
            $em->merge($item);
12415
            $em->flush();
12416
        }
12417
    }
12418
12419
    /**
12420
     * @param int $id
12421
     *
12422
     * @throws \Doctrine\ORM\ORMException
12423
     * @throws \Doctrine\ORM\OptimisticLockException
12424
     * @throws \Doctrine\ORM\TransactionRequiredException
12425
     */
12426
    public static function moveUpCategory($id)
12427
    {
12428
        $id = (int) $id;
12429
        $em = Database::getManager();
12430
        /** @var CLpCategory $item */
12431
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12432
        if ($item) {
12433
            $position = $item->getPosition() - 1;
12434
            $item->setPosition($position);
12435
            $em->persist($item);
12436
            $em->flush();
12437
        }
12438
    }
12439
12440
    /**
12441
     * @param int $id
12442
     *
12443
     * @throws \Doctrine\ORM\ORMException
12444
     * @throws \Doctrine\ORM\OptimisticLockException
12445
     * @throws \Doctrine\ORM\TransactionRequiredException
12446
     */
12447
    public static function moveDownCategory($id)
12448
    {
12449
        $id = (int) $id;
12450
        $em = Database::getManager();
12451
        /** @var CLpCategory $item */
12452
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12453
        if ($item) {
12454
            $position = $item->getPosition() + 1;
12455
            $item->setPosition($position);
12456
            $em->persist($item);
12457
            $em->flush();
12458
        }
12459
    }
12460
12461
    /**
12462
     * @param int $courseId
12463
     *
12464
     * @throws \Doctrine\ORM\Query\QueryException
12465
     *
12466
     * @return int|mixed
12467
     */
12468
    public static function getCountCategories($courseId)
12469
    {
12470
        if (empty($courseId)) {
12471
            return 0;
12472
        }
12473
        $em = Database::getManager();
12474
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12475
        $query->setParameter('id', $courseId);
12476
12477
        return $query->getSingleScalarResult();
12478
    }
12479
12480
    /**
12481
     * @param int $courseId
12482
     *
12483
     * @return mixed
12484
     */
12485
    public static function getCategories($courseId)
12486
    {
12487
        $em = Database::getManager();
12488
        //Default behaviour
12489
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12490
            array('cId' => $course_id),
12491
            array('name' => 'ASC')
12492
        );*/
12493
12494
        // Using doctrine extensions
12495
        /** @var SortableRepository $repo */
12496
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12497
        $items = $repo
12498
            ->getBySortableGroupsQuery(['cId' => $courseId])
12499
            ->getResult();
12500
12501
        return $items;
12502
    }
12503
12504
    /**
12505
     * @param int $id
12506
     *
12507
     * @throws \Doctrine\ORM\ORMException
12508
     * @throws \Doctrine\ORM\OptimisticLockException
12509
     * @throws \Doctrine\ORM\TransactionRequiredException
12510
     *
12511
     * @return CLpCategory
12512
     */
12513
    public static function getCategory($id)
12514
    {
12515
        $id = (int) $id;
12516
        $em = Database::getManager();
12517
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12518
12519
        return $item;
12520
    }
12521
12522
    /**
12523
     * @param int $courseId
12524
     *
12525
     * @return array
12526
     */
12527
    public static function getCategoryByCourse($courseId)
12528
    {
12529
        $em = Database::getManager();
12530
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12531
            ['cId' => $courseId]
12532
        );
12533
12534
        return $items;
12535
    }
12536
12537
    /**
12538
     * @param int $id
12539
     *
12540
     * @throws \Doctrine\ORM\ORMException
12541
     * @throws \Doctrine\ORM\OptimisticLockException
12542
     * @throws \Doctrine\ORM\TransactionRequiredException
12543
     *
12544
     * @return mixed
12545
     */
12546
    public static function deleteCategory($id)
12547
    {
12548
        $em = Database::getManager();
12549
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12550
        if ($item) {
12551
            $courseId = $item->getCId();
12552
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12553
            $query->setParameter('id', $courseId);
12554
            $query->setParameter('catId', $item->getId());
12555
            $lps = $query->getResult();
12556
12557
            // Setting category = 0.
12558
            if ($lps) {
12559
                foreach ($lps as $lpItem) {
12560
                    $lpItem->setCategoryId(0);
12561
                }
12562
            }
12563
12564
            // Removing category.
12565
            $em->remove($item);
12566
            $em->flush();
12567
12568
            $courseInfo = api_get_course_info_by_id($courseId);
12569
            $sessionId = api_get_session_id();
12570
12571
            // Delete link tool
12572
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12573
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12574
            // Delete tools
12575
            $sql = "DELETE FROM $tbl_tool
12576
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12577
            Database::query($sql);
12578
        }
12579
    }
12580
12581
    /**
12582
     * @param int  $courseId
12583
     * @param bool $addSelectOption
12584
     *
12585
     * @return mixed
12586
     */
12587
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12588
    {
12589
        $items = self::getCategoryByCourse($courseId);
12590
        $cats = [];
12591
        if ($addSelectOption) {
12592
            $cats = [get_lang('SelectACategory')];
12593
        }
12594
12595
        if (!empty($items)) {
12596
            foreach ($items as $cat) {
12597
                $cats[$cat->getId()] = $cat->getName();
12598
            }
12599
        }
12600
12601
        return $cats;
12602
    }
12603
12604
    /**
12605
     * @param string $courseCode
12606
     * @param int    $lpId
12607
     * @param int    $user_id
12608
     *
12609
     * @return learnpath
12610
     */
12611
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12612
    {
12613
        $debug = 0;
12614
        $learnPath = null;
12615
        $lpObject = Session::read('lpobject');
12616
        if ($lpObject !== null) {
12617
            $learnPath = unserialize($lpObject);
12618
            if ($debug) {
12619
                error_log('getLpFromSession: unserialize');
12620
                error_log('------getLpFromSession------');
12621
                error_log('------unserialize------');
12622
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12623
                error_log("api_get_sessionid: ".api_get_session_id());
12624
            }
12625
        }
12626
12627
        if (!is_object($learnPath)) {
12628
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12629
            if ($debug) {
12630
                error_log('------getLpFromSession------');
12631
                error_log('getLpFromSession: create new learnpath');
12632
                error_log("create new LP with $courseCode - $lpId - $user_id");
12633
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12634
                error_log("api_get_sessionid: ".api_get_session_id());
12635
            }
12636
        }
12637
12638
        return $learnPath;
12639
    }
12640
12641
    /**
12642
     * @param int $itemId
12643
     *
12644
     * @return learnpathItem|false
12645
     */
12646
    public function getItem($itemId)
12647
    {
12648
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12649
            return $this->items[$itemId];
12650
        }
12651
12652
        return false;
12653
    }
12654
12655
    /**
12656
     * @return int
12657
     */
12658
    public function getCategoryId()
12659
    {
12660
        return (int) $this->categoryId;
12661
    }
12662
12663
    /**
12664
     * @param int $categoryId
12665
     *
12666
     * @return bool
12667
     */
12668
    public function setCategoryId($categoryId)
12669
    {
12670
        $this->categoryId = (int) $categoryId;
12671
        $table = Database::get_course_table(TABLE_LP_MAIN);
12672
        $lp_id = $this->get_id();
12673
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12674
                WHERE iid = $lp_id";
12675
        Database::query($sql);
12676
12677
        return true;
12678
    }
12679
12680
    /**
12681
     * Get whether this is a learning path with the possibility to subscribe
12682
     * users or not.
12683
     *
12684
     * @return int
12685
     */
12686
    public function getSubscribeUsers()
12687
    {
12688
        return $this->subscribeUsers;
12689
    }
12690
12691
    /**
12692
     * Set whether this is a learning path with the possibility to subscribe
12693
     * users or not.
12694
     *
12695
     * @param int $value (0 = false, 1 = true)
12696
     *
12697
     * @return bool
12698
     */
12699
    public function setSubscribeUsers($value)
12700
    {
12701
        if ($this->debug > 0) {
12702
            error_log('In learnpath::set_subscribe_users()', 0);
12703
        }
12704
        $this->subscribeUsers = (int) $value;
12705
        $table = Database::get_course_table(TABLE_LP_MAIN);
12706
        $lp_id = $this->get_id();
12707
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12708
                WHERE iid = $lp_id";
12709
        Database::query($sql);
12710
12711
        return true;
12712
    }
12713
12714
    /**
12715
     * Calculate the count of stars for a user in this LP
12716
     * This calculation is based on the following rules:
12717
     * - the student gets one star when he gets to 50% of the learning path
12718
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12719
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12720
     * - the student gets the final star when the score for the *last* test is >= 80%.
12721
     *
12722
     * @param int $sessionId Optional. The session ID
12723
     *
12724
     * @return int The count of stars
12725
     */
12726
    public function getCalculateStars($sessionId = 0)
12727
    {
12728
        $stars = 0;
12729
        $progress = self::getProgress(
12730
            $this->lp_id,
12731
            $this->user_id,
12732
            $this->course_int_id,
12733
            $sessionId
12734
        );
12735
12736
        if ($progress >= 50) {
12737
            $stars++;
12738
        }
12739
12740
        // Calculate stars chapters evaluation
12741
        $exercisesItems = $this->getExercisesItems();
12742
12743
        if (!empty($exercisesItems)) {
12744
            $totalResult = 0;
12745
12746
            foreach ($exercisesItems as $exerciseItem) {
12747
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12748
                    $this->user_id,
12749
                    $exerciseItem->path,
12750
                    $this->course_int_id,
12751
                    $sessionId,
12752
                    $this->lp_id,
12753
                    $exerciseItem->db_id
12754
                );
12755
12756
                $exerciseResultInfo = end($exerciseResultInfo);
12757
12758
                if (!$exerciseResultInfo) {
12759
                    continue;
12760
                }
12761
12762
                if (!empty($exerciseResultInfo['max_score'])) {
12763
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12764
                } else {
12765
                    $exerciseResult = 0;
12766
                }
12767
                $totalResult += $exerciseResult;
12768
            }
12769
12770
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12771
12772
            if ($totalExerciseAverage >= 50) {
12773
                $stars++;
12774
            }
12775
12776
            if ($totalExerciseAverage >= 80) {
12777
                $stars++;
12778
            }
12779
        }
12780
12781
        // Calculate star for final evaluation
12782
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12783
12784
        if (!empty($finalEvaluationItem)) {
12785
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12786
                $this->user_id,
12787
                $finalEvaluationItem->path,
12788
                $this->course_int_id,
12789
                $sessionId,
12790
                $this->lp_id,
12791
                $finalEvaluationItem->db_id
12792
            );
12793
12794
            $evaluationResultInfo = end($evaluationResultInfo);
12795
12796
            if ($evaluationResultInfo) {
12797
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12798
12799
                if ($evaluationResult >= 80) {
12800
                    $stars++;
12801
                }
12802
            }
12803
        }
12804
12805
        return $stars;
12806
    }
12807
12808
    /**
12809
     * Get the items of exercise type.
12810
     *
12811
     * @return array The items. Otherwise return false
12812
     */
12813
    public function getExercisesItems()
12814
    {
12815
        $exercises = [];
12816
        foreach ($this->items as $item) {
12817
            if ($item->type != 'quiz') {
12818
                continue;
12819
            }
12820
            $exercises[] = $item;
12821
        }
12822
12823
        array_pop($exercises);
12824
12825
        return $exercises;
12826
    }
12827
12828
    /**
12829
     * Get the item of exercise type (evaluation type).
12830
     *
12831
     * @return array The final evaluation. Otherwise return false
12832
     */
12833
    public function getFinalEvaluationItem()
12834
    {
12835
        $exercises = [];
12836
        foreach ($this->items as $item) {
12837
            if ($item->type != 'quiz') {
12838
                continue;
12839
            }
12840
12841
            $exercises[] = $item;
12842
        }
12843
12844
        return array_pop($exercises);
12845
    }
12846
12847
    /**
12848
     * Calculate the total points achieved for the current user in this learning path.
12849
     *
12850
     * @param int $sessionId Optional. The session Id
12851
     *
12852
     * @return int
12853
     */
12854
    public function getCalculateScore($sessionId = 0)
12855
    {
12856
        // Calculate stars chapters evaluation
12857
        $exercisesItems = $this->getExercisesItems();
12858
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12859
        $totalExercisesResult = 0;
12860
        $totalEvaluationResult = 0;
12861
12862
        if ($exercisesItems !== false) {
12863
            foreach ($exercisesItems as $exerciseItem) {
12864
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12865
                    $this->user_id,
12866
                    $exerciseItem->path,
12867
                    $this->course_int_id,
12868
                    $sessionId,
12869
                    $this->lp_id,
12870
                    $exerciseItem->db_id
12871
                );
12872
12873
                $exerciseResultInfo = end($exerciseResultInfo);
12874
12875
                if (!$exerciseResultInfo) {
12876
                    continue;
12877
                }
12878
12879
                $totalExercisesResult += $exerciseResultInfo['score'];
12880
            }
12881
        }
12882
12883
        if (!empty($finalEvaluationItem)) {
12884
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12885
                $this->user_id,
12886
                $finalEvaluationItem->path,
12887
                $this->course_int_id,
12888
                $sessionId,
12889
                $this->lp_id,
12890
                $finalEvaluationItem->db_id
12891
            );
12892
12893
            $evaluationResultInfo = end($evaluationResultInfo);
12894
12895
            if ($evaluationResultInfo) {
12896
                $totalEvaluationResult += $evaluationResultInfo['score'];
12897
            }
12898
        }
12899
12900
        return $totalExercisesResult + $totalEvaluationResult;
12901
    }
12902
12903
    /**
12904
     * Check if URL is not allowed to be show in a iframe.
12905
     *
12906
     * @param string $src
12907
     *
12908
     * @return string
12909
     */
12910
    public function fixBlockedLinks($src)
12911
    {
12912
        $urlInfo = parse_url($src);
12913
12914
        $platformProtocol = 'https';
12915
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12916
            $platformProtocol = 'http';
12917
        }
12918
12919
        $protocolFixApplied = false;
12920
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12921
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12922
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12923
12924
        if ($platformProtocol != $scheme) {
12925
            Session::write('x_frame_source', $src);
12926
            $src = 'blank.php?error=x_frames_options';
12927
            $protocolFixApplied = true;
12928
        }
12929
12930
        if ($protocolFixApplied == false) {
12931
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12932
                // Check X-Frame-Options
12933
                $ch = curl_init();
12934
                $options = [
12935
                    CURLOPT_URL => $src,
12936
                    CURLOPT_RETURNTRANSFER => true,
12937
                    CURLOPT_HEADER => true,
12938
                    CURLOPT_FOLLOWLOCATION => true,
12939
                    CURLOPT_ENCODING => "",
12940
                    CURLOPT_AUTOREFERER => true,
12941
                    CURLOPT_CONNECTTIMEOUT => 120,
12942
                    CURLOPT_TIMEOUT => 120,
12943
                    CURLOPT_MAXREDIRS => 10,
12944
                ];
12945
12946
                $proxySettings = api_get_configuration_value('proxy_settings');
12947
                if (!empty($proxySettings) &&
12948
                    isset($proxySettings['curl_setopt_array'])
12949
                ) {
12950
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12951
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12952
                }
12953
12954
                curl_setopt_array($ch, $options);
12955
                $response = curl_exec($ch);
12956
                $httpCode = curl_getinfo($ch);
12957
                $headers = substr($response, 0, $httpCode['header_size']);
12958
12959
                $error = false;
12960
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12961
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12962
                ) {
12963
                    $error = true;
12964
                }
12965
12966
                if ($error) {
12967
                    Session::write('x_frame_source', $src);
12968
                    $src = 'blank.php?error=x_frames_options';
12969
                }
12970
            }
12971
        }
12972
12973
        return $src;
12974
    }
12975
12976
    /**
12977
     * Check if this LP has a created forum in the basis course.
12978
     *
12979
     * @return bool
12980
     */
12981
    public function lpHasForum()
12982
    {
12983
        $forumTable = Database::get_course_table(TABLE_FORUM);
12984
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12985
12986
        $fakeFrom = "
12987
            $forumTable f
12988
            INNER JOIN $itemProperty ip
12989
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12990
        ";
12991
12992
        $resultData = Database::select(
12993
            'COUNT(f.iid) AS qty',
12994
            $fakeFrom,
12995
            [
12996
                'where' => [
12997
                    'ip.visibility != ? AND ' => 2,
12998
                    'ip.tool = ? AND ' => TOOL_FORUM,
12999
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13000
                    'f.lp_id = ?' => intval($this->lp_id),
13001
                ],
13002
            ],
13003
            'first'
13004
        );
13005
13006
        return $resultData['qty'] > 0;
13007
    }
13008
13009
    /**
13010
     * Get the forum for this learning path.
13011
     *
13012
     * @param int $sessionId
13013
     *
13014
     * @return bool
13015
     */
13016
    public function getForum($sessionId = 0)
13017
    {
13018
        $forumTable = Database::get_course_table(TABLE_FORUM);
13019
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13020
13021
        $fakeFrom = "$forumTable f
13022
            INNER JOIN $itemProperty ip ";
13023
13024
        if ($this->lp_session_id == 0) {
13025
            $fakeFrom .= "
13026
                ON (
13027
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13028
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13029
                    )
13030
                )
13031
            ";
13032
        } else {
13033
            $fakeFrom .= "
13034
                ON (
13035
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13036
                )
13037
            ";
13038
        }
13039
13040
        $resultData = Database::select(
13041
            'f.*',
13042
            $fakeFrom,
13043
            [
13044
                'where' => [
13045
                    'ip.visibility != ? AND ' => 2,
13046
                    'ip.tool = ? AND ' => TOOL_FORUM,
13047
                    'f.session_id = ? AND ' => $sessionId,
13048
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13049
                    'f.lp_id = ?' => intval($this->lp_id),
13050
                ],
13051
            ],
13052
            'first'
13053
        );
13054
13055
        if (empty($resultData)) {
13056
            return false;
13057
        }
13058
13059
        return $resultData;
13060
    }
13061
13062
    /**
13063
     * Create a forum for this learning path.
13064
     *
13065
     * @param int $forumCategoryId
13066
     *
13067
     * @return int The forum ID if was created. Otherwise return false
13068
     */
13069
    public function createForum($forumCategoryId)
13070
    {
13071
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13072
13073
        $forumId = store_forum(
13074
            [
13075
                'lp_id' => $this->lp_id,
13076
                'forum_title' => $this->name,
13077
                'forum_comment' => null,
13078
                'forum_category' => intval($forumCategoryId),
13079
                'students_can_edit_group' => ['students_can_edit' => 0],
13080
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13081
                'default_view_type_group' => ['default_view_type' => 'flat'],
13082
                'group_forum' => 0,
13083
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13084
            ],
13085
            [],
13086
            true
13087
        );
13088
13089
        return $forumId;
13090
    }
13091
13092
    /**
13093
     * Get the LP Final Item form.
13094
     *
13095
     * @throws Exception
13096
     * @throws HTML_QuickForm_Error
13097
     *
13098
     * @return string
13099
     */
13100
    public function getFinalItemForm()
13101
    {
13102
        $finalItem = $this->getFinalItem();
13103
        $title = '';
13104
13105
        if ($finalItem) {
13106
            $title = $finalItem->get_title();
13107
            $buttonText = get_lang('Save');
13108
            $content = $this->getSavedFinalItem();
13109
        } else {
13110
            $buttonText = get_lang('LPCreateDocument');
13111
            $content = $this->getFinalItemTemplate();
13112
        }
13113
13114
        $courseInfo = api_get_course_info();
13115
        $result = $this->generate_lp_folder($courseInfo);
13116
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13117
        $relative_prefix = '../../';
13118
13119
        $editorConfig = [
13120
            'ToolbarSet' => 'LearningPathDocuments',
13121
            'Width' => '100%',
13122
            'Height' => '500',
13123
            'FullPage' => true,
13124
            'CreateDocumentDir' => $relative_prefix,
13125
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13126
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13127
        ];
13128
13129
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13130
            'type' => 'document',
13131
            'lp_id' => $this->lp_id,
13132
        ]);
13133
13134
        $form = new FormValidator('final_item', 'POST', $url);
13135
        $form->addText('title', get_lang('Title'));
13136
        $form->addButtonSave($buttonText);
13137
        $form->addHtml(
13138
            Display::return_message(
13139
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13140
                'normal',
13141
                false
13142
            )
13143
        );
13144
13145
        $renderer = $form->defaultRenderer();
13146
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13147
13148
        $form->addHtmlEditor(
13149
            'content_lp_certificate',
13150
            null,
13151
            true,
13152
            false,
13153
            $editorConfig,
13154
            true
13155
        );
13156
        $form->addHidden('action', 'add_final_item');
13157
        $form->addHidden('path', Session::read('pathItem'));
13158
        $form->addHidden('previous', $this->get_last());
13159
        $form->setDefaults(
13160
            ['title' => $title, 'content_lp_certificate' => $content]
13161
        );
13162
13163
        if ($form->validate()) {
13164
            $values = $form->exportValues();
13165
            $lastItemId = $this->get_last();
13166
13167
            if (!$finalItem) {
13168
                $documentId = $this->create_document(
13169
                    $this->course_info,
13170
                    $values['content_lp_certificate'],
13171
                    $values['title']
13172
                );
13173
                $this->add_item(
13174
                    0,
13175
                    $lastItemId,
13176
                    'final_item',
13177
                    $documentId,
13178
                    $values['title'],
13179
                    ''
13180
                );
13181
13182
                Display::addFlash(
13183
                    Display::return_message(get_lang('Added'))
13184
                );
13185
            } else {
13186
                $this->edit_document($this->course_info);
13187
            }
13188
        }
13189
13190
        return $form->returnForm();
13191
    }
13192
13193
    /**
13194
     * Check if the current lp item is first, both, last or none from lp list.
13195
     *
13196
     * @param int $currentItemId
13197
     *
13198
     * @return string
13199
     */
13200
    public function isFirstOrLastItem($currentItemId)
13201
    {
13202
        if ($this->debug > 0) {
13203
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
13204
        }
13205
13206
        $lpItemId = [];
13207
        $typeListNotToVerify = self::getChapterTypes();
13208
13209
        // Using get_toc() function instead $this->items because returns the correct order of the items
13210
        foreach ($this->get_toc() as $item) {
13211
            if (!in_array($item['type'], $typeListNotToVerify)) {
13212
                $lpItemId[] = $item['id'];
13213
            }
13214
        }
13215
13216
        $lastLpItemIndex = count($lpItemId) - 1;
13217
        $position = array_search($currentItemId, $lpItemId);
13218
13219
        switch ($position) {
13220
            case 0:
13221
                if (!$lastLpItemIndex) {
13222
                    $answer = 'both';
13223
                    break;
13224
                }
13225
13226
                $answer = 'first';
13227
                break;
13228
            case $lastLpItemIndex:
13229
                $answer = 'last';
13230
                break;
13231
            default:
13232
                $answer = 'none';
13233
        }
13234
13235
        return $answer;
13236
    }
13237
13238
    /**
13239
     * Get whether this is a learning path with the accumulated SCORM time or not.
13240
     *
13241
     * @return int
13242
     */
13243
    public function getAccumulateScormTime()
13244
    {
13245
        return $this->accumulateScormTime;
13246
    }
13247
13248
    /**
13249
     * Set whether this is a learning path with the accumulated SCORM time or not.
13250
     *
13251
     * @param int $value (0 = false, 1 = true)
13252
     *
13253
     * @return bool Always returns true
13254
     */
13255
    public function setAccumulateScormTime($value)
13256
    {
13257
        if ($this->debug > 0) {
13258
            error_log('In learnpath::setAccumulateScormTime()', 0);
13259
        }
13260
        $this->accumulateScormTime = intval($value);
13261
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13262
        $lp_id = $this->get_id();
13263
        $sql = "UPDATE $lp_table 
13264
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13265
                WHERE iid = $lp_id";
13266
        Database::query($sql);
13267
13268
        return true;
13269
    }
13270
13271
    /**
13272
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13273
     * the new learning path tool.
13274
     *
13275
     * The function is a big switch on tool type.
13276
     * In each case, we query the corresponding table for information and build the link
13277
     * with that information.
13278
     *
13279
     * @author Yannick Warnier <[email protected]> - rebranding based on
13280
     * previous work (display_addedresource_link_in_learnpath())
13281
     *
13282
     * @param int $course_id      Course code
13283
     * @param int $learningPathId The learning path ID (in lp table)
13284
     * @param int $id_in_path     the unique index in the items table
13285
     * @param int $lpViewId
13286
     *
13287
     * @return string
13288
     */
13289
    public static function rl_get_resource_link_for_learnpath(
13290
        $course_id,
13291
        $learningPathId,
13292
        $id_in_path,
13293
        $lpViewId
13294
    ) {
13295
        $session_id = api_get_session_id();
13296
        $course_info = api_get_course_info_by_id($course_id);
13297
13298
        $learningPathId = (int) $learningPathId;
13299
        $id_in_path = (int) $id_in_path;
13300
        $lpViewId = (int) $lpViewId;
13301
13302
        $em = Database::getManager();
13303
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13304
13305
        /** @var CLpItem $rowItem */
13306
        $rowItem = $lpItemRepo->findOneBy([
13307
            'cId' => $course_id,
13308
            'lpId' => $learningPathId,
13309
            'iid' => $id_in_path,
13310
        ]);
13311
13312
        if (!$rowItem) {
13313
            // Try one more time with "id"
13314
            /** @var CLpItem $rowItem */
13315
            $rowItem = $lpItemRepo->findOneBy([
13316
                'cId' => $course_id,
13317
                'lpId' => $learningPathId,
13318
                'id' => $id_in_path,
13319
            ]);
13320
13321
            if (!$rowItem) {
13322
                return -1;
13323
            }
13324
        }
13325
13326
        $course_code = $course_info['code'];
13327
        $type = $rowItem->getItemType();
13328
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13329
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13330
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13331
        $link = '';
13332
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13333
13334
        switch ($type) {
13335
            case 'dir':
13336
                return $main_dir_path.'lp/blank.php';
13337
            case TOOL_CALENDAR_EVENT:
13338
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13339
            case TOOL_ANNOUNCEMENT:
13340
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13341
            case TOOL_LINK:
13342
                $linkInfo = Link::getLinkInfo($id);
13343
                if (isset($linkInfo['url'])) {
13344
                    return $linkInfo['url'];
13345
                }
13346
13347
                return '';
13348
            case TOOL_QUIZ:
13349
                if (empty($id)) {
13350
                    return '';
13351
                }
13352
13353
                // Get the lp_item_view with the highest view_count.
13354
                $learnpathItemViewResult = $em
13355
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13356
                    ->findBy(
13357
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13358
                        ['viewCount' => 'DESC'],
13359
                        1
13360
                    );
13361
                /** @var CLpItemView $learnpathItemViewData */
13362
                $learnpathItemViewData = current($learnpathItemViewResult);
13363
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13364
13365
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13366
                    .http_build_query([
13367
                        'lp_init' => 1,
13368
                        'learnpath_item_view_id' => $learnpathItemViewId,
13369
                        'learnpath_id' => $learningPathId,
13370
                        'learnpath_item_id' => $id_in_path,
13371
                        'exerciseId' => $id,
13372
                    ]);
13373
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13374
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13375
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13376
                $myrow = Database::fetch_array($result);
13377
                $path = $myrow['path'];
13378
13379
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13380
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13381
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13382
            case TOOL_FORUM:
13383
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13384
            case TOOL_THREAD:
13385
                // forum post
13386
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13387
                if (empty($id)) {
13388
                    return '';
13389
                }
13390
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13391
                $result = Database::query($sql);
13392
                $myrow = Database::fetch_array($result);
13393
13394
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13395
                    .$extraParams;
13396
            case TOOL_POST:
13397
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13398
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13399
                $myrow = Database::fetch_array($result);
13400
13401
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13402
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13403
            case TOOL_READOUT_TEXT:
13404
                return api_get_path(WEB_CODE_PATH).'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13405
                    .$extraParams;
13406
            case TOOL_DOCUMENT:
13407
                $document = $em
13408
                    ->getRepository('ChamiloCourseBundle:CDocument')
13409
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
13410
13411
                if (empty($document)) {
13412
                    // Try with normal id
13413
                    $document = $em
13414
                        ->getRepository('ChamiloCourseBundle:CDocument')
13415
                        ->findOneBy(['cId' => $course_id, 'id' => $id]);
13416
13417
                    if (empty($document)) {
13418
                        return '';
13419
                    }
13420
                }
13421
13422
                $documentPathInfo = pathinfo($document->getPath());
13423
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
13424
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13425
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
13426
13427
                $openmethod = 2;
13428
                $officedoc = false;
13429
                Session::write('openmethod', $openmethod);
13430
                Session::write('officedoc', $officedoc);
13431
13432
                if ($showDirectUrl) {
13433
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13434
                }
13435
13436
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13437
            case TOOL_LP_FINAL_ITEM:
13438
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13439
                    .$extraParams;
13440
            case 'assignments':
13441
                return $main_dir_path.'work/work.php?'.$extraParams;
13442
            case TOOL_DROPBOX:
13443
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13444
            case 'introduction_text': //DEPRECATED
13445
                return '';
13446
            case TOOL_COURSE_DESCRIPTION:
13447
                return $main_dir_path.'course_description?'.$extraParams;
13448
            case TOOL_GROUP:
13449
                return $main_dir_path.'group/group.php?'.$extraParams;
13450
            case TOOL_USER:
13451
                return $main_dir_path.'user/user.php?'.$extraParams;
13452
            case TOOL_STUDENTPUBLICATION:
13453
                if (!empty($rowItem->getPath())) {
13454
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13455
                }
13456
13457
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13458
        } //end switch
13459
13460
        return $link;
13461
    }
13462
13463
    /**
13464
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13465
     *
13466
     * @author Yannick Warnier <[email protected]>
13467
     *
13468
     * @param string $course_code    Course code
13469
     * @param int    $learningPathId
13470
     * @param int    $id_in_path     The resource ID
13471
     *
13472
     * @return string
13473
     */
13474
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13475
    {
13476
        $_course = api_get_course_info($course_code);
13477
        $course_id = $_course['real_id'];
13478
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13479
        $learningPathId = (int) $learningPathId;
13480
        $id_in_path = (int) $id_in_path;
13481
13482
        $sql = "SELECT item_type, title, ref 
13483
                FROM $tbl_lp_item
13484
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13485
        $res_item = Database::query($sql);
13486
13487
        if (Database::num_rows($res_item) < 1) {
13488
            return '';
13489
        }
13490
        $row_item = Database::fetch_array($res_item);
13491
        $type = strtolower($row_item['item_type']);
13492
        $id = $row_item['ref'];
13493
        $output = '';
13494
13495
        switch ($type) {
13496
            case TOOL_CALENDAR_EVENT:
13497
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13498
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13499
                $myrow = Database::fetch_array($result);
13500
                $output = $myrow['title'];
13501
                break;
13502
            case TOOL_ANNOUNCEMENT:
13503
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13504
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13505
                $myrow = Database::fetch_array($result);
13506
                $output = $myrow['title'];
13507
                break;
13508
            case TOOL_LINK:
13509
                // Doesn't take $target into account.
13510
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13511
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13512
                $myrow = Database::fetch_array($result);
13513
                $output = $myrow['title'];
13514
                break;
13515
            case TOOL_QUIZ:
13516
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13517
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13518
                $myrow = Database::fetch_array($result);
13519
                $output = $myrow['title'];
13520
                break;
13521
            case TOOL_FORUM:
13522
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13523
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13524
                $myrow = Database::fetch_array($result);
13525
                $output = $myrow['forum_name'];
13526
                break;
13527
            case TOOL_THREAD:  //=topics
13528
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13529
                // Grabbing the title of the post.
13530
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13531
                $result_title = Database::query($sql_title);
13532
                $myrow_title = Database::fetch_array($result_title);
13533
                $output = $myrow_title['post_title'];
13534
                break;
13535
            case TOOL_POST:
13536
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13537
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
13538
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13539
                $result = Database::query($sql);
13540
                $post = Database::fetch_array($result);
13541
                $output = $post['post_title'];
13542
                break;
13543
            case 'dir':
13544
                $title = $row_item['title'];
13545
                if (!empty($title)) {
13546
                    $output = $title;
13547
                } else {
13548
                    $output = '-';
13549
                }
13550
                break;
13551
            case TOOL_DOCUMENT:
13552
                $title = $row_item['title'];
13553
                if (!empty($title)) {
13554
                    $output = $title;
13555
                } else {
13556
                    $output = '-';
13557
                }
13558
                break;
13559
            case 'hotpotatoes':
13560
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13561
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13562
                $myrow = Database::fetch_array($result);
13563
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13564
                $last = count($pathname) - 1; // Making a correct name for the link.
13565
                $filename = $pathname[$last]; // Making a correct name for the link.
13566
                $ext = explode('.', $filename);
13567
                $ext = strtolower($ext[sizeof($ext) - 1]);
13568
                $myrow['path'] = rawurlencode($myrow['path']);
13569
                $output = $filename;
13570
                break;
13571
        }
13572
13573
        return stripslashes($output);
13574
    }
13575
13576
    /**
13577
     * Get the parent names for the current item.
13578
     *
13579
     * @param int $newItemId Optional. The item ID
13580
     *
13581
     * @return array
13582
     */
13583
    public function getCurrentItemParentNames($newItemId = 0)
13584
    {
13585
        $newItemId = $newItemId ?: $this->get_current_item_id();
13586
        $return = [];
13587
        $item = $this->getItem($newItemId);
13588
        $parent = $this->getItem($item->get_parent());
13589
13590
        while ($parent) {
13591
            $return[] = $parent->get_title();
13592
13593
            $parent = $this->getItem($parent->get_parent());
13594
        }
13595
13596
        return array_reverse($return);
13597
    }
13598
13599
    /**
13600
     * Reads and process "lp_subscription_settings" setting.
13601
     *
13602
     * @return array
13603
     */
13604
    public static function getSubscriptionSettings()
13605
    {
13606
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13607
        if (empty($subscriptionSettings)) {
13608
            // By default allow both settings
13609
            $subscriptionSettings = [
13610
                'allow_add_users_to_lp' => true,
13611
                'allow_add_users_to_lp_category' => true,
13612
            ];
13613
        } else {
13614
            $subscriptionSettings = $subscriptionSettings['options'];
13615
        }
13616
13617
        return $subscriptionSettings;
13618
    }
13619
13620
    /**
13621
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13622
     */
13623
    public function exportToCourseBuildFormat()
13624
    {
13625
        if (!api_is_allowed_to_edit()) {
13626
            return false;
13627
        }
13628
13629
        $courseBuilder = new CourseBuilder();
13630
        $itemList = [];
13631
        /** @var learnpathItem $item */
13632
        foreach ($this->items as $item) {
13633
            $itemList[$item->get_type()][] = $item->get_path();
13634
        }
13635
13636
        if (empty($itemList)) {
13637
            return false;
13638
        }
13639
13640
        if (isset($itemList['document'])) {
13641
            // Get parents
13642
            foreach ($itemList['document'] as $documentId) {
13643
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13644
                if (!empty($documentInfo['parents'])) {
13645
                    foreach ($documentInfo['parents'] as $parentInfo) {
13646
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13647
                            continue;
13648
                        }
13649
                        $itemList['document'][] = $parentInfo['iid'];
13650
                    }
13651
                }
13652
            }
13653
13654
            $courseInfo = api_get_course_info();
13655
            foreach ($itemList['document'] as $documentId) {
13656
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13657
                $items = DocumentManager::get_resources_from_source_html(
13658
                    $documentInfo['absolute_path'],
13659
                    true,
13660
                    TOOL_DOCUMENT
13661
                );
13662
13663
                if (!empty($items)) {
13664
                    foreach ($items as $item) {
13665
                        // Get information about source url
13666
                        $url = $item[0]; // url
13667
                        $scope = $item[1]; // scope (local, remote)
13668
                        $type = $item[2]; // type (rel, abs, url)
13669
13670
                        $origParseUrl = parse_url($url);
13671
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13672
13673
                        if ($scope == 'local') {
13674
                            if ($type == 'abs' || $type == 'rel') {
13675
                                $documentFile = strstr($realOrigPath, 'document');
13676
                                if (strpos($realOrigPath, $documentFile) !== false) {
13677
                                    $documentFile = str_replace('document', '', $documentFile);
13678
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13679
                                    // Document found! Add it to the list
13680
                                    if ($itemDocumentId) {
13681
                                        $itemList['document'][] = $itemDocumentId;
13682
                                    }
13683
                                }
13684
                            }
13685
                        }
13686
                    }
13687
                }
13688
            }
13689
13690
            $courseBuilder->build_documents(
13691
                api_get_session_id(),
13692
                $this->get_course_int_id(),
13693
                true,
13694
                $itemList['document']
13695
            );
13696
        }
13697
13698
        if (isset($itemList['quiz'])) {
13699
            $courseBuilder->build_quizzes(
13700
                api_get_session_id(),
13701
                $this->get_course_int_id(),
13702
                true,
13703
                $itemList['quiz']
13704
            );
13705
        }
13706
13707
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13708
13709
        /*if (!empty($itemList['thread'])) {
13710
            $postList = [];
13711
            foreach ($itemList['thread'] as $postId) {
13712
                $post = get_post_information($postId);
13713
                if ($post) {
13714
                    if (!isset($itemList['forum'])) {
13715
                        $itemList['forum'] = [];
13716
                    }
13717
                    $itemList['forum'][] = $post['forum_id'];
13718
                    $postList[] = $postId;
13719
                }
13720
            }
13721
13722
            if (!empty($postList)) {
13723
                $courseBuilder->build_forum_posts(
13724
                    $this->get_course_int_id(),
13725
                    null,
13726
                    null,
13727
                    $postList
13728
                );
13729
            }
13730
        }*/
13731
13732
        if (!empty($itemList['thread'])) {
13733
            $threadList = [];
13734
            $em = Database::getManager();
13735
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13736
            foreach ($itemList['thread'] as $threadId) {
13737
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13738
                $thread = $repo->find($threadId);
13739
                if ($thread) {
13740
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13741
                    $threadList[] = $thread->getIid();
13742
                }
13743
            }
13744
13745
            if (!empty($threadList)) {
13746
                $courseBuilder->build_forum_topics(
13747
                    api_get_session_id(),
13748
                    $this->get_course_int_id(),
13749
                    null,
13750
                    $threadList
13751
                );
13752
            }
13753
        }
13754
13755
        $forumCategoryList = [];
13756
        if (isset($itemList['forum'])) {
13757
            foreach ($itemList['forum'] as $forumId) {
13758
                $forumInfo = get_forums($forumId);
13759
                $forumCategoryList[] = $forumInfo['forum_category'];
13760
            }
13761
        }
13762
13763
        if (!empty($forumCategoryList)) {
13764
            $courseBuilder->build_forum_category(
13765
                api_get_session_id(),
13766
                $this->get_course_int_id(),
13767
                true,
13768
                $forumCategoryList
13769
            );
13770
        }
13771
13772
        if (!empty($itemList['forum'])) {
13773
            $courseBuilder->build_forums(
13774
                api_get_session_id(),
13775
                $this->get_course_int_id(),
13776
                true,
13777
                $itemList['forum']
13778
            );
13779
        }
13780
13781
        if (isset($itemList['link'])) {
13782
            $courseBuilder->build_links(
13783
                api_get_session_id(),
13784
                $this->get_course_int_id(),
13785
                true,
13786
                $itemList['link']
13787
            );
13788
        }
13789
13790
        if (!empty($itemList['student_publication'])) {
13791
            $courseBuilder->build_works(
13792
                api_get_session_id(),
13793
                $this->get_course_int_id(),
13794
                true,
13795
                $itemList['student_publication']
13796
            );
13797
        }
13798
13799
        $courseBuilder->build_learnpaths(
13800
            api_get_session_id(),
13801
            $this->get_course_int_id(),
13802
            true,
13803
            [$this->get_id()],
13804
            false
13805
        );
13806
13807
        $courseBuilder->restoreDocumentsFromList();
13808
13809
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13810
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13811
        $result = DocumentManager::file_send_for_download(
13812
            $zipPath,
13813
            true,
13814
            $this->get_name().'.zip'
13815
        );
13816
13817
        if ($result) {
13818
            api_not_allowed();
13819
        }
13820
13821
        return true;
13822
    }
13823
13824
    /**
13825
     * Get the depth level of LP item.
13826
     *
13827
     * @param array $items
13828
     * @param int   $currentItemId
13829
     *
13830
     * @return int
13831
     */
13832
    private static function get_level_for_item($items, $currentItemId)
13833
    {
13834
        $parentItemId = 0;
13835
        if (isset($items[$currentItemId])) {
13836
            $parentItemId = $items[$currentItemId]->parent;
13837
        }
13838
13839
        if ($parentItemId == 0) {
13840
            return 0;
13841
        } else {
13842
            return self::get_level_for_item($items, $parentItemId) + 1;
13843
        }
13844
    }
13845
13846
    /**
13847
     * Generate the link for a learnpath category as course tool.
13848
     *
13849
     * @param int $categoryId
13850
     *
13851
     * @return string
13852
     */
13853
    private static function getCategoryLinkForTool($categoryId)
13854
    {
13855
        $categoryId = (int) $categoryId;
13856
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13857
            .http_build_query(
13858
                [
13859
                    'action' => 'view_category',
13860
                    'id' => $categoryId,
13861
                ]
13862
            );
13863
13864
        return $link;
13865
    }
13866
13867
    /**
13868
     * Return the scorm item type object with spaces replaced with _
13869
     * The return result is use to build a css classname like scorm_type_$return.
13870
     *
13871
     * @param $in_type
13872
     *
13873
     * @return mixed
13874
     */
13875
    private static function format_scorm_type_item($in_type)
13876
    {
13877
        return str_replace(' ', '_', $in_type);
13878
    }
13879
13880
    /**
13881
     * Check and obtain the lp final item if exist.
13882
     *
13883
     * @return learnpathItem
13884
     */
13885
    private function getFinalItem()
13886
    {
13887
        if (empty($this->items)) {
13888
            return null;
13889
        }
13890
13891
        foreach ($this->items as $item) {
13892
            if ($item->type !== 'final_item') {
13893
                continue;
13894
            }
13895
13896
            return $item;
13897
        }
13898
    }
13899
13900
    /**
13901
     * Get the LP Final Item Template.
13902
     *
13903
     * @return string
13904
     */
13905
    private function getFinalItemTemplate()
13906
    {
13907
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13908
    }
13909
13910
    /**
13911
     * Get the LP Final Item Url.
13912
     *
13913
     * @return string
13914
     */
13915
    private function getSavedFinalItem()
13916
    {
13917
        $finalItem = $this->getFinalItem();
13918
        $doc = DocumentManager::get_document_data_by_id(
13919
            $finalItem->path,
13920
            $this->cc
13921
        );
13922
        if ($doc && file_exists($doc['absolute_path'])) {
13923
            return file_get_contents($doc['absolute_path']);
13924
        }
13925
13926
        return '';
13927
    }
13928
}
13929