Completed
Push — master ( af42cb...3888f0 )
by Julito
13:17
created

learnpath::get_link()   F

Complexity

Conditions 57
Paths > 20000

Size

Total Lines 318
Code Lines 192

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 57
eloc 192
nc 221216
nop 3
dl 0
loc 318
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/* 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\CItemProperty;
10
use Chamilo\CourseBundle\Entity\CLp;
11
use Chamilo\CourseBundle\Entity\CLpCategory;
12
use Chamilo\CourseBundle\Entity\CLpItem;
13
use Chamilo\CourseBundle\Entity\CLpItemView;
14
use Chamilo\CourseBundle\Entity\CTool;
15
use Chamilo\UserBundle\Entity\User;
16
use ChamiloSession as Session;
17
use Gedmo\Sortable\Entity\Repository\SortableRepository;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\Finder\Finder;
20
21
/**
22
 * Class learnpath
23
 * This class defines the parent attributes and methods for Chamilo learnpaths
24
 * and SCORM learnpaths. It is used by the scorm class.
25
 *
26
 * @todo decouple class
27
 *
28
 * @package chamilo.learnpath
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
37
    public $attempt = 0; // The number for the current ID view.
38
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
39
    public $current; // Id of the current item the user is viewing.
40
    public $current_score; // The score of the current item.
41
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
42
    public $current_time_stop; // The time the user closed this resource.
43
    public $default_status = 'not attempted';
44
    public $encoding = 'UTF-8';
45
    public $error = '';
46
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
47
    public $index; // The index of the active learnpath_item in $ordered_items array.
48
    public $items = [];
49
    public $last; // item_id of last item viewed in the learning path.
50
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
51
    public $license; // Which license this course has been given - not used yet on 20060522.
52
    public $lp_id; // DB iid for this learnpath.
53
    public $lp_view_id; // DB ID for lp_view
54
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
55
    public $message = '';
56
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
57
    public $name; // Learnpath name (they generally have one).
58
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
59
    public $path = ''; // Path inside the scorm directory (if scorm).
60
    public $theme; // The current theme of the learning path.
61
    public $preview_image; // The current image of the learning path.
62
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
63
64
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
65
    public $prevent_reinit = 1;
66
67
    // Describes the mode of progress bar display.
68
    public $seriousgame_mode = 0;
69
    public $progress_bar_mode = '%';
70
71
    // Percentage progress as saved in the db.
72
    public $progress_db = 0;
73
    public $proximity; // Wether the content is distant or local or unknown.
74
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
75
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
76
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
77
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
78
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
79
    public $user_id; //ID of the user that is viewing/using the course
80
    public $update_queue = [];
81
    public $scorm_debug = 0;
82
    public $arrMenu = []; // Array for the menu items.
83
    public $debug = 0; // Logging level.
84
    public $lp_session_id = 0;
85
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
86
    public $prerequisite = 0;
87
    public $use_max_score = 1; // 1 or 0
88
    public $subscribeUsers = 0; // Subscribe users or not
89
    public $created_on = '';
90
    public $modified_on = '';
91
    public $publicated_on = '';
92
    public $expired_on = '';
93
    public $ref = null;
94
    public $course_int_id;
95
    public $course_info = [];
96
    public $categoryId;
97
98
    /**
99
     * Constructor.
100
     * Needs a database handler, a course code and a learnpath id from the database.
101
     * Also builds the list of items into $this->items.
102
     *
103
     * @param string $course  Course code
104
     * @param int    $lp_id   c_lp.iid
105
     * @param int    $user_id
106
     */
107
    public function __construct($course, $lp_id, $user_id)
108
    {
109
        $debug = $this->debug;
110
        $this->encoding = api_get_system_encoding();
111
        if ($debug) {
112
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
113
        }
114
        if (empty($course)) {
115
            $course = api_get_course_id();
116
        }
117
        $course_info = api_get_course_info($course);
118
        if (!empty($course_info)) {
119
            $this->cc = $course_info['code'];
120
            $this->course_info = $course_info;
121
            $course_id = $course_info['real_id'];
122
        } else {
123
            $this->error = 'Course code does not exist in database.';
124
        }
125
126
        $lp_id = (int) $lp_id;
127
        $course_id = (int) $course_id;
128
        $this->set_course_int_id($course_id);
129
        // Check learnpath ID.
130
        if (empty($lp_id) || empty($course_id)) {
131
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
132
        } else {
133
            // TODO: Make it flexible to use any course_code (still using env course code here).
134
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
135
            $sql = "SELECT * FROM $lp_table
136
                    WHERE iid = $lp_id";
137
            if ($debug) {
138
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
139
            }
140
            $res = Database::query($sql);
141
            if (Database::num_rows($res) > 0) {
142
                $this->lp_id = $lp_id;
143
                $row = Database::fetch_array($res);
144
                $this->type = $row['lp_type'];
145
                $this->name = stripslashes($row['name']);
146
                $this->proximity = $row['content_local'];
147
                $this->theme = $row['theme'];
148
                $this->maker = $row['content_maker'];
149
                $this->prevent_reinit = $row['prevent_reinit'];
150
                $this->seriousgame_mode = $row['seriousgame_mode'];
151
                $this->license = $row['content_license'];
152
                $this->scorm_debug = $row['debug'];
153
                $this->js_lib = $row['js_lib'];
154
                $this->path = $row['path'];
155
                $this->preview_image = $row['preview_image'];
156
                $this->author = $row['author'];
157
                $this->hide_toc_frame = $row['hide_toc_frame'];
158
                $this->lp_session_id = $row['session_id'];
159
                $this->use_max_score = $row['use_max_score'];
160
                $this->subscribeUsers = $row['subscribe_users'];
161
                $this->created_on = $row['created_on'];
162
                $this->modified_on = $row['modified_on'];
163
                $this->ref = $row['ref'];
164
                $this->categoryId = $row['category_id'];
165
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
166
167
                if (!empty($row['publicated_on'])) {
168
                    $this->publicated_on = $row['publicated_on'];
169
                }
170
171
                if (!empty($row['expired_on'])) {
172
                    $this->expired_on = $row['expired_on'];
173
                }
174
                if ($this->type == 2) {
175
                    if ($row['force_commit'] == 1) {
176
                        $this->force_commit = true;
177
                    }
178
                }
179
                $this->mode = $row['default_view_mod'];
180
181
                // Check user ID.
182
                if (empty($user_id)) {
183
                    $this->error = 'User ID is empty';
184
                } else {
185
                    $userInfo = api_get_user_info($user_id);
186
                    if (!empty($userInfo)) {
187
                        $this->user_id = $userInfo['user_id'];
188
                    } else {
189
                        $this->error = 'User ID does not exist in database #'.$user_id;
190
                    }
191
                }
192
193
                // End of variables checking.
194
                $session_id = api_get_session_id();
195
                //  Get the session condition for learning paths of the base + session.
196
                $session = api_get_session_condition($session_id);
197
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
198
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
199
200
                // Selecting by view_count descending allows to get the highest view_count first.
201
                $sql = "SELECT * FROM $lp_table
202
                        WHERE 
203
                            c_id = $course_id AND 
204
                            lp_id = $lp_id AND 
205
                            user_id = $user_id 
206
                            $session
207
                        ORDER BY view_count DESC";
208
                $res = Database::query($sql);
209
                if ($debug) {
210
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
211
                }
212
213
                if (Database::num_rows($res) > 0) {
214
                    if ($debug) {
215
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
216
                    }
217
                    $row = Database::fetch_array($res);
218
                    $this->attempt = $row['view_count'];
219
                    $this->lp_view_id = $row['id'];
220
                    $this->last_item_seen = $row['last_item'];
221
                    $this->progress_db = $row['progress'];
222
                    $this->lp_view_session_id = $row['session_id'];
223
                } elseif (!api_is_invitee()) {
224
                    if ($debug) {
225
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
226
                    }
227
                    $this->attempt = 1;
228
                    $params = [
229
                        'c_id' => $course_id,
230
                        'lp_id' => $lp_id,
231
                        'user_id' => $user_id,
232
                        'view_count' => 1,
233
                        'session_id' => $session_id,
234
                        'last_item' => 0,
235
                    ];
236
                    $this->last_item_seen = 0;
237
                    $this->lp_view_session_id = $session_id;
238
                    $this->lp_view_id = Database::insert($lp_table, $params);
239
                    if (!empty($this->lp_view_id)) {
240
                        $sql = "UPDATE $lp_table SET id = iid
241
                                WHERE iid = ".$this->lp_view_id;
242
                        Database::query($sql);
243
                    }
244
                }
245
246
                // Initialise items.
247
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
248
                $sql = "SELECT * FROM $lp_item_table
249
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
250
                        ORDER BY parent_item_id, display_order";
251
                $res = Database::query($sql);
252
253
                if ($debug) {
254
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
255
                    error_log('-- Start while--');
256
                }
257
258
                $lp_item_id_list = [];
259
                while ($row = Database::fetch_array($res)) {
260
                    $lp_item_id_list[] = $row['iid'];
261
                    switch ($this->type) {
262
                        case 3: //aicc
263
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
264
                            if (is_object($oItem)) {
265
                                $my_item_id = $oItem->get_id();
266
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
267
                                $oItem->set_prevent_reinit($this->prevent_reinit);
268
                                // Don't use reference here as the next loop will make the pointed object change.
269
                                $this->items[$my_item_id] = $oItem;
270
                                $this->refs_list[$oItem->ref] = $my_item_id;
271
                                if ($debug) {
272
                                    error_log(
273
                                        'learnpath::__construct() - '.
274
                                        'aicc object with id '.$my_item_id.
275
                                        ' set in items[]',
276
                                        0
277
                                    );
278
                                }
279
                            }
280
                            break;
281
                        case 2:
282
                            $oItem = new scormItem('db', $row['iid'], $course_id);
283
                            if (is_object($oItem)) {
284
                                $my_item_id = $oItem->get_id();
285
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
286
                                $oItem->set_prevent_reinit($this->prevent_reinit);
287
                                // Don't use reference here as the next loop will make the pointed object change.
288
                                $this->items[$my_item_id] = $oItem;
289
                                $this->refs_list[$oItem->ref] = $my_item_id;
290
                                if ($debug) {
291
                                    error_log('object with id '.$my_item_id.' set in items[]');
292
                                }
293
                            }
294
                            break;
295
                        case 1:
296
                        default:
297
                            if ($debug) {
298
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
299
                            }
300
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
301
302
                            if ($debug) {
303
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
304
                            }
305
                            if (is_object($oItem)) {
306
                                $my_item_id = $oItem->get_id();
307
                                // Moved down to when we are sure the item_view exists.
308
                                //$oItem->set_lp_view($this->lp_view_id);
309
                                $oItem->set_prevent_reinit($this->prevent_reinit);
310
                                // Don't use reference here as the next loop will make the pointed object change.
311
                                $this->items[$my_item_id] = $oItem;
312
                                $this->refs_list[$my_item_id] = $my_item_id;
313
                                if ($debug) {
314
                                    error_log(
315
                                        'learnpath::__construct() '.__LINE__.
316
                                        ' - object with id '.$my_item_id.' set in items[]'
317
                                    );
318
                                }
319
                            }
320
                            break;
321
                    }
322
323
                    // Setting the object level with variable $this->items[$i][parent]
324
                    foreach ($this->items as $itemLPObject) {
325
                        $level = self::get_level_for_item(
326
                            $this->items,
327
                            $itemLPObject->db_id
328
                        );
329
                        $itemLPObject->level = $level;
330
                    }
331
332
                    // Setting the view in the item object.
333
                    if (is_object($this->items[$row['iid']])) {
334
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
335
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
336
                            $this->items[$row['iid']]->current_start_time = 0;
337
                            $this->items[$row['iid']]->current_stop_time = 0;
338
                        }
339
                    }
340
                }
341
342
                if ($debug) {
343
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
344
                }
345
346
                if (!empty($lp_item_id_list)) {
347
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
348
                    if (!empty($lp_item_id_list_to_string)) {
349
                        // Get last viewing vars.
350
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
351
                        // This query should only return one or zero result.
352
                        $sql = "SELECT lp_item_id, status
353
                                FROM $itemViewTable
354
                                WHERE
355
                                    c_id = $course_id AND
356
                                    lp_view_id = ".$this->lp_view_id." AND
357
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
358
                                ORDER BY view_count DESC ";
359
360
                        if ($debug) {
361
                            error_log(
362
                                'learnpath::__construct() - Selecting item_views: '.$sql,
363
                                0
364
                            );
365
                        }
366
367
                        $status_list = [];
368
                        $res = Database::query($sql);
369
                        while ($row = Database:: fetch_array($res)) {
370
                            $status_list[$row['lp_item_id']] = $row['status'];
371
                        }
372
373
                        foreach ($lp_item_id_list as $item_id) {
374
                            if (isset($status_list[$item_id])) {
375
                                $status = $status_list[$item_id];
376
                                if (is_object($this->items[$item_id])) {
377
                                    $this->items[$item_id]->set_status($status);
378
                                    if (empty($status)) {
379
                                        $this->items[$item_id]->set_status(
380
                                            $this->default_status
381
                                        );
382
                                    }
383
                                }
384
                            } else {
385
                                if (!api_is_invitee()) {
386
                                    if (is_object($this->items[$item_id])) {
387
                                        $this->items[$item_id]->set_status(
388
                                            $this->default_status
389
                                        );
390
                                    }
391
392
                                    if (!empty($this->lp_view_id)) {
393
                                        // Add that row to the lp_item_view table so that
394
                                        // we have something to show in the stats page.
395
                                        $params = [
396
                                            'c_id' => $course_id,
397
                                            'lp_item_id' => $item_id,
398
                                            'lp_view_id' => $this->lp_view_id,
399
                                            'view_count' => 1,
400
                                            'status' => 'not attempted',
401
                                            'start_time' => time(),
402
                                            'total_time' => 0,
403
                                            'score' => 0,
404
                                        ];
405
                                        $insertId = Database::insert($itemViewTable, $params);
406
407
                                        if ($insertId) {
408
                                            $sql = "UPDATE $itemViewTable SET id = iid
409
                                                    WHERE iid = $insertId";
410
                                            Database::query($sql);
411
                                        }
412
413
                                        $this->items[$item_id]->set_lp_view(
414
                                            $this->lp_view_id,
415
                                            $course_id
416
                                        );
417
                                    }
418
                                }
419
                            }
420
                        }
421
                    }
422
                }
423
424
                $this->ordered_items = self::get_flat_ordered_items_list(
425
                    $this->get_id(),
426
                    0,
427
                    $course_id
428
                );
429
                $this->max_ordered_items = 0;
430
                foreach ($this->ordered_items as $index => $dummy) {
431
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
432
                        $this->max_ordered_items = $index;
433
                    }
434
                }
435
                // TODO: Define the current item better.
436
                $this->first();
437
                if ($debug) {
438
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
439
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
440
                }
441
            } else {
442
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
443
            }
444
        }
445
    }
446
447
    /**
448
     * @return string
449
     */
450
    public function getCourseCode()
451
    {
452
        return $this->cc;
453
    }
454
455
    /**
456
     * @return int
457
     */
458
    public function get_course_int_id()
459
    {
460
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
461
    }
462
463
    /**
464
     * @param $course_id
465
     *
466
     * @return int
467
     */
468
    public function set_course_int_id($course_id)
469
    {
470
        return $this->course_int_id = (int) $course_id;
471
    }
472
473
    /**
474
     * Function rewritten based on old_add_item() from Yannick Warnier.
475
     * Due the fact that users can decide where the item should come, I had to overlook this function and
476
     * I found it better to rewrite it. Old function is still available.
477
     * Added also the possibility to add a description.
478
     *
479
     * @param int    $parent
480
     * @param int    $previous
481
     * @param string $type
482
     * @param int    $id               resource ID (ref)
483
     * @param string $title
484
     * @param string $description
485
     * @param int    $prerequisites
486
     * @param int    $max_time_allowed
487
     * @param int    $userId
488
     *
489
     * @return int
490
     */
491
    public function add_item(
492
        $parent,
493
        $previous,
494
        $type = 'dir',
495
        $id,
496
        $title,
497
        $description,
498
        $prerequisites = 0,
499
        $max_time_allowed = 0,
500
        $userId = 0
501
    ) {
502
        $course_id = $this->course_info['real_id'];
503
        if ($this->debug > 0) {
504
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
505
        }
506
        if (empty($course_id)) {
507
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
508
            $this->course_info = api_get_course_info($this->cc);
509
            $course_id = $this->course_info['real_id'];
510
        }
511
        $userId = empty($userId) ? api_get_user_id() : $userId;
512
        $sessionId = api_get_session_id();
513
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
514
        $_course = $this->course_info;
515
        $parent = (int) $parent;
516
        $previous = (int) $previous;
517
        $id = (int) $id;
518
        $max_time_allowed = htmlentities($max_time_allowed);
519
        if (empty($max_time_allowed)) {
520
            $max_time_allowed = 0;
521
        }
522
        $sql = "SELECT COUNT(iid) AS num
523
                FROM $tbl_lp_item
524
                WHERE
525
                    c_id = $course_id AND
526
                    lp_id = ".$this->get_id()." AND
527
                    parent_item_id = ".$parent;
528
529
        $res_count = Database::query($sql);
530
        $row = Database::fetch_array($res_count);
531
        $num = $row['num'];
532
533
        $tmp_previous = 0;
534
        $display_order = 0;
535
        $next = 0;
536
        if ($num > 0) {
537
            if (empty($previous)) {
538
                $sql = "SELECT iid, next_item_id, display_order
539
                        FROM $tbl_lp_item
540
                        WHERE
541
                            c_id = $course_id AND
542
                            lp_id = ".$this->get_id()." AND
543
                            parent_item_id = $parent AND
544
                            previous_item_id = 0 OR
545
                            previous_item_id = $parent";
546
                $result = Database::query($sql);
547
                $row = Database::fetch_array($result);
548
                if ($row) {
549
                    $next = $row['iid'];
550
                }
551
            } else {
552
                $previous = (int) $previous;
553
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
554
						FROM $tbl_lp_item
555
                        WHERE
556
                            c_id = $course_id AND
557
                            lp_id = ".$this->get_id()." AND
558
                            id = $previous";
559
                $result = Database::query($sql);
560
                $row = Database::fetch_array($result);
561
                if ($row) {
562
                    $tmp_previous = $row['iid'];
563
                    $next = $row['next_item_id'];
564
                    $display_order = $row['display_order'];
565
                }
566
            }
567
        }
568
569
        $id = (int) $id;
570
        $typeCleaned = Database::escape_string($type);
571
        $max_score = 100;
572
        if ($type === 'quiz') {
573
            $sql = 'SELECT SUM(ponderation)
574
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
575
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
576
                    ON
577
                        quiz_question.id = quiz_rel_question.question_id AND
578
                        quiz_question.c_id = quiz_rel_question.c_id
579
                    WHERE
580
                        quiz_rel_question.exercice_id = '.$id." AND
581
                        quiz_question.c_id = $course_id AND
582
                        quiz_rel_question.c_id = $course_id ";
583
            $rsQuiz = Database::query($sql);
584
            $max_score = Database::result($rsQuiz, 0, 0);
585
586
            // Disabling the exercise if we add it inside a LP
587
            $exercise = new Exercise($course_id);
588
            $exercise->read($id);
589
            $exercise->disable();
590
            $exercise->save();
591
        }
592
593
        $params = [
594
            'c_id' => $course_id,
595
            'lp_id' => $this->get_id(),
596
            'item_type' => $typeCleaned,
597
            'ref' => '',
598
            'title' => $title,
599
            'description' => $description,
600
            'path' => $id,
601
            'max_score' => $max_score,
602
            'parent_item_id' => $parent,
603
            'previous_item_id' => $previous,
604
            'next_item_id' => (int) $next,
605
            'display_order' => $display_order + 1,
606
            'prerequisite' => $prerequisites,
607
            'max_time_allowed' => $max_time_allowed,
608
            'min_score' => 0,
609
            'launch_data' => '',
610
        ];
611
612
        if ($prerequisites != 0) {
613
            $params['prerequisite'] = $prerequisites;
614
        }
615
616
        $new_item_id = Database::insert($tbl_lp_item, $params);
617
        if ($new_item_id) {
618
            if ($this->debug > 2) {
619
                error_log('Inserting dir/chapter: '.$new_item_id, 0);
620
            }
621
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
622
            Database::query($sql);
623
624
            if (!empty($next)) {
625
                $sql = "UPDATE $tbl_lp_item
626
                        SET previous_item_id = $new_item_id 
627
                        WHERE c_id = $course_id AND id = $next";
628
                Database::query($sql);
629
            }
630
631
            // Update the item that should be before the new item.
632
            if (!empty($tmp_previous)) {
633
                $sql = "UPDATE $tbl_lp_item
634
                        SET next_item_id = $new_item_id
635
                        WHERE c_id = $course_id AND id = $tmp_previous";
636
                Database::query($sql);
637
            }
638
639
            // Update all the items after the new item.
640
            $sql = "UPDATE $tbl_lp_item
641
                        SET display_order = display_order + 1
642
                    WHERE
643
                        c_id = $course_id AND
644
                        lp_id = ".$this->get_id()." AND
645
                        iid <> $new_item_id AND
646
                        parent_item_id = $parent AND
647
                        display_order > $display_order";
648
            Database::query($sql);
649
650
            // Update the item that should come after the new item.
651
            $sql = "UPDATE $tbl_lp_item
652
                    SET ref = $new_item_id
653
                    WHERE c_id = $course_id AND iid = $new_item_id";
654
            Database::query($sql);
655
656
            // Upload audio.
657
            if (!empty($_FILES['mp3']['name'])) {
658
                // Create the audio folder if it does not exist yet.
659
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
660
                if (!is_dir($filepath.'audio')) {
661
                    mkdir(
662
                        $filepath.'audio',
663
                        api_get_permissions_for_new_directories()
664
                    );
665
                    $audio_id = add_document(
666
                        $_course,
667
                        '/audio',
668
                        'folder',
669
                        0,
670
                        'audio',
671
                        '',
672
                        0,
673
                        true,
674
                        null,
675
                        $sessionId,
676
                        $userId
677
                    );
678
                    api_item_property_update(
679
                        $_course,
680
                        TOOL_DOCUMENT,
681
                        $audio_id,
682
                        'FolderCreated',
683
                        $userId,
684
                        null,
685
                        null,
686
                        null,
687
                        null,
688
                        $sessionId
689
                    );
690
                    api_item_property_update(
691
                        $_course,
692
                        TOOL_DOCUMENT,
693
                        $audio_id,
694
                        'invisible',
695
                        $userId,
696
                        null,
697
                        null,
698
                        null,
699
                        null,
700
                        $sessionId
701
                    );
702
                }
703
704
                $file_path = handle_uploaded_document(
705
                    $_course,
706
                    $_FILES['mp3'],
707
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
708
                    '/audio',
709
                    $userId,
710
                    '',
711
                    '',
712
                    '',
713
                    '',
714
                    false
715
                );
716
717
                // Getting the filename only.
718
                $file_components = explode('/', $file_path);
719
                $file = $file_components[count($file_components) - 1];
720
721
                // Store the mp3 file in the lp_item table.
722
                $sql = "UPDATE $tbl_lp_item SET
723
                          audio = '".Database::escape_string($file)."'
724
                        WHERE iid = '".intval($new_item_id)."'";
725
                Database::query($sql);
726
            }
727
        }
728
729
        return $new_item_id;
730
    }
731
732
    /**
733
     * Static admin function allowing addition of a learnpath to a course.
734
     *
735
     * @param string $courseCode
736
     * @param string $name
737
     * @param string $description
738
     * @param string $learnpath
739
     * @param string $origin
740
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
741
     * @param string $publicated_on
742
     * @param string $expired_on
743
     * @param int    $categoryId
744
     * @param int    $userId
745
     *
746
     * @return int The new learnpath ID on success, 0 on failure
747
     */
748
    public static function add_lp(
749
        $courseCode,
750
        $name,
751
        $description = '',
752
        $learnpath = 'guess',
753
        $origin = 'zip',
754
        $zipname = '',
755
        $publicated_on = '',
756
        $expired_on = '',
757
        $categoryId = 0,
758
        $userId = 0
759
    ) {
760
        global $charset;
761
762
        if (!empty($courseCode)) {
763
            $courseInfo = api_get_course_info($courseCode);
764
            $course_id = $courseInfo['real_id'];
765
        } else {
766
            $course_id = api_get_course_int_id();
767
            $courseInfo = api_get_course_info();
768
        }
769
770
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
771
        // Check course code exists.
772
        // Check lp_name doesn't exist, otherwise append something.
773
        $i = 0;
774
        $name = Database::escape_string($name);
775
        $categoryId = (int) $categoryId;
776
777
        // Session id.
778
        $session_id = api_get_session_id();
779
        $userId = empty($userId) ? api_get_user_id() : $userId;
780
        $check_name = "SELECT * FROM $tbl_lp
781
                       WHERE c_id = $course_id AND name = '$name'";
782
783
        $res_name = Database::query($check_name);
784
785
        if (empty($publicated_on)) {
786
            $publicated_on = null;
787
        } else {
788
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
789
        }
790
791
        if (empty($expired_on)) {
792
            $expired_on = null;
793
        } else {
794
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
795
        }
796
797
        while (Database::num_rows($res_name)) {
798
            // There is already one such name, update the current one a bit.
799
            $i++;
800
            $name = $name.' - '.$i;
801
            $check_name = "SELECT * FROM $tbl_lp 
802
                           WHERE c_id = $course_id AND name = '$name'";
803
            $res_name = Database::query($check_name);
804
        }
805
        // New name does not exist yet; keep it.
806
        // Escape description.
807
        // Kevin: added htmlentities().
808
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
809
        $type = 1;
810
        switch ($learnpath) {
811
            case 'guess':
812
                break;
813
            case 'dokeos':
814
            case 'chamilo':
815
                $type = 1;
816
                break;
817
            case 'aicc':
818
                break;
819
        }
820
821
        switch ($origin) {
822
            case 'zip':
823
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
824
                break;
825
            case 'manual':
826
            default:
827
                $get_max = "SELECT MAX(display_order) 
828
                            FROM $tbl_lp WHERE c_id = $course_id";
829
                $res_max = Database::query($get_max);
830
                if (Database::num_rows($res_max) < 1) {
831
                    $dsp = 1;
832
                } else {
833
                    $row = Database::fetch_array($res_max);
834
                    $dsp = $row[0] + 1;
835
                }
836
837
                $params = [
838
                    'c_id' => $course_id,
839
                    'lp_type' => $type,
840
                    'name' => $name,
841
                    'description' => $description,
842
                    'path' => '',
843
                    'default_view_mod' => 'embedded',
844
                    'default_encoding' => 'UTF-8',
845
                    'display_order' => $dsp,
846
                    'content_maker' => 'Chamilo',
847
                    'content_local' => 'local',
848
                    'js_lib' => '',
849
                    'session_id' => $session_id,
850
                    'created_on' => api_get_utc_datetime(),
851
                    'modified_on' => api_get_utc_datetime(),
852
                    'publicated_on' => $publicated_on,
853
                    'expired_on' => $expired_on,
854
                    'category_id' => $categoryId,
855
                    'force_commit' => 0,
856
                    'content_license' => '',
857
                    'debug' => 0,
858
                    'theme' => '',
859
                    'preview_image' => '',
860
                    'author' => '',
861
                    'prerequisite' => 0,
862
                    'hide_toc_frame' => 0,
863
                    'seriousgame_mode' => 0,
864
                    'autolaunch' => 0,
865
                    'max_attempts' => 0,
866
                    'subscribe_users' => 0,
867
                    'accumulate_scorm_time' => 1,
868
                ];
869
                $id = Database::insert($tbl_lp, $params);
870
871
                if ($id > 0) {
872
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
873
                    Database::query($sql);
874
875
                    // Insert into item_property.
876
                    api_item_property_update(
877
                        $courseInfo,
878
                        TOOL_LEARNPATH,
879
                        $id,
880
                        'LearnpathAdded',
881
                        $userId
882
                    );
883
                    api_set_default_visibility(
884
                        $id,
885
                        TOOL_LEARNPATH,
886
                        0,
887
                        $courseInfo,
888
                        $session_id,
889
                        $userId
890
                    );
891
892
                    return $id;
893
                }
894
                break;
895
        }
896
    }
897
898
    /**
899
     * Auto completes the parents of an item in case it's been completed or passed.
900
     *
901
     * @param int $item Optional ID of the item from which to look for parents
902
     */
903
    public function autocomplete_parents($item)
904
    {
905
        $debug = $this->debug;
906
907
        if ($debug) {
908
            error_log('Learnpath::autocomplete_parents()');
909
        }
910
911
        if (empty($item)) {
912
            $item = $this->current;
913
        }
914
915
        $currentItem = $this->getItem($item);
916
        if ($currentItem) {
917
            $parent_id = $currentItem->get_parent();
918
            $parent = $this->getItem($parent_id);
919
            if ($parent) {
920
                // if $item points to an object and there is a parent.
921
                if ($debug) {
922
                    error_log(
923
                        'Autocompleting parent of item '.$item.' '.
924
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
925
                        0
926
                    );
927
                }
928
929
                // New experiment including failed and browsed in completed status.
930
                //$current_status = $currentItem->get_status();
931
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
932
                // Fixes chapter auto complete
933
                if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
934
                    // If the current item is completed or passes or succeeded.
935
                    $updateParentStatus = true;
936
                    if ($debug) {
937
                        error_log('Status of current item is alright');
938
                    }
939
940
                    foreach ($parent->get_children() as $childItemId) {
941
                        $childItem = $this->getItem($childItemId);
942
943
                        // If children was not set try to get the info
944
                        if (empty($childItem->db_item_view_id)) {
945
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
946
                        }
947
948
                        // Check all his brothers (parent's children) for completion status.
949
                        if ($childItemId != $item) {
950
                            if ($debug) {
951
                                error_log(
952
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
953
                                    0
954
                                );
955
                            }
956
                            // Trying completing parents of failed and browsed items as well.
957
                            if ($childItem->status_is(
958
                                [
959
                                    'completed',
960
                                    'passed',
961
                                    'succeeded',
962
                                    'browsed',
963
                                    'failed',
964
                                ]
965
                            )
966
                            ) {
967
                                // Keep completion status to true.
968
                                continue;
969
                            } else {
970
                                if ($debug > 2) {
971
                                    error_log(
972
                                        '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,
973
                                        0
974
                                    );
975
                                }
976
                                $updateParentStatus = false;
977
                                break;
978
                            }
979
                        }
980
                    }
981
982
                    if ($updateParentStatus) {
983
                        // If all the children were completed:
984
                        $parent->set_status('completed');
985
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
986
                        // Force the status to "completed"
987
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
988
                        $this->update_queue[$parent->get_id()] = 'completed';
989
                        if ($debug) {
990
                            error_log(
991
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
992
                                print_r($this->update_queue, 1),
993
                                0
994
                            );
995
                        }
996
                        // Recursive call.
997
                        $this->autocomplete_parents($parent->get_id());
998
                    }
999
                }
1000
            } else {
1001
                if ($debug) {
1002
                    error_log("Parent #$parent_id does not exists");
1003
                }
1004
            }
1005
        } else {
1006
            if ($debug) {
1007
                error_log("#$item is an item that doesn't have parents");
1008
            }
1009
        }
1010
    }
1011
1012
    /**
1013
     * Closes the current resource.
1014
     *
1015
     * Stops the timer
1016
     * Saves into the database if required
1017
     * Clears the current resource data from this object
1018
     *
1019
     * @return bool True on success, false on failure
1020
     */
1021
    public function close()
1022
    {
1023
        if ($this->debug > 0) {
1024
            error_log('In learnpath::close()', 0);
1025
        }
1026
        if (empty($this->lp_id)) {
1027
            $this->error = 'Trying to close this learnpath but no ID is set';
1028
1029
            return false;
1030
        }
1031
        $this->current_time_stop = time();
1032
        $this->ordered_items = [];
1033
        $this->index = 0;
1034
        unset($this->lp_id);
1035
        //unset other stuff
1036
        return true;
1037
    }
1038
1039
    /**
1040
     * Static admin function allowing removal of a learnpath.
1041
     *
1042
     * @param array  $courseInfo
1043
     * @param int    $id         Learnpath ID
1044
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1045
     *
1046
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1047
     */
1048
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1049
    {
1050
        $course_id = api_get_course_int_id();
1051
        if (!empty($courseInfo)) {
1052
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1053
        }
1054
1055
        // TODO: Implement a way of getting this to work when the current object is not set.
1056
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1057
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1058
        if (!empty($id) && ($id != $this->lp_id)) {
1059
            return false;
1060
        }
1061
1062
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1063
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1064
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1065
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1066
1067
        // Delete lp item id.
1068
        foreach ($this->items as $lpItemId => $dummy) {
1069
            $sql = "DELETE FROM $lp_item_view
1070
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1071
            Database::query($sql);
1072
        }
1073
1074
        // Proposed by Christophe (nickname: clefevre)
1075
        $sql = "DELETE FROM $lp_item
1076
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1077
        Database::query($sql);
1078
1079
        $sql = "DELETE FROM $lp_view 
1080
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1081
        Database::query($sql);
1082
1083
        self::toggle_publish($this->lp_id, 'i');
1084
1085
        if ($this->type == 2 || $this->type == 3) {
1086
            // This is a scorm learning path, delete the files as well.
1087
            $sql = "SELECT path FROM $lp
1088
                    WHERE iid = ".$this->lp_id;
1089
            $res = Database::query($sql);
1090
            if (Database::num_rows($res) > 0) {
1091
                $row = Database::fetch_array($res);
1092
                $path = $row['path'];
1093
                $sql = "SELECT id FROM $lp
1094
                        WHERE 
1095
                            c_id = $course_id AND
1096
                            path = '$path' AND 
1097
                            iid != ".$this->lp_id;
1098
                $res = Database::query($sql);
1099
                if (Database::num_rows($res) > 0) {
1100
                    // Another learning path uses this directory, so don't delete it.
1101
                    if ($this->debug > 2) {
1102
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1103
                    }
1104
                } else {
1105
                    // No other LP uses that directory, delete it.
1106
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1107
                    // The absolute system path for this course.
1108
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1109
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1110
                        if ($this->debug > 2) {
1111
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1112
                        }
1113
                        // Proposed by Christophe (clefevre).
1114
                        if (strcmp(substr($path, -2), "/.") == 0) {
1115
                            $path = substr($path, 0, -1); // Remove "." at the end.
1116
                        }
1117
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1118
                        rmdirr($course_scorm_dir.$path);
1119
                    }
1120
                }
1121
            }
1122
        }
1123
1124
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1125
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1126
        // Delete tools
1127
        $sql = "DELETE FROM $tbl_tool
1128
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1129
        Database::query($sql);
1130
1131
        $sql = "DELETE FROM $lp 
1132
                WHERE iid = ".$this->lp_id;
1133
        Database::query($sql);
1134
        // Updates the display order of all lps.
1135
        $this->update_display_order();
1136
1137
        api_item_property_update(
1138
            api_get_course_info(),
1139
            TOOL_LEARNPATH,
1140
            $this->lp_id,
1141
            'delete',
1142
            api_get_user_id()
1143
        );
1144
1145
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1146
            api_get_course_id(),
1147
            4,
1148
            $id,
1149
            api_get_session_id()
1150
        );
1151
1152
        if ($link_info !== false) {
1153
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1154
        }
1155
1156
        if (api_get_setting('search_enabled') == 'true') {
1157
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1158
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1159
        }
1160
    }
1161
1162
    /**
1163
     * Removes all the children of one item - dangerous!
1164
     *
1165
     * @param int $id Element ID of which children have to be removed
1166
     *
1167
     * @return int Total number of children removed
1168
     */
1169
    public function delete_children_items($id)
1170
    {
1171
        $course_id = $this->course_info['real_id'];
1172
        if ($this->debug > 0) {
1173
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1174
        }
1175
        $num = 0;
1176
        if (empty($id) || $id != strval(intval($id))) {
1177
            return false;
1178
        }
1179
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1180
        $sql = "SELECT * FROM $lp_item 
1181
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1182
        $res = Database::query($sql);
1183
        while ($row = Database::fetch_array($res)) {
1184
            $num += $this->delete_children_items($row['iid']);
1185
            $sql = "DELETE FROM $lp_item 
1186
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1187
            Database::query($sql);
1188
            $num++;
1189
        }
1190
1191
        return $num;
1192
    }
1193
1194
    /**
1195
     * Removes an item from the current learnpath.
1196
     *
1197
     * @param int $id Elem ID (0 if first)
1198
     *
1199
     * @return int Number of elements moved
1200
     *
1201
     * @todo implement resource removal
1202
     */
1203
    public function delete_item($id)
1204
    {
1205
        $course_id = api_get_course_int_id();
1206
        if ($this->debug > 0) {
1207
            error_log('In learnpath::delete_item()', 0);
1208
        }
1209
        // TODO: Implement the resource removal.
1210
        if (empty($id) || $id != strval(intval($id))) {
1211
            return false;
1212
        }
1213
        // First select item to get previous, next, and display order.
1214
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1215
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1216
        $res_sel = Database::query($sql_sel);
1217
        if (Database::num_rows($res_sel) < 1) {
1218
            return false;
1219
        }
1220
        $row = Database::fetch_array($res_sel);
1221
        $previous = $row['previous_item_id'];
1222
        $next = $row['next_item_id'];
1223
        $display = $row['display_order'];
1224
        $parent = $row['parent_item_id'];
1225
        $lp = $row['lp_id'];
1226
        // Delete children items.
1227
        $num = $this->delete_children_items($id);
1228
        if ($this->debug > 2) {
1229
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1230
        }
1231
        // Now delete the item.
1232
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1233
        if ($this->debug > 2) {
1234
            error_log('Deleting item: '.$sql_del, 0);
1235
        }
1236
        Database::query($sql_del);
1237
        // Now update surrounding items.
1238
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1239
                    WHERE iid = $previous";
1240
        Database::query($sql_upd);
1241
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1242
                    WHERE iid = $next";
1243
        Database::query($sql_upd);
1244
        // Now update all following items with new display order.
1245
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1246
                    WHERE 
1247
                        c_id = $course_id AND 
1248
                        lp_id = $lp AND 
1249
                        parent_item_id = $parent AND 
1250
                        display_order > $display";
1251
        Database::query($sql_all);
1252
1253
        //Removing prerequisites since the item will not longer exist
1254
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1255
                    WHERE c_id = $course_id AND prerequisite = $id";
1256
        Database::query($sql_all);
1257
1258
        // Remove from search engine if enabled.
1259
        if (api_get_setting('search_enabled') === 'true') {
1260
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1261
            $sql = 'SELECT * FROM %s 
1262
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1263
                    LIMIT 1';
1264
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1265
            $res = Database::query($sql);
1266
            if (Database::num_rows($res) > 0) {
1267
                $row2 = Database::fetch_array($res);
1268
                $di = new ChamiloIndexer();
1269
                $di->remove_document($row2['search_did']);
1270
            }
1271
            $sql = 'DELETE FROM %s 
1272
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1273
                    LIMIT 1';
1274
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1275
            Database::query($sql);
1276
        }
1277
    }
1278
1279
    /**
1280
     * Updates an item's content in place.
1281
     *
1282
     * @param int    $id               Element ID
1283
     * @param int    $parent           Parent item ID
1284
     * @param int    $previous         Previous item ID
1285
     * @param string $title            Item title
1286
     * @param string $description      Item description
1287
     * @param string $prerequisites    Prerequisites (optional)
1288
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1289
     * @param int    $max_time_allowed
1290
     * @param string $url
1291
     *
1292
     * @return bool True on success, false on error
1293
     */
1294
    public function edit_item(
1295
        $id,
1296
        $parent,
1297
        $previous,
1298
        $title,
1299
        $description,
1300
        $prerequisites = '0',
1301
        $audio = [],
1302
        $max_time_allowed = 0,
1303
        $url = ''
1304
    ) {
1305
        $course_id = api_get_course_int_id();
1306
        $_course = api_get_course_info();
1307
1308
        if ($this->debug > 0) {
1309
            error_log('In learnpath::edit_item()', 0);
1310
        }
1311
        if (empty($max_time_allowed)) {
1312
            $max_time_allowed = 0;
1313
        }
1314
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1315
            return false;
1316
        }
1317
1318
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1319
        $sql = "SELECT * FROM $tbl_lp_item 
1320
                WHERE iid = $id";
1321
        $res_select = Database::query($sql);
1322
        $row_select = Database::fetch_array($res_select);
1323
        $audio_update_sql = '';
1324
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1325
            // Create the audio folder if it does not exist yet.
1326
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1327
            if (!is_dir($filepath.'audio')) {
1328
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1329
                $audio_id = add_document(
1330
                    $_course,
1331
                    '/audio',
1332
                    'folder',
1333
                    0,
1334
                    'audio'
1335
                );
1336
                api_item_property_update(
1337
                    $_course,
1338
                    TOOL_DOCUMENT,
1339
                    $audio_id,
1340
                    'FolderCreated',
1341
                    api_get_user_id(),
1342
                    null,
1343
                    null,
1344
                    null,
1345
                    null,
1346
                    api_get_session_id()
1347
                );
1348
                api_item_property_update(
1349
                    $_course,
1350
                    TOOL_DOCUMENT,
1351
                    $audio_id,
1352
                    'invisible',
1353
                    api_get_user_id(),
1354
                    null,
1355
                    null,
1356
                    null,
1357
                    null,
1358
                    api_get_session_id()
1359
                );
1360
            }
1361
1362
            // Upload file in documents.
1363
            $pi = pathinfo($audio['name']);
1364
            if ($pi['extension'] == 'mp3') {
1365
                $c_det = api_get_course_info($this->cc);
1366
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1367
                $path = handle_uploaded_document(
1368
                    $c_det,
1369
                    $audio,
1370
                    $bp,
1371
                    '/audio',
1372
                    api_get_user_id(),
1373
                    0,
1374
                    null,
1375
                    0,
1376
                    'rename',
1377
                    false,
1378
                    0
1379
                );
1380
                $path = substr($path, 7);
1381
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1382
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1383
            }
1384
        }
1385
1386
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1387
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1388
1389
        // TODO: htmlspecialchars to be checked for encoding related problems.
1390
        if ($same_parent && $same_previous) {
1391
            // Only update title and description.
1392
            $sql = "UPDATE $tbl_lp_item
1393
                    SET title = '".Database::escape_string($title)."',
1394
                        prerequisite = '".$prerequisites."',
1395
                        description = '".Database::escape_string($description)."'
1396
                        ".$audio_update_sql.",
1397
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1398
                    WHERE iid = $id";
1399
            Database::query($sql);
1400
        } else {
1401
            $old_parent = $row_select['parent_item_id'];
1402
            $old_previous = $row_select['previous_item_id'];
1403
            $old_next = $row_select['next_item_id'];
1404
            $old_order = $row_select['display_order'];
1405
            $old_prerequisite = $row_select['prerequisite'];
1406
            $old_max_time_allowed = $row_select['max_time_allowed'];
1407
1408
            /* BEGIN -- virtually remove the current item id */
1409
            /* for the next and previous item it is like the current item doesn't exist anymore */
1410
            if ($old_previous != 0) {
1411
                // Next
1412
                $sql = "UPDATE $tbl_lp_item
1413
                        SET next_item_id = $old_next
1414
                        WHERE iid = $old_previous";
1415
                Database::query($sql);
1416
            }
1417
1418
            if (!empty($old_next)) {
1419
                // Previous
1420
                $sql = "UPDATE $tbl_lp_item
1421
                        SET previous_item_id = $old_previous
1422
                        WHERE iid = $old_next";
1423
                Database::query($sql);
1424
            }
1425
1426
            // display_order - 1 for every item with a display_order
1427
            // bigger then the display_order of the current item.
1428
            $sql = "UPDATE $tbl_lp_item
1429
                    SET display_order = display_order - 1
1430
                    WHERE
1431
                        c_id = $course_id AND
1432
                        display_order > $old_order AND
1433
                        lp_id = ".$this->lp_id." AND
1434
                        parent_item_id = $old_parent";
1435
            Database::query($sql);
1436
            /* END -- virtually remove the current item id */
1437
1438
            /* BEGIN -- update the current item id to his new location */
1439
            if ($previous == 0) {
1440
                // Select the data of the item that should come after the current item.
1441
                $sql = "SELECT id, display_order
1442
                        FROM $tbl_lp_item
1443
                        WHERE
1444
                            c_id = $course_id AND
1445
                            lp_id = ".$this->lp_id." AND
1446
                            parent_item_id = $parent AND
1447
                            previous_item_id = $previous";
1448
                $res_select_old = Database::query($sql);
1449
                $row_select_old = Database::fetch_array($res_select_old);
1450
1451
                // If the new parent didn't have children before.
1452
                if (Database::num_rows($res_select_old) == 0) {
1453
                    $new_next = 0;
1454
                    $new_order = 1;
1455
                } else {
1456
                    $new_next = $row_select_old['id'];
1457
                    $new_order = $row_select_old['display_order'];
1458
                }
1459
            } else {
1460
                // Select the data of the item that should come before the current item.
1461
                $sql = "SELECT next_item_id, display_order
1462
                        FROM $tbl_lp_item
1463
                        WHERE iid = $previous";
1464
                $res_select_old = Database::query($sql);
1465
                $row_select_old = Database::fetch_array($res_select_old);
1466
                $new_next = $row_select_old['next_item_id'];
1467
                $new_order = $row_select_old['display_order'] + 1;
1468
            }
1469
1470
            // TODO: htmlspecialchars to be checked for encoding related problems.
1471
            // Update the current item with the new data.
1472
            $sql = "UPDATE $tbl_lp_item
1473
                    SET
1474
                        title = '".Database::escape_string($title)."',
1475
                        description = '".Database::escape_string($description)."',
1476
                        parent_item_id = $parent,
1477
                        previous_item_id = $previous,
1478
                        next_item_id = $new_next,
1479
                        display_order = $new_order
1480
                        $audio_update_sql
1481
                    WHERE iid = $id";
1482
            Database::query($sql);
1483
1484
            if ($previous != 0) {
1485
                // Update the previous item's next_item_id.
1486
                $sql = "UPDATE $tbl_lp_item
1487
                        SET next_item_id = $id
1488
                        WHERE iid = $previous";
1489
                Database::query($sql);
1490
            }
1491
1492
            if (!empty($new_next)) {
1493
                // Update the next item's previous_item_id.
1494
                $sql = "UPDATE $tbl_lp_item
1495
                        SET previous_item_id = $id
1496
                        WHERE iid = $new_next";
1497
                Database::query($sql);
1498
            }
1499
1500
            if ($old_prerequisite != $prerequisites) {
1501
                $sql = "UPDATE $tbl_lp_item
1502
                        SET prerequisite = '$prerequisites'
1503
                        WHERE iid = $id";
1504
                Database::query($sql);
1505
            }
1506
1507
            if ($old_max_time_allowed != $max_time_allowed) {
1508
                // update max time allowed
1509
                $sql = "UPDATE $tbl_lp_item
1510
                        SET max_time_allowed = $max_time_allowed
1511
                        WHERE iid = $id";
1512
                Database::query($sql);
1513
            }
1514
1515
            // Update all the items with the same or a bigger display_order than the current item.
1516
            $sql = "UPDATE $tbl_lp_item
1517
                    SET display_order = display_order + 1
1518
                    WHERE
1519
                       c_id = $course_id AND
1520
                       lp_id = ".$this->get_id()." AND
1521
                       iid <> $id AND
1522
                       parent_item_id = $parent AND
1523
                       display_order >= $new_order";
1524
            Database::query($sql);
1525
        }
1526
1527
        if ($row_select['item_type'] == 'link') {
1528
            $link = new Link();
1529
            $linkId = $row_select['path'];
1530
            $link->updateLink($linkId, $url);
1531
        }
1532
    }
1533
1534
    /**
1535
     * Updates an item's prereq in place.
1536
     *
1537
     * @param int    $id              Element ID
1538
     * @param string $prerequisite_id Prerequisite Element ID
1539
     * @param int    $mastery_score   Prerequisite min score
1540
     * @param int    $max_score       Prerequisite max score
1541
     *
1542
     * @return bool True on success, false on error
1543
     */
1544
    public function edit_item_prereq(
1545
        $id,
1546
        $prerequisite_id,
1547
        $mastery_score = 0,
1548
        $max_score = 100
1549
    ) {
1550
        $course_id = api_get_course_int_id();
1551
        if ($this->debug > 0) {
1552
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1553
        }
1554
1555
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1556
            return false;
1557
        }
1558
1559
        $prerequisite_id = (int) $prerequisite_id;
1560
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1561
1562
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1563
            $mastery_score = 0;
1564
        }
1565
1566
        if (!is_numeric($max_score) || $max_score < 0) {
1567
            $max_score = 100;
1568
        }
1569
1570
        /*if ($mastery_score > $max_score) {
1571
            $max_score = $mastery_score;
1572
        }*/
1573
1574
        if (!is_numeric($prerequisite_id)) {
1575
            $prerequisite_id = 'NULL';
1576
        }
1577
1578
        $mastery_score = floatval($mastery_score);
1579
        $max_score = floatval($max_score);
1580
1581
        $sql = " UPDATE $tbl_lp_item
1582
                 SET
1583
                    prerequisite = $prerequisite_id ,
1584
                    prerequisite_min_score = $mastery_score ,
1585
                    prerequisite_max_score = $max_score
1586
                 WHERE iid = $id";
1587
        Database::query($sql);
1588
        // TODO: Update the item object (can be ignored for now because refreshed).
1589
        return true;
1590
    }
1591
1592
    /**
1593
     * Gets all the chapters belonging to the same parent as the item/chapter given
1594
     * Can also be called as abstract method.
1595
     *
1596
     * @param int $id Item ID
1597
     *
1598
     * @return array A list of all the "brother items" (or an empty array on failure)
1599
     */
1600
    public function getSiblingDirectories($id)
1601
    {
1602
        $course_id = api_get_course_int_id();
1603
        if ($this->debug > 0) {
1604
            error_log('In learnpath::getSiblingDirectories()', 0);
1605
        }
1606
1607
        if (empty($id) || $id != strval(intval($id))) {
1608
            return [];
1609
        }
1610
1611
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1612
        $sql_parent = "SELECT * FROM $lp_item
1613
                       WHERE iid = $id AND item_type='dir'";
1614
        $res_parent = Database::query($sql_parent);
1615
        if (Database::num_rows($res_parent) > 0) {
1616
            $row_parent = Database::fetch_array($res_parent);
1617
            $parent = $row_parent['parent_item_id'];
1618
            $sql = "SELECT * FROM $lp_item
1619
                    WHERE
1620
                        parent_item_id = $parent AND
1621
                        iid = $id AND
1622
                        item_type='dir'
1623
                    ORDER BY display_order";
1624
            $res_bros = Database::query($sql);
1625
1626
            $list = [];
1627
            while ($row_bro = Database::fetch_array($res_bros)) {
1628
                $list[] = $row_bro;
1629
            }
1630
1631
            return $list;
1632
        }
1633
1634
        return [];
1635
    }
1636
1637
    /**
1638
     * Gets all the items belonging to the same parent as the item given
1639
     * Can also be called as abstract method.
1640
     *
1641
     * @param int $id Item ID
1642
     *
1643
     * @return array A list of all the "brother items" (or an empty array on failure)
1644
     */
1645
    public function get_brother_items($id)
1646
    {
1647
        $course_id = api_get_course_int_id();
1648
        if ($this->debug > 0) {
1649
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1650
        }
1651
1652
        if (empty($id) || $id != strval(intval($id))) {
1653
            return [];
1654
        }
1655
1656
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1657
        $sql_parent = "SELECT * FROM $lp_item 
1658
                       WHERE iid = $id";
1659
        $res_parent = Database::query($sql_parent);
1660
        if (Database::num_rows($res_parent) > 0) {
1661
            $row_parent = Database::fetch_array($res_parent);
1662
            $parent = $row_parent['parent_item_id'];
1663
            $sql = "SELECT * FROM $lp_item 
1664
                    WHERE c_id = $course_id AND parent_item_id = $parent
1665
                    ORDER BY display_order";
1666
            $res_bros = Database::query($sql);
1667
            $list = [];
1668
            while ($row_bro = Database::fetch_array($res_bros)) {
1669
                $list[] = $row_bro;
1670
            }
1671
1672
            return $list;
1673
        }
1674
1675
        return [];
1676
    }
1677
1678
    /**
1679
     * Get the specific prefix index terms of this learning path.
1680
     *
1681
     * @param string $prefix
1682
     *
1683
     * @return array Array of terms
1684
     */
1685
    public function get_common_index_terms_by_prefix($prefix)
1686
    {
1687
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1688
        $terms = get_specific_field_values_list_by_prefix(
1689
            $prefix,
1690
            $this->cc,
1691
            TOOL_LEARNPATH,
1692
            $this->lp_id
1693
        );
1694
        $prefix_terms = [];
1695
        if (!empty($terms)) {
1696
            foreach ($terms as $term) {
1697
                $prefix_terms[] = $term['value'];
1698
            }
1699
        }
1700
1701
        return $prefix_terms;
1702
    }
1703
1704
    /**
1705
     * Gets the number of items currently completed.
1706
     *
1707
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1708
     *
1709
     * @return int The number of items currently completed
1710
     */
1711
    public function get_complete_items_count($failedStatusException = false)
1712
    {
1713
        if ($this->debug > 0) {
1714
            error_log('In learnpath::get_complete_items_count()', 0);
1715
        }
1716
        $i = 0;
1717
        $completedStatusList = [
1718
            'completed',
1719
            'passed',
1720
            'succeeded',
1721
            'browsed',
1722
        ];
1723
1724
        if (!$failedStatusException) {
1725
            $completedStatusList[] = 'failed';
1726
        }
1727
1728
        foreach ($this->items as $id => $dummy) {
1729
            // Trying failed and browsed considered "progressed" as well.
1730
            if ($this->items[$id]->status_is($completedStatusList) &&
1731
                $this->items[$id]->get_type() != 'dir'
1732
            ) {
1733
                $i++;
1734
            }
1735
        }
1736
1737
        return $i;
1738
    }
1739
1740
    /**
1741
     * Gets the current item ID.
1742
     *
1743
     * @return int The current learnpath item id
1744
     */
1745
    public function get_current_item_id()
1746
    {
1747
        $current = 0;
1748
        if ($this->debug > 0) {
1749
            error_log('In learnpath::get_current_item_id()', 0);
1750
        }
1751
        if (!empty($this->current)) {
1752
            $current = $this->current;
1753
        }
1754
        if ($this->debug > 2) {
1755
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1756
        }
1757
1758
        return $current;
1759
    }
1760
1761
    /**
1762
     * Force to get the first learnpath item id.
1763
     *
1764
     * @return int The current learnpath item id
1765
     */
1766
    public function get_first_item_id()
1767
    {
1768
        $current = 0;
1769
        if (is_array($this->ordered_items)) {
1770
            $current = $this->ordered_items[0];
1771
        }
1772
1773
        return $current;
1774
    }
1775
1776
    /**
1777
     * Gets the total number of items available for viewing in this SCORM.
1778
     *
1779
     * @return int The total number of items
1780
     */
1781
    public function get_total_items_count()
1782
    {
1783
        if ($this->debug > 0) {
1784
            error_log('In learnpath::get_total_items_count()', 0);
1785
        }
1786
1787
        return count($this->items);
1788
    }
1789
1790
    /**
1791
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1792
     *
1793
     * @return int The total no-chapters number of items
1794
     */
1795
    public function getTotalItemsCountWithoutDirs()
1796
    {
1797
        if ($this->debug > 0) {
1798
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1799
        }
1800
        $total = 0;
1801
        $typeListNotToCount = self::getChapterTypes();
1802
        foreach ($this->items as $temp2) {
1803
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1804
                $total++;
1805
            }
1806
        }
1807
1808
        return $total;
1809
    }
1810
1811
    /**
1812
     *  Sets the first element URL.
1813
     */
1814
    public function first()
1815
    {
1816
        if ($this->debug > 0) {
1817
            error_log('In learnpath::first()', 0);
1818
            error_log('$this->last_item_seen '.$this->last_item_seen);
1819
        }
1820
1821
        // Test if the last_item_seen exists and is not a dir.
1822
        if (count($this->ordered_items) == 0) {
1823
            $this->index = 0;
1824
        }
1825
1826
        if (!empty($this->last_item_seen) &&
1827
            !empty($this->items[$this->last_item_seen]) &&
1828
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1829
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1830
            //&& !$this->items[$this->last_item_seen]->is_done()
1831
        ) {
1832
            if ($this->debug > 2) {
1833
                error_log(
1834
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1835
                    $this->items[$this->last_item_seen]->get_type()
1836
                );
1837
            }
1838
            $index = -1;
1839
            foreach ($this->ordered_items as $myindex => $item_id) {
1840
                if ($item_id == $this->last_item_seen) {
1841
                    $index = $myindex;
1842
                    break;
1843
                }
1844
            }
1845
            if ($index == -1) {
1846
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1847
                if ($this->debug > 2) {
1848
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1849
                }
1850
1851
                return false;
1852
            } else {
1853
                $this->last = $this->last_item_seen;
1854
                $this->current = $this->last_item_seen;
1855
                $this->index = $index;
1856
            }
1857
        } else {
1858
            if ($this->debug > 2) {
1859
                error_log('In learnpath::first() - No last item seen', 0);
1860
            }
1861
            $index = 0;
1862
            // Loop through all ordered items and stop at the first item that is
1863
            // not a directory *and* that has not been completed yet.
1864
            while (!empty($this->ordered_items[$index]) &&
1865
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1866
                (
1867
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1868
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1869
                ) && $index < $this->max_ordered_items) {
1870
                $index++;
1871
            }
1872
1873
            $this->last = $this->current;
1874
            // current is
1875
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1876
            $this->index = $index;
1877
            if ($this->debug > 2) {
1878
                error_log('$index '.$index);
1879
                error_log('In learnpath::first() - No last item seen');
1880
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1881
            }
1882
        }
1883
        if ($this->debug > 2) {
1884
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1885
        }
1886
    }
1887
1888
    /**
1889
     * Gets the information about an item in a format usable as JavaScript to update
1890
     * the JS API by just printing this content into the <head> section of the message frame.
1891
     *
1892
     * @param int $item_id
1893
     *
1894
     * @return string
1895
     */
1896
    public function get_js_info($item_id = 0)
1897
    {
1898
        if ($this->debug > 0) {
1899
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1900
        }
1901
1902
        $info = '';
1903
        $item_id = intval($item_id);
1904
1905
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1906
            //if item is defined, return values from DB
1907
            $oItem = $this->items[$item_id];
1908
            $info .= '<script language="javascript">';
1909
            $info .= "top.set_score(".$oItem->get_score().");\n";
1910
            $info .= "top.set_max(".$oItem->get_max().");\n";
1911
            $info .= "top.set_min(".$oItem->get_min().");\n";
1912
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1913
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1914
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1915
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1916
            $info .= "top.set_flag_synchronized();";
1917
            $info .= '</script>';
1918
            if ($this->debug > 2) {
1919
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1920
            }
1921
1922
            return $info;
1923
        } else {
1924
            // If item_id is empty, just update to default SCORM data.
1925
            $info .= '<script language="javascript">';
1926
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1927
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1928
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1929
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1930
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1931
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1932
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1933
            $info .= "top.set_flag_synchronized();";
1934
            $info .= '</script>';
1935
            if ($this->debug > 2) {
1936
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1937
            }
1938
1939
            return $info;
1940
        }
1941
    }
1942
1943
    /**
1944
     * Gets the js library from the database.
1945
     *
1946
     * @return string The name of the javascript library to be used
1947
     */
1948
    public function get_js_lib()
1949
    {
1950
        $lib = '';
1951
        if (!empty($this->js_lib)) {
1952
            $lib = $this->js_lib;
1953
        }
1954
1955
        return $lib;
1956
    }
1957
1958
    /**
1959
     * Gets the learnpath database ID.
1960
     *
1961
     * @return int Learnpath ID in the lp table
1962
     */
1963
    public function get_id()
1964
    {
1965
        if (!empty($this->lp_id)) {
1966
            return $this->lp_id;
1967
        } else {
1968
            return 0;
1969
        }
1970
    }
1971
1972
    /**
1973
     * Gets the last element URL.
1974
     *
1975
     * @return string URL to load into the viewer
1976
     */
1977
    public function get_last()
1978
    {
1979
        if ($this->debug > 0) {
1980
            error_log('In learnpath::get_last()', 0);
1981
        }
1982
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1983
        if (count($this->ordered_items) > 0) {
1984
            $this->index = count($this->ordered_items) - 1;
1985
1986
            return $this->ordered_items[$this->index];
1987
        }
1988
1989
        return false;
1990
    }
1991
1992
    /**
1993
     * Gets the navigation bar for the learnpath display screen.
1994
     *
1995
     * @return string The HTML string to use as a navigation bar
1996
     */
1997
    public function get_navigation_bar($idBar = null, $display = null)
1998
    {
1999
        if ($this->debug > 0) {
2000
            error_log('In learnpath::get_navigation_bar()', 0);
2001
        }
2002
        if (empty($idBar)) {
2003
            $idBar = 'control-top';
2004
        }
2005
        $lpId = $this->lp_id;
2006
        $mycurrentitemid = $this->get_current_item_id();
2007
2008
        $reportingText = get_lang('Reporting');
2009
        $previousText = get_lang('ScormPrevious');
2010
        $nextText = get_lang('ScormNext');
2011
        $fullScreenText = get_lang('ScormExitFullScreen');
2012
2013
        $settings = api_get_configuration_value('lp_view_settings');
2014
        $display = isset($settings['display']) ? $settings['display'] : false;
2015
        $reportingIcon = '
2016
            <a class="icon-toolbar" 
2017
                id="stats_link"
2018
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
2019
                onclick="window.parent.API.save_asset(); return true;" 
2020
                target="content_name" title="'.$reportingText.'">
2021
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2022
            </a>';
2023
2024
        if (!empty($display)) {
2025
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2026
            if ($showReporting == false) {
2027
                $reportingIcon = '';
2028
            }
2029
        }
2030
2031
        $previousIcon = '
2032
            <a class="icon-toolbar" id="scorm-previous" href="#" 
2033
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2034
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2035
            </a>';
2036
2037
        $nextIcon = '
2038
            <a class="icon-toolbar" id="scorm-next" href="#" 
2039
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2040
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2041
            </a>';
2042
2043
        if ($this->mode == 'fullscreen') {
2044
            $navbar = '
2045
                  <span id="'.$idBar.'" class="buttons">
2046
                    '.$reportingIcon.'
2047
                    '.$previousIcon.'                    
2048
                    '.$nextIcon.'
2049
                    <a class="icon-toolbar" id="view-embedded" 
2050
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2051
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2052
                    </a>
2053
                  </span>';
2054
        } else {
2055
            $navbar = '
2056
            <span id="'.$idBar.'" class="buttons text-right">
2057
                '.$reportingIcon.'
2058
                '.$previousIcon.'
2059
                '.$nextIcon.'               
2060
            </span>';
2061
        }
2062
2063
        return $navbar;
2064
    }
2065
2066
    /**
2067
     * Gets the next resource in queue (url).
2068
     *
2069
     * @return string URL to load into the viewer
2070
     */
2071
    public function get_next_index()
2072
    {
2073
        if ($this->debug > 0) {
2074
            error_log('In learnpath::get_next_index()', 0);
2075
        }
2076
        // TODO
2077
        $index = $this->index;
2078
        $index++;
2079
        if ($this->debug > 2) {
2080
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2081
        }
2082
        while (
2083
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2084
            $index < $this->max_ordered_items
2085
        ) {
2086
            $index++;
2087
            if ($index == $this->max_ordered_items) {
2088
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2089
                    return $this->index;
2090
                } else {
2091
                    return $index;
2092
                }
2093
            }
2094
        }
2095
        if (empty($this->ordered_items[$index])) {
2096
            return $this->index;
2097
        }
2098
        if ($this->debug > 2) {
2099
            error_log('index is now '.$index, 0);
2100
        }
2101
2102
        return $index;
2103
    }
2104
2105
    /**
2106
     * Gets item_id for the next element.
2107
     *
2108
     * @return int Next item (DB) ID
2109
     */
2110
    public function get_next_item_id()
2111
    {
2112
        if ($this->debug > 0) {
2113
            error_log('In learnpath::get_next_item_id()', 0);
2114
        }
2115
        $new_index = $this->get_next_index();
2116
        if (!empty($new_index)) {
2117
            if (isset($this->ordered_items[$new_index])) {
2118
                if ($this->debug > 2) {
2119
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2120
                }
2121
2122
                return $this->ordered_items[$new_index];
2123
            }
2124
        }
2125
        if ($this->debug > 2) {
2126
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2127
        }
2128
2129
        return 0;
2130
    }
2131
2132
    /**
2133
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2134
     *
2135
     * Generally, the package provided is in the form of a zip file, so the function
2136
     * has been written to test a zip file. If not a zip, the function will return the
2137
     * default return value: ''
2138
     *
2139
     * @param string $file_path the path to the file
2140
     * @param string $file_name the original name of the file
2141
     *
2142
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2143
     */
2144
    public static function get_package_type($file_path, $file_name)
2145
    {
2146
        // Get name of the zip file without the extension.
2147
        $file_info = pathinfo($file_name);
2148
        $extension = $file_info['extension']; // Extension only.
2149
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2150
                'dll',
2151
                'exe',
2152
            ])) {
2153
            return 'oogie';
2154
        }
2155
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2156
                'dll',
2157
                'exe',
2158
            ])) {
2159
            return 'woogie';
2160
        }
2161
2162
        $zipFile = new PclZip($file_path);
2163
        // Check the zip content (real size and file extension).
2164
        $zipContentArray = $zipFile->listContent();
2165
        $package_type = '';
2166
        $manifest = '';
2167
        $aicc_match_crs = 0;
2168
        $aicc_match_au = 0;
2169
        $aicc_match_des = 0;
2170
        $aicc_match_cst = 0;
2171
2172
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2173
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2174
            foreach ($zipContentArray as $thisContent) {
2175
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2176
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2177
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2178
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2179
                    $package_type = 'scorm';
2180
                    break; // Exit the foreach loop.
2181
                } elseif (
2182
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2183
                    in_array(
2184
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2185
                        ['crs', 'au', 'des', 'cst']
2186
                    )
2187
                ) {
2188
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2189
                    switch ($ext) {
2190
                        case 'crs':
2191
                            $aicc_match_crs = 1;
2192
                            break;
2193
                        case 'au':
2194
                            $aicc_match_au = 1;
2195
                            break;
2196
                        case 'des':
2197
                            $aicc_match_des = 1;
2198
                            break;
2199
                        case 'cst':
2200
                            $aicc_match_cst = 1;
2201
                            break;
2202
                        default:
2203
                            break;
2204
                    }
2205
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2206
                } else {
2207
                    $package_type = '';
2208
                }
2209
            }
2210
        }
2211
2212
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2213
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2214
            $package_type = 'aicc';
2215
        }
2216
2217
        // Try with chamilo course builder
2218
        if (empty($package_type)) {
2219
            $package_type = 'chamilo';
2220
        }
2221
2222
        return $package_type;
2223
    }
2224
2225
    /**
2226
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2227
     *
2228
     * @return string URL to load into the viewer
2229
     */
2230
    public function get_previous_index()
2231
    {
2232
        if ($this->debug > 0) {
2233
            error_log('In learnpath::get_previous_index()', 0);
2234
        }
2235
        $index = $this->index;
2236
        if (isset($this->ordered_items[$index - 1])) {
2237
            $index--;
2238
            while (isset($this->ordered_items[$index]) &&
2239
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2240
            ) {
2241
                $index--;
2242
                if ($index < 0) {
2243
                    return $this->index;
2244
                }
2245
            }
2246
        } else {
2247
            if ($this->debug > 2) {
2248
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2249
            }
2250
            // There is no previous item.
2251
        }
2252
2253
        return $index;
2254
    }
2255
2256
    /**
2257
     * Gets item_id for the next element.
2258
     *
2259
     * @return int Previous item (DB) ID
2260
     */
2261
    public function get_previous_item_id()
2262
    {
2263
        if ($this->debug > 0) {
2264
            error_log('In learnpath::get_previous_item_id()', 0);
2265
        }
2266
        $new_index = $this->get_previous_index();
2267
2268
        return $this->ordered_items[$new_index];
2269
    }
2270
2271
    /**
2272
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2273
     *
2274
     * @param int    $lpItemId
2275
     * @param string $autostart
2276
     *
2277
     * @return string The mediaplayer HTML
2278
     */
2279
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2280
    {
2281
        $course_id = api_get_course_int_id();
2282
        $_course = api_get_course_info();
2283
        if (empty($_course)) {
2284
            return '';
2285
        }
2286
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2287
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2288
        $lpItemId = (int) $lpItemId;
2289
2290
        // Getting all the information about the item.
2291
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2292
                INNER JOIN $tbl_lp_item_view as lp_view
2293
                ON (lpi.iid = lp_view.lp_item_id)
2294
                WHERE
2295
                    lpi.iid = $lpItemId AND
2296
                    lp_view.c_id = $course_id";
2297
        $result = Database::query($sql);
2298
        $row = Database::fetch_assoc($result);
2299
        $output = '';
2300
2301
        if (!empty($row['audio'])) {
2302
            $list = $_SESSION['oLP']->get_toc();
2303
            $type_quiz = false;
2304
2305
            foreach ($list as $toc) {
2306
                if ($toc['id'] == $_SESSION['oLP']->current && $toc['type'] == 'quiz') {
2307
                    $type_quiz = true;
2308
                }
2309
            }
2310
2311
            if ($type_quiz) {
2312
                if ($_SESSION['oLP']->prevent_reinit == 1) {
2313
                    $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2314
                } else {
2315
                    $autostart_audio = $autostart;
2316
                }
2317
            } else {
2318
                $autostart_audio = 'true';
2319
            }
2320
2321
            $courseInfo = api_get_course_info();
2322
            $audio = $row['audio'];
2323
2324
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2325
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2326
2327
            if (!file_exists($file)) {
2328
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2329
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2330
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2331
            }
2332
2333
            $player = Display::getMediaPlayer(
2334
                $file,
2335
                [
2336
                    'id' => 'lp_audio_media_player',
2337
                    'url' => $url,
2338
                    'autoplay' => $autostart_audio,
2339
                    'width' => '100%',
2340
                ]
2341
            );
2342
2343
            // The mp3 player.
2344
            $output = '<div id="container">';
2345
            $output .= $player;
2346
            $output .= '</div>';
2347
        }
2348
2349
        return $output;
2350
    }
2351
2352
    /**
2353
     * @param int   $studentId
2354
     * @param int   $prerequisite
2355
     * @param array $courseInfo
2356
     * @param int   $sessionId
2357
     *
2358
     * @return bool
2359
     */
2360
    public static function isBlockedByPrerequisite(
2361
        $studentId,
2362
        $prerequisite,
2363
        $courseInfo,
2364
        $sessionId
2365
    ) {
2366
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2367
        if ($allow) {
2368
            if (api_is_allowed_to_edit() ||
2369
                api_is_platform_admin(true) ||
2370
                api_is_drh() ||
2371
                api_is_coach($sessionId, $courseInfo['real_id'], false)
2372
            ) {
2373
                return false;
2374
            }
2375
        }
2376
2377
        $isBlocked = false;
2378
2379
        if (!empty($prerequisite)) {
2380
            $progress = self::getProgress(
2381
                $prerequisite,
2382
                $studentId,
2383
                $courseInfo['real_id'],
2384
                $sessionId
2385
            );
2386
            if ($progress < 100) {
2387
                $isBlocked = true;
2388
            }
2389
        }
2390
2391
        return $isBlocked;
2392
    }
2393
2394
    /**
2395
     * Checks if the learning path is visible for student after the progress
2396
     * of its prerequisite is completed, considering the time availability and
2397
     * the LP visibility.
2398
     *
2399
     * @param int  $lp_id
2400
     * @param int  $student_id
2401
     * @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...
2402
     * @param int  $sessionId
2403
     *
2404
     * @return bool
2405
     */
2406
    public static function is_lp_visible_for_student(
2407
        $lp_id,
2408
        $student_id,
2409
        $courseCode = null,
2410
        $sessionId = 0
2411
    ) {
2412
        $courseInfo = api_get_course_info($courseCode);
2413
        $lp_id = (int) $lp_id;
2414
        $sessionId = (int) $sessionId;
2415
2416
        if (empty($courseInfo)) {
2417
            return false;
2418
        }
2419
2420
        if (empty($sessionId)) {
2421
            $sessionId = api_get_session_id();
2422
        }
2423
2424
        $itemInfo = api_get_item_property_info(
2425
            $courseInfo['real_id'],
2426
            TOOL_LEARNPATH,
2427
            $lp_id,
2428
            $sessionId
2429
        );
2430
2431
        // If the item was deleted.
2432
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2433
            return false;
2434
        }
2435
2436
        // @todo remove this query and load the row info as a parameter
2437
        $table = Database::get_course_table(TABLE_LP_MAIN);
2438
        // Get current prerequisite
2439
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2440
                FROM $table
2441
                WHERE iid = $lp_id";
2442
        $rs = Database::query($sql);
2443
        $now = time();
2444
        if (Database::num_rows($rs) > 0) {
2445
            $row = Database::fetch_array($rs, 'ASSOC');
2446
            $prerequisite = $row['prerequisite'];
2447
            $is_visible = true;
2448
2449
            $isBlocked = self::isBlockedByPrerequisite(
2450
                $student_id,
2451
                $prerequisite,
2452
                $courseInfo,
2453
                $sessionId
2454
            );
2455
2456
            if ($isBlocked) {
2457
                $is_visible = false;
2458
            }
2459
2460
            // Also check the time availability of the LP
2461
            if ($is_visible) {
2462
                // Adding visibility restrictions
2463
                if (!empty($row['publicated_on'])) {
2464
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2465
                        $is_visible = false;
2466
                    }
2467
                }
2468
                // Blocking empty start times see BT#2800
2469
                global $_custom;
2470
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2471
                    $_custom['lps_hidden_when_no_start_date']
2472
                ) {
2473
                    if (empty($row['publicated_on'])) {
2474
                        $is_visible = false;
2475
                    }
2476
                }
2477
2478
                if (!empty($row['expired_on'])) {
2479
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2480
                        $is_visible = false;
2481
                    }
2482
                }
2483
            }
2484
2485
            $subscriptionSettings = self::getSubscriptionSettings();
2486
2487
            // Check if the subscription users/group to a LP is ON
2488
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2489
                $subscriptionSettings['allow_add_users_to_lp'] === true
2490
            ) {
2491
                // Try group
2492
                $is_visible = false;
2493
                // Checking only the user visibility
2494
                $userVisibility = api_get_item_visibility(
2495
                    $courseInfo,
2496
                    'learnpath',
2497
                    $row['id'],
2498
                    $sessionId,
2499
                    $student_id,
2500
                    'LearnpathSubscription'
2501
                );
2502
2503
                if ($userVisibility == 1) {
2504
                    $is_visible = true;
2505
                } else {
2506
                    $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2507
                    if (!empty($userGroups)) {
2508
                        foreach ($userGroups as $groupInfo) {
2509
                            $groupId = $groupInfo['iid'];
2510
                            $userVisibility = api_get_item_visibility(
2511
                                $courseInfo,
2512
                                'learnpath',
2513
                                $row['id'],
2514
                                $sessionId,
2515
                                null,
2516
                                'LearnpathSubscription',
2517
                                $groupId
2518
                            );
2519
2520
                            if ($userVisibility == 1) {
2521
                                $is_visible = true;
2522
                                break;
2523
                            }
2524
                        }
2525
                    }
2526
                }
2527
            }
2528
2529
            return $is_visible;
2530
        }
2531
2532
        return false;
2533
    }
2534
2535
    /**
2536
     * @param int $lpId
2537
     * @param int $userId
2538
     * @param int $courseId
2539
     * @param int $sessionId
2540
     *
2541
     * @return int
2542
     */
2543
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2544
    {
2545
        $lpId = (int) $lpId;
2546
        $userId = (int) $userId;
2547
        $courseId = (int) $courseId;
2548
        $sessionId = (int) $sessionId;
2549
        $progress = 0;
2550
2551
        $sessionCondition = api_get_session_condition($sessionId);
2552
        $table = Database::get_course_table(TABLE_LP_VIEW);
2553
        $sql = "SELECT * FROM $table
2554
                WHERE
2555
                    c_id = $courseId AND
2556
                    lp_id = $lpId AND
2557
                    user_id = $userId $sessionCondition ";
2558
        $res = Database::query($sql);
2559
        if (Database::num_rows($res) > 0) {
2560
            $row = Database:: fetch_array($res);
2561
            $progress = $row['progress'];
2562
        }
2563
2564
        return (int) $progress;
2565
    }
2566
2567
    /**
2568
     * Displays a progress bar
2569
     * completed so far.
2570
     *
2571
     * @param int    $percentage Progress value to display
2572
     * @param string $text_add   Text to display near the progress value
2573
     *
2574
     * @return string HTML string containing the progress bar
2575
     */
2576
    public static function get_progress_bar($percentage = -1, $text_add = '')
2577
    {
2578
        $text = $percentage.$text_add;
2579
        $output = '<div class="progress">
2580
            <div id="progress_bar_value" 
2581
                class="progress-bar progress-bar-success" role="progressbar" 
2582
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2583
            '.$text.'
2584
            </div>
2585
        </div>';
2586
2587
        return $output;
2588
    }
2589
2590
    /**
2591
     * @param string $mode can be '%' or 'abs'
2592
     *                     otherwise this value will be used $this->progress_bar_mode
2593
     *
2594
     * @return string
2595
     */
2596
    public function getProgressBar($mode = null)
2597
    {
2598
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2599
2600
        return self::get_progress_bar($percentage, $text_add);
2601
    }
2602
2603
    /**
2604
     * Gets the progress bar info to display inside the progress bar.
2605
     * Also used by scorm_api.php.
2606
     *
2607
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2608
     *                     we display a number of completed elements per total elements
2609
     * @param int    $add  Additional steps to fake as completed
2610
     *
2611
     * @return array Percentage or number and symbol (% or /xx)
2612
     */
2613
    public function get_progress_bar_text($mode = '', $add = 0)
2614
    {
2615
        if ($this->debug > 0) {
2616
            error_log('In learnpath::get_progress_bar_text()', 0);
2617
        }
2618
        if (empty($mode)) {
2619
            $mode = $this->progress_bar_mode;
2620
        }
2621
        $total_items = $this->getTotalItemsCountWithoutDirs();
2622
        if ($this->debug > 2) {
2623
            error_log('Total items available in this learnpath: '.$total_items, 0);
2624
        }
2625
        $completeItems = $this->get_complete_items_count();
2626
        if ($this->debug > 2) {
2627
            error_log('Items completed so far: '.$completeItems, 0);
2628
        }
2629
        if ($add != 0) {
2630
            $completeItems += $add;
2631
            if ($this->debug > 2) {
2632
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2633
            }
2634
        }
2635
        $text = '';
2636
        if ($completeItems > $total_items) {
2637
            $completeItems = $total_items;
2638
        }
2639
        $percentage = 0;
2640
        if ($mode == '%') {
2641
            if ($total_items > 0) {
2642
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2643
            } else {
2644
                $percentage = 0;
2645
            }
2646
            $percentage = number_format($percentage, 0);
2647
            $text = '%';
2648
        } elseif ($mode == 'abs') {
2649
            $percentage = $completeItems;
2650
            $text = '/'.$total_items;
2651
        }
2652
2653
        return [
2654
            $percentage,
2655
            $text,
2656
        ];
2657
    }
2658
2659
    /**
2660
     * Gets the progress bar mode.
2661
     *
2662
     * @return string The progress bar mode attribute
2663
     */
2664
    public function get_progress_bar_mode()
2665
    {
2666
        if ($this->debug > 0) {
2667
            error_log('In learnpath::get_progress_bar_mode()', 0);
2668
        }
2669
        if (!empty($this->progress_bar_mode)) {
2670
            return $this->progress_bar_mode;
2671
        } else {
2672
            return '%';
2673
        }
2674
    }
2675
2676
    /**
2677
     * Gets the learnpath theme (remote or local).
2678
     *
2679
     * @return string Learnpath theme
2680
     */
2681
    public function get_theme()
2682
    {
2683
        if ($this->debug > 0) {
2684
            error_log('In learnpath::get_theme()', 0);
2685
        }
2686
        if (!empty($this->theme)) {
2687
            return $this->theme;
2688
        } else {
2689
            return '';
2690
        }
2691
    }
2692
2693
    /**
2694
     * Gets the learnpath session id.
2695
     *
2696
     * @return int
2697
     */
2698
    public function get_lp_session_id()
2699
    {
2700
        if ($this->debug > 0) {
2701
            error_log('In learnpath::get_lp_session_id()', 0);
2702
        }
2703
        if (!empty($this->lp_session_id)) {
2704
            return (int) $this->lp_session_id;
2705
        } else {
2706
            return 0;
2707
        }
2708
    }
2709
2710
    /**
2711
     * Gets the learnpath image.
2712
     *
2713
     * @return string Web URL of the LP image
2714
     */
2715
    public function get_preview_image()
2716
    {
2717
        if ($this->debug > 0) {
2718
            error_log('In learnpath::get_preview_image()', 0);
2719
        }
2720
        if (!empty($this->preview_image)) {
2721
            return $this->preview_image;
2722
        } else {
2723
            return '';
2724
        }
2725
    }
2726
2727
    /**
2728
     * @param string $size
2729
     * @param string $path_type
2730
     *
2731
     * @return bool|string
2732
     */
2733
    public function get_preview_image_path($size = null, $path_type = 'web')
2734
    {
2735
        $preview_image = $this->get_preview_image();
2736
        if (isset($preview_image) && !empty($preview_image)) {
2737
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2738
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2739
2740
            if (isset($size)) {
2741
                $info = pathinfo($preview_image);
2742
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2743
2744
                if (file_exists($image_sys_path.$image_custom_size)) {
2745
                    if ($path_type == 'web') {
2746
                        return $image_path.$image_custom_size;
2747
                    } else {
2748
                        return $image_sys_path.$image_custom_size;
2749
                    }
2750
                }
2751
            } else {
2752
                if ($path_type == 'web') {
2753
                    return $image_path.$preview_image;
2754
                } else {
2755
                    return $image_sys_path.$preview_image;
2756
                }
2757
            }
2758
        }
2759
2760
        return false;
2761
    }
2762
2763
    /**
2764
     * Gets the learnpath author.
2765
     *
2766
     * @return string LP's author
2767
     */
2768
    public function get_author()
2769
    {
2770
        if ($this->debug > 0) {
2771
            error_log('In learnpath::get_author()', 0);
2772
        }
2773
        if (!empty($this->author)) {
2774
            return $this->author;
2775
        } else {
2776
            return '';
2777
        }
2778
    }
2779
2780
    /**
2781
     * Gets hide table of contents.
2782
     *
2783
     * @return int
2784
     */
2785
    public function getHideTableOfContents()
2786
    {
2787
        return (int) $this->hide_toc_frame;
2788
    }
2789
2790
    /**
2791
     * Generate a new prerequisites string for a given item. If this item was a sco and
2792
     * its prerequisites were strings (instead of IDs), then transform those strings into
2793
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2794
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2795
     * same rule as the scormExport() method.
2796
     *
2797
     * @param int $item_id Item ID
2798
     *
2799
     * @return string Prerequisites string ready for the export as SCORM
2800
     */
2801
    public function get_scorm_prereq_string($item_id)
2802
    {
2803
        if ($this->debug > 0) {
2804
            error_log('In learnpath::get_scorm_prereq_string()');
2805
        }
2806
        if (!is_object($this->items[$item_id])) {
2807
            return false;
2808
        }
2809
        /** @var learnpathItem $oItem */
2810
        $oItem = $this->items[$item_id];
2811
        $prereq = $oItem->get_prereq_string();
2812
2813
        if (empty($prereq)) {
2814
            return '';
2815
        }
2816
        if (preg_match('/^\d+$/', $prereq) &&
2817
            isset($this->items[$prereq]) &&
2818
            is_object($this->items[$prereq])
2819
        ) {
2820
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2821
            // then simply return it (with the ITEM_ prefix).
2822
            //return 'ITEM_' . $prereq;
2823
            return $this->items[$prereq]->ref;
2824
        } else {
2825
            if (isset($this->refs_list[$prereq])) {
2826
                // It's a simple string item from which the ID can be found in the refs list,
2827
                // so we can transform it directly to an ID for export.
2828
                return $this->items[$this->refs_list[$prereq]]->ref;
2829
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2830
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2831
            } else {
2832
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2833
                // and replace them, one by one, by the internal IDs (chamilo db)
2834
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2835
                // by a space as well.
2836
                $find = [
2837
                    '&',
2838
                    '|',
2839
                    '~',
2840
                    '=',
2841
                    '<>',
2842
                    '{',
2843
                    '}',
2844
                    '*',
2845
                    '(',
2846
                    ')',
2847
                ];
2848
                $replace = [
2849
                    ' ',
2850
                    ' ',
2851
                    ' ',
2852
                    ' ',
2853
                    ' ',
2854
                    ' ',
2855
                    ' ',
2856
                    ' ',
2857
                    ' ',
2858
                    ' ',
2859
                ];
2860
                $prereq_mod = str_replace($find, $replace, $prereq);
2861
                $ids = explode(' ', $prereq_mod);
2862
                foreach ($ids as $id) {
2863
                    $id = trim($id);
2864
                    if (isset($this->refs_list[$id])) {
2865
                        $prereq = preg_replace(
2866
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2867
                            'ITEM_'.$this->refs_list[$id],
2868
                            $prereq
2869
                        );
2870
                    }
2871
                }
2872
2873
                return $prereq;
2874
            }
2875
        }
2876
    }
2877
2878
    /**
2879
     * Returns the XML DOM document's node.
2880
     *
2881
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2882
     * @param string   $id       The identifier to look for
2883
     *
2884
     * @return mixed The reference to the element found with that identifier. False if not found
2885
     */
2886
    public function get_scorm_xml_node(&$children, $id)
2887
    {
2888
        for ($i = 0; $i < $children->length; $i++) {
2889
            $item_temp = $children->item($i);
2890
            if ($item_temp->nodeName == 'item') {
2891
                if ($item_temp->getAttribute('identifier') == $id) {
2892
                    return $item_temp;
2893
                }
2894
            }
2895
            $subchildren = $item_temp->childNodes;
2896
            if ($subchildren && $subchildren->length > 0) {
2897
                $val = $this->get_scorm_xml_node($subchildren, $id);
2898
                if (is_object($val)) {
2899
                    return $val;
2900
                }
2901
            }
2902
        }
2903
2904
        return false;
2905
    }
2906
2907
    /**
2908
     * Gets the status list for all LP's items.
2909
     *
2910
     * @return array Array of [index] => [item ID => current status]
2911
     */
2912
    public function get_items_status_list()
2913
    {
2914
        if ($this->debug > 0) {
2915
            error_log('In learnpath::get_items_status_list()', 0);
2916
        }
2917
        $list = [];
2918
        foreach ($this->ordered_items as $item_id) {
2919
            $list[] = [
2920
                $item_id => $this->items[$item_id]->get_status(),
2921
            ];
2922
        }
2923
2924
        return $list;
2925
    }
2926
2927
    /**
2928
     * Return the number of interactions for the given learnpath Item View ID.
2929
     * This method can be used as static.
2930
     *
2931
     * @param int $lp_iv_id  Item View ID
2932
     * @param int $course_id course id
2933
     *
2934
     * @return int
2935
     */
2936
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2937
    {
2938
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2939
        $lp_iv_id = (int) $lp_iv_id;
2940
        $course_id = (int) $course_id;
2941
2942
        $sql = "SELECT count(*) FROM $table
2943
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2944
        $res = Database::query($sql);
2945
        $num = 0;
2946
        if (Database::num_rows($res)) {
2947
            $row = Database::fetch_array($res);
2948
            $num = $row[0];
2949
        }
2950
2951
        return $num;
2952
    }
2953
2954
    /**
2955
     * Return the interactions as an array for the given lp_iv_id.
2956
     * This method can be used as static.
2957
     *
2958
     * @param int $lp_iv_id Learnpath Item View ID
2959
     *
2960
     * @return array
2961
     *
2962
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2963
     */
2964
    public static function get_iv_interactions_array($lp_iv_id)
2965
    {
2966
        $course_id = api_get_course_int_id();
2967
        $list = [];
2968
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2969
2970
        if (empty($lp_iv_id)) {
2971
            return [];
2972
        }
2973
2974
        $sql = "SELECT * FROM $table
2975
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2976
                ORDER BY order_id ASC";
2977
        $res = Database::query($sql);
2978
        $num = Database::num_rows($res);
2979
        if ($num > 0) {
2980
            $list[] = [
2981
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2982
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2983
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2984
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2985
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2986
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2987
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2988
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2989
            ];
2990
            while ($row = Database::fetch_array($res)) {
2991
                $list[] = [
2992
                    'order_id' => ($row['order_id'] + 1),
2993
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2994
                    'type' => $row['interaction_type'],
2995
                    'time' => $row['completion_time'],
2996
                    //'correct_responses' => $row['correct_responses'],
2997
                    'correct_responses' => '', // Hide correct responses from students.
2998
                    'student_response' => $row['student_response'],
2999
                    'result' => $row['result'],
3000
                    'latency' => $row['latency'],
3001
                ];
3002
            }
3003
        }
3004
3005
        return $list;
3006
    }
3007
3008
    /**
3009
     * Return the number of objectives for the given learnpath Item View ID.
3010
     * This method can be used as static.
3011
     *
3012
     * @param int $lp_iv_id  Item View ID
3013
     * @param int $course_id Course ID
3014
     *
3015
     * @return int Number of objectives
3016
     */
3017
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3018
    {
3019
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3020
        $course_id = (int) $course_id;
3021
        $lp_iv_id = (int) $lp_iv_id;
3022
        $sql = "SELECT count(*) FROM $table
3023
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3024
        //@todo seems that this always returns 0
3025
        $res = Database::query($sql);
3026
        $num = 0;
3027
        if (Database::num_rows($res)) {
3028
            $row = Database::fetch_array($res);
3029
            $num = $row[0];
3030
        }
3031
3032
        return $num;
3033
    }
3034
3035
    /**
3036
     * Return the objectives as an array for the given lp_iv_id.
3037
     * This method can be used as static.
3038
     *
3039
     * @param int $lpItemViewId Learnpath Item View ID
3040
     *
3041
     * @return array
3042
     *
3043
     * @todo    Translate labels
3044
     */
3045
    public static function get_iv_objectives_array($lpItemViewId = 0)
3046
    {
3047
        $course_id = api_get_course_int_id();
3048
        $lpItemViewId = (int) $lpItemViewId;
3049
3050
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3051
        $sql = "SELECT * FROM $table
3052
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3053
                ORDER BY order_id ASC";
3054
        $res = Database::query($sql);
3055
        $num = Database::num_rows($res);
3056
        $list = [];
3057
        if ($num > 0) {
3058
            $list[] = [
3059
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3060
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3061
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3062
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3063
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3064
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3065
            ];
3066
            while ($row = Database::fetch_array($res)) {
3067
                $list[] = [
3068
                    'order_id' => ($row['order_id'] + 1),
3069
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3070
                    'score_raw' => $row['score_raw'],
3071
                    'score_max' => $row['score_max'],
3072
                    'score_min' => $row['score_min'],
3073
                    'status' => $row['status'],
3074
                ];
3075
            }
3076
        }
3077
3078
        return $list;
3079
    }
3080
3081
    /**
3082
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3083
     * used by get_html_toc() to be ready to display.
3084
     *
3085
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3086
     */
3087
    public function get_toc()
3088
    {
3089
        if ($this->debug > 0) {
3090
            error_log('learnpath::get_toc()', 0);
3091
        }
3092
        $toc = [];
3093
        foreach ($this->ordered_items as $item_id) {
3094
            if ($this->debug > 2) {
3095
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3096
            }
3097
            // TODO: Change this link generation and use new function instead.
3098
            $toc[] = [
3099
                'id' => $item_id,
3100
                'title' => $this->items[$item_id]->get_title(),
3101
                'status' => $this->items[$item_id]->get_status(),
3102
                'level' => $this->items[$item_id]->get_level(),
3103
                'type' => $this->items[$item_id]->get_type(),
3104
                'description' => $this->items[$item_id]->get_description(),
3105
                'path' => $this->items[$item_id]->get_path(),
3106
                'parent' => $this->items[$item_id]->get_parent(),
3107
            ];
3108
        }
3109
        if ($this->debug > 2) {
3110
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3111
        }
3112
3113
        return $toc;
3114
    }
3115
3116
    /**
3117
     * Generate and return the table of contents for this learnpath. The JS
3118
     * table returned is used inside of scorm_api.php.
3119
     *
3120
     * @param string $varname
3121
     *
3122
     * @return string A JS array variable construction
3123
     */
3124
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3125
    {
3126
        if ($this->debug > 0) {
3127
            error_log('In learnpath::get_items_details_as_js()', 0);
3128
        }
3129
        $toc = $varname.' = new Array();';
3130
        foreach ($this->ordered_items as $item_id) {
3131
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3132
        }
3133
        if ($this->debug > 2) {
3134
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3135
        }
3136
3137
        return $toc;
3138
    }
3139
3140
    /**
3141
     * Gets the learning path type.
3142
     *
3143
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3144
     *
3145
     * @return mixed Type ID or name, depending on the parameter
3146
     */
3147
    public function get_type($get_name = false)
3148
    {
3149
        $res = false;
3150
        if ($this->debug > 0) {
3151
            error_log('In learnpath::get_type()', 0);
3152
        }
3153
        if (!empty($this->type) && (!$get_name)) {
3154
            $res = $this->type;
3155
        }
3156
        if ($this->debug > 2) {
3157
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3158
        }
3159
3160
        return $res;
3161
    }
3162
3163
    /**
3164
     * Gets the learning path type as static method.
3165
     *
3166
     * @param int $lp_id
3167
     *
3168
     * @return mixed Type ID or name, depending on the parameter
3169
     */
3170
    public static function get_type_static($lp_id = 0)
3171
    {
3172
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3173
        $lp_id = (int) $lp_id;
3174
        $sql = "SELECT lp_type FROM $tbl_lp
3175
                WHERE iid = $lp_id";
3176
        $res = Database::query($sql);
3177
        if ($res === false) {
3178
            return null;
3179
        }
3180
        if (Database::num_rows($res) <= 0) {
3181
            return null;
3182
        }
3183
        $row = Database::fetch_array($res);
3184
3185
        return $row['lp_type'];
3186
    }
3187
3188
    /**
3189
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3190
     * This method can be used as abstract and is recursive.
3191
     *
3192
     * @param int $lp        Learnpath ID
3193
     * @param int $parent    Parent ID of the items to look for
3194
     * @param int $course_id
3195
     *
3196
     * @return array Ordered list of item IDs (empty array on error)
3197
     */
3198
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3199
    {
3200
        if (empty($course_id)) {
3201
            $course_id = api_get_course_int_id();
3202
        } else {
3203
            $course_id = (int) $course_id;
3204
        }
3205
        $list = [];
3206
3207
        if (empty($lp)) {
3208
            return $list;
3209
        }
3210
3211
        $lp = (int) $lp;
3212
        $parent = (int) $parent;
3213
3214
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3215
        $sql = "SELECT iid FROM $tbl_lp_item
3216
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3217
                ORDER BY display_order";
3218
3219
        $res = Database::query($sql);
3220
        while ($row = Database::fetch_array($res)) {
3221
            $sublist = self::get_flat_ordered_items_list(
3222
                $lp,
3223
                $row['iid'],
3224
                $course_id
3225
            );
3226
            $list[] = $row['iid'];
3227
            foreach ($sublist as $item) {
3228
                $list[] = $item;
3229
            }
3230
        }
3231
3232
        return $list;
3233
    }
3234
3235
    /**
3236
     * @return array
3237
     */
3238
    public static function getChapterTypes()
3239
    {
3240
        return [
3241
            'dir',
3242
        ];
3243
    }
3244
3245
    /**
3246
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3247
     *
3248
     * @param $tree
3249
     *
3250
     * @return array HTML TOC ready to display
3251
     */
3252
    public function getParentToc($tree)
3253
    {
3254
        if ($this->debug > 0) {
3255
            error_log('In learnpath::get_html_toc()', 0);
3256
        }
3257
        if (empty($tree)) {
3258
            $tree = $this->get_toc();
3259
        }
3260
        $dirTypes = self::getChapterTypes();
3261
        $myCurrentId = $this->get_current_item_id();
3262
        $listParent = [];
3263
        $listChildren = [];
3264
        $listNotParent = [];
3265
        $list = [];
3266
        foreach ($tree as $subtree) {
3267
            if (in_array($subtree['type'], $dirTypes)) {
3268
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3269
                $subtree['children'] = $listChildren;
3270
                if (!empty($subtree['children'])) {
3271
                    foreach ($subtree['children'] as $subItem) {
3272
                        if ($subItem['id'] == $this->current) {
3273
                            $subtree['parent_current'] = 'in';
3274
                            $subtree['current'] = 'on';
3275
                        }
3276
                    }
3277
                }
3278
                $listParent[] = $subtree;
3279
            }
3280
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3281
                $classStatus = [
3282
                    'not attempted' => 'scorm_not_attempted',
3283
                    'incomplete' => 'scorm_not_attempted',
3284
                    'failed' => 'scorm_failed',
3285
                    'completed' => 'scorm_completed',
3286
                    'passed' => 'scorm_completed',
3287
                    'succeeded' => 'scorm_completed',
3288
                    'browsed' => 'scorm_completed',
3289
                ];
3290
3291
                if (isset($classStatus[$subtree['status']])) {
3292
                    $cssStatus = $classStatus[$subtree['status']];
3293
                }
3294
3295
                $title = Security::remove_XSS($subtree['title']);
3296
                unset($subtree['title']);
3297
3298
                if (empty($title)) {
3299
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3300
                }
3301
                $classStyle = null;
3302
                if ($subtree['id'] == $this->current) {
3303
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3304
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3305
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3306
                }
3307
                $subtree['title'] = $title;
3308
                $subtree['class'] = $classStyle.' '.$cssStatus;
3309
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3310
                $subtree['current_id'] = $myCurrentId;
3311
                $listNotParent[] = $subtree;
3312
            }
3313
        }
3314
3315
        $list['are_parents'] = $listParent;
3316
        $list['not_parents'] = $listNotParent;
3317
3318
        return $list;
3319
    }
3320
3321
    /**
3322
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3323
     *
3324
     * @param array $tree
3325
     * @param int   $id
3326
     * @param bool  $parent
3327
     *
3328
     * @return array HTML TOC ready to display
3329
     */
3330
    public function getChildrenToc($tree, $id, $parent = true)
3331
    {
3332
        if ($this->debug > 0) {
3333
            error_log('In learnpath::get_html_toc()', 0);
3334
        }
3335
        if (empty($tree)) {
3336
            $tree = $this->get_toc();
3337
        }
3338
3339
        $dirTypes = self::getChapterTypes();
3340
        $mycurrentitemid = $this->get_current_item_id();
3341
        $list = [];
3342
        $classStatus = [
3343
            'not attempted' => 'scorm_not_attempted',
3344
            'incomplete' => 'scorm_not_attempted',
3345
            'failed' => 'scorm_failed',
3346
            'completed' => 'scorm_completed',
3347
            'passed' => 'scorm_completed',
3348
            'succeeded' => 'scorm_completed',
3349
            'browsed' => 'scorm_completed',
3350
        ];
3351
3352
        foreach ($tree as $subtree) {
3353
            $subtree['tree'] = null;
3354
3355
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3356
                if ($subtree['id'] == $this->current) {
3357
                    $subtree['current'] = 'active';
3358
                } else {
3359
                    $subtree['current'] = null;
3360
                }
3361
                if (isset($classStatus[$subtree['status']])) {
3362
                    $cssStatus = $classStatus[$subtree['status']];
3363
                }
3364
3365
                $title = Security::remove_XSS($subtree['title']);
3366
                unset($subtree['title']);
3367
                if (empty($title)) {
3368
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3369
                }
3370
3371
                $classStyle = null;
3372
                if ($subtree['id'] == $this->current) {
3373
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3374
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3375
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3376
                }
3377
3378
                if (in_array($subtree['type'], $dirTypes)) {
3379
                    $subtree['title'] = stripslashes($title);
3380
                } else {
3381
                    $subtree['title'] = $title;
3382
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3383
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3384
                    $subtree['current_id'] = $mycurrentitemid;
3385
                }
3386
                $list[] = $subtree;
3387
            }
3388
        }
3389
3390
        return $list;
3391
    }
3392
3393
    /**
3394
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3395
     *
3396
     * @param array $toc_list
3397
     *
3398
     * @return array HTML TOC ready to display
3399
     */
3400
    public function getListArrayToc($toc_list = [])
3401
    {
3402
        if ($this->debug > 0) {
3403
            error_log('In learnpath::get_html_toc()', 0);
3404
        }
3405
        if (empty($toc_list)) {
3406
            $toc_list = $this->get_toc();
3407
        }
3408
        // Temporary variables.
3409
        $mycurrentitemid = $this->get_current_item_id();
3410
        $list = [];
3411
        $arrayList = [];
3412
        $classStatus = [
3413
            'not attempted' => 'scorm_not_attempted',
3414
            'incomplete' => 'scorm_not_attempted',
3415
            'failed' => 'scorm_failed',
3416
            'completed' => 'scorm_completed',
3417
            'passed' => 'scorm_completed',
3418
            'succeeded' => 'scorm_completed',
3419
            'browsed' => 'scorm_completed',
3420
        ];
3421
3422
        foreach ($toc_list as $item) {
3423
            $list['id'] = $item['id'];
3424
            $list['status'] = $item['status'];
3425
            $cssStatus = null;
3426
3427
            if (isset($classStatus[$item['status']])) {
3428
                $cssStatus = $classStatus[$item['status']];
3429
            }
3430
3431
            $classStyle = ' ';
3432
            $dirTypes = self::getChapterTypes();
3433
3434
            if (in_array($item['type'], $dirTypes)) {
3435
                $classStyle = 'scorm_item_section ';
3436
            }
3437
            if ($item['id'] == $this->current) {
3438
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3439
            } elseif (!in_array($item['type'], $dirTypes)) {
3440
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3441
            }
3442
            $title = $item['title'];
3443
            if (empty($title)) {
3444
                $title = self::rl_get_resource_name(
3445
                    api_get_course_id(),
3446
                    $this->get_id(),
3447
                    $item['id']
3448
                );
3449
            }
3450
            $title = Security::remove_XSS($item['title']);
3451
3452
            if (empty($item['description'])) {
3453
                $list['description'] = $title;
3454
            } else {
3455
                $list['description'] = $item['description'];
3456
            }
3457
3458
            $list['class'] = $classStyle.' '.$cssStatus;
3459
            $list['level'] = $item['level'];
3460
            $list['type'] = $item['type'];
3461
3462
            if (in_array($item['type'], $dirTypes)) {
3463
                $list['css_level'] = 'level_'.$item['level'];
3464
            } else {
3465
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3466
            }
3467
3468
            if (in_array($item['type'], $dirTypes)) {
3469
                $list['title'] = stripslashes($title);
3470
            } else {
3471
                $list['title'] = stripslashes($title);
3472
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3473
                $list['current_id'] = $mycurrentitemid;
3474
            }
3475
            $arrayList[] = $list;
3476
        }
3477
3478
        return $arrayList;
3479
    }
3480
3481
    /**
3482
     * Returns an HTML-formatted string ready to display with teacher buttons
3483
     * in LP view menu.
3484
     *
3485
     * @return string HTML TOC ready to display
3486
     */
3487
    public function get_teacher_toc_buttons()
3488
    {
3489
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3490
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3491
        $html = '';
3492
        if ($isAllow && $hideIcons == false) {
3493
            if ($this->get_lp_session_id() == api_get_session_id()) {
3494
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3495
                $html .= '<div class="btn-group">';
3496
                $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'>".
3497
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3498
                $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'>".
3499
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3500
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3501
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3502
                $html .= '</div>';
3503
                $html .= '</div>';
3504
            }
3505
        }
3506
3507
        return $html;
3508
    }
3509
3510
    /**
3511
     * Gets the learnpath maker name - generally the editor's name.
3512
     *
3513
     * @return string Learnpath maker name
3514
     */
3515
    public function get_maker()
3516
    {
3517
        if ($this->debug > 0) {
3518
            error_log('In learnpath::get_maker()', 0);
3519
        }
3520
        if (!empty($this->maker)) {
3521
            return $this->maker;
3522
        } else {
3523
            return '';
3524
        }
3525
    }
3526
3527
    /**
3528
     * Gets the learnpath name/title.
3529
     *
3530
     * @return string Learnpath name/title
3531
     */
3532
    public function get_name()
3533
    {
3534
        if ($this->debug > 0) {
3535
            error_log('In learnpath::get_name()', 0);
3536
        }
3537
        if (!empty($this->name)) {
3538
            return $this->name;
3539
        } else {
3540
            return 'N/A';
3541
        }
3542
    }
3543
3544
    /**
3545
     * Gets a link to the resource from the present location, depending on item ID.
3546
     *
3547
     * @param string $type         Type of link expected
3548
     * @param int    $item_id      Learnpath item ID
3549
     * @param bool   $provided_toc
3550
     *
3551
     * @return string $provided_toc Link to the lp_item resource
3552
     */
3553
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3554
    {
3555
        $course_id = $this->get_course_int_id();
3556
        if ($this->debug > 0) {
3557
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3558
        }
3559
        if (empty($item_id)) {
3560
            if ($this->debug > 2) {
3561
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3562
                error_log('using current: '.$this->get_current_item_id(), 0);
3563
            }
3564
            $item_id = $this->get_current_item_id();
3565
3566
            if (empty($item_id)) {
3567
                if ($this->debug > 2) {
3568
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3569
                }
3570
                //still empty, this means there was no item_id given and we are not in an object context or
3571
                //the object property is empty, return empty link
3572
                $this->first();
3573
3574
                return '';
3575
            }
3576
        }
3577
3578
        $file = '';
3579
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3580
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3581
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3582
        $item_id = (int) $item_id;
3583
3584
        $sql = "SELECT
3585
                    l.lp_type as ltype,
3586
                    l.path as lpath,
3587
                    li.item_type as litype,
3588
                    li.path as lipath,
3589
                    li.parameters as liparams
3590
        		FROM $lp_table l
3591
                INNER JOIN $lp_item_table li
3592
                ON (li.lp_id = l.iid)
3593
        		WHERE 
3594
        		    li.iid = $item_id 
3595
        		";
3596
        if ($this->debug > 2) {
3597
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3598
        }
3599
        $res = Database::query($sql);
3600
        if (Database::num_rows($res) > 0) {
3601
            $row = Database::fetch_array($res);
3602
            $lp_type = $row['ltype'];
3603
            $lp_path = $row['lpath'];
3604
            $lp_item_type = $row['litype'];
3605
            $lp_item_path = $row['lipath'];
3606
            $lp_item_params = $row['liparams'];
3607
3608
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3609
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3610
            }
3611
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3612
            if ($type === 'http') {
3613
                //web path
3614
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3615
            } else {
3616
                $course_path = $sys_course_path; //system path
3617
            }
3618
3619
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3620
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3621
            if (in_array(
3622
                $lp_item_type,
3623
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3624
            )
3625
            ) {
3626
                $lp_type = 1;
3627
            }
3628
3629
            if ($this->debug > 2) {
3630
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3631
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3632
            }
3633
3634
            // Now go through the specific cases to get the end of the path
3635
            // @todo Use constants instead of int values.
3636
            switch ($lp_type) {
3637
                case 1:
3638
                    $file = self::rl_get_resource_link_for_learnpath(
3639
                        $course_id,
3640
                        $this->get_id(),
3641
                        $item_id,
3642
                        $this->get_view_id()
3643
                    );
3644
3645
                    if ($this->debug > 0) {
3646
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3647
                    }
3648
3649
                    switch ($lp_item_type) {
3650
                        case 'dir':
3651
                            $file = 'lp_content.php?type=dir';
3652
                            break;
3653
                        case 'link':
3654
                            if (Link::is_youtube_link($file)) {
3655
                                $src = Link::get_youtube_video_id($file);
3656
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3657
                            } elseif (Link::isVimeoLink($file)) {
3658
                                $src = Link::getVimeoLinkId($file);
3659
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3660
                            } else {
3661
                                // If the current site is HTTPS and the link is
3662
                                // HTTP, browsers will refuse opening the link
3663
                                $urlId = api_get_current_access_url_id();
3664
                                $url = api_get_access_url($urlId, false);
3665
                                $protocol = substr($url['url'], 0, 5);
3666
                                if ($protocol === 'https') {
3667
                                    $linkProtocol = substr($file, 0, 5);
3668
                                    if ($linkProtocol === 'http:') {
3669
                                        //this is the special intervention case
3670
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3671
                                    }
3672
                                }
3673
                            }
3674
                            break;
3675
                        case 'quiz':
3676
                            // Check how much attempts of a exercise exits in lp
3677
                            $lp_item_id = $this->get_current_item_id();
3678
                            $lp_view_id = $this->get_view_id();
3679
3680
                            $prevent_reinit = null;
3681
                            if (isset($this->items[$this->current])) {
3682
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3683
                            }
3684
3685
                            if (empty($provided_toc)) {
3686
                                if ($this->debug > 0) {
3687
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3688
                                }
3689
                                $list = $this->get_toc();
3690
                            } else {
3691
                                if ($this->debug > 0) {
3692
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3693
                                }
3694
                                $list = $provided_toc;
3695
                            }
3696
3697
                            $type_quiz = false;
3698
3699
                            foreach ($list as $toc) {
3700
                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
3701
                                    $type_quiz = true;
3702
                                }
3703
                            }
3704
3705
                            if ($type_quiz) {
3706
                                $lp_item_id = intval($lp_item_id);
3707
                                $lp_view_id = intval($lp_view_id);
3708
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3709
                                        WHERE
3710
                                            c_id = $course_id AND
3711
                                            lp_item_id='".$lp_item_id."' AND
3712
                                            lp_view_id ='".$lp_view_id."' AND
3713
                                            status='completed'";
3714
                                $result = Database::query($sql);
3715
                                $row_count = Database:: fetch_row($result);
3716
                                $count_item_view = (int) $row_count[0];
3717
                                $not_multiple_attempt = 0;
3718
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3719
                                    $not_multiple_attempt = 1;
3720
                                }
3721
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3722
                            }
3723
                            break;
3724
                    }
3725
3726
                    $tmp_array = explode('/', $file);
3727
                    $document_name = $tmp_array[count($tmp_array) - 1];
3728
                    if (strpos($document_name, '_DELETED_')) {
3729
                        $file = 'blank.php?error=document_deleted';
3730
                    }
3731
3732
                    break;
3733
                case 2:
3734
                    if ($this->debug > 2) {
3735
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3736
                    }
3737
3738
                    if ($lp_item_type != 'dir') {
3739
                        // Quite complex here:
3740
                        // We want to make sure 'http://' (and similar) links can
3741
                        // be loaded as is (withouth the Chamilo path in front) but
3742
                        // some contents use this form: resource.htm?resource=http://blablabla
3743
                        // which means we have to find a protocol at the path's start, otherwise
3744
                        // it should not be considered as an external URL.
3745
                        // if ($this->prerequisites_match($item_id)) {
3746
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3747
                            if ($this->debug > 2) {
3748
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3749
                            }
3750
                            // Distant url, return as is.
3751
                            $file = $lp_item_path;
3752
                        } else {
3753
                            if ($this->debug > 2) {
3754
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3755
                            }
3756
                            // Prevent getting untranslatable urls.
3757
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3758
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3759
                            // Prepare the path.
3760
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3761
                            // TODO: Fix this for urls with protocol header.
3762
                            $file = str_replace('//', '/', $file);
3763
                            $file = str_replace(':/', '://', $file);
3764
                            if (substr($lp_path, -1) == '/') {
3765
                                $lp_path = substr($lp_path, 0, -1);
3766
                            }
3767
3768
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3769
                                // if file not found.
3770
                                $decoded = html_entity_decode($lp_item_path);
3771
                                list($decoded) = explode('?', $decoded);
3772
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3773
                                    $file = self::rl_get_resource_link_for_learnpath(
3774
                                        $course_id,
3775
                                        $this->get_id(),
3776
                                        $item_id,
3777
                                        $this->get_view_id()
3778
                                    );
3779
                                    if (empty($file)) {
3780
                                        $file = 'blank.php?error=document_not_found';
3781
                                    } else {
3782
                                        $tmp_array = explode('/', $file);
3783
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3784
                                        if (strpos($document_name, '_DELETED_')) {
3785
                                            $file = 'blank.php?error=document_deleted';
3786
                                        } else {
3787
                                            $file = 'blank.php?error=document_not_found';
3788
                                        }
3789
                                    }
3790
                                } else {
3791
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3792
                                }
3793
                            }
3794
                        }
3795
3796
                        // We want to use parameters if they were defined in the imsmanifest
3797
                        if (strpos($file, 'blank.php') === false) {
3798
                            $lp_item_params = ltrim($lp_item_params, '?');
3799
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3800
                        }
3801
                    } else {
3802
                        $file = 'lp_content.php?type=dir';
3803
                    }
3804
                    break;
3805
                case 3:
3806
                    if ($this->debug > 2) {
3807
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3808
                    }
3809
                    // Formatting AICC HACP append URL.
3810
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3811
                    if (!empty($lp_item_params)) {
3812
                        $aicc_append .= $lp_item_params.'&';
3813
                    }
3814
                    if ($lp_item_type != 'dir') {
3815
                        // Quite complex here:
3816
                        // We want to make sure 'http://' (and similar) links can
3817
                        // be loaded as is (withouth the Chamilo path in front) but
3818
                        // some contents use this form: resource.htm?resource=http://blablabla
3819
                        // which means we have to find a protocol at the path's start, otherwise
3820
                        // it should not be considered as an external URL.
3821
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3822
                            if ($this->debug > 2) {
3823
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3824
                            }
3825
                            // Distant url, return as is.
3826
                            $file = $lp_item_path;
3827
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3828
                            /*
3829
                            if (stristr($file,'<servername>') !== false) {
3830
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3831
                            }
3832
                            */
3833
                            if (stripos($file, '<servername>') !== false) {
3834
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3835
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3836
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3837
                            }
3838
3839
                            $file .= $aicc_append;
3840
                        } else {
3841
                            if ($this->debug > 2) {
3842
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3843
                            }
3844
                            // Prevent getting untranslatable urls.
3845
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3846
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3847
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3848
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3849
                            // TODO: Fix this for urls with protocol header.
3850
                            $file = str_replace('//', '/', $file);
3851
                            $file = str_replace(':/', '://', $file);
3852
                            $file .= $aicc_append;
3853
                        }
3854
                    } else {
3855
                        $file = 'lp_content.php?type=dir';
3856
                    }
3857
                    break;
3858
                case 4:
3859
                    break;
3860
                default:
3861
                    break;
3862
            }
3863
            // Replace &amp; by & because &amp; will break URL with params
3864
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3865
        }
3866
        if ($this->debug > 2) {
3867
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3868
        }
3869
3870
        return $file;
3871
    }
3872
3873
    /**
3874
     * Gets the latest usable view or generate a new one.
3875
     *
3876
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3877
     *
3878
     * @return int DB lp_view id
3879
     */
3880
    public function get_view($attempt_num = 0)
3881
    {
3882
        if ($this->debug > 0) {
3883
            error_log('In learnpath::get_view()', 0);
3884
        }
3885
        $search = '';
3886
        // Use $attempt_num to enable multi-views management (disabled so far).
3887
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3888
            $search = 'AND view_count = '.$attempt_num;
3889
        }
3890
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3891
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3892
3893
        $course_id = api_get_course_int_id();
3894
        $sessionId = api_get_session_id();
3895
3896
        $sql = "SELECT iid, view_count FROM $lp_view_table
3897
        		WHERE
3898
        		    c_id = $course_id AND
3899
        		    lp_id = ".$this->get_id()." AND
3900
        		    user_id = ".$this->get_user_id()." AND
3901
        		    session_id = $sessionId
3902
        		    $search
3903
                ORDER BY view_count DESC";
3904
        $res = Database::query($sql);
3905
        if (Database::num_rows($res) > 0) {
3906
            $row = Database::fetch_array($res);
3907
            $this->lp_view_id = $row['iid'];
3908
        } elseif (!api_is_invitee()) {
3909
            // There is no database record, create one.
3910
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3911
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3912
            Database::query($sql);
3913
            $id = Database::insert_id();
3914
            $this->lp_view_id = $id;
3915
3916
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3917
            Database::query($sql);
3918
        }
3919
3920
        return $this->lp_view_id;
3921
    }
3922
3923
    /**
3924
     * Gets the current view id.
3925
     *
3926
     * @return int View ID (from lp_view)
3927
     */
3928
    public function get_view_id()
3929
    {
3930
        if ($this->debug > 0) {
3931
            error_log('In learnpath::get_view_id()', 0);
3932
        }
3933
        if (!empty($this->lp_view_id)) {
3934
            return $this->lp_view_id;
3935
        } else {
3936
            return 0;
3937
        }
3938
    }
3939
3940
    /**
3941
     * Gets the update queue.
3942
     *
3943
     * @return array Array containing IDs of items to be updated by JavaScript
3944
     */
3945
    public function get_update_queue()
3946
    {
3947
        if ($this->debug > 0) {
3948
            error_log('In learnpath::get_update_queue()', 0);
3949
        }
3950
3951
        return $this->update_queue;
3952
    }
3953
3954
    /**
3955
     * Gets the user ID.
3956
     *
3957
     * @return int User ID
3958
     */
3959
    public function get_user_id()
3960
    {
3961
        if ($this->debug > 0) {
3962
            error_log('In learnpath::get_user_id()', 0);
3963
        }
3964
        if (!empty($this->user_id)) {
3965
            return $this->user_id;
3966
        } else {
3967
            return false;
3968
        }
3969
    }
3970
3971
    /**
3972
     * Checks if any of the items has an audio element attached.
3973
     *
3974
     * @return bool True or false
3975
     */
3976
    public function has_audio()
3977
    {
3978
        if ($this->debug > 1) {
3979
            error_log('In learnpath::has_audio()', 0);
3980
        }
3981
        $has = false;
3982
        foreach ($this->items as $i => $item) {
3983
            if (!empty($this->items[$i]->audio)) {
3984
                $has = true;
3985
                break;
3986
            }
3987
        }
3988
3989
        return $has;
3990
    }
3991
3992
    /**
3993
     * Moves an item up and down at its level.
3994
     *
3995
     * @param int    $id        Item to move up and down
3996
     * @param string $direction Direction 'up' or 'down'
3997
     *
3998
     * @return bool|int
3999
     */
4000
    public function move_item($id, $direction)
4001
    {
4002
        $course_id = api_get_course_int_id();
4003
        if ($this->debug > 0) {
4004
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
4005
        }
4006
        if (empty($id) || empty($direction)) {
4007
            return false;
4008
        }
4009
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4010
        $sql_sel = "SELECT *
4011
                    FROM $tbl_lp_item
4012
                    WHERE  
4013
                        iid = $id
4014
                    ";
4015
        $res_sel = Database::query($sql_sel);
4016
        // Check if elem exists.
4017
        if (Database::num_rows($res_sel) < 1) {
4018
            return false;
4019
        }
4020
        // Gather data.
4021
        $row = Database::fetch_array($res_sel);
4022
        $previous = $row['previous_item_id'];
4023
        $next = $row['next_item_id'];
4024
        $display = $row['display_order'];
4025
        $parent = $row['parent_item_id'];
4026
        $lp = $row['lp_id'];
4027
        // Update the item (switch with previous/next one).
4028
        switch ($direction) {
4029
            case 'up':
4030
                if ($this->debug > 2) {
4031
                    error_log('Movement up detected', 0);
4032
                }
4033
                if ($display > 1) {
4034
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4035
                                 WHERE iid = $previous";
4036
4037
                    if ($this->debug > 2) {
4038
                        error_log('Selecting previous: '.$sql_sel2, 0);
4039
                    }
4040
                    $res_sel2 = Database::query($sql_sel2);
4041
                    if (Database::num_rows($res_sel2) < 1) {
4042
                        $previous_previous = 0;
4043
                    }
4044
                    // Gather data.
4045
                    $row2 = Database::fetch_array($res_sel2);
4046
                    $previous_previous = $row2['previous_item_id'];
4047
                    // Update previous_previous item (switch "next" with current).
4048
                    if ($previous_previous != 0) {
4049
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4050
                                        next_item_id = $id
4051
                                    WHERE iid = $previous_previous";
4052
                        if ($this->debug > 2) {
4053
                            error_log($sql_upd2, 0);
4054
                        }
4055
                        Database::query($sql_upd2);
4056
                    }
4057
                    // Update previous item (switch with current).
4058
                    if ($previous != 0) {
4059
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4060
                                    next_item_id = $next,
4061
                                    previous_item_id = $id,
4062
                                    display_order = display_order +1
4063
                                    WHERE iid = $previous";
4064
                        if ($this->debug > 2) {
4065
                            error_log($sql_upd2, 0);
4066
                        }
4067
                        Database::query($sql_upd2);
4068
                    }
4069
4070
                    // Update current item (switch with previous).
4071
                    if ($id != 0) {
4072
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4073
                                        next_item_id = $previous,
4074
                                        previous_item_id = $previous_previous,
4075
                                        display_order = display_order-1
4076
                                    WHERE c_id = ".$course_id." AND id = $id";
4077
                        if ($this->debug > 2) {
4078
                            error_log($sql_upd2, 0);
4079
                        }
4080
                        Database::query($sql_upd2);
4081
                    }
4082
                    // Update next item (new previous item).
4083
                    if (!empty($next)) {
4084
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4085
                                     WHERE iid = $next";
4086
                        if ($this->debug > 2) {
4087
                            error_log($sql_upd2, 0);
4088
                        }
4089
                        Database::query($sql_upd2);
4090
                    }
4091
                    $display = $display - 1;
4092
                }
4093
                break;
4094
            case 'down':
4095
                if ($this->debug > 2) {
4096
                    error_log('Movement down detected', 0);
4097
                }
4098
                if ($next != 0) {
4099
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4100
                                 WHERE iid = $next";
4101
                    if ($this->debug > 2) {
4102
                        error_log('Selecting next: '.$sql_sel2, 0);
4103
                    }
4104
                    $res_sel2 = Database::query($sql_sel2);
4105
                    if (Database::num_rows($res_sel2) < 1) {
4106
                        $next_next = 0;
4107
                    }
4108
                    // Gather data.
4109
                    $row2 = Database::fetch_array($res_sel2);
4110
                    $next_next = $row2['next_item_id'];
4111
                    // Update previous item (switch with current).
4112
                    if ($previous != 0) {
4113
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4114
                                     SET next_item_id = $next
4115
                                     WHERE iid = $previous";
4116
                        Database::query($sql_upd2);
4117
                    }
4118
                    // Update current item (switch with previous).
4119
                    if ($id != 0) {
4120
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4121
                                     previous_item_id = $next, 
4122
                                     next_item_id = $next_next, 
4123
                                     display_order = display_order + 1
4124
                                     WHERE iid = $id";
4125
                        Database::query($sql_upd2);
4126
                    }
4127
4128
                    // Update next item (new previous item).
4129
                    if ($next != 0) {
4130
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4131
                                     previous_item_id = $previous, 
4132
                                     next_item_id = $id, 
4133
                                     display_order = display_order-1
4134
                                     WHERE iid = $next";
4135
                        Database::query($sql_upd2);
4136
                    }
4137
4138
                    // Update next_next item (switch "previous" with current).
4139
                    if ($next_next != 0) {
4140
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4141
                                     previous_item_id = $id
4142
                                     WHERE iid = $next_next";
4143
                        Database::query($sql_upd2);
4144
                    }
4145
                    $display = $display + 1;
4146
                }
4147
                break;
4148
            default:
4149
                return false;
4150
        }
4151
4152
        return $display;
4153
    }
4154
4155
    /**
4156
     * Move a LP up (display_order).
4157
     *
4158
     * @param int $lp_id      Learnpath ID
4159
     * @param int $categoryId Category ID
4160
     *
4161
     * @return bool
4162
     */
4163
    public static function move_up($lp_id, $categoryId = 0)
4164
    {
4165
        $courseId = api_get_course_int_id();
4166
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4167
4168
        $categoryCondition = '';
4169
        if (!empty($categoryId)) {
4170
            $categoryId = (int) $categoryId;
4171
            $categoryCondition = " AND category_id = $categoryId";
4172
        }
4173
        $sql = "SELECT * FROM $lp_table
4174
                WHERE c_id = $courseId
4175
                $categoryCondition
4176
                ORDER BY display_order";
4177
        $res = Database::query($sql);
4178
        if ($res === false) {
4179
            return false;
4180
        }
4181
4182
        $lps = [];
4183
        $lp_order = [];
4184
        $num = Database::num_rows($res);
4185
        // First check the order is correct, globally (might be wrong because
4186
        // of versions < 1.8.4)
4187
        if ($num > 0) {
4188
            $i = 1;
4189
            while ($row = Database::fetch_array($res)) {
4190
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4191
                    $sql = "UPDATE $lp_table SET display_order = $i
4192
                            WHERE iid = ".$row['iid'];
4193
                    Database::query($sql);
4194
                }
4195
                $row['display_order'] = $i;
4196
                $lps[$row['iid']] = $row;
4197
                $lp_order[$i] = $row['iid'];
4198
                $i++;
4199
            }
4200
        }
4201
        if ($num > 1) { // If there's only one element, no need to sort.
4202
            $order = $lps[$lp_id]['display_order'];
4203
            if ($order > 1) { // If it's the first element, no need to move up.
4204
                $sql = "UPDATE $lp_table SET display_order = $order
4205
                        WHERE iid = ".$lp_order[$order - 1];
4206
                Database::query($sql);
4207
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4208
                        WHERE iid = $lp_id";
4209
                Database::query($sql);
4210
            }
4211
        }
4212
4213
        return true;
4214
    }
4215
4216
    /**
4217
     * Move a learnpath down (display_order).
4218
     *
4219
     * @param int $lp_id      Learnpath ID
4220
     * @param int $categoryId Category ID
4221
     *
4222
     * @return bool
4223
     */
4224
    public static function move_down($lp_id, $categoryId = 0)
4225
    {
4226
        $courseId = api_get_course_int_id();
4227
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4228
4229
        $categoryCondition = '';
4230
        if (!empty($categoryId)) {
4231
            $categoryId = (int) $categoryId;
4232
            $categoryCondition = " AND category_id = $categoryId";
4233
        }
4234
4235
        $sql = "SELECT * FROM $lp_table
4236
                WHERE c_id = $courseId
4237
                $categoryCondition
4238
                ORDER BY display_order";
4239
        $res = Database::query($sql);
4240
        if ($res === false) {
4241
            return false;
4242
        }
4243
        $lps = [];
4244
        $lp_order = [];
4245
        $num = Database::num_rows($res);
4246
        $max = 0;
4247
        // First check the order is correct, globally (might be wrong because
4248
        // of versions < 1.8.4).
4249
        if ($num > 0) {
4250
            $i = 1;
4251
            while ($row = Database::fetch_array($res)) {
4252
                $max = $i;
4253
                if ($row['display_order'] != $i) {
4254
                    // If we find a gap in the order, we need to fix it.
4255
                    $sql = "UPDATE $lp_table SET display_order = $i
4256
                              WHERE iid = ".$row['iid'];
4257
                    Database::query($sql);
4258
                }
4259
                $row['display_order'] = $i;
4260
                $lps[$row['iid']] = $row;
4261
                $lp_order[$i] = $row['iid'];
4262
                $i++;
4263
            }
4264
        }
4265
        if ($num > 1) { // If there's only one element, no need to sort.
4266
            $order = $lps[$lp_id]['display_order'];
4267
            if ($order < $max) { // If it's the first element, no need to move up.
4268
                $sql = "UPDATE $lp_table SET display_order = $order
4269
                        WHERE iid = ".$lp_order[$order + 1];
4270
                Database::query($sql);
4271
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4272
                        WHERE iid = $lp_id";
4273
                Database::query($sql);
4274
            }
4275
        }
4276
4277
        return true;
4278
    }
4279
4280
    /**
4281
     * Updates learnpath attributes to point to the next element
4282
     * The last part is similar to set_current_item but processing the other way around.
4283
     */
4284
    public function next()
4285
    {
4286
        if ($this->debug > 0) {
4287
            error_log('In learnpath::next()', 0);
4288
        }
4289
        $this->last = $this->get_current_item_id();
4290
        $this->items[$this->last]->save(
4291
            false,
4292
            $this->prerequisites_match($this->last)
4293
        );
4294
        $this->autocomplete_parents($this->last);
4295
        $new_index = $this->get_next_index();
4296
        if ($this->debug > 2) {
4297
            error_log('New index: '.$new_index, 0);
4298
        }
4299
        $this->index = $new_index;
4300
        if ($this->debug > 2) {
4301
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4302
        }
4303
        $this->current = $this->ordered_items[$new_index];
4304
        if ($this->debug > 2) {
4305
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4306
        }
4307
    }
4308
4309
    /**
4310
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4311
     * class, this might be redefined to allow several behaviours depending on the document type.
4312
     *
4313
     * @param int $id Resource ID
4314
     */
4315
    public function open($id)
4316
    {
4317
        if ($this->debug > 0) {
4318
            error_log('In learnpath::open()', 0);
4319
        }
4320
        // TODO:
4321
        // set the current resource attribute to this resource
4322
        // switch on element type (redefine in child class?)
4323
        // set status for this item to "opened"
4324
        // start timer
4325
        // initialise score
4326
        $this->index = 0; //or = the last item seen (see $this->last)
4327
    }
4328
4329
    /**
4330
     * Check that all prerequisites are fulfilled. Returns true and an
4331
     * empty string on success, returns false
4332
     * and the prerequisite string on error.
4333
     * This function is based on the rules for aicc_script language as
4334
     * described in the SCORM 1.2 CAM documentation page 108.
4335
     *
4336
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4337
     *
4338
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4339
     *              string otherwise
4340
     */
4341
    public function prerequisites_match($itemId = null)
4342
    {
4343
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4344
        if ($allow) {
4345
            if (api_is_allowed_to_edit() ||
4346
                api_is_platform_admin(true) ||
4347
                api_is_drh() ||
4348
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4349
            ) {
4350
                return true;
4351
            }
4352
        }
4353
4354
        $debug = $this->debug;
4355
        if ($debug > 0) {
4356
            error_log('In learnpath::prerequisites_match()', 0);
4357
        }
4358
4359
        if (empty($itemId)) {
4360
            $itemId = $this->current;
4361
        }
4362
4363
        $currentItem = $this->getItem($itemId);
4364
4365
        if ($currentItem) {
4366
            if ($this->type == 2) {
4367
                // Getting prereq from scorm
4368
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4369
            } else {
4370
                $prereq_string = $currentItem->get_prereq_string();
4371
            }
4372
4373
            if (empty($prereq_string)) {
4374
                if ($debug > 0) {
4375
                    error_log('Found prereq_string is empty return true');
4376
                }
4377
4378
                return true;
4379
            }
4380
            // Clean spaces.
4381
            $prereq_string = str_replace(' ', '', $prereq_string);
4382
            if ($debug > 0) {
4383
                error_log('Found prereq_string: '.$prereq_string, 0);
4384
            }
4385
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4386
            $result = $currentItem->parse_prereq(
4387
                $prereq_string,
4388
                $this->items,
4389
                $this->refs_list,
4390
                $this->get_user_id()
4391
            );
4392
4393
            if ($result === false) {
4394
                $this->set_error_msg($currentItem->prereq_alert);
4395
            }
4396
        } else {
4397
            $result = true;
4398
            if ($debug > 1) {
4399
                error_log('$this->items['.$itemId.'] was not an object', 0);
4400
            }
4401
        }
4402
4403
        if ($debug > 1) {
4404
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4405
        }
4406
4407
        return $result;
4408
    }
4409
4410
    /**
4411
     * Updates learnpath attributes to point to the previous element
4412
     * The last part is similar to set_current_item but processing the other way around.
4413
     */
4414
    public function previous()
4415
    {
4416
        if ($this->debug > 0) {
4417
            error_log('In learnpath::previous()', 0);
4418
        }
4419
        $this->last = $this->get_current_item_id();
4420
        $this->items[$this->last]->save(
4421
            false,
4422
            $this->prerequisites_match($this->last)
4423
        );
4424
        $this->autocomplete_parents($this->last);
4425
        $new_index = $this->get_previous_index();
4426
        $this->index = $new_index;
4427
        $this->current = $this->ordered_items[$new_index];
4428
    }
4429
4430
    /**
4431
     * Publishes a learnpath. This basically means show or hide the learnpath
4432
     * to normal users.
4433
     * Can be used as abstract.
4434
     *
4435
     * @param int $lp_id          Learnpath ID
4436
     * @param int $set_visibility New visibility
4437
     *
4438
     * @return bool
4439
     */
4440
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4441
    {
4442
        $action = 'visible';
4443
        if ($set_visibility != 1) {
4444
            $action = 'invisible';
4445
            self::toggle_publish($lp_id, 'i');
4446
        }
4447
4448
        return api_item_property_update(
4449
            api_get_course_info(),
4450
            TOOL_LEARNPATH,
4451
            $lp_id,
4452
            $action,
4453
            api_get_user_id()
4454
        );
4455
    }
4456
4457
    /**
4458
     * Publishes a learnpath category.
4459
     * This basically means show or hide the learnpath category to normal users.
4460
     *
4461
     * @param int $id
4462
     * @param int $visibility
4463
     *
4464
     * @throws \Doctrine\ORM\NonUniqueResultException
4465
     * @throws \Doctrine\ORM\ORMException
4466
     * @throws \Doctrine\ORM\OptimisticLockException
4467
     * @throws \Doctrine\ORM\TransactionRequiredException
4468
     *
4469
     * @return bool
4470
     */
4471
    public static function toggleCategoryVisibility($id, $visibility = 1)
4472
    {
4473
        $action = 'visible';
4474
4475
        if ($visibility != 1) {
4476
            $action = 'invisible';
4477
            $list = new LearnpathList(
4478
                api_get_user_id(),
4479
                null,
4480
                null,
4481
                null,
4482
                false,
4483
                $id
4484
            );
4485
4486
            $lpList = $list->get_flat_list();
4487
            foreach ($lpList as $lp) {
4488
                self::toggle_visibility($lp['iid'], 0);
4489
            }
4490
4491
            self::toggleCategoryPublish($id, 0);
4492
        }
4493
4494
        return api_item_property_update(
4495
            api_get_course_info(),
4496
            TOOL_LEARNPATH_CATEGORY,
4497
            $id,
4498
            $action,
4499
            api_get_user_id()
4500
        );
4501
    }
4502
4503
    /**
4504
     * Publishes a learnpath. This basically means show or hide the learnpath
4505
     * on the course homepage
4506
     * Can be used as abstract.
4507
     *
4508
     * @param int    $lp_id          Learnpath id
4509
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4510
     *
4511
     * @return bool
4512
     */
4513
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4514
    {
4515
        $course_id = api_get_course_int_id();
4516
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4517
        $lp_id = (int) $lp_id;
4518
        $sql = "SELECT * FROM $tbl_lp
4519
                WHERE iid = $lp_id";
4520
        $result = Database::query($sql);
4521
        if (Database::num_rows($result)) {
4522
            $row = Database::fetch_array($result);
4523
            $name = Database::escape_string($row['name']);
4524
            if ($set_visibility == 'i') {
4525
                $v = 0;
4526
            }
4527
            if ($set_visibility == 'v') {
4528
                $v = 1;
4529
            }
4530
4531
            $session_id = api_get_session_id();
4532
            $session_condition = api_get_session_condition($session_id);
4533
4534
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4535
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4536
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4537
4538
            $sql = "SELECT * FROM $tbl_tool
4539
                    WHERE
4540
                        c_id = $course_id AND
4541
                        (link = '$link' OR link = '$oldLink') AND
4542
                        image = 'scormbuilder.gif' AND
4543
                        (
4544
                            link LIKE '$link%' OR
4545
                            link LIKE '$oldLink%'
4546
                        )
4547
                        $session_condition
4548
                    ";
4549
4550
            $result = Database::query($sql);
4551
            $num = Database::num_rows($result);
4552
            if ($set_visibility == 'i' && $num > 0) {
4553
                $sql = "DELETE FROM $tbl_tool
4554
                        WHERE 
4555
                            c_id = $course_id AND 
4556
                            (link = '$link' OR link = '$oldLink') AND 
4557
                            image='scormbuilder.gif' 
4558
                            $session_condition";
4559
                Database::query($sql);
4560
            } elseif ($set_visibility == 'v' && $num == 0) {
4561
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4562
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4563
                Database::query($sql);
4564
                $insertId = Database::insert_id();
4565
                if ($insertId) {
4566
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4567
                    Database::query($sql);
4568
                }
4569
            } elseif ($set_visibility == 'v' && $num > 0) {
4570
                $sql = "UPDATE $tbl_tool SET
4571
                            c_id = $course_id,
4572
                            name = '$name',
4573
                            link = '$link',
4574
                            image = 'scormbuilder.gif',
4575
                            visibility = '$v',
4576
                            admin = '0',
4577
                            address = 'pastillegris.gif',
4578
                            added_tool = 0,
4579
                            session_id = $session_id
4580
                        WHERE
4581
                            c_id = ".$course_id." AND
4582
                            (link = '$link' OR link = '$oldLink') AND 
4583
                            image='scormbuilder.gif' 
4584
                            $session_condition
4585
                        ";
4586
                Database::query($sql);
4587
            } else {
4588
                // Parameter and database incompatible, do nothing, exit.
4589
                return false;
4590
            }
4591
        } else {
4592
            return false;
4593
        }
4594
    }
4595
4596
    /**
4597
     * Publishes a learnpath.
4598
     * Show or hide the learnpath category on the course homepage.
4599
     *
4600
     * @param int $id
4601
     * @param int $setVisibility
4602
     *
4603
     * @throws \Doctrine\ORM\NonUniqueResultException
4604
     * @throws \Doctrine\ORM\ORMException
4605
     * @throws \Doctrine\ORM\OptimisticLockException
4606
     * @throws \Doctrine\ORM\TransactionRequiredException
4607
     *
4608
     * @return bool
4609
     */
4610
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4611
    {
4612
        $courseId = api_get_course_int_id();
4613
        $sessionId = api_get_session_id();
4614
        $sessionCondition = api_get_session_condition(
4615
            $sessionId,
4616
            true,
4617
            false,
4618
            't.sessionId'
4619
        );
4620
4621
        $em = Database::getManager();
4622
4623
        /** @var CLpCategory $category */
4624
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4625
4626
        if (!$category) {
4627
            return false;
4628
        }
4629
4630
        $link = self::getCategoryLinkForTool($id);
4631
4632
        /** @var CTool $tool */
4633
        $tool = $em->createQuery("
4634
                SELECT t FROM ChamiloCourseBundle:CTool t
4635
                WHERE
4636
                    t.cId = :course AND
4637
                    t.link = :link1 AND
4638
                    t.image = 'lp_category.gif' AND
4639
                    t.link LIKE :link2
4640
                    $sessionCondition
4641
            ")
4642
            ->setParameters([
4643
                'course' => (int) $courseId,
4644
                'link1' => $link,
4645
                'link2' => "$link%",
4646
            ])
4647
            ->getOneOrNullResult();
4648
4649
        if ($setVisibility == 0 && $tool) {
4650
            $em->remove($tool);
4651
            $em->flush();
4652
4653
            return true;
4654
        }
4655
4656
        if ($setVisibility == 1 && !$tool) {
4657
            $tool = new CTool();
4658
            $tool
4659
                ->setCategory('authoring')
4660
                ->setCId($courseId)
4661
                ->setName(strip_tags($category->getName()))
4662
                ->setLink($link)
4663
                ->setImage('lp_category.gif')
4664
                ->setVisibility(1)
4665
                ->setAdmin(0)
4666
                ->setAddress('pastillegris.gif')
4667
                ->setAddedTool(0)
4668
                ->setSessionId($sessionId)
4669
                ->setTarget('_self');
4670
4671
            $em->persist($tool);
4672
            $em->flush();
4673
4674
            $tool->setId($tool->getIid());
4675
4676
            $em->persist($tool);
4677
            $em->flush();
4678
4679
            return true;
4680
        }
4681
4682
        if ($setVisibility == 1 && $tool) {
4683
            $tool
4684
                ->setName(strip_tags($category->getName()))
4685
                ->setVisibility(1);
4686
4687
            $em->persist($tool);
4688
            $em->flush();
4689
4690
            return true;
4691
        }
4692
4693
        return false;
4694
    }
4695
4696
    /**
4697
     * Check if the learnpath category is visible for a user.
4698
     *
4699
     * @param CLpCategory $category
4700
     * @param User        $user
4701
     *
4702
     * @return bool
4703
     */
4704
    public static function categoryIsVisibleForStudent(
4705
        CLpCategory $category,
4706
        User $user
4707
    ) {
4708
        $subscriptionSettings = self::getSubscriptionSettings();
4709
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4710
            return true;
4711
        }
4712
4713
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4714
4715
        if ($isAllowedToEdit) {
4716
            return true;
4717
        }
4718
4719
        if (empty($category)) {
4720
            return false;
4721
        }
4722
4723
        $users = $category->getUsers();
4724
4725
        if (empty($users) || !$users->count()) {
4726
            return true;
4727
        }
4728
4729
        if ($category->hasUserAdded($user)) {
4730
            return true;
4731
        }
4732
4733
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4734
        if (!empty($groups)) {
4735
            $em = Database::getManager();
4736
4737
            /** @var ItemPropertyRepository $itemRepo */
4738
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4739
4740
            /** @var CourseRepository $courseRepo */
4741
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4742
            $sessionId = api_get_session_id();
4743
            $session = null;
4744
            if (!empty($sessionId)) {
4745
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4746
            }
4747
4748
            $course = $courseRepo->find(api_get_course_int_id());
4749
4750
            // Subscribed groups to a LP
4751
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4752
                TOOL_LEARNPATH_CATEGORY,
4753
                $category->getId(),
4754
                $course,
4755
                $session
4756
            );
4757
4758
            if (!empty($subscribedGroupsInLp)) {
4759
                $groups = array_column($groups, 'iid');
4760
                /** @var CItemProperty $item */
4761
                foreach ($subscribedGroupsInLp as $item) {
4762
                    if ($item->getGroup() &&
4763
                        in_array($item->getGroup()->getId(), $groups)
4764
                    ) {
4765
                        return true;
4766
                    }
4767
                }
4768
            }
4769
        }
4770
4771
        return false;
4772
    }
4773
4774
    /**
4775
     * Check if a learnpath category is published as course tool.
4776
     *
4777
     * @param CLpCategory $category
4778
     * @param int         $courseId
4779
     *
4780
     * @return bool
4781
     */
4782
    public static function categoryIsPublished(
4783
        CLpCategory $category,
4784
        $courseId
4785
    ) {
4786
        $link = self::getCategoryLinkForTool($category->getId());
4787
        $em = Database::getManager();
4788
4789
        $tools = $em
4790
            ->createQuery("
4791
                SELECT t FROM ChamiloCourseBundle:CTool t
4792
                WHERE t.cId = :course AND 
4793
                    t.name = :name AND
4794
                    t.image = 'lp_category.gif' AND
4795
                    t.link LIKE :link
4796
            ")
4797
            ->setParameters([
4798
                'course' => $courseId,
4799
                'name' => strip_tags($category->getName()),
4800
                'link' => "$link%",
4801
            ])
4802
            ->getResult();
4803
4804
        /** @var CTool $tool */
4805
        $tool = current($tools);
4806
4807
        return $tool ? $tool->getVisibility() : false;
4808
    }
4809
4810
    /**
4811
     * Restart the whole learnpath. Return the URL of the first element.
4812
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4813
     * To use a similar method  statically, use the create_new_attempt() method.
4814
     *
4815
     * @return bool
4816
     */
4817
    public function restart()
4818
    {
4819
        if ($this->debug > 0) {
4820
            error_log('In learnpath::restart()', 0);
4821
        }
4822
        // TODO
4823
        // Call autosave method to save the current progress.
4824
        //$this->index = 0;
4825
        if (api_is_invitee()) {
4826
            return false;
4827
        }
4828
        $session_id = api_get_session_id();
4829
        $course_id = api_get_course_int_id();
4830
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4831
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4832
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4833
        if ($this->debug > 2) {
4834
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4835
        }
4836
        Database::query($sql);
4837
        $view_id = Database::insert_id();
4838
4839
        if ($view_id) {
4840
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4841
            Database::query($sql);
4842
            $this->lp_view_id = $view_id;
4843
            $this->attempt = $this->attempt + 1;
4844
        } else {
4845
            $this->error = 'Could not insert into item_view table...';
4846
4847
            return false;
4848
        }
4849
        $this->autocomplete_parents($this->current);
4850
        foreach ($this->items as $index => $dummy) {
4851
            $this->items[$index]->restart();
4852
            $this->items[$index]->set_lp_view($this->lp_view_id);
4853
        }
4854
        $this->first();
4855
4856
        return true;
4857
    }
4858
4859
    /**
4860
     * Saves the current item.
4861
     *
4862
     * @return bool
4863
     */
4864
    public function save_current()
4865
    {
4866
        $debug = $this->debug;
4867
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4868
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4869
        if ($debug) {
4870
            error_log('save_current() saving item '.$this->current, 0);
4871
            error_log(''.print_r($this->items, true), 0);
4872
        }
4873
        if (isset($this->items[$this->current]) &&
4874
            is_object($this->items[$this->current])
4875
        ) {
4876
            if ($debug) {
4877
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4878
            }
4879
4880
            $res = $this->items[$this->current]->save(
4881
                false,
4882
                $this->prerequisites_match($this->current)
4883
            );
4884
            $this->autocomplete_parents($this->current);
4885
            $status = $this->items[$this->current]->get_status();
4886
            $this->update_queue[$this->current] = $status;
4887
4888
            if ($debug) {
4889
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4890
            }
4891
4892
            return $res;
4893
        }
4894
4895
        return false;
4896
    }
4897
4898
    /**
4899
     * Saves the given item.
4900
     *
4901
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4902
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4903
     *
4904
     * @return bool
4905
     */
4906
    public function save_item($item_id = null, $from_outside = true)
4907
    {
4908
        $debug = $this->debug;
4909
        if ($debug) {
4910
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4911
        }
4912
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4913
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4914
        if (empty($item_id)) {
4915
            $item_id = (int) $_REQUEST['id'];
4916
        }
4917
4918
        if (empty($item_id)) {
4919
            $item_id = $this->get_current_item_id();
4920
        }
4921
        if (isset($this->items[$item_id]) &&
4922
            is_object($this->items[$item_id])
4923
        ) {
4924
            if ($debug) {
4925
                error_log('Object exists');
4926
            }
4927
4928
            // Saving the item.
4929
            $res = $this->items[$item_id]->save(
4930
                $from_outside,
4931
                $this->prerequisites_match($item_id)
4932
            );
4933
4934
            if ($debug) {
4935
                error_log('update_queue before:');
4936
                error_log(print_r($this->update_queue, 1));
4937
            }
4938
            $this->autocomplete_parents($item_id);
4939
4940
            $status = $this->items[$item_id]->get_status();
4941
            $this->update_queue[$item_id] = $status;
4942
4943
            if ($debug) {
4944
                error_log('get_status(): '.$status);
4945
                error_log('update_queue after:');
4946
                error_log(print_r($this->update_queue, 1));
4947
            }
4948
4949
            return $res;
4950
        }
4951
4952
        return false;
4953
    }
4954
4955
    /**
4956
     * Saves the last item seen's ID only in case.
4957
     */
4958
    public function save_last()
4959
    {
4960
        $course_id = api_get_course_int_id();
4961
        $debug = $this->debug;
4962
        if ($debug) {
4963
            error_log('In learnpath::save_last()', 0);
4964
        }
4965
        $session_condition = api_get_session_condition(
4966
            api_get_session_id(),
4967
            true,
4968
            false
4969
        );
4970
        $table = Database::get_course_table(TABLE_LP_VIEW);
4971
4972
        if (isset($this->current) && !api_is_invitee()) {
4973
            if ($debug) {
4974
                error_log('Saving current item ('.$this->current.') for later review', 0);
4975
            }
4976
            $sql = "UPDATE $table SET
4977
                        last_item = ".intval($this->get_current_item_id())."
4978
                    WHERE
4979
                        c_id = $course_id AND
4980
                        lp_id = ".$this->get_id()." AND
4981
                        user_id = ".$this->get_user_id()." ".$session_condition;
4982
4983
            if ($debug) {
4984
                error_log('Saving last item seen : '.$sql, 0);
4985
            }
4986
            Database::query($sql);
4987
        }
4988
4989
        if (!api_is_invitee()) {
4990
            // Save progress.
4991
            list($progress) = $this->get_progress_bar_text('%');
4992
            if ($progress >= 0 && $progress <= 100) {
4993
                $progress = (int) $progress;
4994
                $sql = "UPDATE $table SET
4995
                            progress = $progress
4996
                        WHERE
4997
                            c_id = $course_id AND
4998
                            lp_id = ".$this->get_id()." AND
4999
                            user_id = ".$this->get_user_id()." ".$session_condition;
5000
                // Ignore errors as some tables might not have the progress field just yet.
5001
                Database::query($sql);
5002
                if ($debug) {
5003
                    error_log($sql);
5004
                }
5005
                $this->progress_db = $progress;
5006
            }
5007
        }
5008
    }
5009
5010
    /**
5011
     * Sets the current item ID (checks if valid and authorized first).
5012
     *
5013
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5014
     */
5015
    public function set_current_item($item_id = null)
5016
    {
5017
        $debug = $this->debug;
5018
        if ($debug) {
5019
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5020
        }
5021
        if (empty($item_id)) {
5022
            if ($debug) {
5023
                error_log('No new current item given, ignore...', 0);
5024
            }
5025
            // Do nothing.
5026
        } else {
5027
            if ($debug) {
5028
                error_log('New current item given is '.$item_id.'...', 0);
5029
            }
5030
            if (is_numeric($item_id)) {
5031
                $item_id = (int) $item_id;
5032
                // TODO: Check in database here.
5033
                $this->last = $this->current;
5034
                $this->current = $item_id;
5035
                // TODO: Update $this->index as well.
5036
                foreach ($this->ordered_items as $index => $item) {
5037
                    if ($item == $this->current) {
5038
                        $this->index = $index;
5039
                        break;
5040
                    }
5041
                }
5042
                if ($debug) {
5043
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5044
                }
5045
            } else {
5046
                if ($debug) {
5047
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5048
                }
5049
            }
5050
        }
5051
    }
5052
5053
    /**
5054
     * Sets the encoding.
5055
     *
5056
     * @param string $enc New encoding
5057
     *
5058
     * @return bool
5059
     *
5060
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5061
     */
5062
    public function set_encoding($enc = 'UTF-8')
5063
    {
5064
        if ($this->debug > 0) {
5065
            error_log('In learnpath::set_encoding()', 0);
5066
        }
5067
5068
        $enc = api_refine_encoding_id($enc);
5069
        if (empty($enc)) {
5070
            $enc = api_get_system_encoding();
5071
        }
5072
        if (api_is_encoding_supported($enc)) {
5073
            $lp = $this->get_id();
5074
            if ($lp != 0) {
5075
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5076
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5077
                        WHERE iid = ".$lp;
5078
                $res = Database::query($sql);
5079
5080
                return $res;
5081
            }
5082
        }
5083
5084
        return false;
5085
    }
5086
5087
    /**
5088
     * Sets the JS lib setting in the database directly.
5089
     * This is the JavaScript library file this lp needs to load on startup.
5090
     *
5091
     * @param string $lib Proximity setting
5092
     *
5093
     * @return bool True on update success. False otherwise.
5094
     */
5095
    public function set_jslib($lib = '')
5096
    {
5097
        if ($this->debug > 0) {
5098
            error_log('In learnpath::set_jslib()', 0);
5099
        }
5100
        $lp = $this->get_id();
5101
5102
        if ($lp != 0) {
5103
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5104
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5105
                    WHERE iid = $lp";
5106
            $res = Database::query($sql);
5107
5108
            return $res;
5109
        } else {
5110
            return false;
5111
        }
5112
    }
5113
5114
    /**
5115
     * Sets the name of the LP maker (publisher) (and save).
5116
     *
5117
     * @param string $name Optional string giving the new content_maker of this learnpath
5118
     *
5119
     * @return bool True
5120
     */
5121
    public function set_maker($name = '')
5122
    {
5123
        if ($this->debug > 0) {
5124
            error_log('In learnpath::set_maker()', 0);
5125
        }
5126
        if (empty($name)) {
5127
            return false;
5128
        }
5129
        $this->maker = $name;
5130
        $table = Database::get_course_table(TABLE_LP_MAIN);
5131
        $lp_id = $this->get_id();
5132
        $sql = "UPDATE $table SET
5133
                content_maker = '".Database::escape_string($this->maker)."'
5134
                WHERE iid = $lp_id";
5135
        if ($this->debug > 2) {
5136
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5137
        }
5138
        Database::query($sql);
5139
5140
        return true;
5141
    }
5142
5143
    /**
5144
     * Sets the name of the current learnpath (and save).
5145
     *
5146
     * @param string $name Optional string giving the new name of this learnpath
5147
     *
5148
     * @return bool True/False
5149
     */
5150
    public function set_name($name = null)
5151
    {
5152
        if ($this->debug > 0) {
5153
            error_log('In learnpath::set_name()', 0);
5154
        }
5155
        if (empty($name)) {
5156
            return false;
5157
        }
5158
        $this->name = Database::escape_string($name);
5159
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5160
        $lp_id = $this->get_id();
5161
        $course_id = $this->course_info['real_id'];
5162
        $sql = "UPDATE $lp_table SET
5163
                name = '".Database::escape_string($this->name)."'
5164
                WHERE iid = $lp_id";
5165
        if ($this->debug > 2) {
5166
            error_log('lp updated with new name : '.$this->name, 0);
5167
        }
5168
        $result = Database::query($sql);
5169
        // If the lp is visible on the homepage, change his name there.
5170
        if (Database::affected_rows($result)) {
5171
            $session_id = api_get_session_id();
5172
            $session_condition = api_get_session_condition($session_id);
5173
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5174
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5175
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5176
            	    WHERE
5177
            	        c_id = $course_id AND
5178
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5179
            Database::query($sql);
5180
5181
            return true;
5182
        } else {
5183
            return false;
5184
        }
5185
    }
5186
5187
    /**
5188
     * Set index specified prefix terms for all items in this path.
5189
     *
5190
     * @param string $terms_string Comma-separated list of terms
5191
     * @param string $prefix       Xapian term prefix
5192
     *
5193
     * @return bool False on error, true otherwise
5194
     */
5195
    public function set_terms_by_prefix($terms_string, $prefix)
5196
    {
5197
        $course_id = api_get_course_int_id();
5198
        if (api_get_setting('search_enabled') !== 'true') {
5199
            return false;
5200
        }
5201
5202
        if (!extension_loaded('xapian')) {
5203
            return false;
5204
        }
5205
5206
        $terms_string = trim($terms_string);
5207
        $terms = explode(',', $terms_string);
5208
        array_walk($terms, 'trim_value');
5209
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5210
5211
        // Don't do anything if no change, verify only at DB, not the search engine.
5212
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5213
            return false;
5214
        }
5215
5216
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5217
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5218
5219
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5220
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5221
        $lp_id = (int) $_POST['lp_id'];
5222
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5223
        $result = Database::query($sql);
5224
        $di = new ChamiloIndexer();
5225
5226
        while ($lp_item = Database::fetch_array($result)) {
5227
            // Get search_did.
5228
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5229
            $sql = 'SELECT * FROM %s
5230
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5231
                    LIMIT 1';
5232
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5233
5234
            //echo $sql; echo '<br>';
5235
            $res = Database::query($sql);
5236
            if (Database::num_rows($res) > 0) {
5237
                $se_ref = Database::fetch_array($res);
5238
                // Compare terms.
5239
                $doc = $di->get_document($se_ref['search_did']);
5240
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5241
                $xterms = [];
5242
                foreach ($xapian_terms as $xapian_term) {
5243
                    $xterms[] = substr($xapian_term['name'], 1);
5244
                }
5245
5246
                $dterms = $terms;
5247
                $missing_terms = array_diff($dterms, $xterms);
5248
                $deprecated_terms = array_diff($xterms, $dterms);
5249
5250
                // Save it to search engine.
5251
                foreach ($missing_terms as $term) {
5252
                    $doc->add_term($prefix.$term, 1);
5253
                }
5254
                foreach ($deprecated_terms as $term) {
5255
                    $doc->remove_term($prefix.$term);
5256
                }
5257
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5258
                $di->getDb()->flush();
5259
            }
5260
        }
5261
5262
        return true;
5263
    }
5264
5265
    /**
5266
     * Sets the theme of the LP (local/remote) (and save).
5267
     *
5268
     * @param string $name Optional string giving the new theme of this learnpath
5269
     *
5270
     * @return bool Returns true if theme name is not empty
5271
     */
5272
    public function set_theme($name = '')
5273
    {
5274
        if ($this->debug > 0) {
5275
            error_log('In learnpath::set_theme()', 0);
5276
        }
5277
        $this->theme = $name;
5278
        $table = Database::get_course_table(TABLE_LP_MAIN);
5279
        $lp_id = $this->get_id();
5280
        $sql = "UPDATE $table 
5281
                SET theme = '".Database::escape_string($this->theme)."'
5282
                WHERE iid = $lp_id";
5283
        if ($this->debug > 2) {
5284
            error_log('lp updated with new theme : '.$this->theme, 0);
5285
        }
5286
        Database::query($sql);
5287
5288
        return true;
5289
    }
5290
5291
    /**
5292
     * Sets the image of an LP (and save).
5293
     *
5294
     * @param string $name Optional string giving the new image of this learnpath
5295
     *
5296
     * @return bool Returns true if theme name is not empty
5297
     */
5298
    public function set_preview_image($name = '')
5299
    {
5300
        if ($this->debug > 0) {
5301
            error_log('In learnpath::set_preview_image()', 0);
5302
        }
5303
5304
        $this->preview_image = $name;
5305
        $table = Database::get_course_table(TABLE_LP_MAIN);
5306
        $lp_id = $this->get_id();
5307
        $sql = "UPDATE $table SET
5308
                preview_image = '".Database::escape_string($this->preview_image)."'
5309
                WHERE iid = $lp_id";
5310
        if ($this->debug > 2) {
5311
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5312
        }
5313
        Database::query($sql);
5314
5315
        return true;
5316
    }
5317
5318
    /**
5319
     * Sets the author of a LP (and save).
5320
     *
5321
     * @param string $name Optional string giving the new author of this learnpath
5322
     *
5323
     * @return bool Returns true if author's name is not empty
5324
     */
5325
    public function set_author($name = '')
5326
    {
5327
        if ($this->debug > 0) {
5328
            error_log('In learnpath::set_author()', 0);
5329
        }
5330
        $this->author = $name;
5331
        $table = Database::get_course_table(TABLE_LP_MAIN);
5332
        $lp_id = $this->get_id();
5333
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5334
                WHERE iid = $lp_id";
5335
        if ($this->debug > 2) {
5336
            error_log('lp updated with new preview author : '.$this->author, 0);
5337
        }
5338
        Database::query($sql);
5339
5340
        return true;
5341
    }
5342
5343
    /**
5344
     * Sets the hide_toc_frame parameter of a LP (and save).
5345
     *
5346
     * @param int $hide 1 if frame is hidden 0 then else
5347
     *
5348
     * @return bool Returns true if author's name is not empty
5349
     */
5350
    public function set_hide_toc_frame($hide)
5351
    {
5352
        if ($this->debug > 0) {
5353
            error_log('In learnpath::set_hide_toc_frame()', 0);
5354
        }
5355
        if (intval($hide) == $hide) {
5356
            $this->hide_toc_frame = $hide;
5357
            $table = Database::get_course_table(TABLE_LP_MAIN);
5358
            $lp_id = $this->get_id();
5359
            $sql = "UPDATE $table SET
5360
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5361
                    WHERE iid = $lp_id";
5362
            if ($this->debug > 2) {
5363
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5364
            }
5365
            Database::query($sql);
5366
5367
            return true;
5368
        } else {
5369
            return false;
5370
        }
5371
    }
5372
5373
    /**
5374
     * Sets the prerequisite of a LP (and save).
5375
     *
5376
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5377
     *
5378
     * @return bool returns true if prerequisite is not empty
5379
     */
5380
    public function set_prerequisite($prerequisite)
5381
    {
5382
        if ($this->debug > 0) {
5383
            error_log('In learnpath::set_prerequisite()', 0);
5384
        }
5385
        $this->prerequisite = (int) $prerequisite;
5386
        $table = Database::get_course_table(TABLE_LP_MAIN);
5387
        $lp_id = $this->get_id();
5388
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5389
                WHERE iid = $lp_id";
5390
        if ($this->debug > 2) {
5391
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5392
        }
5393
        Database::query($sql);
5394
5395
        return true;
5396
    }
5397
5398
    /**
5399
     * Sets the location/proximity of the LP (local/remote) (and save).
5400
     *
5401
     * @param string $name Optional string giving the new location of this learnpath
5402
     *
5403
     * @return bool True on success / False on error
5404
     */
5405
    public function set_proximity($name = '')
5406
    {
5407
        if ($this->debug > 0) {
5408
            error_log('In learnpath::set_proximity()', 0);
5409
        }
5410
        if (empty($name)) {
5411
            return false;
5412
        }
5413
5414
        $this->proximity = $name;
5415
        $table = Database::get_course_table(TABLE_LP_MAIN);
5416
        $lp_id = $this->get_id();
5417
        $sql = "UPDATE $table SET
5418
                    content_local = '".Database::escape_string($name)."'
5419
                WHERE iid = $lp_id";
5420
        if ($this->debug > 2) {
5421
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5422
        }
5423
        Database::query($sql);
5424
5425
        return true;
5426
    }
5427
5428
    /**
5429
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5430
     *
5431
     * @param int $id DB ID of the item
5432
     */
5433
    public function set_previous_item($id)
5434
    {
5435
        if ($this->debug > 0) {
5436
            error_log('In learnpath::set_previous_item()', 0);
5437
        }
5438
        $this->last = $id;
5439
    }
5440
5441
    /**
5442
     * Sets use_max_score.
5443
     *
5444
     * @param int $use_max_score Optional string giving the new location of this learnpath
5445
     *
5446
     * @return bool True on success / False on error
5447
     */
5448
    public function set_use_max_score($use_max_score = 1)
5449
    {
5450
        if ($this->debug > 0) {
5451
            error_log('In learnpath::set_use_max_score()', 0);
5452
        }
5453
        $use_max_score = (int) $use_max_score;
5454
        $this->use_max_score = $use_max_score;
5455
        $table = Database::get_course_table(TABLE_LP_MAIN);
5456
        $lp_id = $this->get_id();
5457
        $sql = "UPDATE $table SET
5458
                    use_max_score = '".$this->use_max_score."'
5459
                WHERE iid = $lp_id";
5460
5461
        if ($this->debug > 2) {
5462
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5463
        }
5464
        Database::query($sql);
5465
5466
        return true;
5467
    }
5468
5469
    /**
5470
     * Sets and saves the expired_on date.
5471
     *
5472
     * @param string $expired_on Optional string giving the new author of this learnpath
5473
     *
5474
     * @throws \Doctrine\ORM\OptimisticLockException
5475
     *
5476
     * @return bool Returns true if author's name is not empty
5477
     */
5478
    public function set_expired_on($expired_on)
5479
    {
5480
        if ($this->debug > 0) {
5481
            error_log('In learnpath::set_expired_on()', 0);
5482
        }
5483
5484
        $em = Database::getManager();
5485
        /** @var CLp $lp */
5486
        $lp = $em
5487
            ->getRepository('ChamiloCourseBundle:CLp')
5488
            ->findOneBy(
5489
                [
5490
                    'iid' => $this->get_id(),
5491
                ]
5492
            );
5493
5494
        if (!$lp) {
5495
            return false;
5496
        }
5497
5498
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5499
5500
        $lp->setExpiredOn($this->expired_on);
5501
        $em->persist($lp);
5502
        $em->flush();
5503
5504
        if ($this->debug > 2) {
5505
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5506
        }
5507
5508
        return true;
5509
    }
5510
5511
    /**
5512
     * Sets and saves the publicated_on date.
5513
     *
5514
     * @param string $publicated_on Optional string giving the new author of this learnpath
5515
     *
5516
     * @throws \Doctrine\ORM\OptimisticLockException
5517
     *
5518
     * @return bool Returns true if author's name is not empty
5519
     */
5520
    public function set_publicated_on($publicated_on)
5521
    {
5522
        if ($this->debug > 0) {
5523
            error_log('In learnpath::set_expired_on()', 0);
5524
        }
5525
5526
        $em = Database::getManager();
5527
        /** @var CLp $lp */
5528
        $lp = $em
5529
            ->getRepository('ChamiloCourseBundle:CLp')
5530
            ->findOneBy(
5531
                [
5532
                    'iid' => $this->get_id(),
5533
                ]
5534
            );
5535
5536
        if (!$lp) {
5537
            return false;
5538
        }
5539
5540
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5541
        $lp->setPublicatedOn($this->publicated_on);
5542
        $em->persist($lp);
5543
        $em->flush();
5544
5545
        if ($this->debug > 2) {
5546
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5547
        }
5548
5549
        return true;
5550
    }
5551
5552
    /**
5553
     * Sets and saves the expired_on date.
5554
     *
5555
     * @return bool Returns true if author's name is not empty
5556
     */
5557
    public function set_modified_on()
5558
    {
5559
        if ($this->debug > 0) {
5560
            error_log('In learnpath::set_expired_on()', 0);
5561
        }
5562
        $this->modified_on = api_get_utc_datetime();
5563
        $table = Database::get_course_table(TABLE_LP_MAIN);
5564
        $lp_id = $this->get_id();
5565
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5566
                WHERE iid = $lp_id";
5567
        if ($this->debug > 2) {
5568
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5569
        }
5570
        Database::query($sql);
5571
5572
        return true;
5573
    }
5574
5575
    /**
5576
     * Sets the object's error message.
5577
     *
5578
     * @param string $error Error message. If empty, reinits the error string
5579
     */
5580
    public function set_error_msg($error = '')
5581
    {
5582
        if ($this->debug > 0) {
5583
            error_log('In learnpath::set_error_msg()', 0);
5584
        }
5585
        if (empty($error)) {
5586
            $this->error = '';
5587
        } else {
5588
            $this->error .= $error;
5589
        }
5590
    }
5591
5592
    /**
5593
     * Launches the current item if not 'sco'
5594
     * (starts timer and make sure there is a record ready in the DB).
5595
     *
5596
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5597
     *
5598
     * @return bool
5599
     */
5600
    public function start_current_item($allow_new_attempt = false)
5601
    {
5602
        $debug = $this->debug;
5603
        if ($debug) {
5604
            error_log('In learnpath::start_current_item()');
5605
            error_log('current: '.$this->current);
5606
        }
5607
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5608
            $type = $this->get_type();
5609
            $item_type = $this->items[$this->current]->get_type();
5610
            if (($type == 2 && $item_type != 'sco') ||
5611
                ($type == 3 && $item_type != 'au') ||
5612
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5613
            ) {
5614
                if ($debug) {
5615
                    error_log('item type: '.$item_type);
5616
                    error_log('lp type: '.$type);
5617
                }
5618
                $this->items[$this->current]->open($allow_new_attempt);
5619
                $this->autocomplete_parents($this->current);
5620
                $prereq_check = $this->prerequisites_match($this->current);
5621
                if ($debug) {
5622
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5623
                }
5624
                $this->items[$this->current]->save(false, $prereq_check);
5625
            }
5626
            // If sco, then it is supposed to have been updated by some other call.
5627
            if ($item_type == 'sco') {
5628
                $this->items[$this->current]->restart();
5629
            }
5630
        }
5631
        if ($debug) {
5632
            error_log('lp_view_session_id');
5633
            error_log($this->lp_view_session_id);
5634
            error_log('api session id');
5635
            error_log(api_get_session_id());
5636
            error_log('End of learnpath::start_current_item()');
5637
        }
5638
5639
        return true;
5640
    }
5641
5642
    /**
5643
     * Stops the processing and counters for the old item (as held in $this->last).
5644
     *
5645
     * @return bool True/False
5646
     */
5647
    public function stop_previous_item()
5648
    {
5649
        $debug = $this->debug;
5650
        if ($debug) {
5651
            error_log('In learnpath::stop_previous_item()', 0);
5652
        }
5653
5654
        if ($this->last != 0 && $this->last != $this->current &&
5655
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5656
        ) {
5657
            if ($debug) {
5658
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5659
            }
5660
            switch ($this->get_type()) {
5661
                case '3':
5662
                    if ($this->items[$this->last]->get_type() != 'au') {
5663
                        if ($debug) {
5664
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5665
                        }
5666
                        $this->items[$this->last]->close();
5667
                    } else {
5668
                        if ($debug) {
5669
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5670
                        }
5671
                    }
5672
                    break;
5673
                case '2':
5674
                    if ($this->items[$this->last]->get_type() != 'sco') {
5675
                        if ($debug) {
5676
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5677
                        }
5678
                        $this->items[$this->last]->close();
5679
                    } else {
5680
                        if ($debug) {
5681
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5682
                        }
5683
                    }
5684
                    break;
5685
                case '1':
5686
                default:
5687
                    if ($debug) {
5688
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5689
                    }
5690
                    $this->items[$this->last]->close();
5691
                    break;
5692
            }
5693
        } else {
5694
            if ($debug) {
5695
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5696
            }
5697
5698
            return false;
5699
        }
5700
5701
        return true;
5702
    }
5703
5704
    /**
5705
     * Updates the default view mode from fullscreen to embedded and inversely.
5706
     *
5707
     * @return string The current default view mode ('fullscreen' or 'embedded')
5708
     */
5709
    public function update_default_view_mode()
5710
    {
5711
        if ($this->debug > 0) {
5712
            error_log('In learnpath::update_default_view_mode()', 0);
5713
        }
5714
        $table = Database::get_course_table(TABLE_LP_MAIN);
5715
        $sql = "SELECT * FROM $table
5716
                WHERE iid = ".$this->get_id();
5717
        $res = Database::query($sql);
5718
        if (Database::num_rows($res) > 0) {
5719
            $row = Database::fetch_array($res);
5720
            $default_view_mode = $row['default_view_mod'];
5721
            $view_mode = $default_view_mode;
5722
            switch ($default_view_mode) {
5723
                case 'fullscreen': // default with popup
5724
                    $view_mode = 'embedded';
5725
                    break;
5726
                case 'embedded': // default view with left menu
5727
                    $view_mode = 'embedframe';
5728
                    break;
5729
                case 'embedframe': //folded menu
5730
                    $view_mode = 'impress';
5731
                    break;
5732
                case 'impress':
5733
                    $view_mode = 'fullscreen';
5734
                    break;
5735
            }
5736
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5737
                    WHERE iid = ".$this->get_id();
5738
            Database::query($sql);
5739
            $this->mode = $view_mode;
5740
5741
            return $view_mode;
5742
        } else {
5743
            if ($this->debug > 2) {
5744
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5745
            }
5746
        }
5747
5748
        return -1;
5749
    }
5750
5751
    /**
5752
     * Updates the default behaviour about auto-commiting SCORM updates.
5753
     *
5754
     * @return bool True if auto-commit has been set to 'on', false otherwise
5755
     */
5756
    public function update_default_scorm_commit()
5757
    {
5758
        if ($this->debug > 0) {
5759
            error_log('In learnpath::update_default_scorm_commit()', 0);
5760
        }
5761
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5762
        $sql = "SELECT * FROM $lp_table
5763
                WHERE iid = ".$this->get_id();
5764
        $res = Database::query($sql);
5765
        if (Database::num_rows($res) > 0) {
5766
            $row = Database::fetch_array($res);
5767
            $force = $row['force_commit'];
5768
            if ($force == 1) {
5769
                $force = 0;
5770
                $force_return = false;
5771
            } elseif ($force == 0) {
5772
                $force = 1;
5773
                $force_return = true;
5774
            }
5775
            $sql = "UPDATE $lp_table SET force_commit = $force
5776
                    WHERE iid = ".$this->get_id();
5777
            Database::query($sql);
5778
            $this->force_commit = $force_return;
5779
5780
            return $force_return;
5781
        } else {
5782
            if ($this->debug > 2) {
5783
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5784
            }
5785
        }
5786
5787
        return -1;
5788
    }
5789
5790
    /**
5791
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5792
     *
5793
     * @return bool True on success, false on failure
5794
     */
5795
    public function update_display_order()
5796
    {
5797
        $course_id = api_get_course_int_id();
5798
        $table = Database::get_course_table(TABLE_LP_MAIN);
5799
        $sql = "SELECT * FROM $table 
5800
                WHERE c_id = $course_id 
5801
                ORDER BY display_order";
5802
        $res = Database::query($sql);
5803
        if ($res === false) {
5804
            return false;
5805
        }
5806
5807
        $num = Database::num_rows($res);
5808
        // First check the order is correct, globally (might be wrong because
5809
        // of versions < 1.8.4).
5810
        if ($num > 0) {
5811
            $i = 1;
5812
            while ($row = Database::fetch_array($res)) {
5813
                if ($row['display_order'] != $i) {
5814
                    // If we find a gap in the order, we need to fix it.
5815
                    $sql = "UPDATE $table SET display_order = $i
5816
                            WHERE iid = ".$row['iid'];
5817
                    Database::query($sql);
5818
                }
5819
                $i++;
5820
            }
5821
        }
5822
5823
        return true;
5824
    }
5825
5826
    /**
5827
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5828
     *
5829
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5830
     */
5831
    public function update_reinit()
5832
    {
5833
        if ($this->debug > 0) {
5834
            error_log('In learnpath::update_reinit()', 0);
5835
        }
5836
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5837
        $sql = "SELECT * FROM $lp_table
5838
                WHERE iid = ".$this->get_id();
5839
        $res = Database::query($sql);
5840
        if (Database::num_rows($res) > 0) {
5841
            $row = Database::fetch_array($res);
5842
            $force = $row['prevent_reinit'];
5843
            if ($force == 1) {
5844
                $force = 0;
5845
            } elseif ($force == 0) {
5846
                $force = 1;
5847
            }
5848
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5849
                    WHERE iid = ".$this->get_id();
5850
            Database::query($sql);
5851
            $this->prevent_reinit = $force;
5852
5853
            return $force;
5854
        } else {
5855
            if ($this->debug > 2) {
5856
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5857
            }
5858
        }
5859
5860
        return -1;
5861
    }
5862
5863
    /**
5864
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5865
     *
5866
     * @return string 'single', 'multi' or 'seriousgame'
5867
     *
5868
     * @author ndiechburg <[email protected]>
5869
     */
5870
    public function get_attempt_mode()
5871
    {
5872
        //Set default value for seriousgame_mode
5873
        if (!isset($this->seriousgame_mode)) {
5874
            $this->seriousgame_mode = 0;
5875
        }
5876
        // Set default value for prevent_reinit
5877
        if (!isset($this->prevent_reinit)) {
5878
            $this->prevent_reinit = 1;
5879
        }
5880
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5881
            return 'seriousgame';
5882
        }
5883
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5884
            return 'single';
5885
        }
5886
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5887
            return 'multiple';
5888
        }
5889
5890
        return 'single';
5891
    }
5892
5893
    /**
5894
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5895
     *
5896
     * @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...
5897
     *
5898
     * @return bool
5899
     *
5900
     * @author ndiechburg <[email protected]>
5901
     */
5902
    public function set_attempt_mode($mode)
5903
    {
5904
        switch ($mode) {
5905
            case 'seriousgame':
5906
                $sg_mode = 1;
5907
                $prevent_reinit = 1;
5908
                break;
5909
            case 'single':
5910
                $sg_mode = 0;
5911
                $prevent_reinit = 1;
5912
                break;
5913
            case 'multiple':
5914
                $sg_mode = 0;
5915
                $prevent_reinit = 0;
5916
                break;
5917
            default:
5918
                $sg_mode = 0;
5919
                $prevent_reinit = 0;
5920
                break;
5921
        }
5922
        $this->prevent_reinit = $prevent_reinit;
5923
        $this->seriousgame_mode = $sg_mode;
5924
        $table = Database::get_course_table(TABLE_LP_MAIN);
5925
        $sql = "UPDATE $table SET
5926
                prevent_reinit = $prevent_reinit ,
5927
                seriousgame_mode = $sg_mode
5928
                WHERE iid = ".$this->get_id();
5929
        $res = Database::query($sql);
5930
        if ($res) {
5931
            return true;
5932
        } else {
5933
            return false;
5934
        }
5935
    }
5936
5937
    /**
5938
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5939
     *
5940
     * @author ndiechburg <[email protected]>
5941
     */
5942
    public function switch_attempt_mode()
5943
    {
5944
        if ($this->debug > 0) {
5945
            error_log('In learnpath::switch_attempt_mode()', 0);
5946
        }
5947
        $mode = $this->get_attempt_mode();
5948
        switch ($mode) {
5949
            case 'single':
5950
                $next_mode = 'multiple';
5951
                break;
5952
            case 'multiple':
5953
                $next_mode = 'seriousgame';
5954
                break;
5955
            case 'seriousgame':
5956
                $next_mode = 'single';
5957
                break;
5958
            default:
5959
                $next_mode = 'single';
5960
                break;
5961
        }
5962
        $this->set_attempt_mode($next_mode);
5963
    }
5964
5965
    /**
5966
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5967
     * but possibility to do again a completed item.
5968
     *
5969
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5970
     *
5971
     * @author ndiechburg <[email protected]>
5972
     */
5973
    public function set_seriousgame_mode()
5974
    {
5975
        if ($this->debug > 0) {
5976
            error_log('In learnpath::set_seriousgame_mode()', 0);
5977
        }
5978
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5979
        $sql = "SELECT * FROM $lp_table 
5980
                WHERE iid = ".$this->get_id();
5981
        $res = Database::query($sql);
5982
        if (Database::num_rows($res) > 0) {
5983
            $row = Database::fetch_array($res);
5984
            $force = $row['seriousgame_mode'];
5985
            if ($force == 1) {
5986
                $force = 0;
5987
            } elseif ($force == 0) {
5988
                $force = 1;
5989
            }
5990
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5991
			        WHERE iid = ".$this->get_id();
5992
            Database::query($sql);
5993
            $this->seriousgame_mode = $force;
5994
5995
            return $force;
5996
        } else {
5997
            if ($this->debug > 2) {
5998
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
5999
            }
6000
        }
6001
6002
        return -1;
6003
    }
6004
6005
    /**
6006
     * Updates the "scorm_debug" value that shows or hide the debug window.
6007
     *
6008
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6009
     */
6010
    public function update_scorm_debug()
6011
    {
6012
        if ($this->debug > 0) {
6013
            error_log('In learnpath::update_scorm_debug()', 0);
6014
        }
6015
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6016
        $sql = "SELECT * FROM $lp_table
6017
                WHERE iid = ".$this->get_id();
6018
        $res = Database::query($sql);
6019
        if (Database::num_rows($res) > 0) {
6020
            $row = Database::fetch_array($res);
6021
            $force = $row['debug'];
6022
            if ($force == 1) {
6023
                $force = 0;
6024
            } elseif ($force == 0) {
6025
                $force = 1;
6026
            }
6027
            $sql = "UPDATE $lp_table SET debug = $force
6028
                    WHERE iid = ".$this->get_id();
6029
            Database::query($sql);
6030
            $this->scorm_debug = $force;
6031
6032
            return $force;
6033
        } else {
6034
            if ($this->debug > 2) {
6035
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6036
            }
6037
        }
6038
6039
        return -1;
6040
    }
6041
6042
    /**
6043
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6044
     *
6045
     * @author Kevin Van Den Haute
6046
     *
6047
     * @param  array
6048
     */
6049
    public function tree_array($array)
6050
    {
6051
        if ($this->debug > 1) {
6052
            error_log('In learnpath::tree_array()', 0);
6053
        }
6054
        $array = $this->sort_tree_array($array);
6055
        $this->create_tree_array($array);
6056
    }
6057
6058
    /**
6059
     * Creates an array with the elements of the learning path tree in it.
6060
     *
6061
     * @author Kevin Van Den Haute
6062
     *
6063
     * @param array $array
6064
     * @param int   $parent
6065
     * @param int   $depth
6066
     * @param array $tmp
6067
     */
6068
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6069
    {
6070
        if ($this->debug > 1) {
6071
            error_log('In learnpath::create_tree_array())', 0);
6072
        }
6073
6074
        if (is_array($array)) {
6075
            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...
6076
                if ($array[$i]['parent_item_id'] == $parent) {
6077
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6078
                        $tmp[] = $array[$i]['parent_item_id'];
6079
                        $depth++;
6080
                    }
6081
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6082
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6083
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6084
6085
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6086
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6087
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6088
                    $this->arrMenu[] = [
6089
                        'id' => $array[$i]['id'],
6090
                        'ref' => $ref,
6091
                        'item_type' => $array[$i]['item_type'],
6092
                        'title' => $array[$i]['title'],
6093
                        'path' => $path,
6094
                        'description' => $array[$i]['description'],
6095
                        'parent_item_id' => $array[$i]['parent_item_id'],
6096
                        'previous_item_id' => $array[$i]['previous_item_id'],
6097
                        'next_item_id' => $array[$i]['next_item_id'],
6098
                        'min_score' => $array[$i]['min_score'],
6099
                        'max_score' => $array[$i]['max_score'],
6100
                        'mastery_score' => $array[$i]['mastery_score'],
6101
                        'display_order' => $array[$i]['display_order'],
6102
                        'prerequisite' => $preq,
6103
                        'depth' => $depth,
6104
                        'audio' => $audio,
6105
                        'prerequisite_min_score' => $prerequisiteMinScore,
6106
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6107
                    ];
6108
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6109
                }
6110
            }
6111
        }
6112
    }
6113
6114
    /**
6115
     * Sorts a multi dimensional array by parent id and display order.
6116
     *
6117
     * @author Kevin Van Den Haute
6118
     *
6119
     * @param array $array (array with al the learning path items in it)
6120
     *
6121
     * @return array
6122
     */
6123
    public function sort_tree_array($array)
6124
    {
6125
        foreach ($array as $key => $row) {
6126
            $parent[$key] = $row['parent_item_id'];
6127
            $position[$key] = $row['display_order'];
6128
        }
6129
6130
        if (count($array) > 0) {
6131
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6132
        }
6133
6134
        return $array;
6135
    }
6136
6137
    /**
6138
     * Function that creates a html list of learning path items so that we can add audio files to them.
6139
     *
6140
     * @author Kevin Van Den Haute
6141
     *
6142
     * @return string
6143
     */
6144
    public function overview()
6145
    {
6146
        if ($this->debug > 0) {
6147
            error_log('In learnpath::overview()', 0);
6148
        }
6149
6150
        $return = '';
6151
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6152
6153
        // we need to start a form when we want to update all the mp3 files
6154
        if ($update_audio == 'true') {
6155
            $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">';
6156
        }
6157
        $return .= '<div id="message"></div>';
6158
        if (count($this->items) == 0) {
6159
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6160
        } else {
6161
            $return_audio = '<table class="data_table">';
6162
            $return_audio .= '<tr>';
6163
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6164
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6165
            $return_audio .= '</tr>';
6166
6167
            if ($update_audio != 'true') {
6168
                $return .= '<div class="col-md-12">';
6169
                $return .= self::return_new_tree($update_audio);
6170
                $return .= '</div>';
6171
                $return .= Display::div(
6172
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6173
                    ['style' => 'float:left; margin-top:15px;width:100%']
6174
                );
6175
            } else {
6176
                $return_audio .= self::return_new_tree($update_audio);
6177
                $return .= $return_audio.'</table>';
6178
            }
6179
6180
            // We need to close the form when we are updating the mp3 files.
6181
            if ($update_audio == 'true') {
6182
                $return .= '<div class="footer-audio">';
6183
                $return .= Display::button(
6184
                    'save_audio',
6185
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6186
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6187
                );
6188
                $return .= '</div>';
6189
            }
6190
        }
6191
6192
        // We need to close the form when we are updating the mp3 files.
6193
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6194
            $return .= '</form>';
6195
        }
6196
6197
        return $return;
6198
    }
6199
6200
    /**
6201
     * @param string $update_audio
6202
     *
6203
     * @return array
6204
     */
6205
    public function processBuildMenuElements($update_audio = 'false')
6206
    {
6207
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6208
        $course_id = api_get_course_int_id();
6209
        $table = Database::get_course_table(TABLE_LP_ITEM);
6210
6211
        $sql = "SELECT * FROM $table
6212
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6213
6214
        $result = Database::query($sql);
6215
        $arrLP = [];
6216
        while ($row = Database::fetch_array($result)) {
6217
            $arrLP[] = [
6218
                'id' => $row['iid'],
6219
                'item_type' => $row['item_type'],
6220
                'title' => Security::remove_XSS($row['title']),
6221
                'path' => $row['path'],
6222
                'description' => Security::remove_XSS($row['description']),
6223
                'parent_item_id' => $row['parent_item_id'],
6224
                'previous_item_id' => $row['previous_item_id'],
6225
                'next_item_id' => $row['next_item_id'],
6226
                'max_score' => $row['max_score'],
6227
                'min_score' => $row['min_score'],
6228
                'mastery_score' => $row['mastery_score'],
6229
                'prerequisite' => $row['prerequisite'],
6230
                'display_order' => $row['display_order'],
6231
                'audio' => $row['audio'],
6232
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6233
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6234
            ];
6235
        }
6236
6237
        $this->tree_array($arrLP);
6238
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6239
        unset($this->arrMenu);
6240
        $default_data = null;
6241
        $default_content = null;
6242
        $elements = [];
6243
        $return_audio = null;
6244
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6245
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6246
6247
        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...
6248
            $title = $arrLP[$i]['title'];
6249
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6250
6251
            // Link for the documents
6252
            if ($arrLP[$i]['item_type'] == 'document') {
6253
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6254
                $title_cut = Display::url(
6255
                    $title_cut,
6256
                    $url,
6257
                    [
6258
                        'class' => 'ajax moved',
6259
                        'data-title' => $title,
6260
                        'title' => $title,
6261
                    ]
6262
                );
6263
            }
6264
6265
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6266
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6267
                Session::write('pathItem', $arrLP[$i]['path']);
6268
            }
6269
6270
            if (($i % 2) == 0) {
6271
                $oddClass = 'row_odd';
6272
            } else {
6273
                $oddClass = 'row_even';
6274
            }
6275
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6276
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6277
6278
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6279
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6280
            } else {
6281
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6282
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6283
                } else {
6284
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6285
                        $icon = Display::return_icon('certificate.png');
6286
                    } else {
6287
                        $icon = Display::return_icon('folder_document.gif');
6288
                    }
6289
                }
6290
            }
6291
6292
            // The audio column.
6293
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6294
            $audio = '';
6295
            if (!$update_audio || $update_audio != 'true') {
6296
                if (empty($arrLP[$i]['audio'])) {
6297
                    $audio .= '';
6298
                }
6299
            } else {
6300
                $types = self::getChapterTypes();
6301
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6302
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6303
                    if (!empty($arrLP[$i]['audio'])) {
6304
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6305
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6306
                    }
6307
                }
6308
            }
6309
6310
            $return_audio .= Display::span($icon.' '.$title).
6311
                Display::tag(
6312
                    'td',
6313
                    $audio,
6314
                    ['style' => '']
6315
                );
6316
            $return_audio .= '</td>';
6317
            $move_icon = '';
6318
            $move_item_icon = '';
6319
            $edit_icon = '';
6320
            $delete_icon = '';
6321
            $audio_icon = '';
6322
            $prerequisities_icon = '';
6323
            $forumIcon = '';
6324
            $previewIcon = '';
6325
            $pluginCalendarIcon = '';
6326
6327
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6328
            $plugin = null;
6329
            if ($pluginCalendar) {
6330
                $plugin = LearningCalendarPlugin::create();
6331
            }
6332
6333
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6334
6335
            if ($is_allowed_to_edit) {
6336
                if (!$update_audio || $update_audio != 'true') {
6337
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6338
                        $move_icon .= '<a class="moved" href="#">';
6339
                        $move_icon .= Display::return_icon(
6340
                            'move_everywhere.png',
6341
                            get_lang('Move'),
6342
                            [],
6343
                            ICON_SIZE_TINY
6344
                        );
6345
                        $move_icon .= '</a>';
6346
                    }
6347
                }
6348
6349
                // No edit for this item types
6350
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6351
                    if ($arrLP[$i]['item_type'] != 'dir') {
6352
                        $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">';
6353
                        $edit_icon .= Display::return_icon(
6354
                            'edit.png',
6355
                            get_lang('LearnpathEditModule'),
6356
                            [],
6357
                            ICON_SIZE_TINY
6358
                        );
6359
                        $edit_icon .= '</a>';
6360
6361
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6362
                            $forumThread = null;
6363
                            if (isset($this->items[$arrLP[$i]['id']])) {
6364
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6365
                                    $this->course_int_id,
6366
                                    $this->lp_session_id
6367
                                );
6368
                            }
6369
                            if ($forumThread) {
6370
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6371
                                        'action' => 'dissociate_forum',
6372
                                        'id' => $arrLP[$i]['id'],
6373
                                        'lp_id' => $this->lp_id,
6374
                                    ]);
6375
                                $forumIcon = Display::url(
6376
                                    Display::return_icon(
6377
                                        'forum.png',
6378
                                        get_lang('DissociateForumToLPItem'),
6379
                                        [],
6380
                                        ICON_SIZE_TINY
6381
                                    ),
6382
                                    $forumIconUrl,
6383
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6384
                                );
6385
                            } else {
6386
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6387
                                        'action' => 'create_forum',
6388
                                        'id' => $arrLP[$i]['id'],
6389
                                        'lp_id' => $this->lp_id,
6390
                                    ]);
6391
                                $forumIcon = Display::url(
6392
                                    Display::return_icon(
6393
                                        'forum.png',
6394
                                        get_lang('AssociateForumToLPItem'),
6395
                                        [],
6396
                                        ICON_SIZE_TINY
6397
                                    ),
6398
                                    $forumIconUrl,
6399
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6400
                                );
6401
                            }
6402
                        }
6403
                    } else {
6404
                        $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">';
6405
                        $edit_icon .= Display::return_icon(
6406
                            'edit.png',
6407
                            get_lang('LearnpathEditModule'),
6408
                            [],
6409
                            ICON_SIZE_TINY
6410
                        );
6411
                        $edit_icon .= '</a>';
6412
                    }
6413
                } else {
6414
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6415
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6416
                        $edit_icon .= Display::return_icon(
6417
                            'edit.png',
6418
                            get_lang('Edit'),
6419
                            [],
6420
                            ICON_SIZE_TINY
6421
                        );
6422
                        $edit_icon .= '</a>';
6423
                    }
6424
                }
6425
6426
                if ($pluginCalendar) {
6427
                    $pluginLink = $pluginUrl.
6428
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6429
6430
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6431
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6432
                    if ($itemInfo && $itemInfo['value'] == 1) {
6433
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6434
                    }
6435
                    $pluginCalendarIcon = Display::url(
6436
                        $iconCalendar,
6437
                        $pluginLink,
6438
                        ['class' => 'btn btn-default']
6439
                    );
6440
                }
6441
6442
                $delete_icon .= ' <a 
6443
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6444
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6445
                    class="btn btn-default">';
6446
                $delete_icon .= Display::return_icon(
6447
                    'delete.png',
6448
                    get_lang('LearnpathDeleteModule'),
6449
                    [],
6450
                    ICON_SIZE_TINY
6451
                );
6452
                $delete_icon .= '</a>';
6453
6454
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6455
                $previewImage = Display::return_icon(
6456
                    'preview_view.png',
6457
                    get_lang('Preview'),
6458
                    [],
6459
                    ICON_SIZE_TINY
6460
                );
6461
6462
                switch ($arrLP[$i]['item_type']) {
6463
                    case TOOL_DOCUMENT:
6464
                    case TOOL_LP_FINAL_ITEM:
6465
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6466
                        $previewIcon = Display::url(
6467
                            $previewImage,
6468
                            $urlPreviewLink,
6469
                            [
6470
                                'target' => '_blank',
6471
                                'class' => 'btn btn-default',
6472
                                'data-title' => $arrLP[$i]['title'],
6473
                                'title' => $arrLP[$i]['title'],
6474
                            ]
6475
                        );
6476
                        break;
6477
                    case TOOL_THREAD:
6478
                    case TOOL_FORUM:
6479
                    case TOOL_QUIZ:
6480
                    case TOOL_STUDENTPUBLICATION:
6481
                    case TOOL_LP_FINAL_ITEM:
6482
                    case TOOL_LINK:
6483
                        //$target = '';
6484
                        //$class = 'btn btn-default ajax';
6485
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6486
                        $class = 'btn btn-default';
6487
                        $target = '_blank';
6488
                        //}
6489
6490
                        $link = self::rl_get_resource_link_for_learnpath(
6491
                            $this->course_int_id,
6492
                            $this->lp_id,
6493
                            $arrLP[$i]['id'],
6494
                            0
6495
                        );
6496
                        $previewIcon = Display::url(
6497
                            $previewImage,
6498
                            $link,
6499
                            [
6500
                                'class' => $class,
6501
                                'data-title' => $arrLP[$i]['title'],
6502
                                'title' => $arrLP[$i]['title'],
6503
                                'target' => $target,
6504
                            ]
6505
                        );
6506
                        break;
6507
                    default:
6508
                        $previewIcon = Display::url(
6509
                            $previewImage,
6510
                            $url.'&action=view_item',
6511
                            ['class' => 'btn btn-default', 'target' => '_blank']
6512
                        );
6513
                        break;
6514
                }
6515
6516
                if ($arrLP[$i]['item_type'] != 'dir') {
6517
                    $prerequisities_icon = Display::url(
6518
                        Display::return_icon(
6519
                            'accept.png',
6520
                            get_lang('LearnpathPrerequisites'),
6521
                            [],
6522
                            ICON_SIZE_TINY
6523
                        ),
6524
                        $url.'&action=edit_item_prereq',
6525
                        ['class' => 'btn btn-default']
6526
                    );
6527
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6528
                        $move_item_icon = Display::url(
6529
                            Display::return_icon(
6530
                                'move.png',
6531
                                get_lang('Move'),
6532
                                [],
6533
                                ICON_SIZE_TINY
6534
                            ),
6535
                            $url.'&action=move_item',
6536
                            ['class' => 'btn btn-default']
6537
                        );
6538
                    }
6539
                    $audio_icon = Display::url(
6540
                        Display::return_icon(
6541
                            'audio.png',
6542
                            get_lang('UplUpload'),
6543
                            [],
6544
                            ICON_SIZE_TINY
6545
                        ),
6546
                        $url.'&action=add_audio',
6547
                        ['class' => 'btn btn-default']
6548
                    );
6549
                }
6550
            }
6551
            if ($update_audio != 'true') {
6552
                $row = $move_icon.' '.$icon.
6553
                    Display::span($title_cut).
6554
                    Display::tag(
6555
                        'div',
6556
                        "<div class=\"btn-group btn-group-xs\">
6557
                                    $previewIcon 
6558
                                    $audio 
6559
                                    $edit_icon 
6560
                                    $pluginCalendarIcon
6561
                                    $forumIcon 
6562
                                    $prerequisities_icon 
6563
                                    $move_item_icon 
6564
                                    $audio_icon 
6565
                                    $delete_icon
6566
                                </div>",
6567
                        ['class' => 'btn-toolbar button_actions']
6568
                    );
6569
            } else {
6570
                $row =
6571
                    Display::span($title.$icon).
6572
                    Display::span($audio, ['class' => 'button_actions']);
6573
            }
6574
6575
            $parent_id = $arrLP[$i]['parent_item_id'];
6576
            $default_data[$arrLP[$i]['id']] = $row;
6577
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6578
6579
            if (empty($parent_id)) {
6580
                $elements[$arrLP[$i]['id']]['data'] = $row;
6581
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6582
            } else {
6583
                $parent_arrays = [];
6584
                if ($arrLP[$i]['depth'] > 1) {
6585
                    //Getting list of parents
6586
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6587
                        foreach ($arrLP as $item) {
6588
                            if ($item['id'] == $parent_id) {
6589
                                if ($item['parent_item_id'] == 0) {
6590
                                    $parent_id = $item['id'];
6591
                                    break;
6592
                                } else {
6593
                                    $parent_id = $item['parent_item_id'];
6594
                                    if (empty($parent_arrays)) {
6595
                                        $parent_arrays[] = intval($item['id']);
6596
                                    }
6597
                                    $parent_arrays[] = $parent_id;
6598
                                    break;
6599
                                }
6600
                            }
6601
                        }
6602
                    }
6603
                }
6604
6605
                if (!empty($parent_arrays)) {
6606
                    $parent_arrays = array_reverse($parent_arrays);
6607
                    $val = '$elements';
6608
                    $x = 0;
6609
                    foreach ($parent_arrays as $item) {
6610
                        if ($x != count($parent_arrays) - 1) {
6611
                            $val .= '["'.$item.'"]["children"]';
6612
                        } else {
6613
                            $val .= '["'.$item.'"]["children"]';
6614
                        }
6615
                        $x++;
6616
                    }
6617
                    $val .= "";
6618
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6619
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6620
                } else {
6621
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6622
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6623
                }
6624
            }
6625
        }
6626
6627
        return [
6628
            'elements' => $elements,
6629
            'default_data' => $default_data,
6630
            'default_content' => $default_content,
6631
            'return_audio' => $return_audio,
6632
        ];
6633
    }
6634
6635
    /**
6636
     * @param string $updateAudio true/false strings
6637
     *
6638
     * @return string
6639
     */
6640
    public function returnLpItemList($updateAudio)
6641
    {
6642
        $result = $this->processBuildMenuElements($updateAudio);
6643
6644
        $html = self::print_recursive(
6645
            $result['elements'],
6646
            $result['default_data'],
6647
            $result['default_content']
6648
        );
6649
6650
        if (!empty($html)) {
6651
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6652
        }
6653
6654
        return $html;
6655
    }
6656
6657
    /**
6658
     * @param string $update_audio
6659
     * @param bool   $drop_element_here
6660
     *
6661
     * @return string
6662
     */
6663
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6664
    {
6665
        $return = '';
6666
        $result = $this->processBuildMenuElements($update_audio);
6667
6668
        $list = '<ul id="lp_item_list">';
6669
        $tree = $this->print_recursive(
6670
            $result['elements'],
6671
            $result['default_data'],
6672
            $result['default_content']
6673
        );
6674
6675
        if (!empty($tree)) {
6676
            $list .= $tree;
6677
        } else {
6678
            if ($drop_element_here) {
6679
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6680
            }
6681
        }
6682
        $list .= '</ul>';
6683
6684
        $return .= Display::panelCollapse(
6685
            $this->name,
6686
            $list,
6687
            'scorm-list',
6688
            null,
6689
            'scorm-list-accordion',
6690
            'scorm-list-collapse'
6691
        );
6692
6693
        if ($update_audio === 'true') {
6694
            $return = $result['return_audio'];
6695
        }
6696
6697
        return $return;
6698
    }
6699
6700
    /**
6701
     * @param array $elements
6702
     * @param array $default_data
6703
     * @param array $default_content
6704
     *
6705
     * @return string
6706
     */
6707
    public function print_recursive($elements, $default_data, $default_content)
6708
    {
6709
        $return = '';
6710
        foreach ($elements as $key => $item) {
6711
            if (isset($item['load_data']) || empty($item['data'])) {
6712
                $item['data'] = $default_data[$item['load_data']];
6713
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6714
            }
6715
            $sub_list = '';
6716
            if (isset($item['type']) && $item['type'] == 'dir') {
6717
                // empty value
6718
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6719
            }
6720
            if (empty($item['children'])) {
6721
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6722
                $active = null;
6723
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6724
                    $active = 'active';
6725
                }
6726
                $return .= Display::tag(
6727
                    'li',
6728
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6729
                    ['id' => $key, 'class' => 'record li_container']
6730
                );
6731
            } else {
6732
                // Sections
6733
                $data = '';
6734
                if (isset($item['children'])) {
6735
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6736
                }
6737
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6738
                $return .= Display::tag(
6739
                    'li',
6740
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6741
                    ['id' => $key, 'class' => 'record li_container']
6742
                );
6743
            }
6744
        }
6745
6746
        return $return;
6747
    }
6748
6749
    /**
6750
     * This function builds the action menu.
6751
     *
6752
     * @param bool $returnContent          Optional
6753
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6754
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6755
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6756
     *
6757
     * @return string
6758
     */
6759
    public function build_action_menu(
6760
        $returnContent = false,
6761
        $showRequirementButtons = true,
6762
        $isConfigPage = false,
6763
        $allowExpand = true
6764
    ) {
6765
        $actionsLeft = '';
6766
        $actionsRight = '';
6767
        $actionsLeft .= Display::url(
6768
            Display::return_icon(
6769
                'preview_view.png',
6770
                get_lang('Preview'),
6771
                '',
6772
                ICON_SIZE_MEDIUM
6773
            ),
6774
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6775
                'action' => 'view',
6776
                'lp_id' => $this->lp_id,
6777
                'isStudentView' => 'true',
6778
            ])
6779
        );
6780
6781
        $actionsLeft .= Display::url(
6782
            Display::return_icon(
6783
                'upload_audio.png',
6784
                get_lang('UpdateAllAudioFragments'),
6785
                '',
6786
                ICON_SIZE_MEDIUM
6787
            ),
6788
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6789
                'action' => 'admin_view',
6790
                'lp_id' => $this->lp_id,
6791
                'updateaudio' => 'true',
6792
            ])
6793
        );
6794
6795
        if (!$isConfigPage) {
6796
            $actionsLeft .= Display::url(
6797
                Display::return_icon(
6798
                    'settings.png',
6799
                    get_lang('CourseSettings'),
6800
                    '',
6801
                    ICON_SIZE_MEDIUM
6802
                ),
6803
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6804
                    'action' => 'edit',
6805
                    'lp_id' => $this->lp_id,
6806
                ])
6807
            );
6808
        } else {
6809
            $actionsLeft .= Display::url(
6810
                Display::return_icon(
6811
                    'edit.png',
6812
                    get_lang('Edit'),
6813
                    '',
6814
                    ICON_SIZE_MEDIUM
6815
                ),
6816
                'lp_controller.php?'.http_build_query([
6817
                    'action' => 'build',
6818
                    'lp_id' => $this->lp_id,
6819
                ]).'&'.api_get_cidreq()
6820
            );
6821
        }
6822
6823
        if ($allowExpand) {
6824
            $actionsLeft .= Display::url(
6825
                Display::return_icon(
6826
                    'expand.png',
6827
                    get_lang('Expand'),
6828
                    ['id' => 'expand'],
6829
                    ICON_SIZE_MEDIUM
6830
                ).
6831
                Display::return_icon(
6832
                    'contract.png',
6833
                    get_lang('Collapse'),
6834
                    ['id' => 'contract', 'class' => 'hide'],
6835
                    ICON_SIZE_MEDIUM
6836
                ),
6837
                '#',
6838
                ['role' => 'button', 'id' => 'hide_bar_template']
6839
            );
6840
        }
6841
6842
        if ($showRequirementButtons) {
6843
            $buttons = [
6844
                [
6845
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6846
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6847
                        'action' => 'set_previous_step_as_prerequisite',
6848
                        'lp_id' => $this->lp_id,
6849
                    ]),
6850
                ],
6851
                [
6852
                    'title' => get_lang('ClearAllPrerequisites'),
6853
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6854
                        'action' => 'clear_prerequisites',
6855
                        'lp_id' => $this->lp_id,
6856
                    ]),
6857
                ],
6858
            ];
6859
            $actionsRight = Display::groupButtonWithDropDown(
6860
                get_lang('PrerequisitesOptions'),
6861
                $buttons,
6862
                true
6863
            );
6864
        }
6865
6866
        $toolbar = Display::toolbarAction(
6867
            'actions-lp-controller',
6868
            [$actionsLeft, $actionsRight]
6869
        );
6870
6871
        if ($returnContent) {
6872
            return $toolbar;
6873
        }
6874
6875
        echo $toolbar;
6876
    }
6877
6878
    /**
6879
     * Creates the default learning path folder.
6880
     *
6881
     * @param array $course
6882
     * @param int   $creatorId
6883
     *
6884
     * @return bool
6885
     */
6886
    public static function generate_learning_path_folder($course, $creatorId = 0)
6887
    {
6888
        // Creating learning_path folder
6889
        $dir = '/learning_path';
6890
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6891
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6892
6893
        $folder = false;
6894
        if (!is_dir($filepath.'/'.$dir)) {
6895
            $folderData = create_unexisting_directory(
6896
                $course,
6897
                $creatorId,
6898
                0,
6899
                null,
6900
                0,
6901
                $filepath,
6902
                $dir,
6903
                get_lang('LearningPaths'),
6904
                0
6905
            );
6906
            if (!empty($folderData)) {
6907
                $folder = true;
6908
            }
6909
        } else {
6910
            $folder = true;
6911
        }
6912
6913
        return $folder;
6914
    }
6915
6916
    /**
6917
     * @param array  $course
6918
     * @param string $lp_name
6919
     * @param int    $creatorId
6920
     *
6921
     * @return array
6922
     */
6923
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6924
    {
6925
        $filepath = '';
6926
        $dir = '/learning_path/';
6927
6928
        if (empty($lp_name)) {
6929
            $lp_name = $this->name;
6930
        }
6931
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6932
6933
        $folder = self::generate_learning_path_folder($course, $creatorId);
6934
6935
        // Limits title size
6936
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6937
        $dir = $dir.$title;
6938
6939
        // Creating LP folder
6940
        $documentId = null;
6941
        if ($folder) {
6942
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6943
            if (!is_dir($filepath.'/'.$dir)) {
6944
                $folderData = create_unexisting_directory(
6945
                    $course,
6946
                    $creatorId,
6947
                    0,
6948
                    0,
6949
                    0,
6950
                    $filepath,
6951
                    $dir,
6952
                    $lp_name
6953
                );
6954
                if (!empty($folderData)) {
6955
                    $folder = true;
6956
                }
6957
6958
                $documentId = $folderData['id'];
6959
            } else {
6960
                $folder = true;
6961
            }
6962
            $dir = $dir.'/';
6963
            if ($folder) {
6964
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6965
            }
6966
        }
6967
6968
        if (empty($documentId)) {
6969
            $dir = api_remove_trailing_slash($dir);
6970
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6971
        }
6972
6973
        $array = [
6974
            'dir' => $dir,
6975
            'filepath' => $filepath,
6976
            'folder' => $folder,
6977
            'id' => $documentId,
6978
        ];
6979
6980
        return $array;
6981
    }
6982
6983
    /**
6984
     * Create a new document //still needs some finetuning.
6985
     *
6986
     * @param array  $courseInfo
6987
     * @param string $content
6988
     * @param string $title
6989
     * @param string $extension
6990
     * @param int    $parentId
6991
     * @param int    $creatorId  creator id
6992
     *
6993
     * @return int
6994
     */
6995
    public function create_document(
6996
        $courseInfo,
6997
        $content = '',
6998
        $title = '',
6999
        $extension = 'html',
7000
        $parentId = 0,
7001
        $creatorId = 0
7002
    ) {
7003
        if (!empty($courseInfo)) {
7004
            $course_id = $courseInfo['real_id'];
7005
        } else {
7006
            $course_id = api_get_course_int_id();
7007
        }
7008
7009
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7010
        $sessionId = api_get_session_id();
7011
7012
        // Generates folder
7013
        $result = $this->generate_lp_folder($courseInfo);
7014
        $dir = $result['dir'];
7015
7016
        if (empty($parentId) || $parentId == '/') {
7017
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7018
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7019
7020
            if ($parentId === '/') {
7021
                $dir = '/';
7022
            }
7023
7024
            // Please, do not modify this dirname formatting.
7025
            if (strstr($dir, '..')) {
7026
                $dir = '/';
7027
            }
7028
7029
            if (!empty($dir[0]) && $dir[0] == '.') {
7030
                $dir = substr($dir, 1);
7031
            }
7032
            if (!empty($dir[0]) && $dir[0] != '/') {
7033
                $dir = '/'.$dir;
7034
            }
7035
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7036
                $dir .= '/';
7037
            }
7038
        } else {
7039
            $parentInfo = DocumentManager::get_document_data_by_id(
7040
                $parentId,
7041
                $courseInfo['code']
7042
            );
7043
            if (!empty($parentInfo)) {
7044
                $dir = $parentInfo['path'].'/';
7045
            }
7046
        }
7047
7048
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7049
        if (!is_dir($filepath)) {
7050
            $dir = '/';
7051
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7052
        }
7053
7054
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7055
        // is already escaped twice when it gets here.
7056
7057
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7058
        if (!empty($title)) {
7059
            $title = api_replace_dangerous_char(stripslashes($title));
7060
        } else {
7061
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7062
        }
7063
7064
        $title = disable_dangerous_file($title);
7065
        $filename = $title;
7066
        $content = !empty($content) ? $content : $_POST['content_lp'];
7067
        $tmp_filename = $filename;
7068
7069
        $i = 0;
7070
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7071
            $tmp_filename = $filename.'_'.++$i;
7072
        }
7073
7074
        $filename = $tmp_filename.'.'.$extension;
7075
        if ($extension == 'html') {
7076
            $content = stripslashes($content);
7077
            $content = str_replace(
7078
                api_get_path(WEB_COURSE_PATH),
7079
                api_get_path(REL_PATH).'courses/',
7080
                $content
7081
            );
7082
7083
            // Change the path of mp3 to absolute.
7084
7085
            // The first regexp deals with :// urls.
7086
            $content = preg_replace(
7087
                "|(flashvars=\"file=)([^:/]+)/|",
7088
                "$1".api_get_path(
7089
                    REL_COURSE_PATH
7090
                ).$courseInfo['path'].'/document/',
7091
                $content
7092
            );
7093
            // The second regexp deals with audio/ urls.
7094
            $content = preg_replace(
7095
                "|(flashvars=\"file=)([^/]+)/|",
7096
                "$1".api_get_path(
7097
                    REL_COURSE_PATH
7098
                ).$courseInfo['path'].'/document/$2/',
7099
                $content
7100
            );
7101
            // For flv player: To prevent edition problem with firefox,
7102
            // we have to use a strange tip (don't blame me please).
7103
            $content = str_replace(
7104
                '</body>',
7105
                '<style type="text/css">body{}</style></body>',
7106
                $content
7107
            );
7108
        }
7109
7110
        if (!file_exists($filepath.$filename)) {
7111
            if ($fp = @fopen($filepath.$filename, 'w')) {
7112
                fputs($fp, $content);
7113
                fclose($fp);
7114
7115
                $file_size = filesize($filepath.$filename);
7116
                $save_file_path = $dir.$filename;
7117
7118
                $document_id = add_document(
7119
                    $courseInfo,
7120
                    $save_file_path,
7121
                    'file',
7122
                    $file_size,
7123
                    $tmp_filename,
7124
                    '',
7125
                    0, //readonly
7126
                    true,
7127
                    null,
7128
                    $sessionId,
7129
                    $creatorId
7130
                );
7131
7132
                if ($document_id) {
7133
                    api_item_property_update(
7134
                        $courseInfo,
7135
                        TOOL_DOCUMENT,
7136
                        $document_id,
7137
                        'DocumentAdded',
7138
                        $creatorId,
7139
                        null,
7140
                        null,
7141
                        null,
7142
                        null,
7143
                        $sessionId
7144
                    );
7145
7146
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7147
                    $new_title = $originalTitle;
7148
7149
                    if ($new_comment || $new_title) {
7150
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7151
                        $ct = '';
7152
                        if ($new_comment) {
7153
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7154
                        }
7155
                        if ($new_title) {
7156
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7157
                        }
7158
7159
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7160
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7161
                        Database::query($sql);
7162
                    }
7163
                }
7164
7165
                return $document_id;
7166
            }
7167
        }
7168
    }
7169
7170
    /**
7171
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7172
     *
7173
     * @param array $_course array
7174
     */
7175
    public function edit_document($_course)
7176
    {
7177
        $course_id = api_get_course_int_id();
7178
        $urlAppend = api_get_configuration_value('url_append');
7179
        // Please, do not modify this dirname formatting.
7180
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7181
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7182
7183
        if (strstr($dir, '..')) {
7184
            $dir = '/';
7185
        }
7186
7187
        if (isset($dir[0]) && $dir[0] == '.') {
7188
            $dir = substr($dir, 1);
7189
        }
7190
7191
        if (isset($dir[0]) && $dir[0] != '/') {
7192
            $dir = '/'.$dir;
7193
        }
7194
7195
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7196
            $dir .= '/';
7197
        }
7198
7199
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7200
        if (!is_dir($filepath)) {
7201
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7202
        }
7203
7204
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7205
7206
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7207
            $document_id = (int) $_POST['path'];
7208
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7209
            if (empty($documentInfo)) {
7210
                // Try with iid
7211
                $table = Database::get_course_table(TABLE_DOCUMENT);
7212
                $sql = "SELECT id, path FROM $table 
7213
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7214
                $res_doc = Database::query($sql);
7215
                $row = Database::fetch_array($res_doc);
7216
                if ($row) {
7217
                    $document_id = $row['id'];
7218
                    $documentPath = $row['path'];
7219
                }
7220
            } else {
7221
                $documentPath = $documentInfo['path'];
7222
            }
7223
7224
            $content = stripslashes($_POST['content_lp']);
7225
            $file = $filepath.$documentPath;
7226
7227
            if (!file_exists($file)) {
7228
                return false;
7229
            }
7230
7231
            if ($fp = @fopen($file, 'w')) {
7232
                $content = str_replace(
7233
                    api_get_path(WEB_COURSE_PATH),
7234
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7235
                    $content
7236
                );
7237
                // Change the path of mp3 to absolute.
7238
                // The first regexp deals with :// urls.
7239
                $content = preg_replace(
7240
                    "|(flashvars=\"file=)([^:/]+)/|",
7241
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7242
                    $content
7243
                );
7244
                // The second regexp deals with audio/ urls.
7245
                $content = preg_replace(
7246
                    "|(flashvars=\"file=)([^:/]+)/|",
7247
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7248
                    $content
7249
                );
7250
                fputs($fp, $content);
7251
                fclose($fp);
7252
7253
                $sql = "UPDATE $table_doc SET
7254
                            title='".Database::escape_string($_POST['title'])."'
7255
                        WHERE c_id = $course_id AND id = ".$document_id;
7256
                Database::query($sql);
7257
            }
7258
        }
7259
    }
7260
7261
    /**
7262
     * Displays the selected item, with a panel for manipulating the item.
7263
     *
7264
     * @param int    $item_id
7265
     * @param string $msg
7266
     * @param bool   $show_actions
7267
     *
7268
     * @return string
7269
     */
7270
    public function display_item($item_id, $msg = null, $show_actions = true)
7271
    {
7272
        $course_id = api_get_course_int_id();
7273
        $return = '';
7274
        if (is_numeric($item_id)) {
7275
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7276
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7277
                    WHERE lp.iid = ".intval($item_id);
7278
            $result = Database::query($sql);
7279
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7280
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7281
7282
                // Prevents wrong parent selection for document, see Bug#1251.
7283
                if ($row['item_type'] != 'dir') {
7284
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7285
                }
7286
7287
                if ($show_actions) {
7288
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7289
                }
7290
                $return .= '<div style="padding:10px;">';
7291
7292
                if ($msg != '') {
7293
                    $return .= $msg;
7294
                }
7295
7296
                $return .= '<h3>'.$row['title'].'</h3>';
7297
7298
                switch ($row['item_type']) {
7299
                    case TOOL_THREAD:
7300
                        $link = $this->rl_get_resource_link_for_learnpath(
7301
                            $course_id,
7302
                            $row['lp_id'],
7303
                            $item_id,
7304
                            0
7305
                        );
7306
                        $return .= Display::url(
7307
                            get_lang('GoToThread'),
7308
                            $link,
7309
                            ['class' => 'btn btn-primary']
7310
                        );
7311
                        break;
7312
                    case TOOL_FORUM:
7313
                        $return .= Display::url(
7314
                            get_lang('GoToForum'),
7315
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7316
                            ['class' => 'btn btn-primary']
7317
                        );
7318
                        break;
7319
                    case TOOL_QUIZ:
7320
                        if (!empty($row['path'])) {
7321
                            $exercise = new Exercise();
7322
                            $exercise->read($row['path']);
7323
                            $return .= $exercise->description.'<br />';
7324
                            $return .= Display::url(
7325
                                get_lang('GoToExercise'),
7326
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7327
                                ['class' => 'btn btn-primary']
7328
                            );
7329
                        }
7330
                        break;
7331
                    case TOOL_LP_FINAL_ITEM:
7332
                        $return .= $this->getSavedFinalItem();
7333
                        break;
7334
                    case TOOL_DOCUMENT:
7335
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7336
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
7337
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
7338
                        $result = Database::query($sql_doc);
7339
                        $path_file = Database::result($result, 0, 0);
7340
                        $path_parts = pathinfo($path_file);
7341
                        // TODO: Correct the following naive comparisons.
7342
                        if (in_array($path_parts['extension'], [
7343
                            'html',
7344
                            'txt',
7345
                            'png',
7346
                            'jpg',
7347
                            'JPG',
7348
                            'jpeg',
7349
                            'JPEG',
7350
                            'gif',
7351
                            'swf',
7352
                            'pdf',
7353
                            'htm',
7354
                        ])) {
7355
                            $return .= $this->display_document($row['path'], true, true);
7356
                        }
7357
                        break;
7358
                    case TOOL_HOTPOTATOES:
7359
                        $return .= $this->display_document($row['path'], false, true);
7360
                        break;
7361
                }
7362
                $return .= '</div>';
7363
            }
7364
        }
7365
7366
        return $return;
7367
    }
7368
7369
    /**
7370
     * Shows the needed forms for editing a specific item.
7371
     *
7372
     * @param int $item_id
7373
     *
7374
     * @throws Exception
7375
     * @throws HTML_QuickForm_Error
7376
     *
7377
     * @return string
7378
     */
7379
    public function display_edit_item($item_id)
7380
    {
7381
        $course_id = api_get_course_int_id();
7382
        $return = '';
7383
        $item_id = (int) $item_id;
7384
7385
        if (empty($item_id)) {
7386
            return '';
7387
        }
7388
7389
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7390
        $sql = "SELECT * FROM $tbl_lp_item
7391
                WHERE iid = ".$item_id;
7392
        $res = Database::query($sql);
7393
        $row = Database::fetch_array($res);
7394
        switch ($row['item_type']) {
7395
            case 'dir':
7396
            case 'asset':
7397
            case 'sco':
7398
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7399
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7400
                    $return .= $this->display_item_form(
7401
                        $row['item_type'],
7402
                        get_lang('EditCurrentChapter').' :',
7403
                        'edit',
7404
                        $item_id,
7405
                        $row
7406
                    );
7407
                } else {
7408
                    $return .= $this->display_item_form(
7409
                        $row['item_type'],
7410
                        get_lang('EditCurrentChapter').' :',
7411
                        'edit_item',
7412
                        $item_id,
7413
                        $row
7414
                    );
7415
                }
7416
                break;
7417
            case TOOL_DOCUMENT:
7418
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7419
                $sql = "SELECT lp.*, doc.path as dir
7420
                        FROM $tbl_lp_item as lp
7421
                        LEFT JOIN $tbl_doc as doc
7422
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7423
                        WHERE
7424
                            doc.c_id = $course_id AND
7425
                            lp.iid = ".$item_id;
7426
                $res_step = Database::query($sql);
7427
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7428
                $return .= $this->display_manipulate(
7429
                    $item_id,
7430
                    $row['item_type']
7431
                );
7432
                $return .= $this->display_document_form(
7433
                    'edit',
7434
                    $item_id,
7435
                    $row_step
7436
                );
7437
                break;
7438
            case TOOL_LINK:
7439
                $link_id = (string) $row['path'];
7440
                if (ctype_digit($link_id)) {
7441
                    $tbl_link = Database::get_course_table(TABLE_LINK);
7442
                    $sql_select = 'SELECT url FROM '.$tbl_link.'
7443
                                   WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7444
                    $res_link = Database::query($sql_select);
7445
                    $row_link = Database::fetch_array($res_link);
7446
                    if (is_array($row_link)) {
7447
                        $row['url'] = $row_link['url'];
7448
                    }
7449
                }
7450
                $return .= $this->display_manipulate(
7451
                    $item_id,
7452
                    $row['item_type']
7453
                );
7454
                $return .= $this->display_link_form('edit', $item_id, $row);
7455
                break;
7456
            case TOOL_LP_FINAL_ITEM:
7457
                Session::write('finalItem', true);
7458
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7459
                $sql = "SELECT lp.*, doc.path as dir
7460
                        FROM $tbl_lp_item as lp
7461
                        LEFT JOIN $tbl_doc as doc
7462
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7463
                        WHERE
7464
                            doc.c_id = $course_id AND
7465
                            lp.iid = ".$item_id;
7466
                $res_step = Database::query($sql);
7467
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7468
                $return .= $this->display_manipulate(
7469
                    $item_id,
7470
                    $row['item_type']
7471
                );
7472
                $return .= $this->display_document_form(
7473
                    'edit',
7474
                    $item_id,
7475
                    $row_step
7476
                );
7477
                break;
7478
            case TOOL_QUIZ:
7479
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7480
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7481
                break;
7482
            case TOOL_HOTPOTATOES:
7483
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7484
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7485
                break;
7486
            case TOOL_STUDENTPUBLICATION:
7487
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7488
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7489
                break;
7490
            case TOOL_FORUM:
7491
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7492
                $return .= $this->display_forum_form('edit', $item_id, $row);
7493
                break;
7494
            case TOOL_THREAD:
7495
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7496
                $return .= $this->display_thread_form('edit', $item_id, $row);
7497
                break;
7498
        }
7499
7500
        return $return;
7501
    }
7502
7503
    /**
7504
     * Function that displays a list with al the resources that
7505
     * could be added to the learning path.
7506
     *
7507
     * @throws Exception
7508
     * @throws HTML_QuickForm_Error
7509
     *
7510
     * @return bool
7511
     */
7512
    public function display_resources()
7513
    {
7514
        $course_code = api_get_course_id();
7515
7516
        // Get all the docs.
7517
        $documents = $this->get_documents(true);
7518
7519
        // Get all the exercises.
7520
        $exercises = $this->get_exercises();
7521
7522
        // Get all the links.
7523
        $links = $this->get_links();
7524
7525
        // Get all the student publications.
7526
        $works = $this->get_student_publications();
7527
7528
        // Get all the forums.
7529
        $forums = $this->get_forums(null, $course_code);
7530
7531
        // Get the final item form (see BT#11048) .
7532
        $finish = $this->getFinalItemForm();
7533
7534
        $headers = [
7535
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7536
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7537
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7538
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7539
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7540
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7541
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7542
        ];
7543
7544
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7545
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7546
        echo Display::tabs(
7547
            $headers,
7548
            [
7549
                $documents,
7550
                $exercises,
7551
                $links,
7552
                $works,
7553
                $forums,
7554
                $dir,
7555
                $finish,
7556
            ],
7557
            'resource_tab'
7558
        );
7559
7560
        return true;
7561
    }
7562
7563
    /**
7564
     * Returns the extension of a document.
7565
     *
7566
     * @param string $filename
7567
     *
7568
     * @return string Extension (part after the last dot)
7569
     */
7570
    public function get_extension($filename)
7571
    {
7572
        $explode = explode('.', $filename);
7573
7574
        return $explode[count($explode) - 1];
7575
    }
7576
7577
    /**
7578
     * Displays a document by id.
7579
     *
7580
     * @param int  $id
7581
     * @param bool $show_title
7582
     * @param bool $iframe
7583
     * @param bool $edit_link
7584
     *
7585
     * @return string
7586
     */
7587
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7588
    {
7589
        $_course = api_get_course_info();
7590
        $course_id = api_get_course_int_id();
7591
        $id = (int) $id;
7592
        $return = '';
7593
        $table = Database::get_course_table(TABLE_DOCUMENT);
7594
        $sql_doc = "SELECT * FROM $table
7595
                    WHERE c_id = $course_id AND iid = $id";
7596
        $res_doc = Database::query($sql_doc);
7597
        $row_doc = Database::fetch_array($res_doc);
7598
7599
        // TODO: Add a path filter.
7600
        if ($iframe) {
7601
            $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>';
7602
        } else {
7603
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7604
        }
7605
7606
        return $return;
7607
    }
7608
7609
    /**
7610
     * Return HTML form to add/edit a quiz.
7611
     *
7612
     * @param string $action     Action (add/edit)
7613
     * @param int    $id         Item ID if already exists
7614
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7615
     *
7616
     * @throws Exception
7617
     *
7618
     * @return string HTML form
7619
     */
7620
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7621
    {
7622
        $course_id = api_get_course_int_id();
7623
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7624
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7625
7626
        if ($id != 0 && is_array($extra_info)) {
7627
            $item_title = $extra_info['title'];
7628
            $item_description = $extra_info['description'];
7629
        } elseif (is_numeric($extra_info)) {
7630
            $sql = "SELECT title, description
7631
                    FROM $tbl_quiz
7632
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7633
7634
            $result = Database::query($sql);
7635
            $row = Database::fetch_array($result);
7636
            $item_title = $row['title'];
7637
            $item_description = $row['description'];
7638
        } else {
7639
            $item_title = '';
7640
            $item_description = '';
7641
        }
7642
        $item_title = Security::remove_XSS($item_title);
7643
        $item_description = Security::remove_XSS($item_description);
7644
7645
        if ($id != 0 && is_array($extra_info)) {
7646
            $parent = $extra_info['parent_item_id'];
7647
        } else {
7648
            $parent = 0;
7649
        }
7650
7651
        $sql = "SELECT * FROM $tbl_lp_item 
7652
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7653
7654
        $result = Database::query($sql);
7655
        $arrLP = [];
7656
        while ($row = Database::fetch_array($result)) {
7657
            $arrLP[] = [
7658
                'id' => $row['iid'],
7659
                'item_type' => $row['item_type'],
7660
                'title' => $row['title'],
7661
                'path' => $row['path'],
7662
                'description' => $row['description'],
7663
                'parent_item_id' => $row['parent_item_id'],
7664
                'previous_item_id' => $row['previous_item_id'],
7665
                'next_item_id' => $row['next_item_id'],
7666
                'display_order' => $row['display_order'],
7667
                'max_score' => $row['max_score'],
7668
                'min_score' => $row['min_score'],
7669
                'mastery_score' => $row['mastery_score'],
7670
                'prerequisite' => $row['prerequisite'],
7671
                'max_time_allowed' => $row['max_time_allowed'],
7672
            ];
7673
        }
7674
7675
        $this->tree_array($arrLP);
7676
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7677
        unset($this->arrMenu);
7678
7679
        $form = new FormValidator(
7680
            'quiz_form',
7681
            'POST',
7682
            $this->getCurrentBuildingModeURL()
7683
        );
7684
        $defaults = [];
7685
7686
        if ($action == 'add') {
7687
            $legend = get_lang('CreateTheExercise');
7688
        } elseif ($action == 'move') {
7689
            $legend = get_lang('MoveTheCurrentExercise');
7690
        } else {
7691
            $legend = get_lang('EditCurrentExecice');
7692
        }
7693
7694
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7695
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7696
        }
7697
7698
        $form->addHeader($legend);
7699
7700
        if ($action != 'move') {
7701
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7702
            $defaults['title'] = $item_title;
7703
        }
7704
7705
        // Select for Parent item, root or chapter
7706
        $selectParent = $form->addSelect(
7707
            'parent',
7708
            get_lang('Parent'),
7709
            [],
7710
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7711
        );
7712
        $selectParent->addOption($this->name, 0);
7713
7714
        $arrHide = [
7715
            $id,
7716
        ];
7717
        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...
7718
            if ($action != 'add') {
7719
                if (
7720
                    ($arrLP[$i]['item_type'] == 'dir') &&
7721
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7722
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7723
                ) {
7724
                    $selectParent->addOption(
7725
                        $arrLP[$i]['title'],
7726
                        $arrLP[$i]['id'],
7727
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7728
                    );
7729
7730
                    if ($parent == $arrLP[$i]['id']) {
7731
                        $selectParent->setSelected($arrLP[$i]['id']);
7732
                    }
7733
                } else {
7734
                    $arrHide[] = $arrLP[$i]['id'];
7735
                }
7736
            } else {
7737
                if ($arrLP[$i]['item_type'] == 'dir') {
7738
                    $selectParent->addOption(
7739
                        $arrLP[$i]['title'],
7740
                        $arrLP[$i]['id'],
7741
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7742
                    );
7743
7744
                    if ($parent == $arrLP[$i]['id']) {
7745
                        $selectParent->setSelected($arrLP[$i]['id']);
7746
                    }
7747
                }
7748
            }
7749
        }
7750
        if (is_array($arrLP)) {
7751
            reset($arrLP);
7752
        }
7753
7754
        $selectPrevious = $form->addSelect(
7755
            'previous',
7756
            get_lang('Position'),
7757
            [],
7758
            ['id' => 'previous']
7759
        );
7760
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
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 ($arrLP[$i]['parent_item_id'] == $parent &&
7764
                $arrLP[$i]['id'] != $id
7765
            ) {
7766
                $selectPrevious->addOption(
7767
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7768
                    $arrLP[$i]['id']
7769
                );
7770
7771
                if (is_array($extra_info)) {
7772
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7773
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7774
                    }
7775
                } elseif ($action == 'add') {
7776
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7777
                }
7778
            }
7779
        }
7780
7781
        if ($action != 'move') {
7782
            $arrHide = [];
7783
            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...
7784
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7785
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7786
                }
7787
            }
7788
        }
7789
7790
        if ($action == 'add') {
7791
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7792
        } else {
7793
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7794
        }
7795
7796
        if ($action == 'move') {
7797
            $form->addHidden('title', $item_title);
7798
            $form->addHidden('description', $item_description);
7799
        }
7800
7801
        if (is_numeric($extra_info)) {
7802
            $form->addHidden('path', $extra_info);
7803
        } elseif (is_array($extra_info)) {
7804
            $form->addHidden('path', $extra_info['path']);
7805
        }
7806
7807
        $form->addHidden('type', TOOL_QUIZ);
7808
        $form->addHidden('post_time', time());
7809
        $form->setDefaults($defaults);
7810
7811
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7812
    }
7813
7814
    /**
7815
     * Addition of Hotpotatoes tests.
7816
     *
7817
     * @param string $action
7818
     * @param int    $id         Internal ID of the item
7819
     * @param string $extra_info
7820
     *
7821
     * @return string HTML structure to display the hotpotatoes addition formular
7822
     */
7823
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7824
    {
7825
        $course_id = api_get_course_int_id();
7826
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7827
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7828
7829
        if ($id != 0 && is_array($extra_info)) {
7830
            $item_title = stripslashes($extra_info['title']);
7831
            $item_description = stripslashes($extra_info['description']);
7832
        } elseif (is_numeric($extra_info)) {
7833
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7834
7835
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7836
                    WHERE
7837
                        c_id = ".$course_id." AND
7838
                        path LIKE '".$uploadPath."/%/%htm%' AND
7839
                        iid = ".(int) $extra_info."
7840
                    ORDER BY iid ASC";
7841
7842
            $res_hot = Database::query($sql);
7843
            $row = Database::fetch_array($res_hot);
7844
7845
            $item_title = $row['title'];
7846
            $item_description = $row['description'];
7847
7848
            if (!empty($row['comment'])) {
7849
                $item_title = $row['comment'];
7850
            }
7851
        } else {
7852
            $item_title = '';
7853
            $item_description = '';
7854
        }
7855
7856
        if ($id != 0 && is_array($extra_info)) {
7857
            $parent = $extra_info['parent_item_id'];
7858
        } else {
7859
            $parent = 0;
7860
        }
7861
7862
        $sql = "SELECT * FROM $tbl_lp_item
7863
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7864
        $result = Database::query($sql);
7865
        $arrLP = [];
7866
        while ($row = Database::fetch_array($result)) {
7867
            $arrLP[] = [
7868
                'id' => $row['id'],
7869
                'item_type' => $row['item_type'],
7870
                'title' => $row['title'],
7871
                'path' => $row['path'],
7872
                'description' => $row['description'],
7873
                'parent_item_id' => $row['parent_item_id'],
7874
                'previous_item_id' => $row['previous_item_id'],
7875
                'next_item_id' => $row['next_item_id'],
7876
                'display_order' => $row['display_order'],
7877
                'max_score' => $row['max_score'],
7878
                'min_score' => $row['min_score'],
7879
                'mastery_score' => $row['mastery_score'],
7880
                'prerequisite' => $row['prerequisite'],
7881
                'max_time_allowed' => $row['max_time_allowed'],
7882
            ];
7883
        }
7884
7885
        $legend = '<legend>';
7886
        if ($action == 'add') {
7887
            $legend .= get_lang('CreateTheExercise');
7888
        } elseif ($action == 'move') {
7889
            $legend .= get_lang('MoveTheCurrentExercise');
7890
        } else {
7891
            $legend .= get_lang('EditCurrentExecice');
7892
        }
7893
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7894
            $legend .= Display:: return_message(
7895
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7896
            );
7897
        }
7898
        $legend .= '</legend>';
7899
7900
        $return = '<form method="POST">';
7901
        $return .= $legend;
7902
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7903
        $return .= '<tr>';
7904
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7905
        $return .= '<td class="input">';
7906
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7907
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7908
        $arrHide = [
7909
            $id,
7910
        ];
7911
7912
        if (count($arrLP) > 0) {
7913
            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...
7914
                if ($action != 'add') {
7915
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7916
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7917
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7918
                    ) {
7919
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7920
                    } else {
7921
                        $arrHide[] = $arrLP[$i]['id'];
7922
                    }
7923
                } else {
7924
                    if ($arrLP[$i]['item_type'] == 'dir') {
7925
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7926
                    }
7927
                }
7928
            }
7929
            reset($arrLP);
7930
        }
7931
7932
        $return .= '</select>';
7933
        $return .= '</td>';
7934
        $return .= '</tr>';
7935
        $return .= '<tr>';
7936
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7937
        $return .= '<td class="input">';
7938
        $return .= '<select id="previous" name="previous" size="1">';
7939
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7940
7941
        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...
7942
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7943
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7944
                    $selected = 'selected="selected" ';
7945
                } elseif ($action == 'add') {
7946
                    $selected = 'selected="selected" ';
7947
                } else {
7948
                    $selected = '';
7949
                }
7950
7951
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7952
            }
7953
        }
7954
7955
        $return .= '</select>';
7956
        $return .= '</td>';
7957
        $return .= '</tr>';
7958
7959
        if ($action != 'move') {
7960
            $return .= '<tr>';
7961
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7962
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7963
            $return .= '</tr>';
7964
            $id_prerequisite = 0;
7965
            if (is_array($arrLP) && count($arrLP) > 0) {
7966
                foreach ($arrLP as $key => $value) {
7967
                    if ($value['id'] == $id) {
7968
                        $id_prerequisite = $value['prerequisite'];
7969
                        break;
7970
                    }
7971
                }
7972
7973
                $arrHide = [];
7974
                for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7975
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7976
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7977
                    }
7978
                }
7979
            }
7980
        }
7981
7982
        $return .= '<tr>';
7983
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7984
            get_lang('SaveHotpotatoes').'</button></td>';
7985
        $return .= '</tr>';
7986
        $return .= '</table>';
7987
7988
        if ($action == 'move') {
7989
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7990
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7991
        }
7992
7993
        if (is_numeric($extra_info)) {
7994
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7995
        } elseif (is_array($extra_info)) {
7996
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7997
        }
7998
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7999
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8000
        $return .= '</form>';
8001
8002
        return $return;
8003
    }
8004
8005
    /**
8006
     * Return the form to display the forum edit/add option.
8007
     *
8008
     * @param string $action
8009
     * @param int    $id         ID of the lp_item if already exists
8010
     * @param string $extra_info
8011
     *
8012
     * @throws Exception
8013
     *
8014
     * @return string HTML form
8015
     */
8016
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
8017
    {
8018
        $course_id = api_get_course_int_id();
8019
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8020
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8021
8022
        if ($id != 0 && is_array($extra_info)) {
8023
            $item_title = stripslashes($extra_info['title']);
8024
        } elseif (is_numeric($extra_info)) {
8025
            $sql = "SELECT forum_title as title, forum_comment as comment
8026
                    FROM ".$tbl_forum."
8027
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
8028
8029
            $result = Database::query($sql);
8030
            $row = Database::fetch_array($result);
8031
8032
            $item_title = $row['title'];
8033
            $item_description = $row['comment'];
8034
        } else {
8035
            $item_title = '';
8036
            $item_description = '';
8037
        }
8038
8039
        if ($id != 0 && is_array($extra_info)) {
8040
            $parent = $extra_info['parent_item_id'];
8041
        } else {
8042
            $parent = 0;
8043
        }
8044
8045
        $sql = "SELECT * FROM $tbl_lp_item
8046
                WHERE
8047
                    c_id = $course_id AND
8048
                    lp_id = ".$this->lp_id;
8049
        $result = Database::query($sql);
8050
        $arrLP = [];
8051
        while ($row = Database::fetch_array($result)) {
8052
            $arrLP[] = [
8053
                'id' => $row['iid'],
8054
                'item_type' => $row['item_type'],
8055
                'title' => $row['title'],
8056
                'path' => $row['path'],
8057
                'description' => $row['description'],
8058
                'parent_item_id' => $row['parent_item_id'],
8059
                'previous_item_id' => $row['previous_item_id'],
8060
                'next_item_id' => $row['next_item_id'],
8061
                'display_order' => $row['display_order'],
8062
                'max_score' => $row['max_score'],
8063
                'min_score' => $row['min_score'],
8064
                'mastery_score' => $row['mastery_score'],
8065
                'prerequisite' => $row['prerequisite'],
8066
            ];
8067
        }
8068
8069
        $this->tree_array($arrLP);
8070
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8071
        unset($this->arrMenu);
8072
8073
        if ($action == 'add') {
8074
            $legend = get_lang('CreateTheForum');
8075
        } elseif ($action == 'move') {
8076
            $legend = get_lang('MoveTheCurrentForum');
8077
        } else {
8078
            $legend = get_lang('EditCurrentForum');
8079
        }
8080
8081
        $form = new FormValidator(
8082
            'forum_form',
8083
            'POST',
8084
            $this->getCurrentBuildingModeURL()
8085
        );
8086
        $defaults = [];
8087
8088
        $form->addHeader($legend);
8089
8090
        if ($action != 'move') {
8091
            $form->addText(
8092
                'title',
8093
                get_lang('Title'),
8094
                true,
8095
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8096
            );
8097
            $defaults['title'] = $item_title;
8098
        }
8099
8100
        $selectParent = $form->addSelect(
8101
            'parent',
8102
            get_lang('Parent'),
8103
            [],
8104
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8105
        );
8106
        $selectParent->addOption($this->name, 0);
8107
        $arrHide = [
8108
            $id,
8109
        ];
8110
        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...
8111
            if ($action != 'add') {
8112
                if ($arrLP[$i]['item_type'] == 'dir' &&
8113
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8114
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8115
                ) {
8116
                    $selectParent->addOption(
8117
                        $arrLP[$i]['title'],
8118
                        $arrLP[$i]['id'],
8119
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8120
                    );
8121
8122
                    if ($parent == $arrLP[$i]['id']) {
8123
                        $selectParent->setSelected($arrLP[$i]['id']);
8124
                    }
8125
                } else {
8126
                    $arrHide[] = $arrLP[$i]['id'];
8127
                }
8128
            } else {
8129
                if ($arrLP[$i]['item_type'] == 'dir') {
8130
                    $selectParent->addOption(
8131
                        $arrLP[$i]['title'],
8132
                        $arrLP[$i]['id'],
8133
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8134
                    );
8135
8136
                    if ($parent == $arrLP[$i]['id']) {
8137
                        $selectParent->setSelected($arrLP[$i]['id']);
8138
                    }
8139
                }
8140
            }
8141
        }
8142
8143
        if (is_array($arrLP)) {
8144
            reset($arrLP);
8145
        }
8146
8147
        $selectPrevious = $form->addSelect(
8148
            'previous',
8149
            get_lang('Position'),
8150
            [],
8151
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8152
        );
8153
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
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 ($arrLP[$i]['parent_item_id'] == $parent &&
8157
                $arrLP[$i]['id'] != $id
8158
            ) {
8159
                $selectPrevious->addOption(
8160
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8161
                    $arrLP[$i]['id']
8162
                );
8163
8164
                if (isset($extra_info['previous_item_id']) &&
8165
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8166
                ) {
8167
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8168
                } elseif ($action == 'add') {
8169
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8170
                }
8171
            }
8172
        }
8173
8174
        if ($action != 'move') {
8175
            $id_prerequisite = 0;
8176
            if (is_array($arrLP)) {
8177
                foreach ($arrLP as $key => $value) {
8178
                    if ($value['id'] == $id) {
8179
                        $id_prerequisite = $value['prerequisite'];
8180
                        break;
8181
                    }
8182
                }
8183
            }
8184
8185
            $arrHide = [];
8186
            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...
8187
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8188
                    if (isset($extra_info['previous_item_id']) &&
8189
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8190
                    ) {
8191
                        $s_selected_position = $arrLP[$i]['id'];
8192
                    } elseif ($action == 'add') {
8193
                        $s_selected_position = 0;
8194
                    }
8195
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8196
                }
8197
            }
8198
        }
8199
8200
        if ($action == 'add') {
8201
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8202
        } else {
8203
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8204
        }
8205
8206
        if ($action == 'move') {
8207
            $form->addHidden('title', $item_title);
8208
            $form->addHidden('description', $item_description);
8209
        }
8210
8211
        if (is_numeric($extra_info)) {
8212
            $form->addHidden('path', $extra_info);
8213
        } elseif (is_array($extra_info)) {
8214
            $form->addHidden('path', $extra_info['path']);
8215
        }
8216
        $form->addHidden('type', TOOL_FORUM);
8217
        $form->addHidden('post_time', time());
8218
        $form->setDefaults($defaults);
8219
8220
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8221
    }
8222
8223
    /**
8224
     * Return HTML form to add/edit forum threads.
8225
     *
8226
     * @param string $action
8227
     * @param int    $id         Item ID if already exists in learning path
8228
     * @param string $extra_info
8229
     *
8230
     * @throws Exception
8231
     *
8232
     * @return string HTML form
8233
     */
8234
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8235
    {
8236
        $course_id = api_get_course_int_id();
8237
        if (empty($course_id)) {
8238
            return null;
8239
        }
8240
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8241
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8242
8243
        if ($id != 0 && is_array($extra_info)) {
8244
            $item_title = stripslashes($extra_info['title']);
8245
        } elseif (is_numeric($extra_info)) {
8246
            $sql = "SELECT thread_title as title FROM $tbl_forum
8247
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8248
8249
            $result = Database::query($sql);
8250
            $row = Database::fetch_array($result);
8251
8252
            $item_title = $row['title'];
8253
            $item_description = '';
8254
        } else {
8255
            $item_title = '';
8256
            $item_description = '';
8257
        }
8258
8259
        if ($id != 0 && is_array($extra_info)) {
8260
            $parent = $extra_info['parent_item_id'];
8261
        } else {
8262
            $parent = 0;
8263
        }
8264
8265
        $sql = "SELECT * FROM $tbl_lp_item
8266
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8267
        $result = Database::query($sql);
8268
8269
        $arrLP = [];
8270
        while ($row = Database::fetch_array($result)) {
8271
            $arrLP[] = [
8272
                'id' => $row['iid'],
8273
                'item_type' => $row['item_type'],
8274
                'title' => $row['title'],
8275
                'path' => $row['path'],
8276
                'description' => $row['description'],
8277
                'parent_item_id' => $row['parent_item_id'],
8278
                'previous_item_id' => $row['previous_item_id'],
8279
                'next_item_id' => $row['next_item_id'],
8280
                'display_order' => $row['display_order'],
8281
                'max_score' => $row['max_score'],
8282
                'min_score' => $row['min_score'],
8283
                'mastery_score' => $row['mastery_score'],
8284
                'prerequisite' => $row['prerequisite'],
8285
            ];
8286
        }
8287
8288
        $this->tree_array($arrLP);
8289
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8290
        unset($this->arrMenu);
8291
8292
        $form = new FormValidator(
8293
            'thread_form',
8294
            'POST',
8295
            $this->getCurrentBuildingModeURL()
8296
        );
8297
        $defaults = [];
8298
8299
        if ($action == 'add') {
8300
            $legend = get_lang('CreateTheForum');
8301
        } elseif ($action == 'move') {
8302
            $legend = get_lang('MoveTheCurrentForum');
8303
        } else {
8304
            $legend = get_lang('EditCurrentForum');
8305
        }
8306
8307
        $form->addHeader($legend);
8308
        $selectParent = $form->addSelect(
8309
            'parent',
8310
            get_lang('Parent'),
8311
            [],
8312
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8313
        );
8314
        $selectParent->addOption($this->name, 0);
8315
8316
        $arrHide = [
8317
            $id,
8318
        ];
8319
8320
        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...
8321
            if ($action != 'add') {
8322
                if (
8323
                    ($arrLP[$i]['item_type'] == 'dir') &&
8324
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8325
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8326
                ) {
8327
                    $selectParent->addOption(
8328
                        $arrLP[$i]['title'],
8329
                        $arrLP[$i]['id'],
8330
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8331
                    );
8332
8333
                    if ($parent == $arrLP[$i]['id']) {
8334
                        $selectParent->setSelected($arrLP[$i]['id']);
8335
                    }
8336
                } else {
8337
                    $arrHide[] = $arrLP[$i]['id'];
8338
                }
8339
            } else {
8340
                if ($arrLP[$i]['item_type'] == 'dir') {
8341
                    $selectParent->addOption(
8342
                        $arrLP[$i]['title'],
8343
                        $arrLP[$i]['id'],
8344
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8345
                    );
8346
8347
                    if ($parent == $arrLP[$i]['id']) {
8348
                        $selectParent->setSelected($arrLP[$i]['id']);
8349
                    }
8350
                }
8351
            }
8352
        }
8353
8354
        if ($arrLP != null) {
8355
            reset($arrLP);
8356
        }
8357
8358
        $selectPrevious = $form->addSelect(
8359
            'previous',
8360
            get_lang('Position'),
8361
            [],
8362
            ['id' => 'previous']
8363
        );
8364
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8365
8366
        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...
8367
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8368
                $selectPrevious->addOption(
8369
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8370
                    $arrLP[$i]['id']
8371
                );
8372
8373
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8374
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8375
                } elseif ($action == 'add') {
8376
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8377
                }
8378
            }
8379
        }
8380
8381
        if ($action != 'move') {
8382
            $form->addText(
8383
                'title',
8384
                get_lang('Title'),
8385
                true,
8386
                ['id' => 'idTitle']
8387
            );
8388
            $defaults['title'] = $item_title;
8389
8390
            $id_prerequisite = 0;
8391
            if ($arrLP != null) {
8392
                foreach ($arrLP as $key => $value) {
8393
                    if ($value['id'] == $id) {
8394
                        $id_prerequisite = $value['prerequisite'];
8395
                        break;
8396
                    }
8397
                }
8398
            }
8399
8400
            $arrHide = [];
8401
            $s_selected_position = 0;
8402
            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...
8403
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8404
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8405
                        $s_selected_position = $arrLP[$i]['id'];
8406
                    } elseif ($action == 'add') {
8407
                        $s_selected_position = 0;
8408
                    }
8409
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8410
                }
8411
            }
8412
8413
            $selectPrerequisites = $form->addSelect(
8414
                'prerequisites',
8415
                get_lang('LearnpathPrerequisites'),
8416
                [],
8417
                ['id' => 'prerequisites']
8418
            );
8419
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8420
8421
            foreach ($arrHide as $key => $value) {
8422
                $selectPrerequisites->addOption($value['value'], $key);
8423
8424
                if ($key == $s_selected_position && $action == 'add') {
8425
                    $selectPrerequisites->setSelected($key);
8426
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8427
                    $selectPrerequisites->setSelected($key);
8428
                }
8429
            }
8430
        }
8431
8432
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8433
8434
        if ($action == 'move') {
8435
            $form->addHidden('title', $item_title);
8436
            $form->addHidden('description', $item_description);
8437
        }
8438
8439
        if (is_numeric($extra_info)) {
8440
            $form->addHidden('path', $extra_info);
8441
        } elseif (is_array($extra_info)) {
8442
            $form->addHidden('path', $extra_info['path']);
8443
        }
8444
8445
        $form->addHidden('type', TOOL_THREAD);
8446
        $form->addHidden('post_time', time());
8447
        $form->setDefaults($defaults);
8448
8449
        return $form->returnForm();
8450
    }
8451
8452
    /**
8453
     * Return the HTML form to display an item (generally a dir item).
8454
     *
8455
     * @param string $item_type
8456
     * @param string $title
8457
     * @param string $action
8458
     * @param int    $id
8459
     * @param string $extra_info
8460
     *
8461
     * @throws Exception
8462
     * @throws HTML_QuickForm_Error
8463
     *
8464
     * @return string HTML form
8465
     */
8466
    public function display_item_form(
8467
        $item_type,
8468
        $title = '',
8469
        $action = 'add_item',
8470
        $id = 0,
8471
        $extra_info = 'new'
8472
    ) {
8473
        $_course = api_get_course_info();
8474
8475
        global $charset;
8476
8477
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8478
        $item_title = '';
8479
        $item_description = '';
8480
        $item_path_fck = '';
8481
8482
        if ($id != 0 && is_array($extra_info)) {
8483
            $item_title = $extra_info['title'];
8484
            $item_description = $extra_info['description'];
8485
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8486
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8487
        }
8488
        $parent = 0;
8489
        if ($id != 0 && is_array($extra_info)) {
8490
            $parent = $extra_info['parent_item_id'];
8491
        }
8492
8493
        $id = (int) $id;
8494
        $sql = "SELECT * FROM $tbl_lp_item
8495
                WHERE
8496
                    lp_id = ".$this->lp_id." AND
8497
                    iid != $id";
8498
8499
        if ($item_type == 'dir') {
8500
            $sql .= " AND parent_item_id = 0";
8501
        }
8502
8503
        $result = Database::query($sql);
8504
        $arrLP = [];
8505
        while ($row = Database::fetch_array($result)) {
8506
            $arrLP[] = [
8507
                'id' => $row['iid'],
8508
                'item_type' => $row['item_type'],
8509
                'title' => $row['title'],
8510
                'path' => $row['path'],
8511
                'description' => $row['description'],
8512
                'parent_item_id' => $row['parent_item_id'],
8513
                'previous_item_id' => $row['previous_item_id'],
8514
                'next_item_id' => $row['next_item_id'],
8515
                'max_score' => $row['max_score'],
8516
                'min_score' => $row['min_score'],
8517
                'mastery_score' => $row['mastery_score'],
8518
                'prerequisite' => $row['prerequisite'],
8519
                'display_order' => $row['display_order'],
8520
            ];
8521
        }
8522
8523
        $this->tree_array($arrLP);
8524
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8525
        unset($this->arrMenu);
8526
8527
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8528
8529
        $form = new FormValidator('form', 'POST', $url);
8530
        $defaults['title'] = api_html_entity_decode(
8531
            $item_title,
8532
            ENT_QUOTES,
8533
            $charset
8534
        );
8535
        $defaults['description'] = $item_description;
8536
8537
        $form->addHeader($title);
8538
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8539
        $arrHide[0]['padding'] = 20;
8540
        $charset = api_get_system_encoding();
8541
        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...
8542
            if ($action != 'add') {
8543
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8544
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8545
                ) {
8546
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8547
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8548
                    if ($parent == $arrLP[$i]['id']) {
8549
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8550
                    }
8551
                }
8552
            } else {
8553
                if ($arrLP[$i]['item_type'] == 'dir') {
8554
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8555
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8556
                    if ($parent == $arrLP[$i]['id']) {
8557
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8558
                    }
8559
                }
8560
            }
8561
        }
8562
8563
        if ($action != 'move') {
8564
            $form->addElement('text', 'title', get_lang('Title'));
8565
            $form->applyFilter('title', 'html_filter');
8566
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8567
        } else {
8568
            $form->addElement('hidden', 'title');
8569
        }
8570
8571
        $parentSelect = $form->addElement(
8572
            'select',
8573
            'parent',
8574
            get_lang('Parent'),
8575
            '',
8576
            [
8577
                'id' => 'idParent',
8578
                'onchange' => "javascript: load_cbo(this.value);",
8579
            ]
8580
        );
8581
8582
        foreach ($arrHide as $key => $value) {
8583
            $parentSelect->addOption(
8584
                $value['value'],
8585
                $key,
8586
                'style="padding-left:'.$value['padding'].'px;"'
8587
            );
8588
            $lastPosition = $key;
8589
        }
8590
8591
        if (!empty($s_selected_parent)) {
8592
            $parentSelect->setSelected($s_selected_parent);
8593
        }
8594
8595
        if (is_array($arrLP)) {
8596
            reset($arrLP);
8597
        }
8598
        $arrHide = [];
8599
        // POSITION
8600
        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...
8601
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8602
                //this is the same!
8603
                if (isset($extra_info['previous_item_id']) &&
8604
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8605
                ) {
8606
                    $s_selected_position = $arrLP[$i]['id'];
8607
                } elseif ($action == 'add') {
8608
                    $s_selected_position = $arrLP[$i]['id'];
8609
                }
8610
8611
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8612
            }
8613
        }
8614
8615
        $position = $form->addElement(
8616
            'select',
8617
            'previous',
8618
            get_lang('Position'),
8619
            '',
8620
            ['id' => 'previous']
8621
        );
8622
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8623
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8624
8625
        $lastPosition = null;
8626
        foreach ($arrHide as $key => $value) {
8627
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8628
            $lastPosition = $key;
8629
        }
8630
8631
        if (!empty($s_selected_position)) {
8632
            $position->setSelected($s_selected_position);
8633
        }
8634
8635
        // When new chapter add at the end
8636
        if ($action == 'add_item') {
8637
            $position->setSelected($lastPosition);
8638
        }
8639
8640
        if (is_array($arrLP)) {
8641
            reset($arrLP);
8642
        }
8643
8644
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8645
8646
        //fix in order to use the tab
8647
        if ($item_type == 'dir') {
8648
            $form->addElement('hidden', 'type', 'dir');
8649
        }
8650
8651
        $extension = null;
8652
        if (!empty($item_path)) {
8653
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8654
        }
8655
8656
        //assets can't be modified
8657
        //$item_type == 'asset' ||
8658
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8659
            if ($item_type == 'sco') {
8660
                $form->addElement(
8661
                    'html',
8662
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8663
                );
8664
            }
8665
            $renderer = $form->defaultRenderer();
8666
            $renderer->setElementTemplate(
8667
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8668
                'content_lp'
8669
            );
8670
8671
            $relative_prefix = '';
8672
8673
            $editor_config = [
8674
                'ToolbarSet' => 'LearningPathDocuments',
8675
                'Width' => '100%',
8676
                'Height' => '500',
8677
                'FullPage' => true,
8678
                'CreateDocumentDir' => $relative_prefix,
8679
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8680
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8681
            ];
8682
8683
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8684
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8685
            $defaults['content_lp'] = file_get_contents($content_path);
8686
        }
8687
8688
        if (!empty($id)) {
8689
            $form->addHidden('id', $id);
8690
        }
8691
8692
        $form->addElement('hidden', 'type', $item_type);
8693
        $form->addElement('hidden', 'post_time', time());
8694
        $form->setDefaults($defaults);
8695
8696
        return $form->returnForm();
8697
    }
8698
8699
    /**
8700
     * @return string
8701
     */
8702
    public function getCurrentBuildingModeURL()
8703
    {
8704
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8705
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8706
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8707
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8708
8709
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8710
8711
        return $currentUrl;
8712
    }
8713
8714
    /**
8715
     * Returns the form to update or create a document.
8716
     *
8717
     * @param string $action     (add/edit)
8718
     * @param int    $id         ID of the lp_item (if already exists)
8719
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8720
     *
8721
     * @throws Exception
8722
     * @throws HTML_QuickForm_Error
8723
     *
8724
     * @return string HTML form
8725
     */
8726
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8727
    {
8728
        $course_id = api_get_course_int_id();
8729
        $_course = api_get_course_info();
8730
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8731
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8732
8733
        $no_display_edit_textarea = false;
8734
        $item_description = '';
8735
        //If action==edit document
8736
        //We don't display the document form if it's not an editable document (html or txt file)
8737
        if ($action == 'edit') {
8738
            if (is_array($extra_info)) {
8739
                $path_parts = pathinfo($extra_info['dir']);
8740
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8741
                    $no_display_edit_textarea = true;
8742
                }
8743
            }
8744
        }
8745
        $no_display_add = false;
8746
8747
        // If action==add an existing document
8748
        // We don't display the document form if it's not an editable document (html or txt file).
8749
        if ($action == 'add') {
8750
            if (is_numeric($extra_info)) {
8751
                $extra_info = (int) $extra_info;
8752
                $sql_doc = "SELECT path FROM $tbl_doc 
8753
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8754
                $result = Database::query($sql_doc);
8755
                $path_file = Database::result($result, 0, 0);
8756
                $path_parts = pathinfo($path_file);
8757
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8758
                    $no_display_add = true;
8759
                }
8760
            }
8761
        }
8762
        if ($id != 0 && is_array($extra_info)) {
8763
            $item_title = stripslashes($extra_info['title']);
8764
            $item_description = stripslashes($extra_info['description']);
8765
            if (empty($item_title)) {
8766
                $path_parts = pathinfo($extra_info['path']);
8767
                $item_title = stripslashes($path_parts['filename']);
8768
            }
8769
        } elseif (is_numeric($extra_info)) {
8770
            $sql = "SELECT path, title FROM $tbl_doc
8771
                    WHERE
8772
                        c_id = ".$course_id." AND
8773
                        iid = ".intval($extra_info);
8774
            $result = Database::query($sql);
8775
            $row = Database::fetch_array($result);
8776
            $item_title = $row['title'];
8777
            $item_title = str_replace('_', ' ', $item_title);
8778
            if (empty($item_title)) {
8779
                $path_parts = pathinfo($row['path']);
8780
                $item_title = stripslashes($path_parts['filename']);
8781
            }
8782
        } else {
8783
            $item_title = '';
8784
            $item_description = '';
8785
        }
8786
        $return = '<legend>';
8787
        $parent = 0;
8788
        if ($id != 0 && is_array($extra_info)) {
8789
            $parent = $extra_info['parent_item_id'];
8790
        }
8791
8792
        $sql = "SELECT * FROM $tbl_lp_item
8793
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8794
        $result = Database::query($sql);
8795
        $arrLP = [];
8796
8797
        while ($row = Database::fetch_array($result)) {
8798
            $arrLP[] = [
8799
                'id' => $row['iid'],
8800
                'item_type' => $row['item_type'],
8801
                'title' => $row['title'],
8802
                'path' => $row['path'],
8803
                'description' => $row['description'],
8804
                'parent_item_id' => $row['parent_item_id'],
8805
                'previous_item_id' => $row['previous_item_id'],
8806
                'next_item_id' => $row['next_item_id'],
8807
                'display_order' => $row['display_order'],
8808
                'max_score' => $row['max_score'],
8809
                'min_score' => $row['min_score'],
8810
                'mastery_score' => $row['mastery_score'],
8811
                'prerequisite' => $row['prerequisite'],
8812
            ];
8813
        }
8814
8815
        $this->tree_array($arrLP);
8816
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8817
        unset($this->arrMenu);
8818
8819
        if ($action == 'add') {
8820
            $return .= get_lang('CreateTheDocument');
8821
        } elseif ($action == 'move') {
8822
            $return .= get_lang('MoveTheCurrentDocument');
8823
        } else {
8824
            $return .= get_lang('EditTheCurrentDocument');
8825
        }
8826
        $return .= '</legend>';
8827
8828
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8829
            $return .= Display::return_message(
8830
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8831
                false
8832
            );
8833
        }
8834
        $form = new FormValidator(
8835
            'form',
8836
            'POST',
8837
            $this->getCurrentBuildingModeURL(),
8838
            '',
8839
            ['enctype' => 'multipart/form-data']
8840
        );
8841
        $defaults['title'] = Security::remove_XSS($item_title);
8842
        if (empty($item_title)) {
8843
            $defaults['title'] = Security::remove_XSS($item_title);
8844
        }
8845
        $defaults['description'] = $item_description;
8846
        $form->addElement('html', $return);
8847
8848
        if ($action != 'move') {
8849
            $data = $this->generate_lp_folder($_course);
8850
            if ($action != 'edit') {
8851
                $folders = DocumentManager::get_all_document_folders(
8852
                    $_course,
8853
                    0,
8854
                    true
8855
                );
8856
                DocumentManager::build_directory_selector(
8857
                    $folders,
8858
                    '',
8859
                    [],
8860
                    true,
8861
                    $form,
8862
                    'directory_parent_id'
8863
                );
8864
            }
8865
8866
            if (isset($data['id'])) {
8867
                $defaults['directory_parent_id'] = $data['id'];
8868
            }
8869
8870
            $form->addElement(
8871
                'text',
8872
                'title',
8873
                get_lang('Title'),
8874
                ['id' => 'idTitle', 'class' => 'col-md-4']
8875
            );
8876
            $form->applyFilter('title', 'html_filter');
8877
        }
8878
8879
        $arrHide[0]['value'] = $this->name;
8880
        $arrHide[0]['padding'] = 20;
8881
8882
        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...
8883
            if ($action != 'add') {
8884
                if ($arrLP[$i]['item_type'] == 'dir' &&
8885
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8886
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8887
                ) {
8888
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8889
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8890
                }
8891
            } else {
8892
                if ($arrLP[$i]['item_type'] == 'dir') {
8893
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8894
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8895
                }
8896
            }
8897
        }
8898
8899
        $parentSelect = $form->addSelect(
8900
            'parent',
8901
            get_lang('Parent'),
8902
            [],
8903
            [
8904
                'id' => 'idParent',
8905
                'onchange' => 'javascript: load_cbo(this.value);',
8906
            ]
8907
        );
8908
8909
        $my_count = 0;
8910
        foreach ($arrHide as $key => $value) {
8911
            if ($my_count != 0) {
8912
                // The LP name is also the first section and is not in the same charset like the other sections.
8913
                $value['value'] = Security::remove_XSS($value['value']);
8914
                $parentSelect->addOption(
8915
                    $value['value'],
8916
                    $key,
8917
                    'style="padding-left:'.$value['padding'].'px;"'
8918
                );
8919
            } else {
8920
                $value['value'] = Security::remove_XSS($value['value']);
8921
                $parentSelect->addOption(
8922
                    $value['value'],
8923
                    $key,
8924
                    'style="padding-left:'.$value['padding'].'px;"'
8925
                );
8926
            }
8927
            $my_count++;
8928
        }
8929
8930
        if (!empty($id)) {
8931
            $parentSelect->setSelected($parent);
8932
        } else {
8933
            $parent_item_id = Session::read('parent_item_id', 0);
8934
            $parentSelect->setSelected($parent_item_id);
8935
        }
8936
8937
        if (is_array($arrLP)) {
8938
            reset($arrLP);
8939
        }
8940
8941
        $arrHide = [];
8942
        // POSITION
8943
        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...
8944
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8945
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8946
            ) {
8947
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8948
            }
8949
        }
8950
8951
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8952
8953
        $position = $form->addSelect(
8954
            'previous',
8955
            get_lang('Position'),
8956
            [],
8957
            ['id' => 'previous']
8958
        );
8959
8960
        $position->addOption(get_lang('FirstPosition'), 0);
8961
        foreach ($arrHide as $key => $value) {
8962
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8963
            $position->addOption(
8964
                $value['value'],
8965
                $key,
8966
                'style="padding-left:'.$padding.'px;"'
8967
            );
8968
        }
8969
8970
        $position->setSelected($selectedPosition);
8971
8972
        if (is_array($arrLP)) {
8973
            reset($arrLP);
8974
        }
8975
8976
        if ($action != 'move') {
8977
            $arrHide = [];
8978
            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...
8979
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8980
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8981
                ) {
8982
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8983
                }
8984
            }
8985
8986
            if (!$no_display_add) {
8987
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8988
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8989
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8990
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8991
                ) {
8992
                    if (isset($_POST['content'])) {
8993
                        $content = stripslashes($_POST['content']);
8994
                    } elseif (is_array($extra_info)) {
8995
                        //If it's an html document or a text file
8996
                        if (!$no_display_edit_textarea) {
8997
                            $content = $this->display_document(
8998
                                $extra_info['path'],
8999
                                false,
9000
                                false
9001
                            );
9002
                        }
9003
                    } elseif (is_numeric($extra_info)) {
9004
                        $content = $this->display_document(
9005
                            $extra_info,
9006
                            false,
9007
                            false
9008
                        );
9009
                    } else {
9010
                        $content = '';
9011
                    }
9012
9013
                    if (!$no_display_edit_textarea) {
9014
                        // We need to calculate here some specific settings for the online editor.
9015
                        // The calculated settings work for documents in the Documents tool
9016
                        // (on the root or in subfolders).
9017
                        // For documents in native scorm packages it is unclear whether the
9018
                        // online editor should be activated or not.
9019
9020
                        // A new document, it is in the root of the repository.
9021
                        $relative_path = '';
9022
                        $relative_prefix = '';
9023
                        if (is_array($extra_info) && $extra_info != 'new') {
9024
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9025
                            $relative_path = explode('/', $extra_info['dir']);
9026
                            $cnt = count($relative_path) - 2;
9027
                            if ($cnt < 0) {
9028
                                $cnt = 0;
9029
                            }
9030
                            $relative_prefix = str_repeat('../', $cnt);
9031
                            $relative_path = array_slice($relative_path, 1, $cnt);
9032
                            $relative_path = implode('/', $relative_path);
9033
                            if (strlen($relative_path) > 0) {
9034
                                $relative_path = $relative_path.'/';
9035
                            }
9036
                        } else {
9037
                            $result = $this->generate_lp_folder($_course);
9038
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9039
                            $relative_prefix = '../../';
9040
                        }
9041
9042
                        $editor_config = [
9043
                            'ToolbarSet' => 'LearningPathDocuments',
9044
                            'Width' => '100%',
9045
                            'Height' => '500',
9046
                            'FullPage' => true,
9047
                            'CreateDocumentDir' => $relative_prefix,
9048
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9049
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9050
                        ];
9051
9052
                        if ($_GET['action'] == 'add_item') {
9053
                            $class = 'add';
9054
                            $text = get_lang('LPCreateDocument');
9055
                        } else {
9056
                            if ($_GET['action'] == 'edit_item') {
9057
                                $class = 'save';
9058
                                $text = get_lang('SaveDocument');
9059
                            }
9060
                        }
9061
9062
                        $form->addButtonSave($text, 'submit_button');
9063
                        $renderer = $form->defaultRenderer();
9064
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9065
                        $form->addElement('html', '<div class="editor-lp">');
9066
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9067
                        $form->addElement('html', '</div>');
9068
                        $defaults['content_lp'] = $content;
9069
                    }
9070
                } elseif (is_numeric($extra_info)) {
9071
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9072
9073
                    $return = $this->display_document($extra_info, true, true, true);
9074
                    $form->addElement('html', $return);
9075
                }
9076
            }
9077
        }
9078
        if (isset($extra_info['item_type']) &&
9079
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9080
        ) {
9081
            $parentSelect->freeze();
9082
            $position->freeze();
9083
        }
9084
9085
        if ($action == 'move') {
9086
            $form->addElement('hidden', 'title', $item_title);
9087
            $form->addElement('hidden', 'description', $item_description);
9088
        }
9089
        if (is_numeric($extra_info)) {
9090
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9091
            $form->addElement('hidden', 'path', $extra_info);
9092
        } elseif (is_array($extra_info)) {
9093
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9094
            $form->addElement('hidden', 'path', $extra_info['path']);
9095
        }
9096
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9097
        $form->addElement('hidden', 'post_time', time());
9098
        $form->setDefaults($defaults);
9099
9100
        return $form->returnForm();
9101
    }
9102
9103
    /**
9104
     * Return HTML form to add/edit a link item.
9105
     *
9106
     * @param string $action     (add/edit)
9107
     * @param int    $id         Item ID if exists
9108
     * @param mixed  $extra_info
9109
     *
9110
     * @throws Exception
9111
     * @throws HTML_QuickForm_Error
9112
     *
9113
     * @return string HTML form
9114
     */
9115
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9116
    {
9117
        $course_id = api_get_course_int_id();
9118
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9119
        $tbl_link = Database::get_course_table(TABLE_LINK);
9120
9121
        if ($id != 0 && is_array($extra_info)) {
9122
            $item_title = stripslashes($extra_info['title']);
9123
            $item_description = stripslashes($extra_info['description']);
9124
            $item_url = stripslashes($extra_info['url']);
9125
        } elseif (is_numeric($extra_info)) {
9126
            $extra_info = intval($extra_info);
9127
            $sql = "SELECT title, description, url FROM ".$tbl_link."
9128
                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
9129
            $result = Database::query($sql);
9130
            $row = Database::fetch_array($result);
9131
            $item_title = $row['title'];
9132
            $item_description = $row['description'];
9133
            $item_url = $row['url'];
9134
        } else {
9135
            $item_title = '';
9136
            $item_description = '';
9137
            $item_url = '';
9138
        }
9139
9140
        $form = new FormValidator(
9141
            'edit_link',
9142
            'POST',
9143
            $this->getCurrentBuildingModeURL()
9144
        );
9145
        $defaults = [];
9146
        if ($id != 0 && is_array($extra_info)) {
9147
            $parent = $extra_info['parent_item_id'];
9148
        } else {
9149
            $parent = 0;
9150
        }
9151
9152
        $sql = "SELECT * FROM $tbl_lp_item
9153
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9154
        $result = Database::query($sql);
9155
        $arrLP = [];
9156
9157
        while ($row = Database::fetch_array($result)) {
9158
            $arrLP[] = [
9159
                'id' => $row['id'],
9160
                'item_type' => $row['item_type'],
9161
                'title' => $row['title'],
9162
                'path' => $row['path'],
9163
                'description' => $row['description'],
9164
                'parent_item_id' => $row['parent_item_id'],
9165
                'previous_item_id' => $row['previous_item_id'],
9166
                'next_item_id' => $row['next_item_id'],
9167
                'display_order' => $row['display_order'],
9168
                'max_score' => $row['max_score'],
9169
                'min_score' => $row['min_score'],
9170
                'mastery_score' => $row['mastery_score'],
9171
                'prerequisite' => $row['prerequisite'],
9172
            ];
9173
        }
9174
9175
        $this->tree_array($arrLP);
9176
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9177
        unset($this->arrMenu);
9178
9179
        if ($action == 'add') {
9180
            $legend = get_lang('CreateTheLink');
9181
        } elseif ($action == 'move') {
9182
            $legend = get_lang('MoveCurrentLink');
9183
        } else {
9184
            $legend = get_lang('EditCurrentLink');
9185
        }
9186
9187
        $form->addHeader($legend);
9188
9189
        if ($action != 'move') {
9190
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9191
            $defaults['title'] = $item_title;
9192
        }
9193
9194
        $selectParent = $form->addSelect(
9195
            'parent',
9196
            get_lang('Parent'),
9197
            [],
9198
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9199
        );
9200
        $selectParent->addOption($this->name, 0);
9201
        $arrHide = [
9202
            $id,
9203
        ];
9204
9205
        $parent_item_id = Session::read('parent_item_id', 0);
9206
9207
        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...
9208
            if ($action != 'add') {
9209
                if (
9210
                    ($arrLP[$i]['item_type'] == 'dir') &&
9211
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9212
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9213
                ) {
9214
                    $selectParent->addOption(
9215
                        $arrLP[$i]['title'],
9216
                        $arrLP[$i]['id'],
9217
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9218
                    );
9219
9220
                    if ($parent == $arrLP[$i]['id']) {
9221
                        $selectParent->setSelected($arrLP[$i]['id']);
9222
                    }
9223
                } else {
9224
                    $arrHide[] = $arrLP[$i]['id'];
9225
                }
9226
            } else {
9227
                if ($arrLP[$i]['item_type'] == 'dir') {
9228
                    $selectParent->addOption(
9229
                        $arrLP[$i]['title'],
9230
                        $arrLP[$i]['id'],
9231
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9232
                    );
9233
9234
                    if ($parent_item_id == $arrLP[$i]['id']) {
9235
                        $selectParent->setSelected($arrLP[$i]['id']);
9236
                    }
9237
                }
9238
            }
9239
        }
9240
9241
        if (is_array($arrLP)) {
9242
            reset($arrLP);
9243
        }
9244
9245
        $selectPrevious = $form->addSelect(
9246
            'previous',
9247
            get_lang('Position'),
9248
            [],
9249
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9250
        );
9251
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9252
9253
        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...
9254
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9255
                $selectPrevious->addOption(
9256
                    $arrLP[$i]['title'],
9257
                    $arrLP[$i]['id']
9258
                );
9259
9260
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9261
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9262
                } elseif ($action == 'add') {
9263
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9264
                }
9265
            }
9266
        }
9267
9268
        if ($action != 'move') {
9269
            $urlAttributes = ['class' => 'learnpath_item_form'];
9270
9271
            if (is_numeric($extra_info)) {
9272
                $urlAttributes['disabled'] = 'disabled';
9273
            }
9274
9275
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9276
            $defaults['url'] = $item_url;
9277
            $arrHide = [];
9278
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9279
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9280
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9281
                }
9282
            }
9283
        }
9284
9285
        if ($action == 'add') {
9286
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9287
        } else {
9288
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9289
        }
9290
9291
        if ($action == 'move') {
9292
            $form->addHidden('title', $item_title);
9293
            $form->addHidden('description', $item_description);
9294
        }
9295
9296
        if (is_numeric($extra_info)) {
9297
            $form->addHidden('path', $extra_info);
9298
        } elseif (is_array($extra_info)) {
9299
            $form->addHidden('path', $extra_info['path']);
9300
        }
9301
        $form->addHidden('type', TOOL_LINK);
9302
        $form->addHidden('post_time', time());
9303
        $form->setDefaults($defaults);
9304
9305
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9306
    }
9307
9308
    /**
9309
     * Return HTML form to add/edit a student publication (work).
9310
     *
9311
     * @param string $action
9312
     * @param int    $id         Item ID if already exists
9313
     * @param string $extra_info
9314
     *
9315
     * @throws Exception
9316
     *
9317
     * @return string HTML form
9318
     */
9319
    public function display_student_publication_form(
9320
        $action = 'add',
9321
        $id = 0,
9322
        $extra_info = ''
9323
    ) {
9324
        $course_id = api_get_course_int_id();
9325
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9326
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9327
9328
        $item_title = get_lang('Student_publication');
9329
        if ($id != 0 && is_array($extra_info)) {
9330
            $item_title = stripslashes($extra_info['title']);
9331
            $item_description = stripslashes($extra_info['description']);
9332
        } elseif (is_numeric($extra_info)) {
9333
            $extra_info = (int) $extra_info;
9334
            $sql = "SELECT title, description
9335
                    FROM $tbl_publication
9336
                    WHERE c_id = $course_id AND id = ".$extra_info;
9337
9338
            $result = Database::query($sql);
9339
            $row = Database::fetch_array($result);
9340
            if ($row) {
9341
                $item_title = $row['title'];
9342
            }
9343
        }
9344
9345
        $parent = 0;
9346
        if ($id != 0 && is_array($extra_info)) {
9347
            $parent = $extra_info['parent_item_id'];
9348
        }
9349
9350
        $sql = "SELECT * FROM $tbl_lp_item 
9351
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9352
        $result = Database::query($sql);
9353
        $arrLP = [];
9354
9355
        while ($row = Database::fetch_array($result)) {
9356
            $arrLP[] = [
9357
                'id' => $row['iid'],
9358
                'item_type' => $row['item_type'],
9359
                'title' => $row['title'],
9360
                'path' => $row['path'],
9361
                'description' => $row['description'],
9362
                'parent_item_id' => $row['parent_item_id'],
9363
                'previous_item_id' => $row['previous_item_id'],
9364
                'next_item_id' => $row['next_item_id'],
9365
                'display_order' => $row['display_order'],
9366
                'max_score' => $row['max_score'],
9367
                'min_score' => $row['min_score'],
9368
                'mastery_score' => $row['mastery_score'],
9369
                'prerequisite' => $row['prerequisite'],
9370
            ];
9371
        }
9372
9373
        $this->tree_array($arrLP);
9374
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9375
        unset($this->arrMenu);
9376
9377
        $form = new FormValidator('frm_student_publication', 'post', '#');
9378
9379
        if ($action == 'add') {
9380
            $form->addHeader(get_lang('Student_publication'));
9381
        } elseif ($action == 'move') {
9382
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9383
        } else {
9384
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9385
        }
9386
9387
        if ($action != 'move') {
9388
            $form->addText(
9389
                'title',
9390
                get_lang('Title'),
9391
                true,
9392
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9393
            );
9394
        }
9395
9396
        $parentSelect = $form->addSelect(
9397
            'parent',
9398
            get_lang('Parent'),
9399
            ['0' => $this->name],
9400
            [
9401
                'onchange' => 'javascript: load_cbo(this.value);',
9402
                'class' => 'learnpath_item_form',
9403
                'id' => 'idParent',
9404
            ]
9405
        );
9406
9407
        $arrHide = [$id];
9408
        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...
9409
            if ($action != 'add') {
9410
                if (
9411
                    ($arrLP[$i]['item_type'] == 'dir') &&
9412
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9413
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9414
                ) {
9415
                    $parentSelect->addOption(
9416
                        $arrLP[$i]['title'],
9417
                        $arrLP[$i]['id'],
9418
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9419
                    );
9420
9421
                    if ($parent == $arrLP[$i]['id']) {
9422
                        $parentSelect->setSelected($arrLP[$i]['id']);
9423
                    }
9424
                } else {
9425
                    $arrHide[] = $arrLP[$i]['id'];
9426
                }
9427
            } else {
9428
                if ($arrLP[$i]['item_type'] == 'dir') {
9429
                    $parentSelect->addOption(
9430
                        $arrLP[$i]['title'],
9431
                        $arrLP[$i]['id'],
9432
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9433
                    );
9434
9435
                    if ($parent == $arrLP[$i]['id']) {
9436
                        $parentSelect->setSelected($arrLP[$i]['id']);
9437
                    }
9438
                }
9439
            }
9440
        }
9441
9442
        if (is_array($arrLP)) {
9443
            reset($arrLP);
9444
        }
9445
9446
        $previousSelect = $form->addSelect(
9447
            'previous',
9448
            get_lang('Position'),
9449
            ['0' => get_lang('FirstPosition')],
9450
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9451
        );
9452
9453
        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...
9454
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9455
                $previousSelect->addOption(
9456
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9457
                    $arrLP[$i]['id']
9458
                );
9459
9460
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9461
                    $previousSelect->setSelected($arrLP[$i]['id']);
9462
                } elseif ($action == 'add') {
9463
                    $previousSelect->setSelected($arrLP[$i]['id']);
9464
                }
9465
            }
9466
        }
9467
9468
        if ($action == 'add') {
9469
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9470
        } else {
9471
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9472
        }
9473
9474
        if ($action == 'move') {
9475
            $form->addHidden('title', $item_title);
9476
            $form->addHidden('description', $item_description);
9477
        }
9478
9479
        if (is_numeric($extra_info)) {
9480
            $form->addHidden('path', $extra_info);
9481
        } elseif (is_array($extra_info)) {
9482
            $form->addHidden('path', $extra_info['path']);
9483
        }
9484
9485
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9486
        $form->addHidden('post_time', time());
9487
        $form->setDefaults(['title' => $item_title]);
9488
9489
        $return = '<div class="sectioncomment">';
9490
        $return .= $form->returnForm();
9491
        $return .= '</div>';
9492
9493
        return $return;
9494
    }
9495
9496
    /**
9497
     * Displays the menu for manipulating a step.
9498
     *
9499
     * @param id     $item_id
9500
     * @param string $item_type
9501
     *
9502
     * @return string
9503
     */
9504
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9505
    {
9506
        $_course = api_get_course_info();
9507
        $course_code = api_get_course_id();
9508
        $return = '<div class="actions">';
9509
        switch ($item_type) {
9510
            case 'dir':
9511
                // Commented the message cause should not show it.
9512
                //$lang = get_lang('TitleManipulateChapter');
9513
                break;
9514
            case TOOL_LP_FINAL_ITEM:
9515
            case TOOL_DOCUMENT:
9516
                // Commented the message cause should not show it.
9517
                //$lang = get_lang('TitleManipulateDocument');
9518
                break;
9519
            case TOOL_LINK:
9520
            case 'link':
9521
                // Commented the message cause should not show it.
9522
                //$lang = get_lang('TitleManipulateLink');
9523
                break;
9524
            case TOOL_QUIZ:
9525
                // Commented the message cause should not show it.
9526
                //$lang = get_lang('TitleManipulateQuiz');
9527
                break;
9528
            case TOOL_STUDENTPUBLICATION:
9529
                // Commented the message cause should not show it.
9530
                //$lang = get_lang('TitleManipulateStudentPublication');
9531
                break;
9532
        }
9533
9534
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9535
        $item_id = (int) $item_id;
9536
        $sql = "SELECT * FROM $tbl_lp_item 
9537
                WHERE iid = ".$item_id;
9538
        $result = Database::query($sql);
9539
        $row = Database::fetch_assoc($result);
9540
9541
        $audio_player = null;
9542
        // We display an audio player if needed.
9543
        if (!empty($row['audio'])) {
9544
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9545
9546
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9547
                .'<audio src="'.$webAudioPath.'" controls>'
9548
                .'</div><br>';
9549
        }
9550
9551
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9552
9553
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9554
            $return .= Display::url(
9555
                Display::return_icon(
9556
                    'edit.png',
9557
                    get_lang('Edit'),
9558
                    [],
9559
                    ICON_SIZE_SMALL
9560
                ),
9561
                $url.'&action=edit_item&path_item='.$row['path']
9562
            );
9563
9564
            $return .= Display::url(
9565
                Display::return_icon(
9566
                    'move.png',
9567
                    get_lang('Move'),
9568
                    [],
9569
                    ICON_SIZE_SMALL
9570
                ),
9571
                $url.'&action=move_item'
9572
            );
9573
        }
9574
9575
        // Commented for now as prerequisites cannot be added to chapters.
9576
        if ($item_type != 'dir') {
9577
            $return .= Display::url(
9578
                Display::return_icon(
9579
                    'accept.png',
9580
                    get_lang('LearnpathPrerequisites'),
9581
                    [],
9582
                    ICON_SIZE_SMALL
9583
                ),
9584
                $url.'&action=edit_item_prereq'
9585
            );
9586
        }
9587
        $return .= Display::url(
9588
            Display::return_icon(
9589
                'delete.png',
9590
                get_lang('Delete'),
9591
                [],
9592
                ICON_SIZE_SMALL
9593
            ),
9594
            $url.'&action=delete_item'
9595
        );
9596
9597
        if ($item_type == TOOL_HOTPOTATOES) {
9598
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9599
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9600
        }
9601
9602
        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
9603
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9604
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9605
        }
9606
9607
        $return .= '</div>';
9608
9609
        if (!empty($audio_player)) {
9610
            $return .= $audio_player;
9611
        }
9612
9613
        return $return;
9614
    }
9615
9616
    /**
9617
     * Creates the javascript needed for filling up the checkboxes without page reload.
9618
     *
9619
     * @return string
9620
     */
9621
    public function get_js_dropdown_array()
9622
    {
9623
        $course_id = api_get_course_int_id();
9624
        $return = 'var child_name = new Array();'."\n";
9625
        $return .= 'var child_value = new Array();'."\n\n";
9626
        $return .= 'child_name[0] = new Array();'."\n";
9627
        $return .= 'child_value[0] = new Array();'."\n\n";
9628
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9629
        $sql = "SELECT * FROM ".$tbl_lp_item."
9630
                WHERE 
9631
                    c_id = $course_id AND 
9632
                    lp_id = ".$this->lp_id." AND 
9633
                    parent_item_id = 0
9634
                ORDER BY display_order ASC";
9635
        $res_zero = Database::query($sql);
9636
        $i = 0;
9637
9638
        while ($row_zero = Database::fetch_array($res_zero)) {
9639
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9640
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9641
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9642
                }
9643
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9644
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9645
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9646
            }
9647
        }
9648
        $return .= "\n";
9649
        $sql = "SELECT * FROM $tbl_lp_item
9650
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9651
        $res = Database::query($sql);
9652
        while ($row = Database::fetch_array($res)) {
9653
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9654
                           WHERE
9655
                                c_id = ".$course_id." AND
9656
                                parent_item_id = ".$row['iid']."
9657
                           ORDER BY display_order ASC";
9658
            $res_parent = Database::query($sql_parent);
9659
            $i = 0;
9660
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9661
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9662
9663
            while ($row_parent = Database::fetch_array($res_parent)) {
9664
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
9665
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9666
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9667
            }
9668
            $return .= "\n";
9669
        }
9670
9671
        $return .= "
9672
            function load_cbo(id) {
9673
                if (!id) {
9674
                    return false;
9675
                }
9676
            
9677
                var cbo = document.getElementById('previous');
9678
                for(var i = cbo.length - 1; i > 0; i--) {
9679
                    cbo.options[i] = null;
9680
                }
9681
            
9682
                var k=0;
9683
                for(var i = 1; i <= child_name[id].length; i++){
9684
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9685
                    option.style.paddingLeft = '40px';
9686
                    cbo.options[i] = option;
9687
                    k = i;
9688
                }
9689
            
9690
                cbo.options[k].selected = true;
9691
                $('#previous').selectpicker('refresh');
9692
            }";
9693
9694
        return $return;
9695
    }
9696
9697
    /**
9698
     * Display the form to allow moving an item.
9699
     *
9700
     * @param int $item_id Item ID
9701
     *
9702
     * @throws Exception
9703
     * @throws HTML_QuickForm_Error
9704
     *
9705
     * @return string HTML form
9706
     */
9707
    public function display_move_item($item_id)
9708
    {
9709
        $return = '';
9710
        if (is_numeric($item_id)) {
9711
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9712
9713
            $sql = "SELECT * FROM $tbl_lp_item
9714
                    WHERE iid = $item_id";
9715
            $res = Database::query($sql);
9716
            $row = Database::fetch_array($res);
9717
9718
            switch ($row['item_type']) {
9719
                case 'dir':
9720
                case 'asset':
9721
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9722
                    $return .= $this->display_item_form(
9723
                        $row['item_type'],
9724
                        get_lang('MoveCurrentChapter'),
9725
                        'move',
9726
                        $item_id,
9727
                        $row
9728
                    );
9729
                    break;
9730
                case TOOL_DOCUMENT:
9731
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9732
                    $return .= $this->display_document_form('move', $item_id, $row);
9733
                    break;
9734
                case TOOL_LINK:
9735
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9736
                    $return .= $this->display_link_form('move', $item_id, $row);
9737
                    break;
9738
                case TOOL_HOTPOTATOES:
9739
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9740
                    $return .= $this->display_link_form('move', $item_id, $row);
9741
                    break;
9742
                case TOOL_QUIZ:
9743
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9744
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9745
                    break;
9746
                case TOOL_STUDENTPUBLICATION:
9747
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9748
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9749
                    break;
9750
                case TOOL_FORUM:
9751
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9752
                    $return .= $this->display_forum_form('move', $item_id, $row);
9753
                    break;
9754
                case TOOL_THREAD:
9755
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9756
                    $return .= $this->display_forum_form('move', $item_id, $row);
9757
                    break;
9758
            }
9759
        }
9760
9761
        return $return;
9762
    }
9763
9764
    /**
9765
     * Return HTML form to allow prerequisites selection.
9766
     *
9767
     * @todo use FormValidator
9768
     *
9769
     * @param int Item ID
9770
     *
9771
     * @return string HTML form
9772
     */
9773
    public function display_item_prerequisites_form($item_id = 0)
9774
    {
9775
        $course_id = api_get_course_int_id();
9776
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9777
        $item_id = intval($item_id);
9778
        /* Current prerequisite */
9779
        $sql = "SELECT * FROM $tbl_lp_item
9780
                WHERE iid = $item_id";
9781
        $result = Database::query($sql);
9782
        $row = Database::fetch_array($result);
9783
        $prerequisiteId = $row['prerequisite'];
9784
        $return = '<legend>';
9785
        $return .= get_lang('AddEditPrerequisites');
9786
        $return .= '</legend>';
9787
        $return .= '<form method="POST">';
9788
        $return .= '<div class="table-responsive">';
9789
        $return .= '<table class="table table-hover">';
9790
        $return .= '<thead>';
9791
        $return .= '<tr>';
9792
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9793
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9794
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9795
        $return .= '</tr>';
9796
        $return .= '</thead>';
9797
9798
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9799
        $return .= '<tbody>';
9800
        $return .= '<tr>';
9801
        $return .= '<td colspan="3">';
9802
        $return .= '<div class="radio learnpath"><label for="idNone">';
9803
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9804
        $return .= get_lang('None').'</label>';
9805
        $return .= '</div>';
9806
        $return .= '</tr>';
9807
9808
        $sql = "SELECT * FROM $tbl_lp_item
9809
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9810
        $result = Database::query($sql);
9811
        $arrLP = [];
9812
9813
        $selectedMinScore = [];
9814
        $selectedMaxScore = [];
9815
        while ($row = Database::fetch_array($result)) {
9816
            if ($row['id'] == $item_id) {
9817
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9818
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9819
            }
9820
            $arrLP[] = [
9821
                'id' => $row['iid'],
9822
                'item_type' => $row['item_type'],
9823
                'title' => $row['title'],
9824
                'ref' => $row['ref'],
9825
                'description' => $row['description'],
9826
                'parent_item_id' => $row['parent_item_id'],
9827
                'previous_item_id' => $row['previous_item_id'],
9828
                'next_item_id' => $row['next_item_id'],
9829
                'max_score' => $row['max_score'],
9830
                'min_score' => $row['min_score'],
9831
                'mastery_score' => $row['mastery_score'],
9832
                'prerequisite' => $row['prerequisite'],
9833
                'display_order' => $row['display_order'],
9834
                'prerequisite_min_score' => $row['prerequisite_min_score'],
9835
                'prerequisite_max_score' => $row['prerequisite_max_score'],
9836
            ];
9837
        }
9838
9839
        $this->tree_array($arrLP);
9840
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9841
        unset($this->arrMenu);
9842
9843
        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...
9844
            $item = $arrLP[$i];
9845
9846
            if ($item['id'] == $item_id) {
9847
                break;
9848
            }
9849
9850
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9851
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9852
9853
            $return .= '<tr>';
9854
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9855
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9856
            $return .= '<label for="id'.$item['id'].'">';
9857
            $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'].'" />';
9858
9859
            $icon_name = str_replace(' ', '', $item['item_type']);
9860
9861
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9862
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9863
            } else {
9864
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9865
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9866
                } else {
9867
                    $return .= Display::return_icon('folder_document.png');
9868
                }
9869
            }
9870
9871
            $return .= $item['title'].'</label>';
9872
            $return .= '</div>';
9873
            $return .= '</td>';
9874
9875
            if ($item['item_type'] == TOOL_QUIZ) {
9876
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9877
                $lpItemObj = new LpItem($course_id, $item['id']);
9878
                $exercise = new Exercise($course_id);
9879
                $exercise->read($lpItemObj->path);
9880
                $lpItemObj->max_score = $exercise->get_max_score();
9881
                $lpItemObj->update();
9882
                $item['max_score'] = $lpItemObj->max_score;
9883
9884
                $return .= '<td>';
9885
                $return .= '<input 
9886
                    class="form-control" 
9887
                    size="4" maxlength="3" 
9888
                    name="min_'.$item['id'].'" 
9889
                    type="number" 
9890
                    min="0" 
9891
                    step="1" 
9892
                    max="'.$item['max_score'].'" 
9893
                    value="'.$selectedMinScoreValue.'" 
9894
                />';
9895
                $return .= '</td>';
9896
                $return .= '<td>';
9897
                $return .= '<input 
9898
                    class="form-control" 
9899
                    size="4" 
9900
                    maxlength="3" 
9901
                    name="max_'.$item['id'].'" 
9902
                    type="number" 
9903
                    min="0" 
9904
                    step="1" 
9905
                    max="'.$item['max_score'].'" 
9906
                    value="'.$selectedMaxScoreValue.'" 
9907
                />';
9908
                $return .= '</td>';
9909
            }
9910
9911
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9912
                $return .= '<td>';
9913
                $return .= '<input 
9914
                    size="4" 
9915
                    maxlength="3" 
9916
                    name="min_'.$item['id'].'" 
9917
                    type="number" 
9918
                    min="0" 
9919
                    step="1" 
9920
                    max="'.$item['max_score'].'" 
9921
                    value="'.$selectedMinScoreValue.'" 
9922
                />';
9923
                $return .= '</td>';
9924
                $return .= '<td>';
9925
                $return .= '<input 
9926
                    size="4" 
9927
                    maxlength="3" 
9928
                    name="max_'.$item['id'].'" 
9929
                    type="number" 
9930
                    min="0" 
9931
                    step="1" 
9932
                    max="'.$item['max_score'].'" 
9933
                    value="'.$selectedMaxScoreValue.'" 
9934
                />';
9935
                $return .= '</td>';
9936
            }
9937
            $return .= '</tr>';
9938
        }
9939
        $return .= '<tr>';
9940
        $return .= '</tr>';
9941
        $return .= '</tbody>';
9942
        $return .= '</table>';
9943
        $return .= '</div>';
9944
        $return .= '<div class="form-group">';
9945
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9946
            get_lang('ModifyPrerequisites').'</button>';
9947
        $return .= '</form>';
9948
9949
        return $return;
9950
    }
9951
9952
    /**
9953
     * Return HTML list to allow prerequisites selection for lp.
9954
     *
9955
     * @return string HTML form
9956
     */
9957
    public function display_lp_prerequisites_list()
9958
    {
9959
        $course_id = api_get_course_int_id();
9960
        $lp_id = $this->lp_id;
9961
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9962
9963
        // get current prerequisite
9964
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9965
        $result = Database::query($sql);
9966
        $row = Database::fetch_array($result);
9967
        $prerequisiteId = $row['prerequisite'];
9968
        $session_id = api_get_session_id();
9969
        $session_condition = api_get_session_condition($session_id, true, true);
9970
        $sql = "SELECT * FROM $tbl_lp
9971
                WHERE c_id = $course_id $session_condition
9972
                ORDER BY display_order ";
9973
        $rs = Database::query($sql);
9974
        $return = '';
9975
        $return .= '<select name="prerequisites" class="form-control">';
9976
        $return .= '<option value="0">'.get_lang('None').'</option>';
9977
        if (Database::num_rows($rs) > 0) {
9978
            while ($row = Database::fetch_array($rs)) {
9979
                if ($row['id'] == $lp_id) {
9980
                    continue;
9981
                }
9982
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9983
            }
9984
        }
9985
        $return .= '</select>';
9986
9987
        return $return;
9988
    }
9989
9990
    /**
9991
     * Creates a list with all the documents in it.
9992
     *
9993
     * @param bool $showInvisibleFiles
9994
     *
9995
     * @throws Exception
9996
     * @throws HTML_QuickForm_Error
9997
     *
9998
     * @return string
9999
     */
10000
    public function get_documents($showInvisibleFiles = false)
10001
    {
10002
        $course_info = api_get_course_info();
10003
        $sessionId = api_get_session_id();
10004
        $documentTree = DocumentManager::get_document_preview(
10005
            $course_info,
10006
            $this->lp_id,
10007
            null,
10008
            $sessionId,
10009
            true,
10010
            null,
10011
            null,
10012
            $showInvisibleFiles,
10013
            true
10014
        );
10015
10016
        $headers = [
10017
            get_lang('Files'),
10018
            get_lang('CreateTheDocument'),
10019
            get_lang('Upload'),
10020
        ];
10021
10022
        $form = new FormValidator(
10023
            'form_upload',
10024
            'POST',
10025
            $this->getCurrentBuildingModeURL(),
10026
            '',
10027
            ['enctype' => 'multipart/form-data']
10028
        );
10029
10030
        $folders = DocumentManager::get_all_document_folders(
10031
            api_get_course_info(),
10032
            0,
10033
            true
10034
        );
10035
10036
        DocumentManager::build_directory_selector(
10037
            $folders,
10038
            '',
10039
            [],
10040
            true,
10041
            $form,
10042
            'directory_parent_id'
10043
        );
10044
10045
        $group = [
10046
            $form->createElement(
10047
                'radio',
10048
                'if_exists',
10049
                get_lang("UplWhatIfFileExists"),
10050
                get_lang('UplDoNothing'),
10051
                'nothing'
10052
            ),
10053
            $form->createElement(
10054
                'radio',
10055
                'if_exists',
10056
                null,
10057
                get_lang('UplOverwriteLong'),
10058
                'overwrite'
10059
            ),
10060
            $form->createElement(
10061
                'radio',
10062
                'if_exists',
10063
                null,
10064
                get_lang('UplRenameLong'),
10065
                'rename'
10066
            ),
10067
        ];
10068
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10069
10070
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10071
        $defaultFileExistsOption = 'rename';
10072
        if (!empty($fileExistsOption)) {
10073
            $defaultFileExistsOption = $fileExistsOption;
10074
        }
10075
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10076
10077
        // Check box options
10078
        $form->addElement(
10079
            'checkbox',
10080
            'unzip',
10081
            get_lang('Options'),
10082
            get_lang('Uncompress')
10083
        );
10084
10085
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10086
        $form->addMultipleUpload($url);
10087
        $new = $this->display_document_form('add', 0);
10088
        $tabs = Display::tabs(
10089
            $headers,
10090
            [$documentTree, $new, $form->returnForm()],
10091
            'subtab'
10092
        );
10093
10094
        return $tabs;
10095
    }
10096
10097
    /**
10098
     * Creates a list with all the exercises (quiz) in it.
10099
     *
10100
     * @return string
10101
     */
10102
    public function get_exercises()
10103
    {
10104
        $course_id = api_get_course_int_id();
10105
        $session_id = api_get_session_id();
10106
        $userInfo = api_get_user_info();
10107
10108
        // New for hotpotatoes.
10109
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10110
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10111
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10112
        $condition_session = api_get_session_condition($session_id, true, true);
10113
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10114
10115
        $activeCondition = ' active <> -1 ';
10116
        if ($setting) {
10117
            $activeCondition = ' active = 1 ';
10118
        }
10119
10120
        $sql_quiz = "SELECT * FROM $tbl_quiz
10121
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10122
                     ORDER BY title ASC";
10123
10124
        $sql_hot = "SELECT * FROM $tbl_doc
10125
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10126
                     ORDER BY id ASC";
10127
10128
        $res_quiz = Database::query($sql_quiz);
10129
        $res_hot = Database::query($sql_hot);
10130
10131
        $return = '<ul class="lp_resource">';
10132
        $return .= '<li class="lp_resource_element">';
10133
        $return .= Display::return_icon('new_exercice.png');
10134
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10135
            get_lang('NewExercise').'</a>';
10136
        $return .= '</li>';
10137
10138
        $previewIcon = Display::return_icon(
10139
            'preview_view.png',
10140
            get_lang('Preview')
10141
        );
10142
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10143
10144
        // Display hotpotatoes
10145
        while ($row_hot = Database::fetch_array($res_hot)) {
10146
            $link = Display::url(
10147
                $previewIcon,
10148
                $exerciseUrl.'&file='.$row_hot['path'],
10149
                ['target' => '_blank']
10150
            );
10151
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10152
            $return .= '<a class="moved" href="#">';
10153
            $return .= Display::return_icon(
10154
                'move_everywhere.png',
10155
                get_lang('Move'),
10156
                [],
10157
                ICON_SIZE_TINY
10158
            );
10159
            $return .= '</a> ';
10160
            $return .= Display::return_icon('hotpotatoes_s.png');
10161
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10162
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10163
            $return .= '</li>';
10164
        }
10165
10166
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10167
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10168
            $title = strip_tags(
10169
                api_html_entity_decode($row_quiz['title'])
10170
            );
10171
10172
            $link = Display::url(
10173
                $previewIcon,
10174
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10175
                ['target' => '_blank']
10176
            );
10177
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10178
            $return .= '<a class="moved" href="#">';
10179
            $return .= Display::return_icon(
10180
                'move_everywhere.png',
10181
                get_lang('Move'),
10182
                [],
10183
                ICON_SIZE_TINY
10184
            );
10185
            $return .= '</a> ';
10186
            $return .= Display::return_icon(
10187
                'quiz.png',
10188
                '',
10189
                [],
10190
                ICON_SIZE_TINY
10191
            );
10192
            $sessionStar = api_get_session_image(
10193
                $row_quiz['session_id'],
10194
                $userInfo['status']
10195
            );
10196
            $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.'">'.
10197
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
10198
                '</a>';
10199
10200
            $return .= '</li>';
10201
        }
10202
10203
        $return .= '</ul>';
10204
10205
        return $return;
10206
    }
10207
10208
    /**
10209
     * Creates a list with all the links in it.
10210
     *
10211
     * @return string
10212
     */
10213
    public function get_links()
10214
    {
10215
        $selfUrl = api_get_self();
10216
        $courseIdReq = api_get_cidreq();
10217
        $course = api_get_course_info();
10218
        $userInfo = api_get_user_info();
10219
10220
        $course_id = $course['real_id'];
10221
        $tbl_link = Database::get_course_table(TABLE_LINK);
10222
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10223
        $moveEverywhereIcon = Display::return_icon(
10224
            'move_everywhere.png',
10225
            get_lang('Move'),
10226
            [],
10227
            ICON_SIZE_TINY
10228
        );
10229
10230
        $session_id = api_get_session_id();
10231
        $condition_session = api_get_session_condition(
10232
            $session_id,
10233
            true,
10234
            true,
10235
            "link.session_id"
10236
        );
10237
10238
        $sql = "SELECT 
10239
                    link.id as link_id,
10240
                    link.title as link_title,
10241
                    link.session_id as link_session_id,
10242
                    link.category_id as category_id,
10243
                    link_category.category_title as category_title
10244
                FROM $tbl_link as link
10245
                LEFT JOIN $linkCategoryTable as link_category
10246
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10247
                WHERE link.c_id = ".$course_id." $condition_session
10248
                ORDER BY link_category.category_title ASC, link.title ASC";
10249
        $result = Database::query($sql);
10250
        $categorizedLinks = [];
10251
        $categories = [];
10252
10253
        while ($link = Database::fetch_array($result)) {
10254
            if (!$link['category_id']) {
10255
                $link['category_title'] = get_lang('Uncategorized');
10256
            }
10257
            $categories[$link['category_id']] = $link['category_title'];
10258
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10259
        }
10260
10261
        $linksHtmlCode =
10262
            '<script>
10263
            function toggle_tool(tool, id) {
10264
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10265
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10266
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10267
                } else {
10268
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10269
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10270
                }
10271
            }
10272
        </script>
10273
10274
        <ul class="lp_resource">
10275
            <li class="lp_resource_element">
10276
                '.Display::return_icon('linksnew.gif').'
10277
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10278
                get_lang('LinkAdd').'
10279
                </a>
10280
            </li>';
10281
10282
        foreach ($categorizedLinks as $categoryId => $links) {
10283
            $linkNodes = null;
10284
            foreach ($links as $key => $linkInfo) {
10285
                $title = $linkInfo['link_title'];
10286
                $linkSessionId = $linkInfo['link_session_id'];
10287
10288
                $link = Display::url(
10289
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10290
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10291
                    ['target' => '_blank']
10292
                );
10293
10294
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10295
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10296
                    $linkNodes .=
10297
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10298
                        <a class="moved" href="#">'.
10299
                            $moveEverywhereIcon.
10300
                        '</a>
10301
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10302
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10303
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10304
                        Security::remove_XSS($title).$sessionStar.$link.
10305
                        '</a>
10306
                    </li>';
10307
                }
10308
            }
10309
            $linksHtmlCode .=
10310
                '<li>
10311
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10312
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10313
                    align="absbottom" />
10314
                </a>
10315
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10316
            </li>
10317
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10318
        }
10319
        $linksHtmlCode .= '</ul>';
10320
10321
        return $linksHtmlCode;
10322
    }
10323
10324
    /**
10325
     * Creates a list with all the student publications in it.
10326
     *
10327
     * @return string
10328
     */
10329
    public function get_student_publications()
10330
    {
10331
        $return = '<ul class="lp_resource">';
10332
        $return .= '<li class="lp_resource_element">';
10333
        $return .= Display::return_icon('works_new.gif');
10334
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10335
            get_lang('AddAssignmentPage').'</a>';
10336
        $return .= '</li>';
10337
10338
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10339
        $works = getWorkListTeacher(0, 100, null, null, null);
10340
        if (!empty($works)) {
10341
            foreach ($works as $work) {
10342
                $link = Display::url(
10343
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10344
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10345
                    ['target' => '_blank']
10346
                );
10347
10348
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10349
                $return .= '<a class="moved" href="#">';
10350
                $return .= Display::return_icon(
10351
                    'move_everywhere.png',
10352
                    get_lang('Move'),
10353
                    [],
10354
                    ICON_SIZE_TINY
10355
                );
10356
                $return .= '</a> ';
10357
10358
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10359
                $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.'">'.
10360
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10361
                </a>';
10362
10363
                $return .= '</li>';
10364
            }
10365
        }
10366
10367
        $return .= '</ul>';
10368
10369
        return $return;
10370
    }
10371
10372
    /**
10373
     * Creates a list with all the forums in it.
10374
     *
10375
     * @return string
10376
     */
10377
    public function get_forums()
10378
    {
10379
        require_once '../forum/forumfunction.inc.php';
10380
        require_once '../forum/forumconfig.inc.php';
10381
10382
        $forumCategories = get_forum_categories();
10383
        $forumsInNoCategory = get_forums_in_category(0);
10384
        if (!empty($forumsInNoCategory)) {
10385
            $forumCategories = array_merge(
10386
                $forumCategories,
10387
                [
10388
                    [
10389
                        'cat_id' => 0,
10390
                        'session_id' => 0,
10391
                        'visibility' => 1,
10392
                        'cat_comment' => null,
10393
                    ],
10394
                ]
10395
            );
10396
        }
10397
10398
        $forumList = get_forums();
10399
        $a_forums = [];
10400
        foreach ($forumCategories as $forumCategory) {
10401
            // The forums in this category.
10402
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10403
            if (!empty($forumsInCategory)) {
10404
                foreach ($forumList as $forum) {
10405
                    if (isset($forum['forum_category']) &&
10406
                        $forum['forum_category'] == $forumCategory['cat_id']
10407
                    ) {
10408
                        $a_forums[] = $forum;
10409
                    }
10410
                }
10411
            }
10412
        }
10413
10414
        $return = '<ul class="lp_resource">';
10415
10416
        // First add link
10417
        $return .= '<li class="lp_resource_element">';
10418
        $return .= Display::return_icon('new_forum.png');
10419
        $return .= Display::url(
10420
            get_lang('CreateANewForum'),
10421
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10422
                'action' => 'add',
10423
                'content' => 'forum',
10424
                'lp_id' => $this->lp_id,
10425
            ]),
10426
            ['title' => get_lang('CreateANewForum')]
10427
        );
10428
        $return .= '</li>';
10429
10430
        $return .= '<script>
10431
            function toggle_forum(forum_id) {
10432
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10433
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10434
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10435
                } else {
10436
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10437
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10438
                }
10439
            }
10440
        </script>';
10441
10442
        foreach ($a_forums as $forum) {
10443
            if (!empty($forum['forum_id'])) {
10444
                $link = Display::url(
10445
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10446
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10447
                    ['target' => '_blank']
10448
                );
10449
10450
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10451
                $return .= '<a class="moved" href="#">';
10452
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10453
                $return .= ' </a>';
10454
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10455
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10456
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10457
                            </a>
10458
                            <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">'.
10459
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10460
10461
                $return .= '</li>';
10462
10463
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10464
                $a_threads = get_threads($forum['forum_id']);
10465
                if (is_array($a_threads)) {
10466
                    foreach ($a_threads as $thread) {
10467
                        $link = Display::url(
10468
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10469
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10470
                            ['target' => '_blank']
10471
                        );
10472
10473
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10474
                        $return .= '&nbsp;<a class="moved" href="#">';
10475
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10476
                        $return .= ' </a>';
10477
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10478
                        $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.'">'.
10479
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10480
                        $return .= '</li>';
10481
                    }
10482
                }
10483
                $return .= '</div>';
10484
            }
10485
        }
10486
        $return .= '</ul>';
10487
10488
        return $return;
10489
    }
10490
10491
    /**
10492
     * // TODO: The output encoding should be equal to the system encoding.
10493
     *
10494
     * Exports the learning path as a SCORM package. This is the main function that
10495
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10496
     * whole thing and returns the zip.
10497
     *
10498
     * This method needs to be called in PHP5, as it will fail with non-adequate
10499
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10500
     * you need to call it on a learnpath object.
10501
     *
10502
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10503
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10504
     * path has been modified, it should use the generic method here below.
10505
     *
10506
     * @return string Returns the zip package string, or null if error
10507
     */
10508
    public function scormExport()
10509
    {
10510
        api_set_more_memory_and_time_limits();
10511
10512
        $_course = api_get_course_info();
10513
        $course_id = $_course['real_id'];
10514
        // Create the zip handler (this will remain available throughout the method).
10515
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10516
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10517
        $temp_dir_short = uniqid('scorm_export', true);
10518
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10519
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10520
        $zip_folder = new PclZip($temp_zip_file);
10521
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10522
        $root_path = $main_path = api_get_path(SYS_PATH);
10523
        $files_cleanup = [];
10524
10525
        // Place to temporarily stash the zip file.
10526
        // create the temp dir if it doesn't exist
10527
        // or do a cleanup before creating the zip file.
10528
        if (!is_dir($temp_zip_dir)) {
10529
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10530
        } else {
10531
            // Cleanup: Check the temp dir for old files and delete them.
10532
            $handle = opendir($temp_zip_dir);
10533
            while (false !== ($file = readdir($handle))) {
10534
                if ($file != '.' && $file != '..') {
10535
                    unlink("$temp_zip_dir/$file");
10536
                }
10537
            }
10538
            closedir($handle);
10539
        }
10540
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10541
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10542
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10543
        ) {
10544
            // Remove the possible . at the end of the path.
10545
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10546
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10547
            mkdir(
10548
                $dest_path_to_scorm_folder,
10549
                api_get_permissions_for_new_directories(),
10550
                true
10551
            );
10552
            copyr(
10553
                $current_course_path.'/scorm/'.$this->path,
10554
                $dest_path_to_scorm_folder,
10555
                ['imsmanifest'],
10556
                $zip_files
10557
            );
10558
        }
10559
10560
        // Build a dummy imsmanifest structure.
10561
        // Do not add to the zip yet (we still need it).
10562
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10563
        // Aggregation Model official document, section "2.3 Content Packaging".
10564
        // We are going to build a UTF-8 encoded manifest.
10565
        // Later we will recode it to the desired (and supported) encoding.
10566
        $xmldoc = new DOMDocument('1.0');
10567
        $root = $xmldoc->createElement('manifest');
10568
        $root->setAttribute('identifier', 'SingleCourseManifest');
10569
        $root->setAttribute('version', '1.1');
10570
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10571
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10572
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10573
        $root->setAttribute(
10574
            'xsi:schemaLocation',
10575
            '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'
10576
        );
10577
        // Build mandatory sub-root container elements.
10578
        $metadata = $xmldoc->createElement('metadata');
10579
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10580
        $metadata->appendChild($md_schema);
10581
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10582
        $metadata->appendChild($md_schemaversion);
10583
        $root->appendChild($metadata);
10584
10585
        $organizations = $xmldoc->createElement('organizations');
10586
        $resources = $xmldoc->createElement('resources');
10587
10588
        // Build the only organization we will use in building our learnpaths.
10589
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10590
        $organization = $xmldoc->createElement('organization');
10591
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10592
        // To set the title of the SCORM entity (=organization), we take the name given
10593
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10594
        // learning path charset) as it is the encoding that defines how it is stored
10595
        // in the database. Then we convert it to HTML entities again as the "&" character
10596
        // alone is not authorized in XML (must be &amp;).
10597
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10598
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10599
        $organization->appendChild($org_title);
10600
        $folder_name = 'document';
10601
10602
        // Removes the learning_path/scorm_folder path when exporting see #4841
10603
        $path_to_remove = '';
10604
        $path_to_replace = '';
10605
        $result = $this->generate_lp_folder($_course);
10606
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10607
            $path_to_remove = 'document'.$result['dir'];
10608
            $path_to_replace = $folder_name.'/';
10609
        }
10610
10611
        // Fixes chamilo scorm exports
10612
        if ($this->ref === 'chamilo_scorm_export') {
10613
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10614
        }
10615
10616
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10617
        $link_updates = [];
10618
        $links_to_create = [];
10619
        foreach ($this->ordered_items as $index => $itemId) {
10620
            /** @var learnpathItem $item */
10621
            $item = $this->items[$itemId];
10622
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10623
                // Get included documents from this item.
10624
                if ($item->type === 'sco') {
10625
                    $inc_docs = $item->get_resources_from_source(
10626
                        null,
10627
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10628
                    );
10629
                } else {
10630
                    $inc_docs = $item->get_resources_from_source();
10631
                }
10632
10633
                // Give a child element <item> to the <organization> element.
10634
                $my_item_id = $item->get_id();
10635
                $my_item = $xmldoc->createElement('item');
10636
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10637
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10638
                $my_item->setAttribute('isvisible', 'true');
10639
                // Give a child element <title> to the <item> element.
10640
                $my_title = $xmldoc->createElement(
10641
                    'title',
10642
                    htmlspecialchars(
10643
                        api_utf8_encode($item->get_title()),
10644
                        ENT_QUOTES,
10645
                        'UTF-8'
10646
                    )
10647
                );
10648
                $my_item->appendChild($my_title);
10649
                // Give a child element <adlcp:prerequisites> to the <item> element.
10650
                $my_prereqs = $xmldoc->createElement(
10651
                    'adlcp:prerequisites',
10652
                    $this->get_scorm_prereq_string($my_item_id)
10653
                );
10654
                $my_prereqs->setAttribute('type', 'aicc_script');
10655
                $my_item->appendChild($my_prereqs);
10656
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10657
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10658
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10659
                //$xmldoc->createElement('adlcp:timelimitaction','');
10660
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10661
                //$xmldoc->createElement('adlcp:datafromlms','');
10662
                // Give a child element <adlcp:masteryscore> to the <item> element.
10663
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10664
                $my_item->appendChild($my_masteryscore);
10665
10666
                // Attach this item to the organization element or hits parent if there is one.
10667
                if (!empty($item->parent) && $item->parent != 0) {
10668
                    $children = $organization->childNodes;
10669
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10670
                    if (is_object($possible_parent)) {
10671
                        $possible_parent->appendChild($my_item);
10672
                    } else {
10673
                        if ($this->debug > 0) {
10674
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10675
                        }
10676
                    }
10677
                } else {
10678
                    if ($this->debug > 0) {
10679
                        error_log('No parent');
10680
                    }
10681
                    $organization->appendChild($my_item);
10682
                }
10683
10684
                // Get the path of the file(s) from the course directory root.
10685
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10686
                $my_xml_file_path = $my_file_path;
10687
                if (!empty($path_to_remove)) {
10688
                    // From docs
10689
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10690
10691
                    // From quiz
10692
                    if ($this->ref === 'chamilo_scorm_export') {
10693
                        $path_to_remove = 'scorm/'.$this->path.'/';
10694
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10695
                    }
10696
                }
10697
10698
                $my_sub_dir = dirname($my_file_path);
10699
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10700
                $my_xml_sub_dir = $my_sub_dir;
10701
                // Give a <resource> child to the <resources> element
10702
                $my_resource = $xmldoc->createElement('resource');
10703
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10704
                $my_resource->setAttribute('type', 'webcontent');
10705
                $my_resource->setAttribute('href', $my_xml_file_path);
10706
                // adlcp:scormtype can be either 'sco' or 'asset'.
10707
                if ($item->type === 'sco') {
10708
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10709
                } else {
10710
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10711
                }
10712
                // xml:base is the base directory to find the files declared in this resource.
10713
                $my_resource->setAttribute('xml:base', '');
10714
                // Give a <file> child to the <resource> element.
10715
                $my_file = $xmldoc->createElement('file');
10716
                $my_file->setAttribute('href', $my_xml_file_path);
10717
                $my_resource->appendChild($my_file);
10718
10719
                // Dependency to other files - not yet supported.
10720
                $i = 1;
10721
                if ($inc_docs) {
10722
                    foreach ($inc_docs as $doc_info) {
10723
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10724
                            continue;
10725
                        }
10726
                        $my_dep = $xmldoc->createElement('resource');
10727
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10728
                        $my_dep->setAttribute('identifier', $res_id);
10729
                        $my_dep->setAttribute('type', 'webcontent');
10730
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10731
                        $my_dep_file = $xmldoc->createElement('file');
10732
                        // Check type of URL.
10733
                        if ($doc_info[1] == 'remote') {
10734
                            // Remote file. Save url as is.
10735
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10736
                            $my_dep->setAttribute('xml:base', '');
10737
                        } elseif ($doc_info[1] === 'local') {
10738
                            switch ($doc_info[2]) {
10739
                                case 'url':
10740
                                    // Local URL - save path as url for now, don't zip file.
10741
                                    $abs_path = api_get_path(SYS_PATH).
10742
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10743
                                    $current_dir = dirname($abs_path);
10744
                                    $current_dir = str_replace('\\', '/', $current_dir);
10745
                                    $file_path = realpath($abs_path);
10746
                                    $file_path = str_replace('\\', '/', $file_path);
10747
                                    $my_dep_file->setAttribute('href', $file_path);
10748
                                    $my_dep->setAttribute('xml:base', '');
10749
                                    if (strstr($file_path, $main_path) !== false) {
10750
                                        // The calculated real path is really inside Chamilo's root path.
10751
                                        // Reduce file path to what's under the DocumentRoot.
10752
                                        $replace = $file_path;
10753
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10754
                                        $destinationFile = $file_path;
10755
10756
                                        if (strstr($file_path, 'upload/users') !== false) {
10757
                                            $pos = strpos($file_path, 'my_files/');
10758
                                            if ($pos !== false) {
10759
                                                $onlyDirectory = str_replace(
10760
                                                    'upload/users/',
10761
                                                    '',
10762
                                                    substr($file_path, $pos, strlen($file_path))
10763
                                                );
10764
                                            }
10765
                                            $replace = $onlyDirectory;
10766
                                            $destinationFile = $replace;
10767
                                        }
10768
                                        $zip_files_abs[] = $file_path;
10769
                                        $link_updates[$my_file_path][] = [
10770
                                            'orig' => $doc_info[0],
10771
                                            'dest' => $destinationFile,
10772
                                            'replace' => $replace,
10773
                                        ];
10774
                                        $my_dep_file->setAttribute('href', $file_path);
10775
                                        $my_dep->setAttribute('xml:base', '');
10776
                                    } elseif (empty($file_path)) {
10777
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10778
                                        $file_path = str_replace('//', '/', $file_path);
10779
                                        if (file_exists($file_path)) {
10780
                                            // We get the relative path.
10781
                                            $file_path = substr($file_path, strlen($current_dir));
10782
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10783
                                            $link_updates[$my_file_path][] = [
10784
                                                'orig' => $doc_info[0],
10785
                                                'dest' => $file_path,
10786
                                            ];
10787
                                            $my_dep_file->setAttribute('href', $file_path);
10788
                                            $my_dep->setAttribute('xml:base', '');
10789
                                        }
10790
                                    }
10791
                                    break;
10792
                                case 'abs':
10793
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10794
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10795
                                    $my_dep->setAttribute('xml:base', '');
10796
10797
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10798
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10799
                                    $abs_img_path_without_subdir = $doc_info[0];
10800
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10801
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10802
                                    if ($pos === 0) {
10803
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10804
                                    }
10805
10806
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10807
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10808
10809
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10810
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10811
                                    // Check if the current document is in that path.
10812
                                    if (strstr($file_path, $cur_path) !== false) {
10813
                                        $destinationFile = substr($file_path, strlen($cur_path));
10814
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10815
10816
                                        $fileToTest = $cur_path.$my_file_path;
10817
                                        if (!empty($path_to_remove)) {
10818
                                            $fileToTest = str_replace(
10819
                                                $path_to_remove.'/',
10820
                                                $path_to_replace,
10821
                                                $cur_path.$my_file_path
10822
                                            );
10823
                                        }
10824
10825
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10826
10827
                                        // Put the current document in the zip (this array is the array
10828
                                        // that will manage documents already in the course folder - relative).
10829
                                        $zip_files[] = $filePathNoCoursePart;
10830
                                        // Update the links to the current document in the
10831
                                        // containing document (make them relative).
10832
                                        $link_updates[$my_file_path][] = [
10833
                                            'orig' => $doc_info[0],
10834
                                            'dest' => $destinationFile,
10835
                                            'replace' => $relative_path,
10836
                                        ];
10837
10838
                                        $my_dep_file->setAttribute('href', $file_path);
10839
                                        $my_dep->setAttribute('xml:base', '');
10840
                                    } elseif (strstr($file_path, $main_path) !== false) {
10841
                                        // The calculated real path is really inside Chamilo's root path.
10842
                                        // Reduce file path to what's under the DocumentRoot.
10843
                                        $file_path = substr($file_path, strlen($root_path));
10844
                                        $zip_files_abs[] = $file_path;
10845
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10846
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10847
                                        $my_dep->setAttribute('xml:base', '');
10848
                                    } elseif (empty($file_path)) {
10849
                                        // Probably this is an image inside "/main" directory
10850
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10851
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10852
10853
                                        if (file_exists($file_path)) {
10854
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10855
                                                // We get the relative path.
10856
                                                $pos = strpos($file_path, 'main/default_course_document/');
10857
                                                if ($pos !== false) {
10858
                                                    $onlyDirectory = str_replace(
10859
                                                        'main/default_course_document/',
10860
                                                        '',
10861
                                                        substr($file_path, $pos, strlen($file_path))
10862
                                                    );
10863
                                                }
10864
10865
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10866
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10867
                                                $zip_files_abs[] = $fileAbs;
10868
                                                $link_updates[$my_file_path][] = [
10869
                                                    'orig' => $doc_info[0],
10870
                                                    'dest' => $destinationFile,
10871
                                                ];
10872
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10873
                                                $my_dep->setAttribute('xml:base', '');
10874
                                            }
10875
                                        }
10876
                                    }
10877
                                    break;
10878
                                case 'rel':
10879
                                    // Path relative to the current document.
10880
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10881
                                    if (substr($doc_info[0], 0, 2) === '..') {
10882
                                        // Relative path going up.
10883
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10884
                                        $current_dir = str_replace('\\', '/', $current_dir);
10885
                                        $file_path = realpath($current_dir.$doc_info[0]);
10886
                                        $file_path = str_replace('\\', '/', $file_path);
10887
                                        if (strstr($file_path, $main_path) !== false) {
10888
                                            // The calculated real path is really inside Chamilo's root path.
10889
                                            // Reduce file path to what's under the DocumentRoot.
10890
                                            $file_path = substr($file_path, strlen($root_path));
10891
                                            $zip_files_abs[] = $file_path;
10892
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10893
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10894
                                            $my_dep->setAttribute('xml:base', '');
10895
                                        }
10896
                                    } else {
10897
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10898
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10899
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10900
                                    }
10901
                                    break;
10902
                                default:
10903
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10904
                                    $my_dep->setAttribute('xml:base', '');
10905
                                    break;
10906
                            }
10907
                        }
10908
                        $my_dep->appendChild($my_dep_file);
10909
                        $resources->appendChild($my_dep);
10910
                        $dependency = $xmldoc->createElement('dependency');
10911
                        $dependency->setAttribute('identifierref', $res_id);
10912
                        $my_resource->appendChild($dependency);
10913
                        $i++;
10914
                    }
10915
                }
10916
                $resources->appendChild($my_resource);
10917
                $zip_files[] = $my_file_path;
10918
            } else {
10919
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10920
                switch ($item->type) {
10921
                    case TOOL_LINK:
10922
                        $my_item = $xmldoc->createElement('item');
10923
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10924
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10925
                        $my_item->setAttribute('isvisible', 'true');
10926
                        // Give a child element <title> to the <item> element.
10927
                        $my_title = $xmldoc->createElement(
10928
                            'title',
10929
                            htmlspecialchars(
10930
                                api_utf8_encode($item->get_title()),
10931
                                ENT_QUOTES,
10932
                                'UTF-8'
10933
                            )
10934
                        );
10935
                        $my_item->appendChild($my_title);
10936
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10937
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10938
                        $my_prereqs->setAttribute('type', 'aicc_script');
10939
                        $my_item->appendChild($my_prereqs);
10940
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10941
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10942
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10943
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10944
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10945
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10946
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10947
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10948
                        $my_item->appendChild($my_masteryscore);
10949
10950
                        // Attach this item to the organization element or its parent if there is one.
10951
                        if (!empty($item->parent) && $item->parent != 0) {
10952
                            $children = $organization->childNodes;
10953
                            for ($i = 0; $i < $children->length; $i++) {
10954
                                $item_temp = $children->item($i);
10955
                                if ($item_temp->nodeName == 'item') {
10956
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10957
                                        $item_temp->appendChild($my_item);
10958
                                    }
10959
                                }
10960
                            }
10961
                        } else {
10962
                            $organization->appendChild($my_item);
10963
                        }
10964
10965
                        $my_file_path = 'link_'.$item->get_id().'.html';
10966
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10967
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10968
                        $rs = Database::query($sql);
10969
                        if ($link = Database::fetch_array($rs)) {
10970
                            $url = $link['url'];
10971
                            $title = stripslashes($link['title']);
10972
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10973
                            $my_xml_file_path = $my_file_path;
10974
                            $my_sub_dir = dirname($my_file_path);
10975
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10976
                            $my_xml_sub_dir = $my_sub_dir;
10977
                            // Give a <resource> child to the <resources> element.
10978
                            $my_resource = $xmldoc->createElement('resource');
10979
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10980
                            $my_resource->setAttribute('type', 'webcontent');
10981
                            $my_resource->setAttribute('href', $my_xml_file_path);
10982
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10983
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10984
                            // xml:base is the base directory to find the files declared in this resource.
10985
                            $my_resource->setAttribute('xml:base', '');
10986
                            // give a <file> child to the <resource> element.
10987
                            $my_file = $xmldoc->createElement('file');
10988
                            $my_file->setAttribute('href', $my_xml_file_path);
10989
                            $my_resource->appendChild($my_file);
10990
                            $resources->appendChild($my_resource);
10991
                        }
10992
                        break;
10993
                    case TOOL_QUIZ:
10994
                        $exe_id = $item->path;
10995
                        // Should be using ref when everything will be cleaned up in this regard.
10996
                        $exe = new Exercise();
10997
                        $exe->read($exe_id);
10998
                        $my_item = $xmldoc->createElement('item');
10999
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11000
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11001
                        $my_item->setAttribute('isvisible', 'true');
11002
                        // Give a child element <title> to the <item> element.
11003
                        $my_title = $xmldoc->createElement(
11004
                            'title',
11005
                            htmlspecialchars(
11006
                                api_utf8_encode($item->get_title()),
11007
                                ENT_QUOTES,
11008
                                'UTF-8'
11009
                            )
11010
                        );
11011
                        $my_item->appendChild($my_title);
11012
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11013
                        $my_item->appendChild($my_max_score);
11014
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11015
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11016
                        $my_prereqs->setAttribute('type', 'aicc_script');
11017
                        $my_item->appendChild($my_prereqs);
11018
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11019
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11020
                        $my_item->appendChild($my_masteryscore);
11021
11022
                        // Attach this item to the organization element or hits parent if there is one.
11023
                        if (!empty($item->parent) && $item->parent != 0) {
11024
                            $children = $organization->childNodes;
11025
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11026
                            if ($possible_parent) {
11027
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11028
                                    $possible_parent->appendChild($my_item);
11029
                                }
11030
                            }
11031
                        } else {
11032
                            $organization->appendChild($my_item);
11033
                        }
11034
11035
                        // Get the path of the file(s) from the course directory root
11036
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11037
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11038
                        // Write the contents of the exported exercise into a (big) html file
11039
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11040
                        $scormExercise = new ScormExercise($exe, true);
11041
                        $contents = $scormExercise->export();
11042
11043
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11044
                        $res = file_put_contents($tmp_file_path, $contents);
11045
                        if ($res === false) {
11046
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11047
                        }
11048
                        $files_cleanup[] = $tmp_file_path;
11049
                        $my_xml_file_path = $my_file_path;
11050
                        $my_sub_dir = dirname($my_file_path);
11051
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11052
                        $my_xml_sub_dir = $my_sub_dir;
11053
                        // Give a <resource> child to the <resources> element.
11054
                        $my_resource = $xmldoc->createElement('resource');
11055
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11056
                        $my_resource->setAttribute('type', 'webcontent');
11057
                        $my_resource->setAttribute('href', $my_xml_file_path);
11058
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11059
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11060
                        // xml:base is the base directory to find the files declared in this resource.
11061
                        $my_resource->setAttribute('xml:base', '');
11062
                        // Give a <file> child to the <resource> element.
11063
                        $my_file = $xmldoc->createElement('file');
11064
                        $my_file->setAttribute('href', $my_xml_file_path);
11065
                        $my_resource->appendChild($my_file);
11066
11067
                        // Get included docs.
11068
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11069
11070
                        // Dependency to other files - not yet supported.
11071
                        $i = 1;
11072
                        foreach ($inc_docs as $doc_info) {
11073
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11074
                                continue;
11075
                            }
11076
                            $my_dep = $xmldoc->createElement('resource');
11077
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11078
                            $my_dep->setAttribute('identifier', $res_id);
11079
                            $my_dep->setAttribute('type', 'webcontent');
11080
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11081
                            $my_dep_file = $xmldoc->createElement('file');
11082
                            // Check type of URL.
11083
                            if ($doc_info[1] == 'remote') {
11084
                                // Remote file. Save url as is.
11085
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11086
                                $my_dep->setAttribute('xml:base', '');
11087
                            } elseif ($doc_info[1] == 'local') {
11088
                                switch ($doc_info[2]) {
11089
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11090
                                        // Save file but as local file (retrieve from URL).
11091
                                        $abs_path = api_get_path(SYS_PATH).
11092
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11093
                                        $current_dir = dirname($abs_path);
11094
                                        $current_dir = str_replace('\\', '/', $current_dir);
11095
                                        $file_path = realpath($abs_path);
11096
                                        $file_path = str_replace('\\', '/', $file_path);
11097
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11098
                                        $my_dep->setAttribute('xml:base', '');
11099
                                        if (strstr($file_path, $main_path) !== false) {
11100
                                            // The calculated real path is really inside the chamilo root path.
11101
                                            // Reduce file path to what's under the DocumentRoot.
11102
                                            $file_path = substr($file_path, strlen($root_path));
11103
                                            $zip_files_abs[] = $file_path;
11104
                                            $link_updates[$my_file_path][] = [
11105
                                                'orig' => $doc_info[0],
11106
                                                'dest' => 'document/'.$file_path,
11107
                                            ];
11108
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11109
                                            $my_dep->setAttribute('xml:base', '');
11110
                                        } elseif (empty($file_path)) {
11111
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11112
                                            $file_path = str_replace('//', '/', $file_path);
11113
                                            if (file_exists($file_path)) {
11114
                                                $file_path = substr($file_path, strlen($current_dir));
11115
                                                // We get the relative path.
11116
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11117
                                                $link_updates[$my_file_path][] = [
11118
                                                    'orig' => $doc_info[0],
11119
                                                    'dest' => 'document/'.$file_path,
11120
                                                ];
11121
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11122
                                                $my_dep->setAttribute('xml:base', '');
11123
                                            }
11124
                                        }
11125
                                        break;
11126
                                    case 'abs':
11127
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11128
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11129
                                        $current_dir = str_replace('\\', '/', $current_dir);
11130
                                        $file_path = realpath($doc_info[0]);
11131
                                        $file_path = str_replace('\\', '/', $file_path);
11132
                                        $my_dep_file->setAttribute('href', $file_path);
11133
                                        $my_dep->setAttribute('xml:base', '');
11134
11135
                                        if (strstr($file_path, $main_path) !== false) {
11136
                                            // The calculated real path is really inside the chamilo root path.
11137
                                            // Reduce file path to what's under the DocumentRoot.
11138
                                            $file_path = substr($file_path, strlen($root_path));
11139
                                            $zip_files_abs[] = $file_path;
11140
                                            $link_updates[$my_file_path][] = [
11141
                                                'orig' => $doc_info[0],
11142
                                                'dest' => $file_path,
11143
                                            ];
11144
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11145
                                            $my_dep->setAttribute('xml:base', '');
11146
                                        } elseif (empty($file_path)) {
11147
                                            $docSysPartPath = str_replace(
11148
                                                api_get_path(REL_COURSE_PATH),
11149
                                                '',
11150
                                                $doc_info[0]
11151
                                            );
11152
11153
                                            $docSysPartPathNoCourseCode = str_replace(
11154
                                                $_course['directory'].'/',
11155
                                                '',
11156
                                                $docSysPartPath
11157
                                            );
11158
11159
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11160
                                            if (file_exists($docSysPath)) {
11161
                                                $file_path = $docSysPartPathNoCourseCode;
11162
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11163
                                                $link_updates[$my_file_path][] = [
11164
                                                    'orig' => $doc_info[0],
11165
                                                    'dest' => $file_path,
11166
                                                ];
11167
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11168
                                                $my_dep->setAttribute('xml:base', '');
11169
                                            }
11170
                                        }
11171
                                        break;
11172
                                    case 'rel':
11173
                                        // Path relative to the current document. Save xml:base as current document's
11174
                                        // directory and save file in zip as subdir.file_path
11175
                                        if (substr($doc_info[0], 0, 2) === '..') {
11176
                                            // Relative path going up.
11177
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11178
                                            $current_dir = str_replace('\\', '/', $current_dir);
11179
                                            $file_path = realpath($current_dir.$doc_info[0]);
11180
                                            $file_path = str_replace('\\', '/', $file_path);
11181
                                            //error_log($file_path.' <-> '.$main_path, 0);
11182
                                            if (strstr($file_path, $main_path) !== false) {
11183
                                                // The calculated real path is really inside Chamilo's root path.
11184
                                                // Reduce file path to what's under the DocumentRoot.
11185
11186
                                                $file_path = substr($file_path, strlen($root_path));
11187
                                                $file_path_dest = $file_path;
11188
11189
                                                // File path is courses/CHAMILO/document/....
11190
                                                $info_file_path = explode('/', $file_path);
11191
                                                if ($info_file_path[0] == 'courses') {
11192
                                                    // Add character "/" in file path.
11193
                                                    $file_path_dest = 'document/'.$file_path;
11194
                                                }
11195
                                                $zip_files_abs[] = $file_path;
11196
11197
                                                $link_updates[$my_file_path][] = [
11198
                                                    'orig' => $doc_info[0],
11199
                                                    'dest' => $file_path_dest,
11200
                                                ];
11201
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11202
                                                $my_dep->setAttribute('xml:base', '');
11203
                                            }
11204
                                        } else {
11205
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11206
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11207
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11208
                                        }
11209
                                        break;
11210
                                    default:
11211
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11212
                                        $my_dep->setAttribute('xml:base', '');
11213
                                        break;
11214
                                }
11215
                            }
11216
                            $my_dep->appendChild($my_dep_file);
11217
                            $resources->appendChild($my_dep);
11218
                            $dependency = $xmldoc->createElement('dependency');
11219
                            $dependency->setAttribute('identifierref', $res_id);
11220
                            $my_resource->appendChild($dependency);
11221
                            $i++;
11222
                        }
11223
                        $resources->appendChild($my_resource);
11224
                        $zip_files[] = $my_file_path;
11225
                        break;
11226
                    default:
11227
                        // Get the path of the file(s) from the course directory root
11228
                        $my_file_path = 'non_exportable.html';
11229
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11230
                        $my_xml_file_path = $my_file_path;
11231
                        $my_sub_dir = dirname($my_file_path);
11232
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11233
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11234
                        $my_xml_sub_dir = $my_sub_dir;
11235
                        // Give a <resource> child to the <resources> element.
11236
                        $my_resource = $xmldoc->createElement('resource');
11237
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11238
                        $my_resource->setAttribute('type', 'webcontent');
11239
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11240
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11241
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11242
                        // xml:base is the base directory to find the files declared in this resource.
11243
                        $my_resource->setAttribute('xml:base', '');
11244
                        // Give a <file> child to the <resource> element.
11245
                        $my_file = $xmldoc->createElement('file');
11246
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11247
                        $my_resource->appendChild($my_file);
11248
                        $resources->appendChild($my_resource);
11249
                        break;
11250
                }
11251
            }
11252
        }
11253
        $organizations->appendChild($organization);
11254
        $root->appendChild($organizations);
11255
        $root->appendChild($resources);
11256
        $xmldoc->appendChild($root);
11257
11258
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11259
11260
        // then add the file to the zip, then destroy the file (this is done automatically).
11261
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11262
        foreach ($zip_files as $file_path) {
11263
            if (empty($file_path)) {
11264
                continue;
11265
            }
11266
11267
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11268
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11269
11270
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11271
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11272
            }
11273
11274
            $this->create_path($dest_file);
11275
            @copy($filePath, $dest_file);
11276
11277
            // Check if the file needs a link update.
11278
            if (in_array($file_path, array_keys($link_updates))) {
11279
                $string = file_get_contents($dest_file);
11280
                unlink($dest_file);
11281
                foreach ($link_updates[$file_path] as $old_new) {
11282
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11283
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11284
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11285
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11286
                    if (substr($old_new['dest'], -3) === 'flv' &&
11287
                        substr($old_new['dest'], 0, 5) === 'main/'
11288
                    ) {
11289
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11290
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11291
                        substr($old_new['dest'], 0, 6) === 'video/'
11292
                    ) {
11293
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11294
                    }
11295
11296
                    // Fix to avoid problems with default_course_document
11297
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11298
                        $newDestination = $old_new['dest'];
11299
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11300
                            $newDestination = $old_new['replace'];
11301
                        }
11302
                    } else {
11303
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11304
                    }
11305
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11306
11307
                    // Add files inside the HTMLs
11308
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11309
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11310
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11311
                        copy(
11312
                            $sys_course_path.$new_path,
11313
                            $destinationFile
11314
                        );
11315
                    }
11316
                }
11317
                file_put_contents($dest_file, $string);
11318
            }
11319
11320
            if (file_exists($filePath) && $copyAll) {
11321
                $extension = $this->get_extension($filePath);
11322
                if (in_array($extension, ['html', 'html'])) {
11323
                    $containerOrigin = dirname($filePath);
11324
                    $containerDestination = dirname($dest_file);
11325
11326
                    $finder = new Finder();
11327
                    $finder->files()->in($containerOrigin)
11328
                        ->notName('*_DELETED_*')
11329
                        ->exclude('share_folder')
11330
                        ->exclude('chat_files')
11331
                        ->exclude('certificates')
11332
                    ;
11333
11334
                    if (is_dir($containerOrigin) &&
11335
                        is_dir($containerDestination)
11336
                    ) {
11337
                        $fs = new Filesystem();
11338
                        $fs->mirror(
11339
                            $containerOrigin,
11340
                            $containerDestination,
11341
                            $finder
11342
                        );
11343
                    }
11344
                }
11345
            }
11346
        }
11347
11348
        foreach ($zip_files_abs as $file_path) {
11349
            if (empty($file_path)) {
11350
                continue;
11351
            }
11352
11353
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11354
                continue;
11355
            }
11356
11357
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11358
            if (strstr($file_path, 'upload/users') !== false) {
11359
                $pos = strpos($file_path, 'my_files/');
11360
                if ($pos !== false) {
11361
                    $onlyDirectory = str_replace(
11362
                        'upload/users/',
11363
                        '',
11364
                        substr($file_path, $pos, strlen($file_path))
11365
                    );
11366
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11367
                }
11368
            }
11369
11370
            if (strstr($file_path, 'default_course_document/') !== false) {
11371
                $replace = str_replace('/main', '', $file_path);
11372
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11373
            }
11374
11375
            if (empty($dest_file)) {
11376
                continue;
11377
            }
11378
11379
            $this->create_path($dest_file);
11380
            copy($main_path.$file_path, $dest_file);
11381
            // Check if the file needs a link update.
11382
            if (in_array($file_path, array_keys($link_updates))) {
11383
                $string = file_get_contents($dest_file);
11384
                unlink($dest_file);
11385
                foreach ($link_updates[$file_path] as $old_new) {
11386
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11387
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11388
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11389
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11390
                    if (substr($old_new['dest'], -3) == 'flv' &&
11391
                        substr($old_new['dest'], 0, 5) == 'main/'
11392
                    ) {
11393
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11394
                    }
11395
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11396
                }
11397
                file_put_contents($dest_file, $string);
11398
            }
11399
        }
11400
11401
        if (is_array($links_to_create)) {
11402
            foreach ($links_to_create as $file => $link) {
11403
                $content = '<!DOCTYPE html><head>
11404
                            <meta charset="'.api_get_language_isocode().'" />
11405
                            <title>'.$link['title'].'</title>
11406
                            </head>
11407
                            <body dir="'.api_get_text_direction().'">
11408
                            <div style="text-align:center">
11409
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11410
                            </body>
11411
                            </html>';
11412
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11413
            }
11414
        }
11415
11416
        // Add non exportable message explanation.
11417
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11418
        $file_content = '<!DOCTYPE html><head>
11419
                        <meta charset="'.api_get_language_isocode().'" />
11420
                        <title>'.$lang_not_exportable.'</title>
11421
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11422
                        </head>
11423
                        <body dir="'.api_get_text_direction().'">';
11424
        $file_content .=
11425
            <<<EOD
11426
                    <style>
11427
            .error-message {
11428
                font-family: arial, verdana, helvetica, sans-serif;
11429
                border-width: 1px;
11430
                border-style: solid;
11431
                left: 50%;
11432
                margin: 10px auto;
11433
                min-height: 30px;
11434
                padding: 5px;
11435
                right: 50%;
11436
                width: 500px;
11437
                background-color: #FFD1D1;
11438
                border-color: #FF0000;
11439
                color: #000;
11440
            }
11441
        </style>
11442
    <body>
11443
        <div class="error-message">
11444
            $lang_not_exportable
11445
        </div>
11446
    </body>
11447
</html>
11448
EOD;
11449
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11450
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11451
        }
11452
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11453
11454
        // Add the extra files that go along with a SCORM package.
11455
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11456
11457
        $fs = new Filesystem();
11458
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11459
11460
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11461
        $manifest = @$xmldoc->saveXML();
11462
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11463
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11464
        $zip_folder->add(
11465
            $archivePath.'/'.$temp_dir_short,
11466
            PCLZIP_OPT_REMOVE_PATH,
11467
            $archivePath.'/'.$temp_dir_short.'/'
11468
        );
11469
11470
        // Clean possible temporary files.
11471
        foreach ($files_cleanup as $file) {
11472
            $res = unlink($file);
11473
            if ($res === false) {
11474
                error_log(
11475
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11476
                    0
11477
                );
11478
            }
11479
        }
11480
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11481
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11482
    }
11483
11484
    /**
11485
     * @param int $lp_id
11486
     *
11487
     * @return bool
11488
     */
11489
    public function scorm_export_to_pdf($lp_id)
11490
    {
11491
        $lp_id = (int) $lp_id;
11492
        $files_to_export = [];
11493
        $course_data = api_get_course_info($this->cc);
11494
        if (!empty($course_data)) {
11495
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11496
            $list = self::get_flat_ordered_items_list($lp_id);
11497
            if (!empty($list)) {
11498
                foreach ($list as $item_id) {
11499
                    $item = $this->items[$item_id];
11500
                    switch ($item->type) {
11501
                        case 'document':
11502
                            //Getting documents from a LP with chamilo documents
11503
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11504
                            // Try loading document from the base course.
11505
                            if (empty($file_data) && !empty($sessionId)) {
11506
                                $file_data = DocumentManager::get_document_data_by_id(
11507
                                    $item->path,
11508
                                    $this->cc,
11509
                                    false,
11510
                                    0
11511
                                );
11512
                            }
11513
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11514
                            if (file_exists($file_path)) {
11515
                                $files_to_export[] = [
11516
                                    'title' => $item->get_title(),
11517
                                    'path' => $file_path,
11518
                                ];
11519
                            }
11520
                            break;
11521
                        case 'asset': //commes from a scorm package generated by chamilo
11522
                        case 'sco':
11523
                            $file_path = $scorm_path.'/'.$item->path;
11524
                            if (file_exists($file_path)) {
11525
                                $files_to_export[] = [
11526
                                    'title' => $item->get_title(),
11527
                                    'path' => $file_path,
11528
                                ];
11529
                            }
11530
                            break;
11531
                        case 'dir':
11532
                            $files_to_export[] = [
11533
                                'title' => $item->get_title(),
11534
                                'path' => null,
11535
                            ];
11536
                            break;
11537
                    }
11538
                }
11539
            }
11540
            $pdf = new PDF();
11541
            $result = $pdf->html_to_pdf(
11542
                $files_to_export,
11543
                $this->name,
11544
                $this->cc,
11545
                true
11546
            );
11547
11548
            return $result;
11549
        }
11550
11551
        return false;
11552
    }
11553
11554
    /**
11555
     * Temp function to be moved in main_api or the best place around for this.
11556
     * Creates a file path if it doesn't exist.
11557
     *
11558
     * @param string $path
11559
     */
11560
    public function create_path($path)
11561
    {
11562
        $path_bits = explode('/', dirname($path));
11563
11564
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11565
        $path_built = IS_WINDOWS_OS ? '' : '/';
11566
        foreach ($path_bits as $bit) {
11567
            if (!empty($bit)) {
11568
                $new_path = $path_built.$bit;
11569
                if (is_dir($new_path)) {
11570
                    $path_built = $new_path.'/';
11571
                } else {
11572
                    mkdir($new_path, api_get_permissions_for_new_directories());
11573
                    $path_built = $new_path.'/';
11574
                }
11575
            }
11576
        }
11577
    }
11578
11579
    /**
11580
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11581
     *
11582
     * @return bool The results of the unlink function, or false if there was no image to start with
11583
     */
11584
    public function delete_lp_image()
11585
    {
11586
        $img = $this->get_preview_image();
11587
        if ($img != '') {
11588
            $del_file = $this->get_preview_image_path(null, 'sys');
11589
            if (isset($del_file) && file_exists($del_file)) {
11590
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11591
                if (file_exists($del_file_2)) {
11592
                    unlink($del_file_2);
11593
                }
11594
                $this->set_preview_image('');
11595
11596
                return @unlink($del_file);
11597
            }
11598
        }
11599
11600
        return false;
11601
    }
11602
11603
    /**
11604
     * Uploads an author image to the upload/learning_path/images directory.
11605
     *
11606
     * @param array    The image array, coming from the $_FILES superglobal
11607
     *
11608
     * @return bool True on success, false on error
11609
     */
11610
    public function upload_image($image_array)
11611
    {
11612
        if (!empty($image_array['name'])) {
11613
            $upload_ok = process_uploaded_file($image_array);
11614
            $has_attachment = true;
11615
        }
11616
11617
        if ($upload_ok && $has_attachment) {
11618
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11619
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11620
            $updir = $sys_course_path.$courseDir;
11621
            // Try to add an extension to the file if it hasn't one.
11622
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11623
11624
            if (filter_extension($new_file_name)) {
11625
                $file_extension = explode('.', $image_array['name']);
11626
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
11627
                $filename = uniqid('');
11628
                $new_file_name = $filename.'.'.$file_extension;
11629
                $new_path = $updir.'/'.$new_file_name;
11630
11631
                // Resize the image.
11632
                $temp = new Image($image_array['tmp_name']);
11633
                $temp->resize(104);
11634
                $result = $temp->send_image($new_path);
11635
11636
                // Storing the image filename.
11637
                if ($result) {
11638
                    $this->set_preview_image($new_file_name);
11639
11640
                    //Resize to 64px to use on course homepage
11641
                    $temp->resize(64);
11642
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11643
11644
                    return true;
11645
                }
11646
            }
11647
        }
11648
11649
        return false;
11650
    }
11651
11652
    /**
11653
     * @param int    $lp_id
11654
     * @param string $status
11655
     */
11656
    public function set_autolaunch($lp_id, $status)
11657
    {
11658
        $course_id = api_get_course_int_id();
11659
        $lp_id = intval($lp_id);
11660
        $status = intval($status);
11661
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11662
11663
        // Setting everything to autolaunch = 0
11664
        $attributes['autolaunch'] = 0;
11665
        $where = [
11666
            'session_id = ? AND c_id = ? ' => [
11667
                api_get_session_id(),
11668
                $course_id,
11669
            ],
11670
        ];
11671
        Database::update($lp_table, $attributes, $where);
11672
        if ($status == 1) {
11673
            //Setting my lp_id to autolaunch = 1
11674
            $attributes['autolaunch'] = 1;
11675
            $where = [
11676
                'iid = ? AND session_id = ? AND c_id = ?' => [
11677
                    $lp_id,
11678
                    api_get_session_id(),
11679
                    $course_id,
11680
                ],
11681
            ];
11682
            Database::update($lp_table, $attributes, $where);
11683
        }
11684
    }
11685
11686
    /**
11687
     * Gets previous_item_id for the next element of the lp_item table.
11688
     *
11689
     * @author Isaac flores paz
11690
     *
11691
     * @return int Previous item ID
11692
     */
11693
    public function select_previous_item_id()
11694
    {
11695
        $course_id = api_get_course_int_id();
11696
        if ($this->debug > 0) {
11697
            error_log('In learnpath::select_previous_item_id()', 0);
11698
        }
11699
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11700
11701
        // Get the max order of the items
11702
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11703
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11704
        $rs_max_order = Database::query($sql);
11705
        $row_max_order = Database::fetch_object($rs_max_order);
11706
        $max_order = $row_max_order->display_order;
11707
        // Get the previous item ID
11708
        $sql = "SELECT iid as previous FROM $table_lp_item
11709
                WHERE 
11710
                    c_id = $course_id AND 
11711
                    lp_id = ".$this->lp_id." AND 
11712
                    display_order = '$max_order' ";
11713
        $rs_max = Database::query($sql);
11714
        $row_max = Database::fetch_object($rs_max);
11715
11716
        // Return the previous item ID
11717
        return $row_max->previous;
11718
    }
11719
11720
    /**
11721
     * Copies an LP.
11722
     */
11723
    public function copy()
11724
    {
11725
        // Course builder
11726
        $cb = new CourseBuilder();
11727
11728
        //Setting tools that will be copied
11729
        $cb->set_tools_to_build(['learnpaths']);
11730
11731
        //Setting elements that will be copied
11732
        $cb->set_tools_specific_id_list(
11733
            ['learnpaths' => [$this->lp_id]]
11734
        );
11735
11736
        $course = $cb->build();
11737
11738
        //Course restorer
11739
        $course_restorer = new CourseRestorer($course);
11740
        $course_restorer->set_add_text_in_items(true);
11741
        $course_restorer->set_tool_copy_settings(
11742
            ['learnpaths' => ['reset_dates' => true]]
11743
        );
11744
        $course_restorer->restore(
11745
            api_get_course_id(),
11746
            api_get_session_id(),
11747
            false,
11748
            false
11749
        );
11750
    }
11751
11752
    /**
11753
     * Verify document size.
11754
     *
11755
     * @param string $s
11756
     *
11757
     * @return bool
11758
     */
11759
    public static function verify_document_size($s)
11760
    {
11761
        $post_max = ini_get('post_max_size');
11762
        if (substr($post_max, -1, 1) == 'M') {
11763
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11764
        } elseif (substr($post_max, -1, 1) == 'G') {
11765
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11766
        }
11767
        $upl_max = ini_get('upload_max_filesize');
11768
        if (substr($upl_max, -1, 1) == 'M') {
11769
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11770
        } elseif (substr($upl_max, -1, 1) == 'G') {
11771
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11772
        }
11773
        $documents_total_space = DocumentManager::documents_total_space();
11774
        $course_max_space = DocumentManager::get_course_quota();
11775
        $total_size = filesize($s) + $documents_total_space;
11776
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11777
            return true;
11778
        } else {
11779
            return false;
11780
        }
11781
    }
11782
11783
    /**
11784
     * Clear LP prerequisites.
11785
     */
11786
    public function clear_prerequisites()
11787
    {
11788
        $course_id = $this->get_course_int_id();
11789
        if ($this->debug > 0) {
11790
            error_log('In learnpath::clear_prerequisites()', 0);
11791
        }
11792
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11793
        $lp_id = $this->get_id();
11794
        //Cleaning prerequisites
11795
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11796
                WHERE c_id = $course_id AND lp_id = $lp_id";
11797
        Database::query($sql);
11798
11799
        //Cleaning mastery score for exercises
11800
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11801
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11802
        Database::query($sql);
11803
    }
11804
11805
    public function set_previous_step_as_prerequisite_for_all_items()
11806
    {
11807
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11808
        $course_id = $this->get_course_int_id();
11809
        $lp_id = $this->get_id();
11810
11811
        if (!empty($this->items)) {
11812
            $previous_item_id = null;
11813
            $previous_item_max = 0;
11814
            $previous_item_type = null;
11815
            $last_item_not_dir = null;
11816
            $last_item_not_dir_type = null;
11817
            $last_item_not_dir_max = null;
11818
11819
            foreach ($this->ordered_items as $itemId) {
11820
                $item = $this->getItem($itemId);
11821
                // if there was a previous item... (otherwise jump to set it)
11822
                if (!empty($previous_item_id)) {
11823
                    $current_item_id = $item->get_id(); //save current id
11824
                    if ($item->get_type() != 'dir') {
11825
                        // Current item is not a folder, so it qualifies to get a prerequisites
11826
                        if ($last_item_not_dir_type == 'quiz') {
11827
                            // if previous is quiz, mark its max score as default score to be achieved
11828
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11829
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11830
                            Database::query($sql);
11831
                        }
11832
                        // now simply update the prerequisite to set it to the last non-chapter item
11833
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11834
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11835
                        Database::query($sql);
11836
                        // record item as 'non-chapter' reference
11837
                        $last_item_not_dir = $item->get_id();
11838
                        $last_item_not_dir_type = $item->get_type();
11839
                        $last_item_not_dir_max = $item->get_max();
11840
                    }
11841
                } else {
11842
                    if ($item->get_type() != 'dir') {
11843
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11844
                        $last_item_not_dir = $item->get_id();
11845
                        $last_item_not_dir_type = $item->get_type();
11846
                        $last_item_not_dir_max = $item->get_max();
11847
                    }
11848
                }
11849
                // Saving the item as "previous item" for the next loop
11850
                $previous_item_id = $item->get_id();
11851
                $previous_item_max = $item->get_max();
11852
                $previous_item_type = $item->get_type();
11853
            }
11854
        }
11855
    }
11856
11857
    /**
11858
     * @param array $params
11859
     *
11860
     * @throws \Doctrine\ORM\OptimisticLockException
11861
     *
11862
     * @return int
11863
     */
11864
    public static function createCategory($params)
11865
    {
11866
        $em = Database::getManager();
11867
        $item = new CLpCategory();
11868
        $item->setName($params['name']);
11869
        $item->setCId($params['c_id']);
11870
        $em->persist($item);
11871
        $em->flush();
11872
11873
        api_item_property_update(
11874
            api_get_course_info(),
11875
            TOOL_LEARNPATH_CATEGORY,
11876
            $item->getId(),
11877
            'visible',
11878
            api_get_user_id()
11879
        );
11880
11881
        return $item->getId();
11882
    }
11883
11884
    /**
11885
     * @param array $params
11886
     *
11887
     * @throws \Doctrine\ORM\ORMException
11888
     * @throws \Doctrine\ORM\OptimisticLockException
11889
     * @throws \Doctrine\ORM\TransactionRequiredException
11890
     */
11891
    public static function updateCategory($params)
11892
    {
11893
        $em = Database::getManager();
11894
        /** @var CLpCategory $item */
11895
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11896
        if ($item) {
11897
            $item->setName($params['name']);
11898
            $em->merge($item);
11899
            $em->flush();
11900
        }
11901
    }
11902
11903
    /**
11904
     * @param int $id
11905
     *
11906
     * @throws \Doctrine\ORM\ORMException
11907
     * @throws \Doctrine\ORM\OptimisticLockException
11908
     * @throws \Doctrine\ORM\TransactionRequiredException
11909
     */
11910
    public static function moveUpCategory($id)
11911
    {
11912
        $id = (int) $id;
11913
        $em = Database::getManager();
11914
        /** @var CLpCategory $item */
11915
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11916
        if ($item) {
11917
            $position = $item->getPosition() - 1;
11918
            $item->setPosition($position);
11919
            $em->persist($item);
11920
            $em->flush();
11921
        }
11922
    }
11923
11924
    /**
11925
     * @param int $id
11926
     *
11927
     * @throws \Doctrine\ORM\ORMException
11928
     * @throws \Doctrine\ORM\OptimisticLockException
11929
     * @throws \Doctrine\ORM\TransactionRequiredException
11930
     */
11931
    public static function moveDownCategory($id)
11932
    {
11933
        $id = (int) $id;
11934
        $em = Database::getManager();
11935
        /** @var CLpCategory $item */
11936
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11937
        if ($item) {
11938
            $position = $item->getPosition() + 1;
11939
            $item->setPosition($position);
11940
            $em->persist($item);
11941
            $em->flush();
11942
        }
11943
    }
11944
11945
    /**
11946
     * @param int $courseId
11947
     *
11948
     * @throws \Doctrine\ORM\Query\QueryException
11949
     *
11950
     * @return int|mixed
11951
     */
11952
    public static function getCountCategories($courseId)
11953
    {
11954
        if (empty($course_id)) {
11955
            return 0;
11956
        }
11957
        $em = Database::getManager();
11958
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11959
        $query->setParameter('id', $courseId);
11960
11961
        return $query->getSingleScalarResult();
11962
    }
11963
11964
    /**
11965
     * @param int $courseId
11966
     *
11967
     * @return mixed
11968
     */
11969
    public static function getCategories($courseId)
11970
    {
11971
        $em = Database::getManager();
11972
        //Default behaviour
11973
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11974
            array('cId' => $course_id),
11975
            array('name' => 'ASC')
11976
        );*/
11977
11978
        // Using doctrine extensions
11979
        /** @var SortableRepository $repo */
11980
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11981
        $items = $repo
11982
            ->getBySortableGroupsQuery(['cId' => $courseId])
11983
            ->getResult();
11984
11985
        return $items;
11986
    }
11987
11988
    /**
11989
     * @param int $id
11990
     *
11991
     * @throws \Doctrine\ORM\ORMException
11992
     * @throws \Doctrine\ORM\OptimisticLockException
11993
     * @throws \Doctrine\ORM\TransactionRequiredException
11994
     *
11995
     * @return CLpCategory
11996
     */
11997
    public static function getCategory($id)
11998
    {
11999
        $id = (int) $id;
12000
        $em = Database::getManager();
12001
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12002
12003
        return $item;
12004
    }
12005
12006
    /**
12007
     * @param int $courseId
12008
     *
12009
     * @return array
12010
     */
12011
    public static function getCategoryByCourse($courseId)
12012
    {
12013
        $em = Database::getManager();
12014
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12015
            ['cId' => $courseId]
12016
        );
12017
12018
        return $items;
12019
    }
12020
12021
    /**
12022
     * @param int $id
12023
     *
12024
     * @throws \Doctrine\ORM\ORMException
12025
     * @throws \Doctrine\ORM\OptimisticLockException
12026
     * @throws \Doctrine\ORM\TransactionRequiredException
12027
     *
12028
     * @return mixed
12029
     */
12030
    public static function deleteCategory($id)
12031
    {
12032
        $em = Database::getManager();
12033
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12034
        if ($item) {
12035
            $courseId = $item->getCId();
12036
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12037
            $query->setParameter('id', $courseId);
12038
            $query->setParameter('catId', $item->getId());
12039
            $lps = $query->getResult();
12040
12041
            // Setting category = 0.
12042
            if ($lps) {
12043
                foreach ($lps as $lpItem) {
12044
                    $lpItem->setCategoryId(0);
12045
                }
12046
            }
12047
12048
            // Removing category.
12049
            $em->remove($item);
12050
            $em->flush();
12051
12052
            $courseInfo = api_get_course_info_by_id($courseId);
12053
            $sessionId = api_get_session_id();
12054
12055
            // Delete link tool
12056
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12057
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12058
            // Delete tools
12059
            $sql = "DELETE FROM $tbl_tool
12060
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12061
            Database::query($sql);
12062
        }
12063
    }
12064
12065
    /**
12066
     * @param int  $courseId
12067
     * @param bool $addSelectOption
12068
     *
12069
     * @return mixed
12070
     */
12071
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12072
    {
12073
        $items = self::getCategoryByCourse($courseId);
12074
        $cats = [];
12075
        if ($addSelectOption) {
12076
            $cats = [get_lang('SelectACategory')];
12077
        }
12078
12079
        if (!empty($items)) {
12080
            foreach ($items as $cat) {
12081
                $cats[$cat->getId()] = $cat->getName();
12082
            }
12083
        }
12084
12085
        return $cats;
12086
    }
12087
12088
    /**
12089
     * @param string $courseCode
12090
     * @param int    $lpId
12091
     * @param int    $user_id
12092
     *
12093
     * @return learnpath
12094
     */
12095
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12096
    {
12097
        $debug = 0;
12098
        $learnPath = null;
12099
        $lpObject = Session::read('lpobject');
12100
        if ($lpObject !== null) {
12101
            $learnPath = unserialize($lpObject);
12102
            if ($debug) {
12103
                error_log('getLpFromSession: unserialize');
12104
                error_log('------getLpFromSession------');
12105
                error_log('------unserialize------');
12106
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12107
                error_log("api_get_sessionid: ".api_get_session_id());
12108
            }
12109
        }
12110
12111
        if (!is_object($learnPath)) {
12112
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12113
            if ($debug) {
12114
                error_log('------getLpFromSession------');
12115
                error_log('getLpFromSession: create new learnpath');
12116
                error_log("create new LP with $courseCode - $lpId - $user_id");
12117
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12118
                error_log("api_get_sessionid: ".api_get_session_id());
12119
            }
12120
        }
12121
12122
        return $learnPath;
12123
    }
12124
12125
    /**
12126
     * @param int $itemId
12127
     *
12128
     * @return learnpathItem|false
12129
     */
12130
    public function getItem($itemId)
12131
    {
12132
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12133
            return $this->items[$itemId];
12134
        }
12135
12136
        return false;
12137
    }
12138
12139
    /**
12140
     * @return int
12141
     */
12142
    public function getCategoryId()
12143
    {
12144
        return (int) $this->categoryId;
12145
    }
12146
12147
    /**
12148
     * @param int $categoryId
12149
     *
12150
     * @return bool
12151
     */
12152
    public function setCategoryId($categoryId)
12153
    {
12154
        $this->categoryId = (int) $categoryId;
12155
        $table = Database::get_course_table(TABLE_LP_MAIN);
12156
        $lp_id = $this->get_id();
12157
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12158
                WHERE iid = $lp_id";
12159
        Database::query($sql);
12160
12161
        return true;
12162
    }
12163
12164
    /**
12165
     * Get whether this is a learning path with the possibility to subscribe
12166
     * users or not.
12167
     *
12168
     * @return int
12169
     */
12170
    public function getSubscribeUsers()
12171
    {
12172
        return $this->subscribeUsers;
12173
    }
12174
12175
    /**
12176
     * Set whether this is a learning path with the possibility to subscribe
12177
     * users or not.
12178
     *
12179
     * @param int $value (0 = false, 1 = true)
12180
     *
12181
     * @return bool
12182
     */
12183
    public function setSubscribeUsers($value)
12184
    {
12185
        if ($this->debug > 0) {
12186
            error_log('In learnpath::set_subscribe_users()', 0);
12187
        }
12188
        $this->subscribeUsers = (int) $value;
12189
        $table = Database::get_course_table(TABLE_LP_MAIN);
12190
        $lp_id = $this->get_id();
12191
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12192
                WHERE iid = $lp_id";
12193
        Database::query($sql);
12194
12195
        return true;
12196
    }
12197
12198
    /**
12199
     * Calculate the count of stars for a user in this LP
12200
     * This calculation is based on the following rules:
12201
     * - the student gets one star when he gets to 50% of the learning path
12202
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12203
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12204
     * - the student gets the final star when the score for the *last* test is >= 80%.
12205
     *
12206
     * @param int $sessionId Optional. The session ID
12207
     *
12208
     * @return int The count of stars
12209
     */
12210
    public function getCalculateStars($sessionId = 0)
12211
    {
12212
        $stars = 0;
12213
        $progress = self::getProgress(
12214
            $this->lp_id,
12215
            $this->user_id,
12216
            $this->course_int_id,
12217
            $sessionId
12218
        );
12219
12220
        if ($progress >= 50) {
12221
            $stars++;
12222
        }
12223
12224
        // Calculate stars chapters evaluation
12225
        $exercisesItems = $this->getExercisesItems();
12226
12227
        if (!empty($exercisesItems)) {
12228
            $totalResult = 0;
12229
12230
            foreach ($exercisesItems as $exerciseItem) {
12231
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12232
                    $this->user_id,
12233
                    $exerciseItem->path,
12234
                    $this->course_int_id,
12235
                    $sessionId,
12236
                    $this->lp_id,
12237
                    $exerciseItem->db_id
12238
                );
12239
12240
                $exerciseResultInfo = end($exerciseResultInfo);
12241
12242
                if (!$exerciseResultInfo) {
12243
                    continue;
12244
                }
12245
12246
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12247
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12248
                } else {
12249
                    $exerciseResult = 0;
12250
                }
12251
                $totalResult += $exerciseResult;
12252
            }
12253
12254
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12255
12256
            if ($totalExerciseAverage >= 50) {
12257
                $stars++;
12258
            }
12259
12260
            if ($totalExerciseAverage >= 80) {
12261
                $stars++;
12262
            }
12263
        }
12264
12265
        // Calculate star for final evaluation
12266
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12267
12268
        if (!empty($finalEvaluationItem)) {
12269
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12270
                $this->user_id,
12271
                $finalEvaluationItem->path,
12272
                $this->course_int_id,
12273
                $sessionId,
12274
                $this->lp_id,
12275
                $finalEvaluationItem->db_id
12276
            );
12277
12278
            $evaluationResultInfo = end($evaluationResultInfo);
12279
12280
            if ($evaluationResultInfo) {
12281
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12282
12283
                if ($evaluationResult >= 80) {
12284
                    $stars++;
12285
                }
12286
            }
12287
        }
12288
12289
        return $stars;
12290
    }
12291
12292
    /**
12293
     * Get the items of exercise type.
12294
     *
12295
     * @return array The items. Otherwise return false
12296
     */
12297
    public function getExercisesItems()
12298
    {
12299
        $exercises = [];
12300
        foreach ($this->items as $item) {
12301
            if ($item->type != 'quiz') {
12302
                continue;
12303
            }
12304
            $exercises[] = $item;
12305
        }
12306
12307
        array_pop($exercises);
12308
12309
        return $exercises;
12310
    }
12311
12312
    /**
12313
     * Get the item of exercise type (evaluation type).
12314
     *
12315
     * @return array The final evaluation. Otherwise return false
12316
     */
12317
    public function getFinalEvaluationItem()
12318
    {
12319
        $exercises = [];
12320
        foreach ($this->items as $item) {
12321
            if ($item->type != 'quiz') {
12322
                continue;
12323
            }
12324
12325
            $exercises[] = $item;
12326
        }
12327
12328
        return array_pop($exercises);
12329
    }
12330
12331
    /**
12332
     * Calculate the total points achieved for the current user in this learning path.
12333
     *
12334
     * @param int $sessionId Optional. The session Id
12335
     *
12336
     * @return int
12337
     */
12338
    public function getCalculateScore($sessionId = 0)
12339
    {
12340
        // Calculate stars chapters evaluation
12341
        $exercisesItems = $this->getExercisesItems();
12342
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12343
        $totalExercisesResult = 0;
12344
        $totalEvaluationResult = 0;
12345
12346
        if ($exercisesItems !== false) {
12347
            foreach ($exercisesItems as $exerciseItem) {
12348
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12349
                    $this->user_id,
12350
                    $exerciseItem->path,
12351
                    $this->course_int_id,
12352
                    $sessionId,
12353
                    $this->lp_id,
12354
                    $exerciseItem->db_id
12355
                );
12356
12357
                $exerciseResultInfo = end($exerciseResultInfo);
12358
12359
                if (!$exerciseResultInfo) {
12360
                    continue;
12361
                }
12362
12363
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12364
            }
12365
        }
12366
12367
        if (!empty($finalEvaluationItem)) {
12368
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12369
                $this->user_id,
12370
                $finalEvaluationItem->path,
12371
                $this->course_int_id,
12372
                $sessionId,
12373
                $this->lp_id,
12374
                $finalEvaluationItem->db_id
12375
            );
12376
12377
            $evaluationResultInfo = end($evaluationResultInfo);
12378
12379
            if ($evaluationResultInfo) {
12380
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12381
            }
12382
        }
12383
12384
        return $totalExercisesResult + $totalEvaluationResult;
12385
    }
12386
12387
    /**
12388
     * Check if URL is not allowed to be show in a iframe.
12389
     *
12390
     * @param string $src
12391
     *
12392
     * @return string
12393
     */
12394
    public function fixBlockedLinks($src)
12395
    {
12396
        $urlInfo = parse_url($src);
12397
12398
        $platformProtocol = 'https';
12399
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12400
            $platformProtocol = 'http';
12401
        }
12402
12403
        $protocolFixApplied = false;
12404
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12405
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12406
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12407
12408
        if ($platformProtocol != $scheme) {
12409
            Session::write('x_frame_source', $src);
12410
            $src = 'blank.php?error=x_frames_options';
12411
            $protocolFixApplied = true;
12412
        }
12413
12414
        if ($protocolFixApplied == false) {
12415
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12416
                // Check X-Frame-Options
12417
                $ch = curl_init();
12418
                $options = [
12419
                    CURLOPT_URL => $src,
12420
                    CURLOPT_RETURNTRANSFER => true,
12421
                    CURLOPT_HEADER => true,
12422
                    CURLOPT_FOLLOWLOCATION => true,
12423
                    CURLOPT_ENCODING => "",
12424
                    CURLOPT_AUTOREFERER => true,
12425
                    CURLOPT_CONNECTTIMEOUT => 120,
12426
                    CURLOPT_TIMEOUT => 120,
12427
                    CURLOPT_MAXREDIRS => 10,
12428
                ];
12429
12430
                $proxySettings = api_get_configuration_value('proxy_settings');
12431
                if (!empty($proxySettings) &&
12432
                    isset($proxySettings['curl_setopt_array'])
12433
                ) {
12434
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12435
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12436
                }
12437
12438
                curl_setopt_array($ch, $options);
12439
                $response = curl_exec($ch);
12440
                $httpCode = curl_getinfo($ch);
12441
                $headers = substr($response, 0, $httpCode['header_size']);
12442
12443
                $error = false;
12444
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12445
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12446
                ) {
12447
                    $error = true;
12448
                }
12449
12450
                if ($error) {
12451
                    Session::write('x_frame_source', $src);
12452
                    $src = 'blank.php?error=x_frames_options';
12453
                }
12454
            }
12455
        }
12456
12457
        return $src;
12458
    }
12459
12460
    /**
12461
     * Check if this LP has a created forum in the basis course.
12462
     *
12463
     * @return bool
12464
     */
12465
    public function lpHasForum()
12466
    {
12467
        $forumTable = Database::get_course_table(TABLE_FORUM);
12468
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12469
12470
        $fakeFrom = "
12471
            $forumTable f
12472
            INNER JOIN $itemProperty ip
12473
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12474
        ";
12475
12476
        $resultData = Database::select(
12477
            'COUNT(f.iid) AS qty',
12478
            $fakeFrom,
12479
            [
12480
                'where' => [
12481
                    'ip.visibility != ? AND ' => 2,
12482
                    'ip.tool = ? AND ' => TOOL_FORUM,
12483
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12484
                    'f.lp_id = ?' => intval($this->lp_id),
12485
                ],
12486
            ],
12487
            'first'
12488
        );
12489
12490
        return $resultData['qty'] > 0;
12491
    }
12492
12493
    /**
12494
     * Get the forum for this learning path.
12495
     *
12496
     * @param int $sessionId
12497
     *
12498
     * @return bool
12499
     */
12500
    public function getForum($sessionId = 0)
12501
    {
12502
        $forumTable = Database::get_course_table(TABLE_FORUM);
12503
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12504
12505
        $fakeFrom = "$forumTable f
12506
            INNER JOIN $itemProperty ip ";
12507
12508
        if ($this->lp_session_id == 0) {
12509
            $fakeFrom .= "
12510
                ON (
12511
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12512
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12513
                    )
12514
                )
12515
            ";
12516
        } else {
12517
            $fakeFrom .= "
12518
                ON (
12519
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12520
                )
12521
            ";
12522
        }
12523
12524
        $resultData = Database::select(
12525
            'f.*',
12526
            $fakeFrom,
12527
            [
12528
                'where' => [
12529
                    'ip.visibility != ? AND ' => 2,
12530
                    'ip.tool = ? AND ' => TOOL_FORUM,
12531
                    'f.session_id = ? AND ' => $sessionId,
12532
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12533
                    'f.lp_id = ?' => intval($this->lp_id),
12534
                ],
12535
            ],
12536
            'first'
12537
        );
12538
12539
        if (empty($resultData)) {
12540
            return false;
12541
        }
12542
12543
        return $resultData;
12544
    }
12545
12546
    /**
12547
     * Create a forum for this learning path.
12548
     *
12549
     * @param int $forumCategoryId
12550
     *
12551
     * @return int The forum ID if was created. Otherwise return false
12552
     */
12553
    public function createForum($forumCategoryId)
12554
    {
12555
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12556
12557
        $forumId = store_forum(
12558
            [
12559
                'lp_id' => $this->lp_id,
12560
                'forum_title' => $this->name,
12561
                'forum_comment' => null,
12562
                'forum_category' => intval($forumCategoryId),
12563
                'students_can_edit_group' => ['students_can_edit' => 0],
12564
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12565
                'default_view_type_group' => ['default_view_type' => 'flat'],
12566
                'group_forum' => 0,
12567
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12568
            ],
12569
            [],
12570
            true
12571
        );
12572
12573
        return $forumId;
12574
    }
12575
12576
    /**
12577
     * Get the LP Final Item form.
12578
     *
12579
     * @throws Exception
12580
     * @throws HTML_QuickForm_Error
12581
     *
12582
     * @return string
12583
     */
12584
    public function getFinalItemForm()
12585
    {
12586
        $finalItem = $this->getFinalItem();
12587
        $title = '';
12588
12589
        if ($finalItem) {
12590
            $title = $finalItem->get_title();
12591
            $buttonText = get_lang('Save');
12592
            $content = $this->getSavedFinalItem();
12593
        } else {
12594
            $buttonText = get_lang('LPCreateDocument');
12595
            $content = $this->getFinalItemTemplate();
12596
        }
12597
12598
        $courseInfo = api_get_course_info();
12599
        $result = $this->generate_lp_folder($courseInfo);
12600
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12601
        $relative_prefix = '../../';
12602
12603
        $editorConfig = [
12604
            'ToolbarSet' => 'LearningPathDocuments',
12605
            'Width' => '100%',
12606
            'Height' => '500',
12607
            'FullPage' => true,
12608
            'CreateDocumentDir' => $relative_prefix,
12609
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12610
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12611
        ];
12612
12613
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12614
            'type' => 'document',
12615
            'lp_id' => $this->lp_id,
12616
        ]);
12617
12618
        $form = new FormValidator('final_item', 'POST', $url);
12619
        $form->addText('title', get_lang('Title'));
12620
        $form->addButtonSave($buttonText);
12621
        $form->addHtml(
12622
            Display::return_message(
12623
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12624
                'normal',
12625
                false
12626
            )
12627
        );
12628
12629
        $renderer = $form->defaultRenderer();
12630
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12631
12632
        $form->addHtmlEditor(
12633
            'content_lp_certificate',
12634
            null,
12635
            true,
12636
            false,
12637
            $editorConfig,
12638
            true
12639
        );
12640
        $form->addHidden('action', 'add_final_item');
12641
        $form->addHidden('path', Session::read('pathItem'));
12642
        $form->addHidden('previous', $this->get_last());
12643
        $form->setDefaults(
12644
            ['title' => $title, 'content_lp_certificate' => $content]
12645
        );
12646
12647
        if ($form->validate()) {
12648
            $values = $form->exportValues();
12649
            $lastItemId = $this->get_last();
12650
12651
            if (!$finalItem) {
12652
                $documentId = $this->create_document(
12653
                    $this->course_info,
12654
                    $values['content_lp_certificate'],
12655
                    $values['title']
12656
                );
12657
                $this->add_item(
12658
                    0,
12659
                    $lastItemId,
12660
                    'final_item',
12661
                    $documentId,
12662
                    $values['title'],
12663
                    ''
12664
                );
12665
12666
                Display::addFlash(
12667
                    Display::return_message(get_lang('Added'))
12668
                );
12669
            } else {
12670
                $this->edit_document($this->course_info);
12671
            }
12672
        }
12673
12674
        return $form->returnForm();
12675
    }
12676
12677
    /**
12678
     * Check if the current lp item is first, both, last or none from lp list.
12679
     *
12680
     * @param int $currentItemId
12681
     *
12682
     * @return string
12683
     */
12684
    public function isFirstOrLastItem($currentItemId)
12685
    {
12686
        if ($this->debug > 0) {
12687
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
12688
        }
12689
12690
        $lpItemId = [];
12691
        $typeListNotToVerify = self::getChapterTypes();
12692
12693
        // Using get_toc() function instead $this->items because returns the correct order of the items
12694
        foreach ($this->get_toc() as $item) {
12695
            if (!in_array($item['type'], $typeListNotToVerify)) {
12696
                $lpItemId[] = $item['id'];
12697
            }
12698
        }
12699
12700
        $lastLpItemIndex = count($lpItemId) - 1;
12701
        $position = array_search($currentItemId, $lpItemId);
12702
12703
        switch ($position) {
12704
            case 0:
12705
                if (!$lastLpItemIndex) {
12706
                    $answer = 'both';
12707
                    break;
12708
                }
12709
12710
                $answer = 'first';
12711
                break;
12712
            case $lastLpItemIndex:
12713
                $answer = 'last';
12714
                break;
12715
            default:
12716
                $answer = 'none';
12717
        }
12718
12719
        return $answer;
12720
    }
12721
12722
    /**
12723
     * Get whether this is a learning path with the accumulated SCORM time or not.
12724
     *
12725
     * @return int
12726
     */
12727
    public function getAccumulateScormTime()
12728
    {
12729
        return $this->accumulateScormTime;
12730
    }
12731
12732
    /**
12733
     * Set whether this is a learning path with the accumulated SCORM time or not.
12734
     *
12735
     * @param int $value (0 = false, 1 = true)
12736
     *
12737
     * @return bool Always returns true
12738
     */
12739
    public function setAccumulateScormTime($value)
12740
    {
12741
        if ($this->debug > 0) {
12742
            error_log('In learnpath::setAccumulateScormTime()', 0);
12743
        }
12744
        $this->accumulateScormTime = intval($value);
12745
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12746
        $lp_id = $this->get_id();
12747
        $sql = "UPDATE $lp_table 
12748
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12749
                WHERE iid = $lp_id";
12750
        Database::query($sql);
12751
12752
        return true;
12753
    }
12754
12755
    /**
12756
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12757
     * the new learning path tool.
12758
     *
12759
     * The function is a big switch on tool type.
12760
     * In each case, we query the corresponding table for information and build the link
12761
     * with that information.
12762
     *
12763
     * @author Yannick Warnier <[email protected]> - rebranding based on
12764
     * previous work (display_addedresource_link_in_learnpath())
12765
     *
12766
     * @param int $course_id      Course code
12767
     * @param int $learningPathId The learning path ID (in lp table)
12768
     * @param int $id_in_path     the unique index in the items table
12769
     * @param int $lpViewId
12770
     *
12771
     * @return string
12772
     */
12773
    public static function rl_get_resource_link_for_learnpath(
12774
        $course_id,
12775
        $learningPathId,
12776
        $id_in_path,
12777
        $lpViewId
12778
    ) {
12779
        $session_id = api_get_session_id();
12780
        $course_info = api_get_course_info_by_id($course_id);
12781
12782
        $learningPathId = (int) $learningPathId;
12783
        $id_in_path = (int) $id_in_path;
12784
        $lpViewId = (int) $lpViewId;
12785
12786
        $em = Database::getManager();
12787
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12788
12789
        /** @var CLpItem $rowItem */
12790
        $rowItem = $lpItemRepo->findOneBy([
12791
            'cId' => $course_id,
12792
            'lpId' => $learningPathId,
12793
            'iid' => $id_in_path,
12794
        ]);
12795
12796
        if (!$rowItem) {
12797
            // Try one more time with "id"
12798
            /** @var CLpItem $rowItem */
12799
            $rowItem = $lpItemRepo->findOneBy([
12800
                'cId' => $course_id,
12801
                'lpId' => $learningPathId,
12802
                'id' => $id_in_path,
12803
            ]);
12804
12805
            if (!$rowItem) {
12806
                return -1;
12807
            }
12808
        }
12809
12810
        $course_code = $course_info['code'];
12811
        $type = $rowItem->getItemType();
12812
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12813
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12814
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12815
        $link = '';
12816
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12817
12818
        switch ($type) {
12819
            case 'dir':
12820
                return $main_dir_path.'lp/blank.php';
12821
            case TOOL_CALENDAR_EVENT:
12822
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12823
            case TOOL_ANNOUNCEMENT:
12824
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12825
            case TOOL_LINK:
12826
                $linkInfo = Link::getLinkInfo($id);
12827
                if (isset($linkInfo['url'])) {
12828
                    return $linkInfo['url'];
12829
                }
12830
12831
                return '';
12832
            case TOOL_QUIZ:
12833
                if (empty($id)) {
12834
                    return '';
12835
                }
12836
12837
                // Get the lp_item_view with the highest view_count.
12838
                $learnpathItemViewResult = $em
12839
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12840
                    ->findBy(
12841
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12842
                        ['viewCount' => 'DESC'],
12843
                        1
12844
                    );
12845
                /** @var CLpItemView $learnpathItemViewData */
12846
                $learnpathItemViewData = current($learnpathItemViewResult);
12847
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12848
12849
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12850
                    .http_build_query([
12851
                        'lp_init' => 1,
12852
                        'learnpath_item_view_id' => $learnpathItemViewId,
12853
                        'learnpath_id' => $learningPathId,
12854
                        'learnpath_item_id' => $id_in_path,
12855
                        'exerciseId' => $id,
12856
                    ]);
12857
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12858
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12859
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12860
                $myrow = Database::fetch_array($result);
12861
                $path = $myrow['path'];
12862
12863
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12864
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12865
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12866
            case TOOL_FORUM:
12867
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12868
            case TOOL_THREAD:
12869
                // forum post
12870
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12871
                if (empty($id)) {
12872
                    return '';
12873
                }
12874
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12875
                $result = Database::query($sql);
12876
                $myrow = Database::fetch_array($result);
12877
12878
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12879
                    .$extraParams;
12880
            case TOOL_POST:
12881
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12882
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12883
                $myrow = Database::fetch_array($result);
12884
12885
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12886
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12887
            case TOOL_DOCUMENT:
12888
                $document = $em
12889
                    ->getRepository('ChamiloCourseBundle:CDocument')
12890
                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
12891
12892
                if (empty($document)) {
12893
                    // Try with normal id
12894
                    $document = $em
12895
                        ->getRepository('ChamiloCourseBundle:CDocument')
12896
                        ->findOneBy(['cId' => $course_id, 'id' => $id]);
12897
12898
                    if (empty($document)) {
12899
                        return '';
12900
                    }
12901
                }
12902
12903
                $documentPathInfo = pathinfo($document->getPath());
12904
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
12905
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12906
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
12907
12908
                $openmethod = 2;
12909
                $officedoc = false;
12910
                Session::write('openmethod', $openmethod);
12911
                Session::write('officedoc', $officedoc);
12912
12913
                if ($showDirectUrl) {
12914
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12915
                }
12916
12917
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12918
            case TOOL_LP_FINAL_ITEM:
12919
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12920
                    .$extraParams;
12921
            case 'assignments':
12922
                return $main_dir_path.'work/work.php?'.$extraParams;
12923
            case TOOL_DROPBOX:
12924
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12925
            case 'introduction_text': //DEPRECATED
12926
                return '';
12927
            case TOOL_COURSE_DESCRIPTION:
12928
                return $main_dir_path.'course_description?'.$extraParams;
12929
            case TOOL_GROUP:
12930
                return $main_dir_path.'group/group.php?'.$extraParams;
12931
            case TOOL_USER:
12932
                return $main_dir_path.'user/user.php?'.$extraParams;
12933
            case TOOL_STUDENTPUBLICATION:
12934
                if (!empty($rowItem->getPath())) {
12935
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12936
                }
12937
12938
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12939
        } //end switch
12940
12941
        return $link;
12942
    }
12943
12944
    /**
12945
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12946
     *
12947
     * @author Yannick Warnier <[email protected]>
12948
     *
12949
     * @param string $course_code    Course code
12950
     * @param int    $learningPathId
12951
     * @param int    $id_in_path     The resource ID
12952
     *
12953
     * @return string
12954
     */
12955
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12956
    {
12957
        $_course = api_get_course_info($course_code);
12958
        $course_id = $_course['real_id'];
12959
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12960
        $learningPathId = (int) $learningPathId;
12961
        $id_in_path = (int) $id_in_path;
12962
12963
        $sql = "SELECT item_type, title, ref 
12964
                FROM $tbl_lp_item
12965
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12966
        $res_item = Database::query($sql);
12967
12968
        if (Database::num_rows($res_item) < 1) {
12969
            return '';
12970
        }
12971
        $row_item = Database::fetch_array($res_item);
12972
        $type = strtolower($row_item['item_type']);
12973
        $id = $row_item['ref'];
12974
        $output = '';
12975
12976
        switch ($type) {
12977
            case TOOL_CALENDAR_EVENT:
12978
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12979
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12980
                $myrow = Database::fetch_array($result);
12981
                $output = $myrow['title'];
12982
                break;
12983
            case TOOL_ANNOUNCEMENT:
12984
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12985
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12986
                $myrow = Database::fetch_array($result);
12987
                $output = $myrow['title'];
12988
                break;
12989
            case TOOL_LINK:
12990
                // Doesn't take $target into account.
12991
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12992
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12993
                $myrow = Database::fetch_array($result);
12994
                $output = $myrow['title'];
12995
                break;
12996
            case TOOL_QUIZ:
12997
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12998
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12999
                $myrow = Database::fetch_array($result);
13000
                $output = $myrow['title'];
13001
                break;
13002
            case TOOL_FORUM:
13003
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13004
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13005
                $myrow = Database::fetch_array($result);
13006
                $output = $myrow['forum_name'];
13007
                break;
13008
            case TOOL_THREAD:  //=topics
13009
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13010
                // Grabbing the title of the post.
13011
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13012
                $result_title = Database::query($sql_title);
13013
                $myrow_title = Database::fetch_array($result_title);
13014
                $output = $myrow_title['post_title'];
13015
                break;
13016
            case TOOL_POST:
13017
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13018
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
13019
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13020
                $result = Database::query($sql);
13021
                $post = Database::fetch_array($result);
13022
                $output = $post['post_title'];
13023
                break;
13024
            case 'dir':
13025
                $title = $row_item['title'];
13026
                if (!empty($title)) {
13027
                    $output = $title;
13028
                } else {
13029
                    $output = '-';
13030
                }
13031
                break;
13032
            case TOOL_DOCUMENT:
13033
                $title = $row_item['title'];
13034
                if (!empty($title)) {
13035
                    $output = $title;
13036
                } else {
13037
                    $output = '-';
13038
                }
13039
                break;
13040
            case 'hotpotatoes':
13041
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13042
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13043
                $myrow = Database::fetch_array($result);
13044
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13045
                $last = count($pathname) - 1; // Making a correct name for the link.
13046
                $filename = $pathname[$last]; // Making a correct name for the link.
13047
                $ext = explode('.', $filename);
13048
                $ext = strtolower($ext[sizeof($ext) - 1]);
13049
                $myrow['path'] = rawurlencode($myrow['path']);
13050
                $output = $filename;
13051
                break;
13052
        }
13053
13054
        return stripslashes($output);
13055
    }
13056
13057
    /**
13058
     * Get the parent names for the current item.
13059
     *
13060
     * @param int $newItemId Optional. The item ID
13061
     *
13062
     * @return array
13063
     */
13064
    public function getCurrentItemParentNames($newItemId = 0)
13065
    {
13066
        $newItemId = $newItemId ?: $this->get_current_item_id();
13067
        $return = [];
13068
        $item = $this->getItem($newItemId);
13069
        $parent = $this->getItem($item->get_parent());
13070
13071
        while ($parent) {
13072
            $return[] = $parent->get_title();
13073
13074
            $parent = $this->getItem($parent->get_parent());
13075
        }
13076
13077
        return array_reverse($return);
13078
    }
13079
13080
    /**
13081
     * Reads and process "lp_subscription_settings" setting.
13082
     *
13083
     * @return array
13084
     */
13085
    public static function getSubscriptionSettings()
13086
    {
13087
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13088
        if (empty($subscriptionSettings)) {
13089
            // By default allow both settings
13090
            $subscriptionSettings = [
13091
                'allow_add_users_to_lp' => true,
13092
                'allow_add_users_to_lp_category' => true,
13093
            ];
13094
        } else {
13095
            $subscriptionSettings = $subscriptionSettings['options'];
13096
        }
13097
13098
        return $subscriptionSettings;
13099
    }
13100
13101
    /**
13102
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13103
     */
13104
    public function exportToCourseBuildFormat()
13105
    {
13106
        if (!api_is_allowed_to_edit()) {
13107
            return false;
13108
        }
13109
13110
        $courseBuilder = new CourseBuilder();
13111
        $itemList = [];
13112
        /** @var learnpathItem $item */
13113
        foreach ($this->items as $item) {
13114
            $itemList[$item->get_type()][] = $item->get_path();
13115
        }
13116
13117
        if (empty($itemList)) {
13118
            return false;
13119
        }
13120
13121
        if (isset($itemList['document'])) {
13122
            // Get parents
13123
            foreach ($itemList['document'] as $documentId) {
13124
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13125
                if (!empty($documentInfo['parents'])) {
13126
                    foreach ($documentInfo['parents'] as $parentInfo) {
13127
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13128
                            continue;
13129
                        }
13130
                        $itemList['document'][] = $parentInfo['iid'];
13131
                    }
13132
                }
13133
            }
13134
13135
            $courseInfo = api_get_course_info();
13136
            foreach ($itemList['document'] as $documentId) {
13137
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13138
                $items = DocumentManager::get_resources_from_source_html(
13139
                    $documentInfo['absolute_path'],
13140
                    true,
13141
                    TOOL_DOCUMENT
13142
                );
13143
13144
                if (!empty($items)) {
13145
                    foreach ($items as $item) {
13146
                        // Get information about source url
13147
                        $url = $item[0]; // url
13148
                        $scope = $item[1]; // scope (local, remote)
13149
                        $type = $item[2]; // type (rel, abs, url)
13150
13151
                        $origParseUrl = parse_url($url);
13152
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13153
13154
                        if ($scope == 'local') {
13155
                            if ($type == 'abs' || $type == 'rel') {
13156
                                $documentFile = strstr($realOrigPath, 'document');
13157
                                if (strpos($realOrigPath, $documentFile) !== false) {
13158
                                    $documentFile = str_replace('document', '', $documentFile);
13159
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13160
                                    // Document found! Add it to the list
13161
                                    if ($itemDocumentId) {
13162
                                        $itemList['document'][] = $itemDocumentId;
13163
                                    }
13164
                                }
13165
                            }
13166
                        }
13167
                    }
13168
                }
13169
            }
13170
13171
            $courseBuilder->build_documents(
13172
                api_get_session_id(),
13173
                $this->get_course_int_id(),
13174
                true,
13175
                $itemList['document']
13176
            );
13177
        }
13178
13179
        if (isset($itemList['quiz'])) {
13180
            $courseBuilder->build_quizzes(
13181
                api_get_session_id(),
13182
                $this->get_course_int_id(),
13183
                true,
13184
                $itemList['quiz']
13185
            );
13186
        }
13187
13188
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13189
13190
        /*if (!empty($itemList['thread'])) {
13191
            $postList = [];
13192
            foreach ($itemList['thread'] as $postId) {
13193
                $post = get_post_information($postId);
13194
                if ($post) {
13195
                    if (!isset($itemList['forum'])) {
13196
                        $itemList['forum'] = [];
13197
                    }
13198
                    $itemList['forum'][] = $post['forum_id'];
13199
                    $postList[] = $postId;
13200
                }
13201
            }
13202
13203
            if (!empty($postList)) {
13204
                $courseBuilder->build_forum_posts(
13205
                    $this->get_course_int_id(),
13206
                    null,
13207
                    null,
13208
                    $postList
13209
                );
13210
            }
13211
        }*/
13212
13213
        if (!empty($itemList['thread'])) {
13214
            $threadList = [];
13215
            $em = Database::getManager();
13216
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13217
            foreach ($itemList['thread'] as $threadId) {
13218
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13219
                $thread = $repo->find($threadId);
13220
                if ($thread) {
13221
                    $itemList['forum'][] = $thread->getForumId();
13222
                    $threadList[] = $thread->getIid();
13223
                }
13224
            }
13225
13226
            if (!empty($threadList)) {
13227
                $courseBuilder->build_forum_topics(
13228
                    api_get_session_id(),
13229
                    $this->get_course_int_id(),
13230
                    null,
13231
                    $threadList
13232
                );
13233
            }
13234
        }
13235
13236
        $forumCategoryList = [];
13237
        if (isset($itemList['forum'])) {
13238
            foreach ($itemList['forum'] as $forumId) {
13239
                $forumInfo = get_forums($forumId);
13240
                $forumCategoryList[] = $forumInfo['forum_category'];
13241
            }
13242
        }
13243
13244
        if (!empty($forumCategoryList)) {
13245
            $courseBuilder->build_forum_category(
13246
                api_get_session_id(),
13247
                $this->get_course_int_id(),
13248
                true,
13249
                $forumCategoryList
13250
            );
13251
        }
13252
13253
        if (!empty($itemList['forum'])) {
13254
            $courseBuilder->build_forums(
13255
                api_get_session_id(),
13256
                $this->get_course_int_id(),
13257
                true,
13258
                $itemList['forum']
13259
            );
13260
        }
13261
13262
        if (isset($itemList['link'])) {
13263
            $courseBuilder->build_links(
13264
                api_get_session_id(),
13265
                $this->get_course_int_id(),
13266
                true,
13267
                $itemList['link']
13268
            );
13269
        }
13270
13271
        if (!empty($itemList['student_publication'])) {
13272
            $courseBuilder->build_works(
13273
                api_get_session_id(),
13274
                $this->get_course_int_id(),
13275
                true,
13276
                $itemList['student_publication']
13277
            );
13278
        }
13279
13280
        $courseBuilder->build_learnpaths(
13281
            api_get_session_id(),
13282
            $this->get_course_int_id(),
13283
            true,
13284
            [$this->get_id()],
13285
            false
13286
        );
13287
13288
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13289
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13290
        $result = DocumentManager::file_send_for_download(
13291
            $zipPath,
13292
            true,
13293
            $this->get_name().'.zip'
13294
        );
13295
13296
        if ($result) {
13297
            api_not_allowed();
13298
        }
13299
13300
        return true;
13301
    }
13302
13303
    /**
13304
     * Get the depth level of LP item.
13305
     *
13306
     * @param array $items
13307
     * @param int   $currentItemId
13308
     *
13309
     * @return int
13310
     */
13311
    private static function get_level_for_item($items, $currentItemId)
13312
    {
13313
        $parentItemId = 0;
13314
        if (isset($items[$currentItemId])) {
13315
            $parentItemId = $items[$currentItemId]->parent;
13316
        }
13317
13318
        if ($parentItemId == 0) {
13319
            return 0;
13320
        } else {
13321
            return self::get_level_for_item($items, $parentItemId) + 1;
13322
        }
13323
    }
13324
13325
    /**
13326
     * Generate the link for a learnpath category as course tool.
13327
     *
13328
     * @param int $categoryId
13329
     *
13330
     * @return string
13331
     */
13332
    private static function getCategoryLinkForTool($categoryId)
13333
    {
13334
        $categoryId = (int) $categoryId;
13335
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13336
            .http_build_query(
13337
                [
13338
                    'action' => 'view_category',
13339
                    'id' => $categoryId,
13340
                ]
13341
            );
13342
13343
        return $link;
13344
    }
13345
13346
    /**
13347
     * Return the scorm item type object with spaces replaced with _
13348
     * The return result is use to build a css classname like scorm_type_$return.
13349
     *
13350
     * @param $in_type
13351
     *
13352
     * @return mixed
13353
     */
13354
    private static function format_scorm_type_item($in_type)
13355
    {
13356
        return str_replace(' ', '_', $in_type);
13357
    }
13358
13359
    /**
13360
     * Check and obtain the lp final item if exist.
13361
     *
13362
     * @return learnpathItem
13363
     */
13364
    private function getFinalItem()
13365
    {
13366
        if (empty($this->items)) {
13367
            return null;
13368
        }
13369
13370
        foreach ($this->items as $item) {
13371
            if ($item->type !== 'final_item') {
13372
                continue;
13373
            }
13374
13375
            return $item;
13376
        }
13377
    }
13378
13379
    /**
13380
     * Get the LP Final Item Template.
13381
     *
13382
     * @return string
13383
     */
13384
    private function getFinalItemTemplate()
13385
    {
13386
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13387
    }
13388
13389
    /**
13390
     * Get the LP Final Item Url.
13391
     *
13392
     * @return string
13393
     */
13394
    private function getSavedFinalItem()
13395
    {
13396
        $finalItem = $this->getFinalItem();
13397
        $doc = DocumentManager::get_document_data_by_id(
13398
            $finalItem->path,
13399
            $this->cc
13400
        );
13401
        if ($doc && file_exists($doc['absolute_path'])) {
13402
            return file_get_contents($doc['absolute_path']);
13403
        }
13404
13405
        return '';
13406
    }
13407
}
13408