Completed
Push — master ( b77cfe...000f93 )
by Julito
22:58 queued 11:19
created

learnpath::display_hotpotatoes_form()   F

Complexity

Conditions 39
Paths > 20000

Size

Total Lines 184
Code Lines 133

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 39
eloc 133
nc 28800
nop 3
dl 0
loc 184
rs 2
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
    public $attempt = 0; // The number for the current ID view.
36
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
37
    public $current; // Id of the current item the user is viewing.
38
    public $current_score; // The score of the current item.
39
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
40
    public $current_time_stop; // The time the user closed this resource.
41
    public $default_status = 'not attempted';
42
    public $encoding = 'UTF-8';
43
    public $error = '';
44
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
45
    public $index; // The index of the active learnpath_item in $ordered_items array.
46
    public $items = [];
47
    public $last; // item_id of last item viewed in the learning path.
48
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
49
    public $license; // Which license this course has been given - not used yet on 20060522.
50
    public $lp_id; // DB iid for this learnpath.
51
    public $lp_view_id; // DB ID for lp_view
52
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
53
    public $message = '';
54
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
55
    public $name; // Learnpath name (they generally have one).
56
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
57
    public $path = ''; // Path inside the scorm directory (if scorm).
58
    public $theme; // The current theme of the learning path.
59
    public $preview_image; // The current image of the learning path.
60
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
61
62
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
63
    public $prevent_reinit = 1;
64
65
    // Describes the mode of progress bar display.
66
    public $seriousgame_mode = 0;
67
    public $progress_bar_mode = '%';
68
69
    // Percentage progress as saved in the db.
70
    public $progress_db = 0;
71
    public $proximity; // Wether the content is distant or local or unknown.
72
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
73
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
74
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
75
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
76
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
77
    public $user_id; //ID of the user that is viewing/using the course
78
    public $update_queue = [];
79
    public $scorm_debug = 0;
80
    public $arrMenu = []; // Array for the menu items.
81
    public $debug = 0; // Logging level.
82
    public $lp_session_id = 0;
83
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
84
    public $prerequisite = 0;
85
    public $use_max_score = 1; // 1 or 0
86
    public $subscribeUsers = 0; // Subscribe users or not
87
    public $created_on = '';
88
    public $modified_on = '';
89
    public $publicated_on = '';
90
    public $expired_on = '';
91
    public $ref = null;
92
    public $course_int_id;
93
    public $course_info = [];
94
    public $categoryId;
95
96
    /**
97
     * Constructor.
98
     * Needs a database handler, a course code and a learnpath id from the database.
99
     * Also builds the list of items into $this->items.
100
     *
101
     * @param string $course  Course code
102
     * @param int    $lp_id
103
     * @param int    $user_id
104
     */
105
    public function __construct($course, $lp_id, $user_id)
106
    {
107
        $debug = $this->debug;
108
        $this->encoding = api_get_system_encoding();
109
        if ($debug) {
110
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
111
        }
112
        if (empty($course)) {
113
            $course = api_get_course_id();
114
        }
115
        $course_info = api_get_course_info($course);
116
        if (!empty($course_info)) {
117
            $this->cc = $course_info['code'];
118
            $this->course_info = $course_info;
119
            $course_id = $course_info['real_id'];
120
        } else {
121
            $this->error = 'Course code does not exist in database.';
122
        }
123
124
        $lp_id = (int) $lp_id;
125
        $course_id = (int) $course_id;
126
        $this->set_course_int_id($course_id);
127
        // Check learnpath ID.
128
        if (empty($lp_id) || empty($course_id)) {
129
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
130
        } else {
131
            // TODO: Make it flexible to use any course_code (still using env course code here).
132
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
133
            $sql = "SELECT * FROM $lp_table
134
                    WHERE iid = $lp_id";
135
            if ($debug) {
136
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
137
            }
138
            $res = Database::query($sql);
139
            if (Database::num_rows($res) > 0) {
140
                $this->lp_id = $lp_id;
141
                $row = Database::fetch_array($res);
142
                $this->type = $row['lp_type'];
143
                $this->name = stripslashes($row['name']);
144
                $this->proximity = $row['content_local'];
145
                $this->theme = $row['theme'];
146
                $this->maker = $row['content_maker'];
147
                $this->prevent_reinit = $row['prevent_reinit'];
148
                $this->seriousgame_mode = $row['seriousgame_mode'];
149
                $this->license = $row['content_license'];
150
                $this->scorm_debug = $row['debug'];
151
                $this->js_lib = $row['js_lib'];
152
                $this->path = $row['path'];
153
                $this->preview_image = $row['preview_image'];
154
                $this->author = $row['author'];
155
                $this->hide_toc_frame = $row['hide_toc_frame'];
156
                $this->lp_session_id = $row['session_id'];
157
                $this->use_max_score = $row['use_max_score'];
158
                $this->subscribeUsers = $row['subscribe_users'];
159
                $this->created_on = $row['created_on'];
160
                $this->modified_on = $row['modified_on'];
161
                $this->ref = $row['ref'];
162
                $this->categoryId = $row['category_id'];
163
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
164
165
                if (!empty($row['publicated_on'])) {
166
                    $this->publicated_on = $row['publicated_on'];
167
                }
168
169
                if (!empty($row['expired_on'])) {
170
                    $this->expired_on = $row['expired_on'];
171
                }
172
                if ($this->type == 2) {
173
                    if ($row['force_commit'] == 1) {
174
                        $this->force_commit = true;
175
                    }
176
                }
177
                $this->mode = $row['default_view_mod'];
178
179
                // Check user ID.
180
                if (empty($user_id)) {
181
                    $this->error = 'User ID is empty';
182
                } else {
183
                    $userInfo = api_get_user_info($user_id);
184
                    if (!empty($userInfo)) {
185
                        $this->user_id = $userInfo['user_id'];
186
                    } else {
187
                        $this->error = 'User ID does not exist in database #'.$user_id;
188
                    }
189
                }
190
191
                // End of variables checking.
192
                $session_id = api_get_session_id();
193
                //  Get the session condition for learning paths of the base + session.
194
                $session = api_get_session_condition($session_id);
195
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
196
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
197
198
                // Selecting by view_count descending allows to get the highest view_count first.
199
                $sql = "SELECT * FROM $lp_table
200
                        WHERE 
201
                            c_id = $course_id AND 
202
                            lp_id = $lp_id AND 
203
                            user_id = $user_id 
204
                            $session
205
                        ORDER BY view_count DESC";
206
                $res = Database::query($sql);
207
                if ($debug) {
208
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
209
                }
210
211
                if (Database::num_rows($res) > 0) {
212
                    if ($debug) {
213
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
214
                    }
215
                    $row = Database::fetch_array($res);
216
                    $this->attempt = $row['view_count'];
217
                    $this->lp_view_id = $row['id'];
218
                    $this->last_item_seen = $row['last_item'];
219
                    $this->progress_db = $row['progress'];
220
                    $this->lp_view_session_id = $row['session_id'];
221
                } elseif (!api_is_invitee()) {
222
                    if ($debug) {
223
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
224
                    }
225
                    $this->attempt = 1;
226
                    $params = [
227
                        'c_id' => $course_id,
228
                        'lp_id' => $lp_id,
229
                        'user_id' => $user_id,
230
                        'view_count' => 1,
231
                        'session_id' => $session_id,
232
                        'last_item' => 0,
233
                    ];
234
                    $this->last_item_seen = 0;
235
                    $this->lp_view_session_id = $session_id;
236
                    $this->lp_view_id = Database::insert($lp_table, $params);
237
                    if (!empty($this->lp_view_id)) {
238
                        $sql = "UPDATE $lp_table SET id = iid
239
                                WHERE iid = ".$this->lp_view_id;
240
                        Database::query($sql);
241
                    }
242
                }
243
244
                // Initialise items.
245
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
246
                $sql = "SELECT * FROM $lp_item_table
247
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
248
                        ORDER BY parent_item_id, display_order";
249
                $res = Database::query($sql);
250
251
                if ($debug) {
252
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
253
                    error_log('-- Start while--');
254
                }
255
256
                $lp_item_id_list = [];
257
                while ($row = Database::fetch_array($res)) {
258
                    $lp_item_id_list[] = $row['iid'];
259
                    switch ($this->type) {
260
                        case 3: //aicc
261
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
262
                            if (is_object($oItem)) {
263
                                $my_item_id = $oItem->get_id();
264
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
265
                                $oItem->set_prevent_reinit($this->prevent_reinit);
266
                                // Don't use reference here as the next loop will make the pointed object change.
267
                                $this->items[$my_item_id] = $oItem;
268
                                $this->refs_list[$oItem->ref] = $my_item_id;
269
                                if ($debug) {
270
                                    error_log(
271
                                        'learnpath::__construct() - '.
272
                                        'aicc object with id '.$my_item_id.
273
                                        ' set in items[]',
274
                                        0
275
                                    );
276
                                }
277
                            }
278
                            break;
279
                        case 2:
280
                            $oItem = new scormItem('db', $row['iid'], $course_id);
281
                            if (is_object($oItem)) {
282
                                $my_item_id = $oItem->get_id();
283
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
284
                                $oItem->set_prevent_reinit($this->prevent_reinit);
285
                                // Don't use reference here as the next loop will make the pointed object change.
286
                                $this->items[$my_item_id] = $oItem;
287
                                $this->refs_list[$oItem->ref] = $my_item_id;
288
                                if ($debug) {
289
                                    error_log('object with id '.$my_item_id.' set in items[]');
290
                                }
291
                            }
292
                            break;
293
                        case 1:
294
                        default:
295
                            if ($debug) {
296
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
297
                            }
298
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
299
300
                            if ($debug) {
301
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
302
                            }
303
                            if (is_object($oItem)) {
304
                                $my_item_id = $oItem->get_id();
305
                                // Moved down to when we are sure the item_view exists.
306
                                //$oItem->set_lp_view($this->lp_view_id);
307
                                $oItem->set_prevent_reinit($this->prevent_reinit);
308
                                // Don't use reference here as the next loop will make the pointed object change.
309
                                $this->items[$my_item_id] = $oItem;
310
                                $this->refs_list[$my_item_id] = $my_item_id;
311
                                if ($debug) {
312
                                    error_log(
313
                                        'learnpath::__construct() '.__LINE__.
314
                                        ' - object with id '.$my_item_id.' set in items[]'
315
                                    );
316
                                }
317
                            }
318
                            break;
319
                    }
320
321
                    // Setting the object level with variable $this->items[$i][parent]
322
                    foreach ($this->items as $itemLPObject) {
323
                        $level = self::get_level_for_item(
324
                            $this->items,
325
                            $itemLPObject->db_id
326
                        );
327
                        $itemLPObject->level = $level;
328
                    }
329
330
                    // Setting the view in the item object.
331
                    if (is_object($this->items[$row['iid']])) {
332
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
333
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
334
                            $this->items[$row['iid']]->current_start_time = 0;
335
                            $this->items[$row['iid']]->current_stop_time = 0;
336
                        }
337
                    }
338
                }
339
340
                if ($debug) {
341
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
342
                }
343
344
                if (!empty($lp_item_id_list)) {
345
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
346
                    if (!empty($lp_item_id_list_to_string)) {
347
                        // Get last viewing vars.
348
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
349
                        // This query should only return one or zero result.
350
                        $sql = "SELECT lp_item_id, status
351
                                FROM $itemViewTable
352
                                WHERE
353
                                    c_id = $course_id AND
354
                                    lp_view_id = ".$this->lp_view_id." AND
355
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
356
                                ORDER BY view_count DESC ";
357
358
                        if ($debug) {
359
                            error_log(
360
                                'learnpath::__construct() - Selecting item_views: '.$sql,
361
                                0
362
                            );
363
                        }
364
365
                        $status_list = [];
366
                        $res = Database::query($sql);
367
                        while ($row = Database:: fetch_array($res)) {
368
                            $status_list[$row['lp_item_id']] = $row['status'];
369
                        }
370
371
                        foreach ($lp_item_id_list as $item_id) {
372
                            if (isset($status_list[$item_id])) {
373
                                $status = $status_list[$item_id];
374
                                if (is_object($this->items[$item_id])) {
375
                                    $this->items[$item_id]->set_status($status);
376
                                    if (empty($status)) {
377
                                        $this->items[$item_id]->set_status(
378
                                            $this->default_status
379
                                        );
380
                                    }
381
                                }
382
                            } else {
383
                                if (!api_is_invitee()) {
384
                                    if (is_object($this->items[$item_id])) {
385
                                        $this->items[$item_id]->set_status(
386
                                            $this->default_status
387
                                        );
388
                                    }
389
390
                                    if (!empty($this->lp_view_id)) {
391
                                        // Add that row to the lp_item_view table so that
392
                                        // we have something to show in the stats page.
393
                                        $params = [
394
                                            'c_id' => $course_id,
395
                                            'lp_item_id' => $item_id,
396
                                            'lp_view_id' => $this->lp_view_id,
397
                                            'view_count' => 1,
398
                                            'status' => 'not attempted',
399
                                            'start_time' => time(),
400
                                            'total_time' => 0,
401
                                            'score' => 0,
402
                                        ];
403
                                        $insertId = Database::insert($itemViewTable, $params);
404
405
                                        if ($insertId) {
406
                                            $sql = "UPDATE $itemViewTable SET id = iid
407
                                                    WHERE iid = $insertId";
408
                                            Database::query($sql);
409
                                        }
410
411
                                        $this->items[$item_id]->set_lp_view(
412
                                            $this->lp_view_id,
413
                                            $course_id
414
                                        );
415
                                    }
416
                                }
417
                            }
418
                        }
419
                    }
420
                }
421
422
                $this->ordered_items = self::get_flat_ordered_items_list(
423
                    $this->get_id(),
424
                    0,
425
                    $course_id
426
                );
427
                $this->max_ordered_items = 0;
428
                foreach ($this->ordered_items as $index => $dummy) {
429
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
430
                        $this->max_ordered_items = $index;
431
                    }
432
                }
433
                // TODO: Define the current item better.
434
                $this->first();
435
                if ($debug) {
436
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
437
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
438
                }
439
            } else {
440
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
441
            }
442
        }
443
    }
444
445
    /**
446
     * @return string
447
     */
448
    public function getCourseCode()
449
    {
450
        return $this->cc;
451
    }
452
453
    /**
454
     * @return int
455
     */
456
    public function get_course_int_id()
457
    {
458
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
459
    }
460
461
    /**
462
     * @param $course_id
463
     *
464
     * @return int
465
     */
466
    public function set_course_int_id($course_id)
467
    {
468
        return $this->course_int_id = (int) $course_id;
469
    }
470
471
    /**
472
     * Function rewritten based on old_add_item() from Yannick Warnier.
473
     * Due the fact that users can decide where the item should come, I had to overlook this function and
474
     * I found it better to rewrite it. Old function is still available.
475
     * Added also the possibility to add a description.
476
     *
477
     * @param int    $parent
478
     * @param int    $previous
479
     * @param string $type
480
     * @param int    $id               resource ID (ref)
481
     * @param string $title
482
     * @param string $description
483
     * @param int    $prerequisites
484
     * @param int    $max_time_allowed
485
     * @param int    $userId
486
     *
487
     * @return int
488
     */
489
    public function add_item(
490
        $parent,
491
        $previous,
492
        $type = 'dir',
493
        $id,
494
        $title,
495
        $description,
496
        $prerequisites = 0,
497
        $max_time_allowed = 0,
498
        $userId = 0
499
    ) {
500
        $course_id = $this->course_info['real_id'];
501
        if ($this->debug > 0) {
502
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
503
        }
504
        if (empty($course_id)) {
505
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
506
            $this->course_info = api_get_course_info($this->cc);
507
            $course_id = $this->course_info['real_id'];
508
        }
509
        $userId = empty($userId) ? api_get_user_id() : $userId;
510
        $sessionId = api_get_session_id();
511
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
512
        $_course = $this->course_info;
513
        $parent = intval($parent);
514
        $previous = intval($previous);
515
        $id = intval($id);
516
        $max_time_allowed = htmlentities($max_time_allowed);
517
        if (empty($max_time_allowed)) {
518
            $max_time_allowed = 0;
519
        }
520
        $sql = "SELECT COUNT(iid) AS num
521
                FROM $tbl_lp_item
522
                WHERE
523
                    c_id = $course_id AND
524
                    lp_id = ".$this->get_id()." AND
525
                    parent_item_id = ".$parent;
526
527
        $res_count = Database::query($sql);
528
        $row = Database::fetch_array($res_count);
529
        $num = $row['num'];
530
531
        if ($num > 0) {
532
            if (empty($previous)) {
533
                $sql = "SELECT iid, next_item_id, display_order
534
                        FROM $tbl_lp_item
535
                        WHERE
536
                            c_id = $course_id AND
537
                            lp_id = ".$this->get_id()." AND
538
                            parent_item_id = $parent AND
539
                            previous_item_id = 0 OR
540
                            previous_item_id = $parent";
541
                $result = Database::query($sql);
542
                $row = Database::fetch_array($result);
543
                $tmp_previous = 0;
544
                $next = $row['iid'];
545
                $display_order = 0;
546
            } else {
547
                $previous = (int) $previous;
548
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
549
						FROM $tbl_lp_item
550
                        WHERE
551
                            c_id = $course_id AND
552
                            lp_id = ".$this->get_id()." AND
553
                            id = $previous";
554
                $result = Database::query($sql);
555
                $row = Database:: fetch_array($result);
556
                $tmp_previous = $row['iid'];
557
                $next = $row['next_item_id'];
558
                $display_order = $row['display_order'];
559
            }
560
        } else {
561
            $tmp_previous = 0;
562
            $next = 0;
563
            $display_order = 0;
564
        }
565
566
        $id = intval($id);
567
        $typeCleaned = Database::escape_string($type);
568
        $max_score = 100;
569
        if ($type == 'quiz') {
570
            $sql = 'SELECT SUM(ponderation)
571
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
572
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
573
                    ON
574
                        quiz_question.id = quiz_rel_question.question_id AND
575
                        quiz_question.c_id = quiz_rel_question.c_id
576
                    WHERE
577
                        quiz_rel_question.exercice_id = '.$id." AND
578
                        quiz_question.c_id = $course_id AND
579
                        quiz_rel_question.c_id = $course_id ";
580
            $rsQuiz = Database::query($sql);
581
            $max_score = Database::result($rsQuiz, 0, 0);
582
583
            // Disabling the exercise if we add it inside a LP
584
            $exercise = new Exercise($course_id);
585
            $exercise->read($id);
586
            $exercise->disable();
587
            $exercise->save();
588
        }
589
590
        $params = [
591
            "c_id" => $course_id,
592
            "lp_id" => $this->get_id(),
593
            "item_type" => $typeCleaned,
594
            "ref" => '',
595
            "title" => $title,
596
            "description" => $description,
597
            "path" => $id,
598
            "max_score" => $max_score,
599
            "parent_item_id" => $parent,
600
            "previous_item_id" => $previous,
601
            "next_item_id" => intval($next),
602
            "display_order" => $display_order + 1,
603
            "prerequisite" => $prerequisites,
604
            "max_time_allowed" => $max_time_allowed,
605
            'min_score' => 0,
606
            'launch_data' => '',
607
        ];
608
609
        if ($prerequisites != 0) {
610
            $params['prerequisite'] = $prerequisites;
611
        }
612
613
        $new_item_id = Database::insert($tbl_lp_item, $params);
614
615
        if ($this->debug > 2) {
616
            error_log('Inserting dir/chapter: '.$new_item_id, 0);
617
        }
618
619
        if ($new_item_id) {
620
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
621
            Database::query($sql);
622
623
            $sql = "UPDATE $tbl_lp_item
624
                    SET previous_item_id = $new_item_id 
625
                    WHERE c_id = $course_id AND id = $next";
626
            Database::query($sql);
627
628
            // Update the item that should be before the new item.
629
            $sql = "UPDATE $tbl_lp_item
630
                    SET next_item_id = $new_item_id
631
                    WHERE c_id = $course_id AND id = $tmp_previous";
632
            Database::query($sql);
633
634
            // Update all the items after the new item.
635
            $sql = "UPDATE $tbl_lp_item
636
                        SET display_order = display_order + 1
637
                    WHERE
638
                        c_id = $course_id AND
639
                        lp_id = ".$this->get_id()." AND
640
                        iid <> $new_item_id AND
641
                        parent_item_id = $parent AND
642
                        display_order > $display_order";
643
            Database::query($sql);
644
645
            // Update the item that should come after the new item.
646
            $sql = "UPDATE $tbl_lp_item
647
                    SET ref = $new_item_id
648
                    WHERE c_id = $course_id AND iid = $new_item_id";
649
            Database::query($sql);
650
651
            // Upload audio.
652
            if (!empty($_FILES['mp3']['name'])) {
653
                // Create the audio folder if it does not exist yet.
654
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
655
                if (!is_dir($filepath.'audio')) {
656
                    mkdir(
657
                        $filepath.'audio',
658
                        api_get_permissions_for_new_directories()
659
                    );
660
                    $audio_id = add_document(
661
                        $_course,
662
                        '/audio',
663
                        'folder',
664
                        0,
665
                        'audio',
666
                        '',
667
                        0,
668
                        true,
669
                        null,
670
                        $sessionId,
671
                        $userId
672
                    );
673
                    api_item_property_update(
674
                        $_course,
675
                        TOOL_DOCUMENT,
676
                        $audio_id,
677
                        'FolderCreated',
678
                        $userId,
679
                        null,
680
                        null,
681
                        null,
682
                        null,
683
                        $sessionId
684
                    );
685
                    api_item_property_update(
686
                        $_course,
687
                        TOOL_DOCUMENT,
688
                        $audio_id,
689
                        'invisible',
690
                        $userId,
691
                        null,
692
                        null,
693
                        null,
694
                        null,
695
                        $sessionId
696
                    );
697
                }
698
699
                $file_path = handle_uploaded_document(
700
                    $_course,
701
                    $_FILES['mp3'],
702
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
703
                    '/audio',
704
                    $userId,
705
                    '',
706
                    '',
707
                    '',
708
                    '',
709
                    false
710
                );
711
712
                // Getting the filename only.
713
                $file_components = explode('/', $file_path);
714
                $file = $file_components[count($file_components) - 1];
715
716
                // Store the mp3 file in the lp_item table.
717
                $sql = "UPDATE $tbl_lp_item SET
718
                          audio = '".Database::escape_string($file)."'
719
                        WHERE iid = '".intval($new_item_id)."'";
720
                Database::query($sql);
721
            }
722
        }
723
724
        return $new_item_id;
725
    }
726
727
    /**
728
     * Static admin function allowing addition of a learnpath to a course.
729
     *
730
     * @param string $courseCode
731
     * @param string $name
732
     * @param string $description
733
     * @param string $learnpath
734
     * @param string $origin
735
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
736
     * @param string $publicated_on
737
     * @param string $expired_on
738
     * @param int    $categoryId
739
     * @param int    $userId
740
     *
741
     * @return int The new learnpath ID on success, 0 on failure
742
     */
743
    public static function add_lp(
744
        $courseCode,
745
        $name,
746
        $description = '',
747
        $learnpath = 'guess',
748
        $origin = 'zip',
749
        $zipname = '',
750
        $publicated_on = '',
751
        $expired_on = '',
752
        $categoryId = 0,
753
        $userId = 0
754
    ) {
755
        global $charset;
756
757
        if (!empty($courseCode)) {
758
            $courseInfo = api_get_course_info($courseCode);
759
            $course_id = $courseInfo['real_id'];
760
        } else {
761
            $course_id = api_get_course_int_id();
762
            $courseInfo = api_get_course_info();
763
        }
764
765
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
766
        // Check course code exists.
767
        // Check lp_name doesn't exist, otherwise append something.
768
        $i = 0;
769
        $name = Database::escape_string($name);
770
        $categoryId = intval($categoryId);
771
772
        // Session id.
773
        $session_id = api_get_session_id();
774
        $userId = empty($userId) ? api_get_user_id() : $userId;
775
        $check_name = "SELECT * FROM $tbl_lp
776
                       WHERE c_id = $course_id AND name = '$name'";
777
778
        $res_name = Database::query($check_name);
779
780
        if (empty($publicated_on)) {
781
            $publicated_on = null;
782
        } else {
783
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
784
        }
785
786
        if (empty($expired_on)) {
787
            $expired_on = null;
788
        } else {
789
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
790
        }
791
792
        while (Database::num_rows($res_name)) {
793
            // There is already one such name, update the current one a bit.
794
            $i++;
795
            $name = $name.' - '.$i;
796
            $check_name = "SELECT * FROM $tbl_lp 
797
                           WHERE c_id = $course_id AND name = '$name'";
798
            $res_name = Database::query($check_name);
799
        }
800
        // New name does not exist yet; keep it.
801
        // Escape description.
802
        // Kevin: added htmlentities().
803
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
804
        $type = 1;
805
        switch ($learnpath) {
806
            case 'guess':
807
                break;
808
            case 'dokeos':
809
            case 'chamilo':
810
                $type = 1;
811
                break;
812
            case 'aicc':
813
                break;
814
        }
815
816
        switch ($origin) {
817
            case 'zip':
818
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
819
                break;
820
            case 'manual':
821
            default:
822
                $get_max = "SELECT MAX(display_order) 
823
                            FROM $tbl_lp WHERE c_id = $course_id";
824
                $res_max = Database::query($get_max);
825
                if (Database::num_rows($res_max) < 1) {
826
                    $dsp = 1;
827
                } else {
828
                    $row = Database::fetch_array($res_max);
829
                    $dsp = $row[0] + 1;
830
                }
831
832
                $params = [
833
                    'c_id' => $course_id,
834
                    'lp_type' => $type,
835
                    'name' => $name,
836
                    'description' => $description,
837
                    'path' => '',
838
                    'default_view_mod' => 'embedded',
839
                    'default_encoding' => 'UTF-8',
840
                    'display_order' => $dsp,
841
                    'content_maker' => 'Chamilo',
842
                    'content_local' => 'local',
843
                    'js_lib' => '',
844
                    'session_id' => $session_id,
845
                    'created_on' => api_get_utc_datetime(),
846
                    'modified_on' => api_get_utc_datetime(),
847
                    'publicated_on' => $publicated_on,
848
                    'expired_on' => $expired_on,
849
                    'category_id' => $categoryId,
850
                    'force_commit' => 0,
851
                    'content_license' => '',
852
                    'debug' => 0,
853
                    'theme' => '',
854
                    'preview_image' => '',
855
                    'author' => '',
856
                    'prerequisite' => 0,
857
                    'hide_toc_frame' => 0,
858
                    'seriousgame_mode' => 0,
859
                    'autolaunch' => 0,
860
                    'max_attempts' => 0,
861
                    'subscribe_users' => 0,
862
                    'accumulate_scorm_time' => 1,
863
                ];
864
                $id = Database::insert($tbl_lp, $params);
865
866
                if ($id > 0) {
867
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
868
                    Database::query($sql);
869
870
                    // Insert into item_property.
871
                    api_item_property_update(
872
                        $courseInfo,
873
                        TOOL_LEARNPATH,
874
                        $id,
875
                        'LearnpathAdded',
876
                        $userId
877
                    );
878
                    api_set_default_visibility(
879
                        $id,
880
                        TOOL_LEARNPATH,
881
                        0,
882
                        $courseInfo,
883
                        $session_id,
884
                        $userId
885
                    );
886
887
                    return $id;
888
                }
889
                break;
890
        }
891
    }
892
893
    /**
894
     * Auto completes the parents of an item in case it's been completed or passed.
895
     *
896
     * @param int $item Optional ID of the item from which to look for parents
897
     */
898
    public function autocomplete_parents($item)
899
    {
900
        $debug = $this->debug;
901
902
        if ($debug) {
903
            error_log('Learnpath::autocomplete_parents()');
904
        }
905
906
        if (empty($item)) {
907
            $item = $this->current;
908
        }
909
910
        $currentItem = $this->getItem($item);
911
        if ($currentItem) {
912
            $parent_id = $currentItem->get_parent();
913
            $parent = $this->getItem($parent_id);
914
            if ($parent) {
915
                // if $item points to an object and there is a parent.
916
                if ($debug) {
917
                    error_log(
918
                        'Autocompleting parent of item '.$item.' '.
919
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
920
                        0
921
                    );
922
                }
923
924
                // New experiment including failed and browsed in completed status.
925
                //$current_status = $currentItem->get_status();
926
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
927
                // Fixes chapter auto complete
928
                if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
929
                    // If the current item is completed or passes or succeeded.
930
                    $updateParentStatus = true;
931
                    if ($debug) {
932
                        error_log('Status of current item is alright');
933
                    }
934
935
                    foreach ($parent->get_children() as $childItemId) {
936
                        $childItem = $this->getItem($childItemId);
937
938
                        // If children was not set try to get the info
939
                        if (empty($childItem->db_item_view_id)) {
940
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
941
                        }
942
943
                        // Check all his brothers (parent's children) for completion status.
944
                        if ($childItemId != $item) {
945
                            if ($debug) {
946
                                error_log(
947
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
948
                                    0
949
                                );
950
                            }
951
                            // Trying completing parents of failed and browsed items as well.
952
                            if ($childItem->status_is(
953
                                [
954
                                    'completed',
955
                                    'passed',
956
                                    'succeeded',
957
                                    'browsed',
958
                                    'failed',
959
                                ]
960
                            )
961
                            ) {
962
                                // Keep completion status to true.
963
                                continue;
964
                            } else {
965
                                if ($debug > 2) {
966
                                    error_log(
967
                                        '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,
968
                                        0
969
                                    );
970
                                }
971
                                $updateParentStatus = false;
972
                                break;
973
                            }
974
                        }
975
                    }
976
977
                    if ($updateParentStatus) {
978
                        // If all the children were completed:
979
                        $parent->set_status('completed');
980
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
981
                        // Force the status to "completed"
982
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
983
                        $this->update_queue[$parent->get_id()] = 'completed';
984
                        if ($debug) {
985
                            error_log(
986
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
987
                                print_r($this->update_queue, 1),
988
                                0
989
                            );
990
                        }
991
                        // Recursive call.
992
                        $this->autocomplete_parents($parent->get_id());
993
                    }
994
                }
995
            } else {
996
                if ($debug) {
997
                    error_log("Parent #$parent_id does not exists");
998
                }
999
            }
1000
        } else {
1001
            if ($debug) {
1002
                error_log("#$item is an item that doesn't have parents");
1003
            }
1004
        }
1005
    }
1006
1007
    /**
1008
     * Closes the current resource.
1009
     *
1010
     * Stops the timer
1011
     * Saves into the database if required
1012
     * Clears the current resource data from this object
1013
     *
1014
     * @return bool True on success, false on failure
1015
     */
1016
    public function close()
1017
    {
1018
        if ($this->debug > 0) {
1019
            error_log('In learnpath::close()', 0);
1020
        }
1021
        if (empty($this->lp_id)) {
1022
            $this->error = 'Trying to close this learnpath but no ID is set';
1023
1024
            return false;
1025
        }
1026
        $this->current_time_stop = time();
1027
        $this->ordered_items = [];
1028
        $this->index = 0;
1029
        unset($this->lp_id);
1030
        //unset other stuff
1031
        return true;
1032
    }
1033
1034
    /**
1035
     * Static admin function allowing removal of a learnpath.
1036
     *
1037
     * @param array  $courseInfo
1038
     * @param int    $id         Learnpath ID
1039
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1040
     *
1041
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1042
     */
1043
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1044
    {
1045
        $course_id = api_get_course_int_id();
1046
        if (!empty($courseInfo)) {
1047
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1048
        }
1049
1050
        // TODO: Implement a way of getting this to work when the current object is not set.
1051
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1052
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1053
        if (!empty($id) && ($id != $this->lp_id)) {
1054
            return false;
1055
        }
1056
1057
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1058
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1059
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1060
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1061
1062
        // Delete lp item id.
1063
        foreach ($this->items as $id => $dummy) {
1064
            $sql = "DELETE FROM $lp_item_view
1065
                    WHERE c_id = $course_id AND lp_item_id = '".$id."'";
1066
            Database::query($sql);
1067
        }
1068
1069
        // Proposed by Christophe (nickname: clefevre)
1070
        $sql = "DELETE FROM $lp_item
1071
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1072
        Database::query($sql);
1073
1074
        $sql = "DELETE FROM $lp_view 
1075
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1076
        Database::query($sql);
1077
1078
        self::toggle_publish($this->lp_id, 'i');
1079
1080
        if ($this->type == 2 || $this->type == 3) {
1081
            // This is a scorm learning path, delete the files as well.
1082
            $sql = "SELECT path FROM $lp
1083
                    WHERE iid = ".$this->lp_id;
1084
            $res = Database::query($sql);
1085
            if (Database::num_rows($res) > 0) {
1086
                $row = Database::fetch_array($res);
1087
                $path = $row['path'];
1088
                $sql = "SELECT id FROM $lp
1089
                        WHERE 
1090
                            c_id = $course_id AND
1091
                            path = '$path' AND 
1092
                            iid != ".$this->lp_id;
1093
                $res = Database::query($sql);
1094
                if (Database::num_rows($res) > 0) {
1095
                    // Another learning path uses this directory, so don't delete it.
1096
                    if ($this->debug > 2) {
1097
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1098
                    }
1099
                } else {
1100
                    // No other LP uses that directory, delete it.
1101
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1102
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; // The absolute system path for this course.
1103
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1104
                        if ($this->debug > 2) {
1105
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1106
                        }
1107
                        // Proposed by Christophe (clefevre).
1108
                        if (strcmp(substr($path, -2), "/.") == 0) {
1109
                            $path = substr($path, 0, -1); // Remove "." at the end.
1110
                        }
1111
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1112
                        rmdirr($course_scorm_dir.$path);
1113
                    }
1114
                }
1115
            }
1116
        }
1117
1118
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1119
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1120
        // Delete tools
1121
        $sql = "DELETE FROM $tbl_tool
1122
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1123
        Database::query($sql);
1124
1125
        $sql = "DELETE FROM $lp 
1126
                WHERE iid = ".$this->lp_id;
1127
        Database::query($sql);
1128
        // Updates the display order of all lps.
1129
        $this->update_display_order();
1130
1131
        api_item_property_update(
1132
            api_get_course_info(),
1133
            TOOL_LEARNPATH,
1134
            $this->lp_id,
1135
            'delete',
1136
            api_get_user_id()
1137
        );
1138
1139
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1140
            api_get_course_id(),
1141
            4,
1142
            $id,
1143
            api_get_session_id()
1144
        );
1145
1146
        if ($link_info !== false) {
1147
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1148
        }
1149
1150
        if (api_get_setting('search_enabled') == 'true') {
1151
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1152
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1153
        }
1154
    }
1155
1156
    /**
1157
     * Removes all the children of one item - dangerous!
1158
     *
1159
     * @param int $id Element ID of which children have to be removed
1160
     *
1161
     * @return int Total number of children removed
1162
     */
1163
    public function delete_children_items($id)
1164
    {
1165
        $course_id = $this->course_info['real_id'];
1166
        if ($this->debug > 0) {
1167
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1168
        }
1169
        $num = 0;
1170
        if (empty($id) || $id != strval(intval($id))) {
1171
            return false;
1172
        }
1173
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1174
        $sql = "SELECT * FROM $lp_item 
1175
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1176
        $res = Database::query($sql);
1177
        while ($row = Database::fetch_array($res)) {
1178
            $num += $this->delete_children_items($row['iid']);
1179
            $sql = "DELETE FROM $lp_item 
1180
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1181
            Database::query($sql);
1182
            $num++;
1183
        }
1184
1185
        return $num;
1186
    }
1187
1188
    /**
1189
     * Removes an item from the current learnpath.
1190
     *
1191
     * @param int $id     Elem ID (0 if first)
1192
     * @param int $remove Whether to remove the resource/data from the
1193
     *                    system or leave it (default: 'keep', others 'remove')
1194
     *
1195
     * @return int Number of elements moved
1196
     *
1197
     * @todo implement resource removal
1198
     */
1199
    public function delete_item($id, $remove = 'keep')
1200
    {
1201
        $course_id = api_get_course_int_id();
1202
        if ($this->debug > 0) {
1203
            error_log('In learnpath::delete_item()', 0);
1204
        }
1205
        // TODO: Implement the resource removal.
1206
        if (empty($id) || $id != strval(intval($id))) {
1207
            return false;
1208
        }
1209
        // First select item to get previous, next, and display order.
1210
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1211
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1212
        $res_sel = Database::query($sql_sel);
1213
        if (Database::num_rows($res_sel) < 1) {
1214
            return false;
1215
        }
1216
        $row = Database::fetch_array($res_sel);
1217
        $previous = $row['previous_item_id'];
1218
        $next = $row['next_item_id'];
1219
        $display = $row['display_order'];
1220
        $parent = $row['parent_item_id'];
1221
        $lp = $row['lp_id'];
1222
        // Delete children items.
1223
        $num = $this->delete_children_items($id);
1224
        if ($this->debug > 2) {
1225
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1226
        }
1227
        // Now delete the item.
1228
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1229
        if ($this->debug > 2) {
1230
            error_log('Deleting item: '.$sql_del, 0);
1231
        }
1232
        Database::query($sql_del);
1233
        // Now update surrounding items.
1234
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1235
                    WHERE iid = $previous";
1236
        Database::query($sql_upd);
1237
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1238
                    WHERE iid = $next";
1239
        Database::query($sql_upd);
1240
        // Now update all following items with new display order.
1241
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1242
                    WHERE 
1243
                        c_id = $course_id AND 
1244
                        lp_id = $lp AND 
1245
                        parent_item_id = $parent AND 
1246
                        display_order > $display";
1247
        Database::query($sql_all);
1248
1249
        //Removing prerequisites since the item will not longer exist
1250
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1251
                    WHERE c_id = $course_id AND prerequisite = $id";
1252
        Database::query($sql_all);
1253
1254
        // Remove from search engine if enabled.
1255
        if (api_get_setting('search_enabled') == 'true') {
1256
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1257
            $sql = 'SELECT * FROM %s 
1258
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1259
                    LIMIT 1';
1260
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1261
            $res = Database::query($sql);
1262
            if (Database::num_rows($res) > 0) {
1263
                $row2 = Database::fetch_array($res);
1264
                $di = new ChamiloIndexer();
1265
                $di->remove_document($row2['search_did']);
1266
            }
1267
            $sql = 'DELETE FROM %s 
1268
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1269
                    LIMIT 1';
1270
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1271
            Database::query($sql);
1272
        }
1273
    }
1274
1275
    /**
1276
     * Updates an item's content in place.
1277
     *
1278
     * @param int    $id               Element ID
1279
     * @param int    $parent           Parent item ID
1280
     * @param int    $previous         Previous item ID
1281
     * @param string $title            Item title
1282
     * @param string $description      Item description
1283
     * @param string $prerequisites    Prerequisites (optional)
1284
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1285
     * @param int    $max_time_allowed
1286
     * @param string $url
1287
     *
1288
     * @return bool True on success, false on error
1289
     */
1290
    public function edit_item(
1291
        $id,
1292
        $parent,
1293
        $previous,
1294
        $title,
1295
        $description,
1296
        $prerequisites = '0',
1297
        $audio = [],
1298
        $max_time_allowed = 0,
1299
        $url = ''
1300
    ) {
1301
        $course_id = api_get_course_int_id();
1302
        $_course = api_get_course_info();
1303
1304
        if ($this->debug > 0) {
1305
            error_log('In learnpath::edit_item()', 0);
1306
        }
1307
        if (empty($max_time_allowed)) {
1308
            $max_time_allowed = 0;
1309
        }
1310
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1311
            return false;
1312
        }
1313
1314
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1315
        $sql = "SELECT * FROM $tbl_lp_item 
1316
                WHERE iid = $id";
1317
        $res_select = Database::query($sql);
1318
        $row_select = Database::fetch_array($res_select);
1319
        $audio_update_sql = '';
1320
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1321
            // Create the audio folder if it does not exist yet.
1322
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1323
            if (!is_dir($filepath.'audio')) {
1324
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1325
                $audio_id = add_document(
1326
                    $_course,
1327
                    '/audio',
1328
                    'folder',
1329
                    0,
1330
                    'audio'
1331
                );
1332
                api_item_property_update(
1333
                    $_course,
1334
                    TOOL_DOCUMENT,
1335
                    $audio_id,
1336
                    'FolderCreated',
1337
                    api_get_user_id(),
1338
                    null,
1339
                    null,
1340
                    null,
1341
                    null,
1342
                    api_get_session_id()
1343
                );
1344
                api_item_property_update(
1345
                    $_course,
1346
                    TOOL_DOCUMENT,
1347
                    $audio_id,
1348
                    'invisible',
1349
                    api_get_user_id(),
1350
                    null,
1351
                    null,
1352
                    null,
1353
                    null,
1354
                    api_get_session_id()
1355
                );
1356
            }
1357
1358
            // Upload file in documents.
1359
            $pi = pathinfo($audio['name']);
1360
            if ($pi['extension'] == 'mp3') {
1361
                $c_det = api_get_course_info($this->cc);
1362
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1363
                $path = handle_uploaded_document(
1364
                    $c_det,
1365
                    $audio,
1366
                    $bp,
1367
                    '/audio',
1368
                    api_get_user_id(),
1369
                    0,
1370
                    null,
1371
                    0,
1372
                    'rename',
1373
                    false,
1374
                    0
1375
                );
1376
                $path = substr($path, 7);
1377
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1378
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1379
            }
1380
        }
1381
1382
        $same_parent = ($row_select['parent_item_id'] == $parent) ? true : false;
1383
        $same_previous = ($row_select['previous_item_id'] == $previous) ? true : false;
1384
1385
        // TODO: htmlspecialchars to be checked for encoding related problems.
1386
        if ($same_parent && $same_previous) {
1387
            // Only update title and description.
1388
            $sql = "UPDATE $tbl_lp_item
1389
                    SET title = '".Database::escape_string($title)."',
1390
                        prerequisite = '".$prerequisites."',
1391
                        description = '".Database::escape_string($description)."'
1392
                        ".$audio_update_sql.",
1393
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1394
                    WHERE iid = $id";
1395
            Database::query($sql);
1396
        } else {
1397
            $old_parent = $row_select['parent_item_id'];
1398
            $old_previous = $row_select['previous_item_id'];
1399
            $old_next = $row_select['next_item_id'];
1400
            $old_order = $row_select['display_order'];
1401
            $old_prerequisite = $row_select['prerequisite'];
1402
            $old_max_time_allowed = $row_select['max_time_allowed'];
1403
1404
            /* BEGIN -- virtually remove the current item id */
1405
            /* for the next and previous item it is like the current item doesn't exist anymore */
1406
            if ($old_previous != 0) {
1407
                // Next
1408
                $sql = "UPDATE $tbl_lp_item
1409
                        SET next_item_id = $old_next
1410
                        WHERE iid = $old_previous";
1411
                Database::query($sql);
1412
            }
1413
1414
            if ($old_next != 0) {
1415
                // Previous
1416
                $sql = "UPDATE $tbl_lp_item
1417
                        SET previous_item_id = $old_previous
1418
                        WHERE iid = $old_next";
1419
                Database::query($sql);
1420
            }
1421
1422
            // display_order - 1 for every item with a display_order
1423
            // bigger then the display_order of the current item.
1424
            $sql = "UPDATE $tbl_lp_item
1425
                    SET display_order = display_order - 1
1426
                    WHERE
1427
                        c_id = $course_id AND
1428
                        display_order > $old_order AND
1429
                        lp_id = ".$this->lp_id." AND
1430
                        parent_item_id = $old_parent";
1431
            Database::query($sql);
1432
            /* END -- virtually remove the current item id */
1433
1434
            /* BEGIN -- update the current item id to his new location */
1435
            if ($previous == 0) {
1436
                // Select the data of the item that should come after the current item.
1437
                $sql = "SELECT id, display_order
1438
                        FROM $tbl_lp_item
1439
                        WHERE
1440
                            c_id = $course_id AND
1441
                            lp_id = ".$this->lp_id." AND
1442
                            parent_item_id = $parent AND
1443
                            previous_item_id = $previous";
1444
                $res_select_old = Database::query($sql);
1445
                $row_select_old = Database::fetch_array($res_select_old);
1446
1447
                // If the new parent didn't have children before.
1448
                if (Database::num_rows($res_select_old) == 0) {
1449
                    $new_next = 0;
1450
                    $new_order = 1;
1451
                } else {
1452
                    $new_next = $row_select_old['id'];
1453
                    $new_order = $row_select_old['display_order'];
1454
                }
1455
            } else {
1456
                // Select the data of the item that should come before the current item.
1457
                $sql = "SELECT next_item_id, display_order
1458
                        FROM $tbl_lp_item
1459
                        WHERE iid = $previous";
1460
                $res_select_old = Database::query($sql);
1461
                $row_select_old = Database::fetch_array($res_select_old);
1462
                $new_next = $row_select_old['next_item_id'];
1463
                $new_order = $row_select_old['display_order'] + 1;
1464
            }
1465
1466
            // TODO: htmlspecialchars to be checked for encoding related problems.
1467
            // Update the current item with the new data.
1468
            $sql = "UPDATE $tbl_lp_item
1469
                    SET
1470
                        title = '".Database::escape_string($title)."',
1471
                        description = '".Database::escape_string($description)."',
1472
                        parent_item_id = $parent,
1473
                        previous_item_id = $previous,
1474
                        next_item_id = $new_next,
1475
                        display_order = $new_order
1476
                        $audio_update_sql
1477
                    WHERE iid = $id";
1478
            Database::query($sql);
1479
1480
            if ($previous != 0) {
1481
                // Update the previous item's next_item_id.
1482
                $sql = "UPDATE $tbl_lp_item
1483
                        SET next_item_id = $id
1484
                        WHERE iid = $previous";
1485
                Database::query($sql);
1486
            }
1487
1488
            if ($new_next != 0) {
1489
                // Update the next item's previous_item_id.
1490
                $sql = "UPDATE $tbl_lp_item
1491
                        SET previous_item_id = $id
1492
                        WHERE iid = $new_next";
1493
                Database::query($sql);
1494
            }
1495
1496
            if ($old_prerequisite != $prerequisites) {
1497
                $sql = "UPDATE $tbl_lp_item
1498
                        SET prerequisite = '$prerequisites'
1499
                        WHERE iid = $id";
1500
                Database::query($sql);
1501
            }
1502
1503
            if ($old_max_time_allowed != $max_time_allowed) {
1504
                // update max time allowed
1505
                $sql = "UPDATE $tbl_lp_item
1506
                        SET max_time_allowed = $max_time_allowed
1507
                        WHERE iid = $id";
1508
                Database::query($sql);
1509
            }
1510
1511
            // Update all the items with the same or a bigger display_order than the current item.
1512
            $sql = "UPDATE $tbl_lp_item
1513
                    SET display_order = display_order + 1
1514
                    WHERE
1515
                       c_id = $course_id AND
1516
                       lp_id = ".$this->get_id()." AND
1517
                       iid <> $id AND
1518
                       parent_item_id = $parent AND
1519
                       display_order >= $new_order";
1520
            Database::query($sql);
1521
        }
1522
1523
        if ($row_select['item_type'] == 'link') {
1524
            $link = new Link();
1525
            $linkId = $row_select['path'];
1526
            $link->updateLink($linkId, $url);
1527
        }
1528
    }
1529
1530
    /**
1531
     * Updates an item's prereq in place.
1532
     *
1533
     * @param int    $id              Element ID
1534
     * @param string $prerequisite_id Prerequisite Element ID
1535
     * @param int    $mastery_score   Prerequisite min score
1536
     * @param int    $max_score       Prerequisite max score
1537
     *
1538
     * @return bool True on success, false on error
1539
     */
1540
    public function edit_item_prereq(
1541
        $id,
1542
        $prerequisite_id,
1543
        $mastery_score = 0,
1544
        $max_score = 100
1545
    ) {
1546
        $course_id = api_get_course_int_id();
1547
        if ($this->debug > 0) {
1548
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1549
        }
1550
1551
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1552
            return false;
1553
        }
1554
1555
        $prerequisite_id = intval($prerequisite_id);
1556
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1557
1558
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1559
            $mastery_score = 0;
1560
        }
1561
1562
        if (!is_numeric($max_score) || $max_score < 0) {
1563
            $max_score = 100;
1564
        }
1565
1566
        /*if ($mastery_score > $max_score) {
1567
            $max_score = $mastery_score;
1568
        }*/
1569
1570
        if (!is_numeric($prerequisite_id)) {
1571
            $prerequisite_id = 'NULL';
1572
        }
1573
1574
        $mastery_score = floatval($mastery_score);
1575
        $max_score = floatval($max_score);
1576
1577
        $sql = " UPDATE $tbl_lp_item
1578
                 SET
1579
                    prerequisite = $prerequisite_id ,
1580
                    prerequisite_min_score = $mastery_score ,
1581
                    prerequisite_max_score = $max_score
1582
                 WHERE iid = $id";
1583
        Database::query($sql);
1584
        // TODO: Update the item object (can be ignored for now because refreshed).
1585
        return true;
1586
    }
1587
1588
    /**
1589
     * Gets all the chapters belonging to the same parent as the item/chapter given
1590
     * Can also be called as abstract method.
1591
     *
1592
     * @param int $id Item ID
1593
     *
1594
     * @return array A list of all the "brother items" (or an empty array on failure)
1595
     */
1596
    public function getSiblingDirectories($id)
1597
    {
1598
        $course_id = api_get_course_int_id();
1599
        if ($this->debug > 0) {
1600
            error_log('In learnpath::getSiblingDirectories()', 0);
1601
        }
1602
1603
        if (empty($id) || $id != strval(intval($id))) {
1604
            return [];
1605
        }
1606
1607
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1608
        $sql_parent = "SELECT * FROM $lp_item
1609
                       WHERE iid = $id AND item_type='dir'";
1610
        $res_parent = Database::query($sql_parent);
1611
        if (Database::num_rows($res_parent) > 0) {
1612
            $row_parent = Database::fetch_array($res_parent);
1613
            $parent = $row_parent['parent_item_id'];
1614
            $sql = "SELECT * FROM $lp_item
1615
                    WHERE
1616
                        parent_item_id = $parent AND
1617
                        iid = $id AND
1618
                        item_type='dir'
1619
                    ORDER BY display_order";
1620
            $res_bros = Database::query($sql);
1621
1622
            $list = [];
1623
            while ($row_bro = Database::fetch_array($res_bros)) {
1624
                $list[] = $row_bro;
1625
            }
1626
1627
            return $list;
1628
        }
1629
1630
        return [];
1631
    }
1632
1633
    /**
1634
     * Gets all the items belonging to the same parent as the item given
1635
     * Can also be called as abstract method.
1636
     *
1637
     * @param int $id Item ID
1638
     *
1639
     * @return array A list of all the "brother items" (or an empty array on failure)
1640
     */
1641
    public function get_brother_items($id)
1642
    {
1643
        $course_id = api_get_course_int_id();
1644
        if ($this->debug > 0) {
1645
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1646
        }
1647
1648
        if (empty($id) || $id != strval(intval($id))) {
1649
            return [];
1650
        }
1651
1652
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1653
        $sql_parent = "SELECT * FROM $lp_item 
1654
                       WHERE iid = $id";
1655
        $res_parent = Database::query($sql_parent);
1656
        if (Database::num_rows($res_parent) > 0) {
1657
            $row_parent = Database::fetch_array($res_parent);
1658
            $parent = $row_parent['parent_item_id'];
1659
            $sql = "SELECT * FROM $lp_item 
1660
                    WHERE c_id = $course_id AND parent_item_id = $parent
1661
                    ORDER BY display_order";
1662
            $res_bros = Database::query($sql);
1663
            $list = [];
1664
            while ($row_bro = Database::fetch_array($res_bros)) {
1665
                $list[] = $row_bro;
1666
            }
1667
1668
            return $list;
1669
        }
1670
1671
        return [];
1672
    }
1673
1674
    /**
1675
     * Get the specific prefix index terms of this learning path.
1676
     *
1677
     * @param string $prefix
1678
     *
1679
     * @return array Array of terms
1680
     */
1681
    public function get_common_index_terms_by_prefix($prefix)
1682
    {
1683
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1684
        $terms = get_specific_field_values_list_by_prefix(
1685
            $prefix,
1686
            $this->cc,
1687
            TOOL_LEARNPATH,
1688
            $this->lp_id
1689
        );
1690
        $prefix_terms = [];
1691
        if (!empty($terms)) {
1692
            foreach ($terms as $term) {
1693
                $prefix_terms[] = $term['value'];
1694
            }
1695
        }
1696
1697
        return $prefix_terms;
1698
    }
1699
1700
    /**
1701
     * Gets the number of items currently completed.
1702
     *
1703
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1704
     *
1705
     * @return int The number of items currently completed
1706
     */
1707
    public function get_complete_items_count($failedStatusException = false)
1708
    {
1709
        if ($this->debug > 0) {
1710
            error_log('In learnpath::get_complete_items_count()', 0);
1711
        }
1712
        $i = 0;
1713
        $completedStatusList = [
1714
            'completed',
1715
            'passed',
1716
            'succeeded',
1717
            'browsed',
1718
        ];
1719
1720
        if (!$failedStatusException) {
1721
            $completedStatusList[] = 'failed';
1722
        }
1723
1724
        foreach ($this->items as $id => $dummy) {
1725
            // Trying failed and browsed considered "progressed" as well.
1726
            if ($this->items[$id]->status_is($completedStatusList) &&
1727
                $this->items[$id]->get_type() != 'dir'
1728
            ) {
1729
                $i++;
1730
            }
1731
        }
1732
1733
        return $i;
1734
    }
1735
1736
    /**
1737
     * Gets the current item ID.
1738
     *
1739
     * @return int The current learnpath item id
1740
     */
1741
    public function get_current_item_id()
1742
    {
1743
        $current = 0;
1744
        if ($this->debug > 0) {
1745
            error_log('In learnpath::get_current_item_id()', 0);
1746
        }
1747
        if (!empty($this->current)) {
1748
            $current = $this->current;
1749
        }
1750
        if ($this->debug > 2) {
1751
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1752
        }
1753
1754
        return $current;
1755
    }
1756
1757
    /**
1758
     * Force to get the first learnpath item id.
1759
     *
1760
     * @return int The current learnpath item id
1761
     */
1762
    public function get_first_item_id()
1763
    {
1764
        $current = 0;
1765
        if (is_array($this->ordered_items)) {
1766
            $current = $this->ordered_items[0];
1767
        }
1768
1769
        return $current;
1770
    }
1771
1772
    /**
1773
     * Gets the total number of items available for viewing in this SCORM.
1774
     *
1775
     * @return int The total number of items
1776
     */
1777
    public function get_total_items_count()
1778
    {
1779
        if ($this->debug > 0) {
1780
            error_log('In learnpath::get_total_items_count()', 0);
1781
        }
1782
1783
        return count($this->items);
1784
    }
1785
1786
    /**
1787
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1788
     *
1789
     * @return int The total no-chapters number of items
1790
     */
1791
    public function getTotalItemsCountWithoutDirs()
1792
    {
1793
        if ($this->debug > 0) {
1794
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1795
        }
1796
        $total = 0;
1797
        $typeListNotToCount = self::getChapterTypes();
1798
        foreach ($this->items as $temp2) {
1799
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1800
                $total++;
1801
            }
1802
        }
1803
1804
        return $total;
1805
    }
1806
1807
    /**
1808
     *  Sets the first element URL.
1809
     */
1810
    public function first()
1811
    {
1812
        if ($this->debug > 0) {
1813
            error_log('In learnpath::first()', 0);
1814
            error_log('$this->last_item_seen '.$this->last_item_seen);
1815
        }
1816
1817
        // Test if the last_item_seen exists and is not a dir.
1818
        if (count($this->ordered_items) == 0) {
1819
            $this->index = 0;
1820
        }
1821
1822
        if (!empty($this->last_item_seen) &&
1823
            !empty($this->items[$this->last_item_seen]) &&
1824
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1825
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1826
            //&& !$this->items[$this->last_item_seen]->is_done()
1827
        ) {
1828
            if ($this->debug > 2) {
1829
                error_log('In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.$this->items[$this->last_item_seen]->get_type(), 0);
1830
            }
1831
            $index = -1;
1832
            foreach ($this->ordered_items as $myindex => $item_id) {
1833
                if ($item_id == $this->last_item_seen) {
1834
                    $index = $myindex;
1835
                    break;
1836
                }
1837
            }
1838
            if ($index == -1) {
1839
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1840
                if ($this->debug > 2) {
1841
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1842
                }
1843
1844
                return false;
1845
            } else {
1846
                $this->last = $this->last_item_seen;
1847
                $this->current = $this->last_item_seen;
1848
                $this->index = $index;
1849
            }
1850
        } else {
1851
            if ($this->debug > 2) {
1852
                error_log('In learnpath::first() - No last item seen', 0);
1853
            }
1854
            $index = 0;
1855
            // Loop through all ordered items and stop at the first item that is
1856
            // not a directory *and* that has not been completed yet.
1857
            while (!empty($this->ordered_items[$index]) &&
1858
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1859
                (
1860
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1861
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1862
                ) && $index < $this->max_ordered_items) {
1863
                $index++;
1864
            }
1865
1866
            $this->last = $this->current;
1867
            // current is
1868
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1869
            $this->index = $index;
1870
            if ($this->debug > 2) {
1871
                error_log('$index '.$index);
1872
                error_log('In learnpath::first() - No last item seen');
1873
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1874
            }
1875
        }
1876
        if ($this->debug > 2) {
1877
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1878
        }
1879
    }
1880
1881
    /**
1882
     * Gets the information about an item in a format usable as JavaScript to update
1883
     * the JS API by just printing this content into the <head> section of the message frame.
1884
     *
1885
     * @param int $item_id
1886
     *
1887
     * @return string
1888
     */
1889
    public function get_js_info($item_id = 0)
1890
    {
1891
        if ($this->debug > 0) {
1892
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1893
        }
1894
1895
        $info = '';
1896
        $item_id = intval($item_id);
1897
1898
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1899
            //if item is defined, return values from DB
1900
            $oItem = $this->items[$item_id];
1901
            $info .= '<script language="javascript">';
1902
            $info .= "top.set_score(".$oItem->get_score().");\n";
1903
            $info .= "top.set_max(".$oItem->get_max().");\n";
1904
            $info .= "top.set_min(".$oItem->get_min().");\n";
1905
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1906
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1907
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1908
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1909
            $info .= "top.set_flag_synchronized();";
1910
            $info .= '</script>';
1911
            if ($this->debug > 2) {
1912
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1913
            }
1914
1915
            return $info;
1916
        } else {
1917
            // If item_id is empty, just update to default SCORM data.
1918
            $info .= '<script language="javascript">';
1919
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1920
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1921
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1922
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1923
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1924
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1925
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1926
            $info .= "top.set_flag_synchronized();";
1927
            $info .= '</script>';
1928
            if ($this->debug > 2) {
1929
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1930
            }
1931
1932
            return $info;
1933
        }
1934
    }
1935
1936
    /**
1937
     * Gets the js library from the database.
1938
     *
1939
     * @return string The name of the javascript library to be used
1940
     */
1941
    public function get_js_lib()
1942
    {
1943
        $lib = '';
1944
        if (!empty($this->js_lib)) {
1945
            $lib = $this->js_lib;
1946
        }
1947
1948
        return $lib;
1949
    }
1950
1951
    /**
1952
     * Gets the learnpath database ID.
1953
     *
1954
     * @return int Learnpath ID in the lp table
1955
     */
1956
    public function get_id()
1957
    {
1958
        if (!empty($this->lp_id)) {
1959
            return $this->lp_id;
1960
        } else {
1961
            return 0;
1962
        }
1963
    }
1964
1965
    /**
1966
     * Gets the last element URL.
1967
     *
1968
     * @return string URL to load into the viewer
1969
     */
1970
    public function get_last()
1971
    {
1972
        if ($this->debug > 0) {
1973
            error_log('In learnpath::get_last()', 0);
1974
        }
1975
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1976
        if (count($this->ordered_items) > 0) {
1977
            $this->index = count($this->ordered_items) - 1;
1978
1979
            return $this->ordered_items[$this->index];
1980
        }
1981
1982
        return false;
1983
    }
1984
1985
    /**
1986
     * Gets the navigation bar for the learnpath display screen.
1987
     *
1988
     * @return string The HTML string to use as a navigation bar
1989
     */
1990
    public function get_navigation_bar($idBar = null, $display = null)
1991
    {
1992
        if ($this->debug > 0) {
1993
            error_log('In learnpath::get_navigation_bar()', 0);
1994
        }
1995
        if (empty($idBar)) {
1996
            $idBar = 'control-top';
1997
        }
1998
        $lpId = $this->lp_id;
1999
        $mycurrentitemid = $this->get_current_item_id();
2000
2001
        $reportingText = get_lang('Reporting');
2002
        $previousText = get_lang('ScormPrevious');
2003
        $nextText = get_lang('ScormNext');
2004
        $fullScreenText = get_lang('ScormExitFullScreen');
2005
2006
        $settings = api_get_configuration_value('lp_view_settings');
2007
        $display = isset($settings['display']) ? $settings['display'] : false;
2008
        $reportingIcon = '
2009
            <a class="icon-toolbar" 
2010
                id="stats_link"
2011
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
2012
                onclick="window.parent.API.save_asset(); return true;" 
2013
                target="content_name" title="'.$reportingText.'">
2014
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2015
            </a>';
2016
2017
        if (!empty($display)) {
2018
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2019
            if ($showReporting == false) {
2020
                $reportingIcon = '';
2021
            }
2022
        }
2023
2024
        $previousIcon = '
2025
            <a class="icon-toolbar" id="scorm-previous" href="#" 
2026
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2027
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2028
            </a>';
2029
2030
        $nextIcon = '
2031
            <a class="icon-toolbar" id="scorm-next" href="#" 
2032
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2033
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2034
            </a>';
2035
2036
        if ($this->mode == 'fullscreen') {
2037
            $navbar = '
2038
                  <span id="'.$idBar.'" class="buttons">
2039
                    '.$reportingIcon.'
2040
                    '.$previousIcon.'                    
2041
                    '.$nextIcon.'
2042
                    <a class="icon-toolbar" id="view-embedded" 
2043
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2044
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2045
                    </a>
2046
                  </span>';
2047
        } else {
2048
            $navbar = '
2049
            <span id="'.$idBar.'" class="buttons text-right">
2050
                '.$reportingIcon.'
2051
                '.$previousIcon.'
2052
                '.$nextIcon.'               
2053
            </span>';
2054
        }
2055
2056
        return $navbar;
2057
    }
2058
2059
    /**
2060
     * Gets the next resource in queue (url).
2061
     *
2062
     * @return string URL to load into the viewer
2063
     */
2064
    public function get_next_index()
2065
    {
2066
        if ($this->debug > 0) {
2067
            error_log('In learnpath::get_next_index()', 0);
2068
        }
2069
        // TODO
2070
        $index = $this->index;
2071
        $index++;
2072
        if ($this->debug > 2) {
2073
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2074
        }
2075
        while (
2076
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2077
            $index < $this->max_ordered_items
2078
        ) {
2079
            $index++;
2080
            if ($index == $this->max_ordered_items) {
2081
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2082
                    return $this->index;
2083
                } else {
2084
                    return $index;
2085
                }
2086
            }
2087
        }
2088
        if (empty($this->ordered_items[$index])) {
2089
            return $this->index;
2090
        }
2091
        if ($this->debug > 2) {
2092
            error_log('index is now '.$index, 0);
2093
        }
2094
2095
        return $index;
2096
    }
2097
2098
    /**
2099
     * Gets item_id for the next element.
2100
     *
2101
     * @return int Next item (DB) ID
2102
     */
2103
    public function get_next_item_id()
2104
    {
2105
        if ($this->debug > 0) {
2106
            error_log('In learnpath::get_next_item_id()', 0);
2107
        }
2108
        $new_index = $this->get_next_index();
2109
        if (!empty($new_index)) {
2110
            if (isset($this->ordered_items[$new_index])) {
2111
                if ($this->debug > 2) {
2112
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2113
                }
2114
2115
                return $this->ordered_items[$new_index];
2116
            }
2117
        }
2118
        if ($this->debug > 2) {
2119
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2120
        }
2121
2122
        return 0;
2123
    }
2124
2125
    /**
2126
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2127
     *
2128
     * Generally, the package provided is in the form of a zip file, so the function
2129
     * has been written to test a zip file. If not a zip, the function will return the
2130
     * default return value: ''
2131
     *
2132
     * @param string $file_path the path to the file
2133
     * @param string $file_name the original name of the file
2134
     *
2135
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2136
     */
2137
    public static function get_package_type($file_path, $file_name)
2138
    {
2139
        // Get name of the zip file without the extension.
2140
        $file_info = pathinfo($file_name);
2141
        $extension = $file_info['extension']; // Extension only.
2142
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2143
                'dll',
2144
                'exe',
2145
            ])) {
2146
            return 'oogie';
2147
        }
2148
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2149
                'dll',
2150
                'exe',
2151
            ])) {
2152
            return 'woogie';
2153
        }
2154
2155
        $zipFile = new PclZip($file_path);
2156
        // Check the zip content (real size and file extension).
2157
        $zipContentArray = $zipFile->listContent();
2158
        $package_type = '';
2159
        $manifest = '';
2160
        $aicc_match_crs = 0;
2161
        $aicc_match_au = 0;
2162
        $aicc_match_des = 0;
2163
        $aicc_match_cst = 0;
2164
2165
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2166
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2167
            foreach ($zipContentArray as $thisContent) {
2168
                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...
2169
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2170
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2171
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2172
                    $package_type = 'scorm';
2173
                    break; // Exit the foreach loop.
2174
                } elseif (
2175
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2176
                    in_array(
2177
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2178
                        ['crs', 'au', 'des', 'cst']
2179
                    )
2180
                ) {
2181
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2182
                    switch ($ext) {
2183
                        case 'crs':
2184
                            $aicc_match_crs = 1;
2185
                            break;
2186
                        case 'au':
2187
                            $aicc_match_au = 1;
2188
                            break;
2189
                        case 'des':
2190
                            $aicc_match_des = 1;
2191
                            break;
2192
                        case 'cst':
2193
                            $aicc_match_cst = 1;
2194
                            break;
2195
                        default:
2196
                            break;
2197
                    }
2198
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2199
                } else {
2200
                    $package_type = '';
2201
                }
2202
            }
2203
        }
2204
2205
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2206
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2207
            $package_type = 'aicc';
2208
        }
2209
2210
        // Try with chamilo course builder
2211
        if (empty($package_type)) {
2212
            $package_type = 'chamilo';
2213
        }
2214
2215
        return $package_type;
2216
    }
2217
2218
    /**
2219
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2220
     *
2221
     * @return string URL to load into the viewer
2222
     */
2223
    public function get_previous_index()
2224
    {
2225
        if ($this->debug > 0) {
2226
            error_log('In learnpath::get_previous_index()', 0);
2227
        }
2228
        $index = $this->index;
2229
        if (isset($this->ordered_items[$index - 1])) {
2230
            $index--;
2231
            while (isset($this->ordered_items[$index]) &&
2232
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2233
            ) {
2234
                $index--;
2235
                if ($index < 0) {
2236
                    return $this->index;
2237
                }
2238
            }
2239
        } else {
2240
            if ($this->debug > 2) {
2241
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2242
            }
2243
            // There is no previous item.
2244
        }
2245
2246
        return $index;
2247
    }
2248
2249
    /**
2250
     * Gets item_id for the next element.
2251
     *
2252
     * @return int Previous item (DB) ID
2253
     */
2254
    public function get_previous_item_id()
2255
    {
2256
        if ($this->debug > 0) {
2257
            error_log('In learnpath::get_previous_item_id()', 0);
2258
        }
2259
        $new_index = $this->get_previous_index();
2260
2261
        return $this->ordered_items[$new_index];
2262
    }
2263
2264
    /**
2265
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2266
     *
2267
     * @param int    $lpItemId
2268
     * @param string $autostart
2269
     *
2270
     * @return string The mediaplayer HTML
2271
     */
2272
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2273
    {
2274
        $course_id = api_get_course_int_id();
2275
        $_course = api_get_course_info();
2276
        if (empty($_course)) {
2277
            return '';
2278
        }
2279
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2280
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2281
        $lpItemId = (int) $lpItemId;
2282
2283
        // Getting all the information about the item.
2284
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2285
                INNER JOIN $tbl_lp_item_view as lp_view
2286
                ON (lpi.iid = lp_view.lp_item_id)
2287
                WHERE
2288
                    lpi.iid = $lpItemId AND
2289
                    lp_view.c_id = $course_id";
2290
        $result = Database::query($sql);
2291
        $row = Database::fetch_assoc($result);
2292
        $output = '';
2293
2294
        if (!empty($row['audio'])) {
2295
            $list = $_SESSION['oLP']->get_toc();
2296
            $type_quiz = false;
2297
2298
            foreach ($list as $toc) {
2299
                if ($toc['id'] == $_SESSION['oLP']->current && $toc['type'] == 'quiz') {
2300
                    $type_quiz = true;
2301
                }
2302
            }
2303
2304
            if ($type_quiz) {
2305
                if ($_SESSION['oLP']->prevent_reinit == 1) {
2306
                    $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2307
                } else {
2308
                    $autostart_audio = $autostart;
2309
                }
2310
            } else {
2311
                $autostart_audio = 'true';
2312
            }
2313
2314
            $courseInfo = api_get_course_info();
2315
            $audio = $row['audio'];
2316
2317
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2318
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2319
2320
            if (!file_exists($file)) {
2321
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2322
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2323
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2324
            }
2325
2326
            $player = Display::getMediaPlayer(
2327
                $file,
2328
                [
2329
                    'id' => 'lp_audio_media_player',
2330
                    'url' => $url,
2331
                    'autoplay' => $autostart_audio,
2332
                    'width' => '100%',
2333
                ]
2334
            );
2335
2336
            // The mp3 player.
2337
            $output = '<div id="container">';
2338
            $output .= $player;
2339
            $output .= '</div>';
2340
        }
2341
2342
        return $output;
2343
    }
2344
2345
    /**
2346
     * @param int   $studentId
2347
     * @param int   $prerequisite
2348
     * @param array $courseInfo
2349
     * @param int   $sessionId
2350
     *
2351
     * @return bool
2352
     */
2353
    public static function isBlockedByPrerequisite(
2354
        $studentId,
2355
        $prerequisite,
2356
        $courseInfo,
2357
        $sessionId
2358
    ) {
2359
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2360
        if ($allow) {
2361
            if (api_is_allowed_to_edit() ||
2362
                api_is_platform_admin(true) ||
2363
                api_is_drh() ||
2364
                api_is_coach($sessionId, $courseInfo['real_id'], false)
2365
            ) {
2366
                return false;
2367
            }
2368
        }
2369
2370
        $isBlocked = false;
2371
2372
        if (!empty($prerequisite)) {
2373
            $progress = self::getProgress(
2374
                $prerequisite,
2375
                $studentId,
2376
                $courseInfo['real_id'],
2377
                $sessionId
2378
            );
2379
            if ($progress < 100) {
2380
                $isBlocked = true;
2381
            }
2382
        }
2383
2384
        return $isBlocked;
2385
    }
2386
2387
    /**
2388
     * Checks if the learning path is visible for student after the progress
2389
     * of its prerequisite is completed, considering the time availability and
2390
     * the LP visibility.
2391
     *
2392
     * @param int  $lp_id
2393
     * @param int  $student_id
2394
     * @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...
2395
     * @param int  $sessionId
2396
     *
2397
     * @return bool
2398
     */
2399
    public static function is_lp_visible_for_student(
2400
        $lp_id,
2401
        $student_id,
2402
        $courseCode = null,
2403
        $sessionId = 0
2404
    ) {
2405
        $courseInfo = api_get_course_info($courseCode);
2406
        $lp_id = (int) $lp_id;
2407
        $sessionId = (int) $sessionId;
2408
2409
        if (empty($courseInfo)) {
2410
            return false;
2411
        }
2412
2413
        if (empty($sessionId)) {
2414
            $sessionId = api_get_session_id();
2415
        }
2416
2417
        $itemInfo = api_get_item_property_info(
2418
            $courseInfo['real_id'],
2419
            TOOL_LEARNPATH,
2420
            $lp_id,
2421
            $sessionId
2422
        );
2423
2424
        // If the item was deleted.
2425
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2426
            return false;
2427
        }
2428
2429
        // @todo remove this query and load the row info as a parameter
2430
        $table = Database::get_course_table(TABLE_LP_MAIN);
2431
        // Get current prerequisite
2432
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2433
                FROM $table
2434
                WHERE iid = $lp_id";
2435
        $rs = Database::query($sql);
2436
        $now = time();
2437
        if (Database::num_rows($rs) > 0) {
2438
            $row = Database::fetch_array($rs, 'ASSOC');
2439
            $prerequisite = $row['prerequisite'];
2440
            $is_visible = true;
2441
2442
            $isBlocked = self::isBlockedByPrerequisite(
2443
                $student_id,
2444
                $prerequisite,
2445
                $courseInfo,
2446
                $sessionId
2447
            );
2448
2449
            if ($isBlocked) {
2450
                $is_visible = false;
2451
            }
2452
2453
            // Also check the time availability of the LP
2454
            if ($is_visible) {
2455
                // Adding visibility restrictions
2456
                if (!empty($row['publicated_on'])) {
2457
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2458
                        $is_visible = false;
2459
                    }
2460
                }
2461
                // Blocking empty start times see BT#2800
2462
                global $_custom;
2463
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2464
                    $_custom['lps_hidden_when_no_start_date']
2465
                ) {
2466
                    if (empty($row['publicated_on'])) {
2467
                        $is_visible = false;
2468
                    }
2469
                }
2470
2471
                if (!empty($row['expired_on'])) {
2472
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2473
                        $is_visible = false;
2474
                    }
2475
                }
2476
            }
2477
2478
            $subscriptionSettings = learnpath::getSubscriptionSettings();
2479
2480
            // Check if the subscription users/group to a LP is ON
2481
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2482
                $subscriptionSettings['allow_add_users_to_lp'] === true
2483
            ) {
2484
                // Try group
2485
                $is_visible = false;
2486
                // Checking only the user visibility
2487
                $userVisibility = api_get_item_visibility(
2488
                    $courseInfo,
2489
                    'learnpath',
2490
                    $row['id'],
2491
                    $sessionId,
2492
                    $student_id,
2493
                    'LearnpathSubscription'
2494
                );
2495
2496
                if ($userVisibility == 1) {
2497
                    $is_visible = true;
2498
                } else {
2499
                    $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2500
                    if (!empty($userGroups)) {
2501
                        foreach ($userGroups as $groupInfo) {
2502
                            $groupId = $groupInfo['iid'];
2503
                            $userVisibility = api_get_item_visibility(
2504
                                $courseInfo,
2505
                                'learnpath',
2506
                                $row['id'],
2507
                                $sessionId,
2508
                                null,
2509
                                'LearnpathSubscription',
2510
                                $groupId
2511
                            );
2512
2513
                            if ($userVisibility == 1) {
2514
                                $is_visible = true;
2515
                                break;
2516
                            }
2517
                        }
2518
                    }
2519
                }
2520
            }
2521
2522
            return $is_visible;
2523
        }
2524
2525
        return false;
2526
    }
2527
2528
    /**
2529
     * @param int $lpId
2530
     * @param int $userId
2531
     * @param int $courseId
2532
     * @param int $sessionId
2533
     *
2534
     * @return int
2535
     */
2536
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2537
    {
2538
        $lpId = (int) $lpId;
2539
        $userId = (int) $userId;
2540
        $courseId = (int) $courseId;
2541
        $sessionId = (int) $sessionId;
2542
        $progress = 0;
2543
2544
        $sessionCondition = api_get_session_condition($sessionId);
2545
        $table = Database::get_course_table(TABLE_LP_VIEW);
2546
        $sql = "SELECT * FROM $table
2547
                WHERE
2548
                    c_id = $courseId AND
2549
                    lp_id = $lpId AND
2550
                    user_id = $userId $sessionCondition ";
2551
        $res = Database::query($sql);
2552
        if (Database::num_rows($res) > 0) {
2553
            $row = Database:: fetch_array($res);
2554
            $progress = $row['progress'];
2555
        }
2556
2557
        return (int) $progress;
2558
    }
2559
2560
    /**
2561
     * Displays a progress bar
2562
     * completed so far.
2563
     *
2564
     * @param int    $percentage Progress value to display
2565
     * @param string $text_add   Text to display near the progress value
2566
     *
2567
     * @return string HTML string containing the progress bar
2568
     */
2569
    public static function get_progress_bar($percentage = -1, $text_add = '')
2570
    {
2571
        $text = $percentage.$text_add;
2572
        $output = '<div class="progress">
2573
            <div id="progress_bar_value" 
2574
                class="progress-bar progress-bar-success" role="progressbar" 
2575
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2576
            '.$text.'
2577
            </div>
2578
        </div>';
2579
2580
        return $output;
2581
    }
2582
2583
    /**
2584
     * @param string $mode can be '%' or 'abs'
2585
     *                     otherwise this value will be used $this->progress_bar_mode
2586
     *
2587
     * @return string
2588
     */
2589
    public function getProgressBar($mode = null)
2590
    {
2591
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2592
2593
        return self::get_progress_bar($percentage, $text_add);
2594
    }
2595
2596
    /**
2597
     * Gets the progress bar info to display inside the progress bar.
2598
     * Also used by scorm_api.php.
2599
     *
2600
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2601
     *                     we display a number of completed elements per total elements
2602
     * @param int    $add  Additional steps to fake as completed
2603
     *
2604
     * @return array Percentage or number and symbol (% or /xx)
2605
     */
2606
    public function get_progress_bar_text($mode = '', $add = 0)
2607
    {
2608
        if ($this->debug > 0) {
2609
            error_log('In learnpath::get_progress_bar_text()', 0);
2610
        }
2611
        if (empty($mode)) {
2612
            $mode = $this->progress_bar_mode;
2613
        }
2614
        $total_items = $this->getTotalItemsCountWithoutDirs();
2615
        if ($this->debug > 2) {
2616
            error_log('Total items available in this learnpath: '.$total_items, 0);
2617
        }
2618
        $completeItems = $this->get_complete_items_count();
2619
        if ($this->debug > 2) {
2620
            error_log('Items completed so far: '.$completeItems, 0);
2621
        }
2622
        if ($add != 0) {
2623
            $completeItems += $add;
2624
            if ($this->debug > 2) {
2625
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2626
            }
2627
        }
2628
        $text = '';
2629
        if ($completeItems > $total_items) {
2630
            $completeItems = $total_items;
2631
        }
2632
        $percentage = 0;
2633
        if ($mode == '%') {
2634
            if ($total_items > 0) {
2635
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2636
            } else {
2637
                $percentage = 0;
2638
            }
2639
            $percentage = number_format($percentage, 0);
2640
            $text = '%';
2641
        } elseif ($mode == 'abs') {
2642
            $percentage = $completeItems;
2643
            $text = '/'.$total_items;
2644
        }
2645
2646
        return [
2647
            $percentage,
2648
            $text,
2649
        ];
2650
    }
2651
2652
    /**
2653
     * Gets the progress bar mode.
2654
     *
2655
     * @return string The progress bar mode attribute
2656
     */
2657
    public function get_progress_bar_mode()
2658
    {
2659
        if ($this->debug > 0) {
2660
            error_log('In learnpath::get_progress_bar_mode()', 0);
2661
        }
2662
        if (!empty($this->progress_bar_mode)) {
2663
            return $this->progress_bar_mode;
2664
        } else {
2665
            return '%';
2666
        }
2667
    }
2668
2669
    /**
2670
     * Gets the learnpath theme (remote or local).
2671
     *
2672
     * @return string Learnpath theme
2673
     */
2674
    public function get_theme()
2675
    {
2676
        if ($this->debug > 0) {
2677
            error_log('In learnpath::get_theme()', 0);
2678
        }
2679
        if (!empty($this->theme)) {
2680
            return $this->theme;
2681
        } else {
2682
            return '';
2683
        }
2684
    }
2685
2686
    /**
2687
     * Gets the learnpath session id.
2688
     *
2689
     * @return int
2690
     */
2691
    public function get_lp_session_id()
2692
    {
2693
        if ($this->debug > 0) {
2694
            error_log('In learnpath::get_lp_session_id()', 0);
2695
        }
2696
        if (!empty($this->lp_session_id)) {
2697
            return (int) $this->lp_session_id;
2698
        } else {
2699
            return 0;
2700
        }
2701
    }
2702
2703
    /**
2704
     * Gets the learnpath image.
2705
     *
2706
     * @return string Web URL of the LP image
2707
     */
2708
    public function get_preview_image()
2709
    {
2710
        if ($this->debug > 0) {
2711
            error_log('In learnpath::get_preview_image()', 0);
2712
        }
2713
        if (!empty($this->preview_image)) {
2714
            return $this->preview_image;
2715
        } else {
2716
            return '';
2717
        }
2718
    }
2719
2720
    /**
2721
     * @param string $size
2722
     * @param string $path_type
2723
     *
2724
     * @return bool|string
2725
     */
2726
    public function get_preview_image_path($size = null, $path_type = 'web')
2727
    {
2728
        $preview_image = $this->get_preview_image();
2729
        if (isset($preview_image) && !empty($preview_image)) {
2730
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2731
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2732
2733
            if (isset($size)) {
2734
                $info = pathinfo($preview_image);
2735
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2736
2737
                if (file_exists($image_sys_path.$image_custom_size)) {
2738
                    if ($path_type == 'web') {
2739
                        return $image_path.$image_custom_size;
2740
                    } else {
2741
                        return $image_sys_path.$image_custom_size;
2742
                    }
2743
                }
2744
            } else {
2745
                if ($path_type == 'web') {
2746
                    return $image_path.$preview_image;
2747
                } else {
2748
                    return $image_sys_path.$preview_image;
2749
                }
2750
            }
2751
        }
2752
2753
        return false;
2754
    }
2755
2756
    /**
2757
     * Gets the learnpath author.
2758
     *
2759
     * @return string LP's author
2760
     */
2761
    public function get_author()
2762
    {
2763
        if ($this->debug > 0) {
2764
            error_log('In learnpath::get_author()', 0);
2765
        }
2766
        if (!empty($this->author)) {
2767
            return $this->author;
2768
        } else {
2769
            return '';
2770
        }
2771
    }
2772
2773
    /**
2774
     * Gets hide table of contents.
2775
     *
2776
     * @return int
2777
     */
2778
    public function getHideTableOfContents()
2779
    {
2780
        return (int) $this->hide_toc_frame;
2781
    }
2782
2783
    /**
2784
     * Generate a new prerequisites string for a given item. If this item was a sco and
2785
     * its prerequisites were strings (instead of IDs), then transform those strings into
2786
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2787
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2788
     * same rule as the scorm_export() method.
2789
     *
2790
     * @param int $item_id Item ID
2791
     *
2792
     * @return string Prerequisites string ready for the export as SCORM
2793
     */
2794
    public function get_scorm_prereq_string($item_id)
2795
    {
2796
        if ($this->debug > 0) {
2797
            error_log('In learnpath::get_scorm_prereq_string()');
2798
        }
2799
        if (!is_object($this->items[$item_id])) {
2800
            return false;
2801
        }
2802
        /** @var learnpathItem $oItem */
2803
        $oItem = $this->items[$item_id];
2804
        $prereq = $oItem->get_prereq_string();
2805
2806
        if (empty($prereq)) {
2807
            return '';
2808
        }
2809
        if (preg_match('/^\d+$/', $prereq) &&
2810
            isset($this->items[$prereq]) &&
2811
            is_object($this->items[$prereq])
2812
        ) {
2813
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2814
            // then simply return it (with the ITEM_ prefix).
2815
            //return 'ITEM_' . $prereq;
2816
            return $this->items[$prereq]->ref;
2817
        } else {
2818
            if (isset($this->refs_list[$prereq])) {
2819
                // It's a simple string item from which the ID can be found in the refs list,
2820
                // so we can transform it directly to an ID for export.
2821
                return $this->items[$this->refs_list[$prereq]]->ref;
2822
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2823
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2824
            } else {
2825
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2826
                // and replace them, one by one, by the internal IDs (chamilo db)
2827
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2828
                // by a space as well.
2829
                $find = [
2830
                    '&',
2831
                    '|',
2832
                    '~',
2833
                    '=',
2834
                    '<>',
2835
                    '{',
2836
                    '}',
2837
                    '*',
2838
                    '(',
2839
                    ')',
2840
                ];
2841
                $replace = [
2842
                    ' ',
2843
                    ' ',
2844
                    ' ',
2845
                    ' ',
2846
                    ' ',
2847
                    ' ',
2848
                    ' ',
2849
                    ' ',
2850
                    ' ',
2851
                    ' ',
2852
                ];
2853
                $prereq_mod = str_replace($find, $replace, $prereq);
2854
                $ids = explode(' ', $prereq_mod);
2855
                foreach ($ids as $id) {
2856
                    $id = trim($id);
2857
                    if (isset($this->refs_list[$id])) {
2858
                        $prereq = preg_replace(
2859
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2860
                            'ITEM_'.$this->refs_list[$id],
2861
                            $prereq
2862
                        );
2863
                    }
2864
                }
2865
2866
                return $prereq;
2867
            }
2868
        }
2869
    }
2870
2871
    /**
2872
     * Returns the XML DOM document's node.
2873
     *
2874
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2875
     * @param string   $id       The identifier to look for
2876
     *
2877
     * @return mixed The reference to the element found with that identifier. False if not found
2878
     */
2879
    public function get_scorm_xml_node(&$children, $id)
2880
    {
2881
        for ($i = 0; $i < $children->length; $i++) {
2882
            $item_temp = $children->item($i);
2883
            if ($item_temp->nodeName == 'item') {
2884
                if ($item_temp->getAttribute('identifier') == $id) {
2885
                    return $item_temp;
2886
                }
2887
            }
2888
            $subchildren = $item_temp->childNodes;
2889
            if ($subchildren && $subchildren->length > 0) {
2890
                $val = $this->get_scorm_xml_node($subchildren, $id);
2891
                if (is_object($val)) {
2892
                    return $val;
2893
                }
2894
            }
2895
        }
2896
2897
        return false;
2898
    }
2899
2900
    /**
2901
     * Gets the status list for all LP's items.
2902
     *
2903
     * @return array Array of [index] => [item ID => current status]
2904
     */
2905
    public function get_items_status_list()
2906
    {
2907
        if ($this->debug > 0) {
2908
            error_log('In learnpath::get_items_status_list()', 0);
2909
        }
2910
        $list = [];
2911
        foreach ($this->ordered_items as $item_id) {
2912
            $list[] = [
2913
                $item_id => $this->items[$item_id]->get_status(),
2914
            ];
2915
        }
2916
2917
        return $list;
2918
    }
2919
2920
    /**
2921
     * Return the number of interactions for the given learnpath Item View ID.
2922
     * This method can be used as static.
2923
     *
2924
     * @param int $lp_iv_id  Item View ID
2925
     * @param int $course_id course id
2926
     *
2927
     * @return int
2928
     */
2929
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2930
    {
2931
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2932
        $lp_iv_id = intval($lp_iv_id);
2933
        $course_id = intval($course_id);
2934
2935
        $sql = "SELECT count(*) FROM $table
2936
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2937
        $res = Database::query($sql);
2938
        $num = 0;
2939
        if (Database::num_rows($res)) {
2940
            $row = Database::fetch_array($res);
2941
            $num = $row[0];
2942
        }
2943
2944
        return $num;
2945
    }
2946
2947
    /**
2948
     * Return the interactions as an array for the given lp_iv_id.
2949
     * This method can be used as static.
2950
     *
2951
     * @param int $lp_iv_id Learnpath Item View ID
2952
     *
2953
     * @return array
2954
     *
2955
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2956
     */
2957
    public static function get_iv_interactions_array($lp_iv_id)
2958
    {
2959
        $course_id = api_get_course_int_id();
2960
        $list = [];
2961
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2962
2963
        if (empty($lp_iv_id)) {
2964
            return [];
2965
        }
2966
2967
        $sql = "SELECT * FROM $table
2968
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2969
                ORDER BY order_id ASC";
2970
        $res = Database::query($sql);
2971
        $num = Database::num_rows($res);
2972
        if ($num > 0) {
2973
            $list[] = [
2974
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2975
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2976
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2977
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2978
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2979
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2980
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2981
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2982
            ];
2983
            while ($row = Database::fetch_array($res)) {
2984
                $list[] = [
2985
                    'order_id' => ($row['order_id'] + 1),
2986
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2987
                    'type' => $row['interaction_type'],
2988
                    'time' => $row['completion_time'],
2989
                    //'correct_responses' => $row['correct_responses'],
2990
                    'correct_responses' => '', // Hide correct responses from students.
2991
                    'student_response' => $row['student_response'],
2992
                    'result' => $row['result'],
2993
                    'latency' => $row['latency'],
2994
                ];
2995
            }
2996
        }
2997
2998
        return $list;
2999
    }
3000
3001
    /**
3002
     * Return the number of objectives for the given learnpath Item View ID.
3003
     * This method can be used as static.
3004
     *
3005
     * @param int $lp_iv_id  Item View ID
3006
     * @param int $course_id Course ID
3007
     *
3008
     * @return int Number of objectives
3009
     */
3010
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3011
    {
3012
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3013
        $course_id = intval($course_id);
3014
        $lp_iv_id = intval($lp_iv_id);
3015
        $sql = "SELECT count(*) FROM $table
3016
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3017
        //@todo seems that this always returns 0
3018
        $res = Database::query($sql);
3019
        $num = 0;
3020
        if (Database::num_rows($res)) {
3021
            $row = Database::fetch_array($res);
3022
            $num = $row[0];
3023
        }
3024
3025
        return $num;
3026
    }
3027
3028
    /**
3029
     * Return the objectives as an array for the given lp_iv_id.
3030
     * This method can be used as static.
3031
     *
3032
     * @param int $lpItemViewId Learnpath Item View ID
3033
     *
3034
     * @return array
3035
     *
3036
     * @todo    Translate labels
3037
     */
3038
    public static function get_iv_objectives_array($lpItemViewId = 0)
3039
    {
3040
        $course_id = api_get_course_int_id();
3041
        $lpItemViewId = (int) $lpItemViewId;
3042
3043
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3044
        $sql = "SELECT * FROM $table
3045
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3046
                ORDER BY order_id ASC";
3047
        $res = Database::query($sql);
3048
        $num = Database::num_rows($res);
3049
        $list = [];
3050
        if ($num > 0) {
3051
            $list[] = [
3052
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3053
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3054
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3055
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3056
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3057
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3058
            ];
3059
            while ($row = Database::fetch_array($res)) {
3060
                $list[] = [
3061
                    'order_id' => ($row['order_id'] + 1),
3062
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3063
                    'score_raw' => $row['score_raw'],
3064
                    'score_max' => $row['score_max'],
3065
                    'score_min' => $row['score_min'],
3066
                    'status' => $row['status'],
3067
                ];
3068
            }
3069
        }
3070
3071
        return $list;
3072
    }
3073
3074
    /**
3075
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3076
     * used by get_html_toc() to be ready to display.
3077
     *
3078
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3079
     */
3080
    public function get_toc()
3081
    {
3082
        if ($this->debug > 0) {
3083
            error_log('learnpath::get_toc()', 0);
3084
        }
3085
        $toc = [];
3086
        foreach ($this->ordered_items as $item_id) {
3087
            if ($this->debug > 2) {
3088
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3089
            }
3090
            // TODO: Change this link generation and use new function instead.
3091
            $toc[] = [
3092
                'id' => $item_id,
3093
                'title' => $this->items[$item_id]->get_title(),
3094
                'status' => $this->items[$item_id]->get_status(),
3095
                'level' => $this->items[$item_id]->get_level(),
3096
                'type' => $this->items[$item_id]->get_type(),
3097
                'description' => $this->items[$item_id]->get_description(),
3098
                'path' => $this->items[$item_id]->get_path(),
3099
                'parent' => $this->items[$item_id]->get_parent(),
3100
            ];
3101
        }
3102
        if ($this->debug > 2) {
3103
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3104
        }
3105
3106
        return $toc;
3107
    }
3108
3109
    /**
3110
     * Generate and return the table of contents for this learnpath. The JS
3111
     * table returned is used inside of scorm_api.php.
3112
     *
3113
     * @param string $varname
3114
     *
3115
     * @return string A JS array variable construction
3116
     */
3117
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3118
    {
3119
        if ($this->debug > 0) {
3120
            error_log('In learnpath::get_items_details_as_js()', 0);
3121
        }
3122
        $toc = $varname.' = new Array();';
3123
        foreach ($this->ordered_items as $item_id) {
3124
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3125
        }
3126
        if ($this->debug > 2) {
3127
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3128
        }
3129
3130
        return $toc;
3131
    }
3132
3133
    /**
3134
     * Gets the learning path type.
3135
     *
3136
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3137
     *
3138
     * @return mixed Type ID or name, depending on the parameter
3139
     */
3140
    public function get_type($get_name = false)
3141
    {
3142
        $res = false;
3143
        if ($this->debug > 0) {
3144
            error_log('In learnpath::get_type()', 0);
3145
        }
3146
        if (!empty($this->type) && (!$get_name)) {
3147
            $res = $this->type;
3148
        }
3149
        if ($this->debug > 2) {
3150
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3151
        }
3152
3153
        return $res;
3154
    }
3155
3156
    /**
3157
     * Gets the learning path type as static method.
3158
     *
3159
     * @param int $lp_id
3160
     *
3161
     * @return mixed Type ID or name, depending on the parameter
3162
     */
3163
    public static function get_type_static($lp_id = 0)
3164
    {
3165
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3166
        $lp_id = intval($lp_id);
3167
        $sql = "SELECT lp_type FROM $tbl_lp
3168
                WHERE iid = $lp_id";
3169
        $res = Database::query($sql);
3170
        if ($res === false) {
3171
            return null;
3172
        }
3173
        if (Database::num_rows($res) <= 0) {
3174
            return null;
3175
        }
3176
        $row = Database::fetch_array($res);
3177
3178
        return $row['lp_type'];
3179
    }
3180
3181
    /**
3182
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3183
     * This method can be used as abstract and is recursive.
3184
     *
3185
     * @param int $lp        Learnpath ID
3186
     * @param int $parent    Parent ID of the items to look for
3187
     * @param int $course_id
3188
     *
3189
     * @return array Ordered list of item IDs (empty array on error)
3190
     */
3191
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3192
    {
3193
        if (empty($course_id)) {
3194
            $course_id = api_get_course_int_id();
3195
        } else {
3196
            $course_id = (int) $course_id;
3197
        }
3198
        $list = [];
3199
3200
        if (empty($lp)) {
3201
            return $list;
3202
        }
3203
3204
        $lp = (int) $lp;
3205
        $parent = (int) $parent;
3206
3207
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3208
        $sql = "SELECT iid FROM $tbl_lp_item
3209
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3210
                ORDER BY display_order";
3211
3212
        $res = Database::query($sql);
3213
        while ($row = Database::fetch_array($res)) {
3214
            $sublist = self::get_flat_ordered_items_list(
3215
                $lp,
3216
                $row['iid'],
3217
                $course_id
3218
            );
3219
            $list[] = $row['iid'];
3220
            foreach ($sublist as $item) {
3221
                $list[] = $item;
3222
            }
3223
        }
3224
3225
        return $list;
3226
    }
3227
3228
    /**
3229
     * @return array
3230
     */
3231
    public static function getChapterTypes()
3232
    {
3233
        return [
3234
            'dir',
3235
        ];
3236
    }
3237
3238
    /**
3239
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3240
     *
3241
     * @param $tree
3242
     *
3243
     * @return array HTML TOC ready to display
3244
     */
3245
    public function getParentToc($tree)
3246
    {
3247
        if ($this->debug > 0) {
3248
            error_log('In learnpath::get_html_toc()', 0);
3249
        }
3250
        if (empty($tree)) {
3251
            $tree = $this->get_toc();
3252
        }
3253
        $dirTypes = self::getChapterTypes();
3254
        $myCurrentId = $this->get_current_item_id();
3255
        $listParent = [];
3256
        $listChildren = [];
3257
        $listNotParent = [];
3258
        $list = [];
3259
        foreach ($tree as $subtree) {
3260
            if (in_array($subtree['type'], $dirTypes)) {
3261
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3262
                $subtree['children'] = $listChildren;
3263
                if (!empty($subtree['children'])) {
3264
                    foreach ($subtree['children'] as $subItem) {
3265
                        if ($subItem['id'] == $this->current) {
3266
                            $subtree['parent_current'] = 'in';
3267
                            $subtree['current'] = 'on';
3268
                        }
3269
                    }
3270
                }
3271
                $listParent[] = $subtree;
3272
            }
3273
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3274
                $classStatus = [
3275
                    'not attempted' => 'scorm_not_attempted',
3276
                    'incomplete' => 'scorm_not_attempted',
3277
                    'failed' => 'scorm_failed',
3278
                    'completed' => 'scorm_completed',
3279
                    'passed' => 'scorm_completed',
3280
                    'succeeded' => 'scorm_completed',
3281
                    'browsed' => 'scorm_completed',
3282
                ];
3283
3284
                if (isset($classStatus[$subtree['status']])) {
3285
                    $cssStatus = $classStatus[$subtree['status']];
3286
                }
3287
3288
                $title = Security::remove_XSS($subtree['title']);
3289
                unset($subtree['title']);
3290
3291
                if (empty($title)) {
3292
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3293
                }
3294
                $classStyle = null;
3295
                if ($subtree['id'] == $this->current) {
3296
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3297
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3298
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3299
                }
3300
                $subtree['title'] = $title;
3301
                $subtree['class'] = $classStyle.' '.$cssStatus;
3302
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3303
                $subtree['current_id'] = $myCurrentId;
3304
                $listNotParent[] = $subtree;
3305
            }
3306
        }
3307
3308
        $list['are_parents'] = $listParent;
3309
        $list['not_parents'] = $listNotParent;
3310
3311
        return $list;
3312
    }
3313
3314
    /**
3315
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3316
     *
3317
     * @param array $tree
3318
     * @param int   $id
3319
     * @param bool  $parent
3320
     *
3321
     * @return array HTML TOC ready to display
3322
     */
3323
    public function getChildrenToc($tree, $id, $parent = true)
3324
    {
3325
        if ($this->debug > 0) {
3326
            error_log('In learnpath::get_html_toc()', 0);
3327
        }
3328
        if (empty($tree)) {
3329
            $tree = $this->get_toc();
3330
        }
3331
3332
        $dirTypes = self::getChapterTypes();
3333
        $mycurrentitemid = $this->get_current_item_id();
3334
        $list = [];
3335
        $classStatus = [
3336
            'not attempted' => 'scorm_not_attempted',
3337
            'incomplete' => 'scorm_not_attempted',
3338
            'failed' => 'scorm_failed',
3339
            'completed' => 'scorm_completed',
3340
            'passed' => 'scorm_completed',
3341
            'succeeded' => 'scorm_completed',
3342
            'browsed' => 'scorm_completed',
3343
        ];
3344
3345
        foreach ($tree as $subtree) {
3346
            $subtree['tree'] = null;
3347
3348
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3349
                if ($subtree['id'] == $this->current) {
3350
                    $subtree['current'] = 'active';
3351
                } else {
3352
                    $subtree['current'] = null;
3353
                }
3354
                if (isset($classStatus[$subtree['status']])) {
3355
                    $cssStatus = $classStatus[$subtree['status']];
3356
                }
3357
3358
                $title = Security::remove_XSS($subtree['title']);
3359
                unset($subtree['title']);
3360
                if (empty($title)) {
3361
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3362
                }
3363
3364
                $classStyle = null;
3365
                if ($subtree['id'] == $this->current) {
3366
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3367
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3368
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3369
                }
3370
3371
                if (in_array($subtree['type'], $dirTypes)) {
3372
                    $subtree['title'] = stripslashes($title);
3373
                } else {
3374
                    $subtree['title'] = $title;
3375
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3376
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3377
                    $subtree['current_id'] = $mycurrentitemid;
3378
                }
3379
                $list[] = $subtree;
3380
            }
3381
        }
3382
3383
        return $list;
3384
    }
3385
3386
    /**
3387
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3388
     *
3389
     * @param array $toc_list
3390
     *
3391
     * @return array HTML TOC ready to display
3392
     */
3393
    public function getListArrayToc($toc_list = [])
3394
    {
3395
        if ($this->debug > 0) {
3396
            error_log('In learnpath::get_html_toc()', 0);
3397
        }
3398
        if (empty($toc_list)) {
3399
            $toc_list = $this->get_toc();
3400
        }
3401
        // Temporary variables.
3402
        $mycurrentitemid = $this->get_current_item_id();
3403
        $list = [];
3404
        $arrayList = [];
3405
        $classStatus = [
3406
            'not attempted' => 'scorm_not_attempted',
3407
            'incomplete' => 'scorm_not_attempted',
3408
            'failed' => 'scorm_failed',
3409
            'completed' => 'scorm_completed',
3410
            'passed' => 'scorm_completed',
3411
            'succeeded' => 'scorm_completed',
3412
            'browsed' => 'scorm_completed',
3413
        ];
3414
3415
        foreach ($toc_list as $item) {
3416
            $list['id'] = $item['id'];
3417
            $list['status'] = $item['status'];
3418
            $cssStatus = null;
3419
3420
            if (isset($classStatus[$item['status']])) {
3421
                $cssStatus = $classStatus[$item['status']];
3422
            }
3423
3424
            $classStyle = ' ';
3425
            $dirTypes = self::getChapterTypes();
3426
3427
            if (in_array($item['type'], $dirTypes)) {
3428
                $classStyle = 'scorm_item_section ';
3429
            }
3430
            if ($item['id'] == $this->current) {
3431
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3432
            } elseif (!in_array($item['type'], $dirTypes)) {
3433
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3434
            }
3435
            $title = $item['title'];
3436
            if (empty($title)) {
3437
                $title = self::rl_get_resource_name(
3438
                    api_get_course_id(),
3439
                    $this->get_id(),
3440
                    $item['id']
3441
                );
3442
            }
3443
            $title = Security::remove_XSS($item['title']);
3444
3445
            if (empty($item['description'])) {
3446
                $list['description'] = $title;
3447
            } else {
3448
                $list['description'] = $item['description'];
3449
            }
3450
3451
            $list['class'] = $classStyle.' '.$cssStatus;
3452
            $list['level'] = $item['level'];
3453
            $list['type'] = $item['type'];
3454
3455
            if (in_array($item['type'], $dirTypes)) {
3456
                $list['css_level'] = 'level_'.$item['level'];
3457
            } else {
3458
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.learnpath::format_scorm_type_item($item['type']);
3459
            }
3460
3461
            if (in_array($item['type'], $dirTypes)) {
3462
                $list['title'] = stripslashes($title);
3463
            } else {
3464
                $list['title'] = stripslashes($title);
3465
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3466
                $list['current_id'] = $mycurrentitemid;
3467
            }
3468
            $arrayList[] = $list;
3469
        }
3470
3471
        return $arrayList;
3472
    }
3473
3474
    /**
3475
     * Returns an HTML-formatted string ready to display with teacher buttons
3476
     * in LP view menu.
3477
     *
3478
     * @return string HTML TOC ready to display
3479
     */
3480
    public function get_teacher_toc_buttons()
3481
    {
3482
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3483
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3484
        $html = '';
3485
        if ($isAllow && $hideIcons == false) {
3486
            if ($this->get_lp_session_id() == api_get_session_id()) {
3487
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3488
                $html .= '<div class="btn-group">';
3489
                $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'>".
3490
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3491
                $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'>".
3492
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3493
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3494
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3495
                $html .= '</div>';
3496
                $html .= '</div>';
3497
            }
3498
        }
3499
3500
        return $html;
3501
    }
3502
3503
    /**
3504
     * Gets the learnpath maker name - generally the editor's name.
3505
     *
3506
     * @return string Learnpath maker name
3507
     */
3508
    public function get_maker()
3509
    {
3510
        if ($this->debug > 0) {
3511
            error_log('In learnpath::get_maker()', 0);
3512
        }
3513
        if (!empty($this->maker)) {
3514
            return $this->maker;
3515
        } else {
3516
            return '';
3517
        }
3518
    }
3519
3520
    /**
3521
     * Gets the learnpath name/title.
3522
     *
3523
     * @return string Learnpath name/title
3524
     */
3525
    public function get_name()
3526
    {
3527
        if ($this->debug > 0) {
3528
            error_log('In learnpath::get_name()', 0);
3529
        }
3530
        if (!empty($this->name)) {
3531
            return $this->name;
3532
        } else {
3533
            return 'N/A';
3534
        }
3535
    }
3536
3537
    /**
3538
     * Gets a link to the resource from the present location, depending on item ID.
3539
     *
3540
     * @param string $type         Type of link expected
3541
     * @param int    $item_id      Learnpath item ID
3542
     * @param bool   $provided_toc
3543
     *
3544
     * @return string $provided_toc Link to the lp_item resource
3545
     */
3546
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3547
    {
3548
        $course_id = $this->get_course_int_id();
3549
        if ($this->debug > 0) {
3550
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3551
        }
3552
        if (empty($item_id)) {
3553
            if ($this->debug > 2) {
3554
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3555
                error_log('using current: '.$this->get_current_item_id(), 0);
3556
            }
3557
            $item_id = $this->get_current_item_id();
3558
        }
3559
3560
        if (empty($item_id)) {
3561
            if ($this->debug > 2) {
3562
                error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3563
            }
3564
            //still empty, this means there was no item_id given and we are not in an object context or
3565
            //the object property is empty, return empty link
3566
            $this->first();
3567
3568
            return '';
3569
        }
3570
3571
        $file = '';
3572
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3573
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3574
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3575
        $item_id = intval($item_id);
3576
3577
        $sql = "SELECT
3578
                    l.lp_type as ltype,
3579
                    l.path as lpath,
3580
                    li.item_type as litype,
3581
                    li.path as lipath,
3582
                    li.parameters as liparams
3583
        		FROM $lp_table l
3584
                INNER JOIN $lp_item_table li
3585
                ON (li.lp_id = l.iid)
3586
        		WHERE 
3587
        		    li.iid = $item_id 
3588
        		";
3589
        if ($this->debug > 2) {
3590
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3591
        }
3592
        $res = Database::query($sql);
3593
        if (Database::num_rows($res) > 0) {
3594
            $row = Database::fetch_array($res);
3595
            $lp_type = $row['ltype'];
3596
            $lp_path = $row['lpath'];
3597
            $lp_item_type = $row['litype'];
3598
            $lp_item_path = $row['lipath'];
3599
            $lp_item_params = $row['liparams'];
3600
3601
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3602
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3603
            }
3604
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3605
            if ($type == 'http') {
3606
                //web path
3607
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3608
            } else {
3609
                $course_path = $sys_course_path; //system path
3610
            }
3611
3612
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3613
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3614
            if (in_array(
3615
                $lp_item_type,
3616
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3617
            )
3618
            ) {
3619
                $lp_type = 1;
3620
            }
3621
3622
            if ($this->debug > 2) {
3623
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3624
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3625
            }
3626
3627
            // Now go through the specific cases to get the end of the path
3628
            // @todo Use constants instead of int values.
3629
3630
            switch ($lp_type) {
3631
                case 1:
3632
                    $file = self::rl_get_resource_link_for_learnpath(
3633
                        $course_id,
3634
                        $this->get_id(),
3635
                        $item_id,
3636
                        $this->get_view_id()
3637
                    );
3638
3639
                    if ($this->debug > 0) {
3640
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3641
                    }
3642
3643
                    switch ($lp_item_type) {
3644
                        case 'dir':
3645
                            $file = 'lp_content.php?type=dir';
3646
                            break;
3647
                        case 'link':
3648
                            if (Link::is_youtube_link($file)) {
3649
                                $src = Link::get_youtube_video_id($file);
3650
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3651
                            } elseif (Link::isVimeoLink($file)) {
3652
                                $src = Link::getVimeoLinkId($file);
3653
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3654
                            } else {
3655
                                // If the current site is HTTPS and the link is
3656
                                // HTTP, browsers will refuse opening the link
3657
                                $urlId = api_get_current_access_url_id();
3658
                                $url = api_get_access_url($urlId, false);
3659
                                $protocol = substr($url['url'], 0, 5);
3660
                                if ($protocol === 'https') {
3661
                                    $linkProtocol = substr($file, 0, 5);
3662
                                    if ($linkProtocol === 'http:') {
3663
                                        //this is the special intervention case
3664
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3665
                                    }
3666
                                }
3667
                            }
3668
                            break;
3669
                        case 'quiz':
3670
                            // Check how much attempts of a exercise exits in lp
3671
                            $lp_item_id = $this->get_current_item_id();
3672
                            $lp_view_id = $this->get_view_id();
3673
3674
                            $prevent_reinit = null;
3675
                            if (isset($this->items[$this->current])) {
3676
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3677
                            }
3678
3679
                            if (empty($provided_toc)) {
3680
                                if ($this->debug > 0) {
3681
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3682
                                }
3683
                                $list = $this->get_toc();
3684
                            } else {
3685
                                if ($this->debug > 0) {
3686
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3687
                                }
3688
                                $list = $provided_toc;
3689
                            }
3690
3691
                            $type_quiz = false;
3692
3693
                            foreach ($list as $toc) {
3694
                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
3695
                                    $type_quiz = true;
3696
                                }
3697
                            }
3698
3699
                            if ($type_quiz) {
3700
                                $lp_item_id = intval($lp_item_id);
3701
                                $lp_view_id = intval($lp_view_id);
3702
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3703
                                        WHERE
3704
                                            c_id = $course_id AND
3705
                                            lp_item_id='".$lp_item_id."' AND
3706
                                            lp_view_id ='".$lp_view_id."' AND
3707
                                            status='completed'";
3708
                                $result = Database::query($sql);
3709
                                $row_count = Database:: fetch_row($result);
3710
                                $count_item_view = (int) $row_count[0];
3711
                                $not_multiple_attempt = 0;
3712
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3713
                                    $not_multiple_attempt = 1;
3714
                                }
3715
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3716
                            }
3717
                            break;
3718
                    }
3719
3720
                    $tmp_array = explode('/', $file);
3721
                    $document_name = $tmp_array[count($tmp_array) - 1];
3722
                    if (strpos($document_name, '_DELETED_')) {
3723
                        $file = 'blank.php?error=document_deleted';
3724
                    }
3725
3726
                    break;
3727
                case 2:
3728
                    if ($this->debug > 2) {
3729
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3730
                    }
3731
3732
                    if ($lp_item_type != 'dir') {
3733
                        // Quite complex here:
3734
                        // We want to make sure 'http://' (and similar) links can
3735
                        // be loaded as is (withouth the Chamilo path in front) but
3736
                        // some contents use this form: resource.htm?resource=http://blablabla
3737
                        // which means we have to find a protocol at the path's start, otherwise
3738
                        // it should not be considered as an external URL.
3739
                        // if ($this->prerequisites_match($item_id)) {
3740
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3741
                            if ($this->debug > 2) {
3742
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3743
                            }
3744
                            // Distant url, return as is.
3745
                            $file = $lp_item_path;
3746
                        } else {
3747
                            if ($this->debug > 2) {
3748
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3749
                            }
3750
                            // Prevent getting untranslatable urls.
3751
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3752
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3753
                            // Prepare the path.
3754
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3755
                            // TODO: Fix this for urls with protocol header.
3756
                            $file = str_replace('//', '/', $file);
3757
                            $file = str_replace(':/', '://', $file);
3758
                            if (substr($lp_path, -1) == '/') {
3759
                                $lp_path = substr($lp_path, 0, -1);
3760
                            }
3761
3762
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3763
                                // if file not found.
3764
                                $decoded = html_entity_decode($lp_item_path);
3765
                                list($decoded) = explode('?', $decoded);
3766
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3767
                                    $file = self::rl_get_resource_link_for_learnpath(
3768
                                        $course_id,
3769
                                        $this->get_id(),
3770
                                        $item_id,
3771
                                        $this->get_view_id()
3772
                                    );
3773
                                    if (empty($file)) {
3774
                                        $file = 'blank.php?error=document_not_found';
3775
                                    } else {
3776
                                        $tmp_array = explode('/', $file);
3777
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3778
                                        if (strpos($document_name, '_DELETED_')) {
3779
                                            $file = 'blank.php?error=document_deleted';
3780
                                        } else {
3781
                                            $file = 'blank.php?error=document_not_found';
3782
                                        }
3783
                                    }
3784
                                } else {
3785
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3786
                                }
3787
                            }
3788
                        }
3789
3790
                        // We want to use parameters if they were defined in the imsmanifest
3791
                        if (strpos($file, 'blank.php') === false) {
3792
                            $lp_item_params = ltrim($lp_item_params, '?');
3793
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3794
                        }
3795
                    } else {
3796
                        $file = 'lp_content.php?type=dir';
3797
                    }
3798
                    break;
3799
                case 3:
3800
                    if ($this->debug > 2) {
3801
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3802
                    }
3803
                    // Formatting AICC HACP append URL.
3804
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3805
                    if (!empty($lp_item_params)) {
3806
                        $aicc_append .= $lp_item_params.'&';
3807
                    }
3808
                    if ($lp_item_type != 'dir') {
3809
                        // Quite complex here:
3810
                        // We want to make sure 'http://' (and similar) links can
3811
                        // be loaded as is (withouth the Chamilo path in front) but
3812
                        // some contents use this form: resource.htm?resource=http://blablabla
3813
                        // which means we have to find a protocol at the path's start, otherwise
3814
                        // it should not be considered as an external URL.
3815
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3816
                            if ($this->debug > 2) {
3817
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3818
                            }
3819
                            // Distant url, return as is.
3820
                            $file = $lp_item_path;
3821
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3822
                            /*
3823
                            if (stristr($file,'<servername>') !== false) {
3824
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3825
                            }
3826
                            */
3827
                            if (stripos($file, '<servername>') !== false) {
3828
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3829
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3830
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3831
                            }
3832
3833
                            $file .= $aicc_append;
3834
                        } else {
3835
                            if ($this->debug > 2) {
3836
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3837
                            }
3838
                            // Prevent getting untranslatable urls.
3839
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3840
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3841
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3842
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3843
                            // TODO: Fix this for urls with protocol header.
3844
                            $file = str_replace('//', '/', $file);
3845
                            $file = str_replace(':/', '://', $file);
3846
                            $file .= $aicc_append;
3847
                        }
3848
                    } else {
3849
                        $file = 'lp_content.php?type=dir';
3850
                    }
3851
                    break;
3852
                case 4:
3853
                    break;
3854
                default:
3855
                    break;
3856
            }
3857
            // Replace &amp; by & because &amp; will break URL with params
3858
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3859
        }
3860
        if ($this->debug > 2) {
3861
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3862
        }
3863
3864
        return $file;
3865
    }
3866
3867
    /**
3868
     * Gets the latest usable view or generate a new one.
3869
     *
3870
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3871
     *
3872
     * @return int DB lp_view id
3873
     */
3874
    public function get_view($attempt_num = 0)
3875
    {
3876
        if ($this->debug > 0) {
3877
            error_log('In learnpath::get_view()', 0);
3878
        }
3879
        $search = '';
3880
        // Use $attempt_num to enable multi-views management (disabled so far).
3881
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3882
            $search = 'AND view_count = '.$attempt_num;
3883
        }
3884
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3885
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3886
3887
        $course_id = api_get_course_int_id();
3888
        $sessionId = api_get_session_id();
3889
3890
        $sql = "SELECT iid, view_count FROM $lp_view_table
3891
        		WHERE
3892
        		    c_id = $course_id AND
3893
        		    lp_id = ".$this->get_id()." AND
3894
        		    user_id = ".$this->get_user_id()." AND
3895
        		    session_id = $sessionId
3896
        		    $search
3897
                ORDER BY view_count DESC";
3898
        $res = Database::query($sql);
3899
        if (Database::num_rows($res) > 0) {
3900
            $row = Database::fetch_array($res);
3901
            $this->lp_view_id = $row['iid'];
3902
        } elseif (!api_is_invitee()) {
3903
            // There is no database record, create one.
3904
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3905
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3906
            Database::query($sql);
3907
            $id = Database::insert_id();
3908
            $this->lp_view_id = $id;
3909
3910
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3911
            Database::query($sql);
3912
        }
3913
3914
        return $this->lp_view_id;
3915
    }
3916
3917
    /**
3918
     * Gets the current view id.
3919
     *
3920
     * @return int View ID (from lp_view)
3921
     */
3922
    public function get_view_id()
3923
    {
3924
        if ($this->debug > 0) {
3925
            error_log('In learnpath::get_view_id()', 0);
3926
        }
3927
        if (!empty($this->lp_view_id)) {
3928
            return $this->lp_view_id;
3929
        } else {
3930
            return 0;
3931
        }
3932
    }
3933
3934
    /**
3935
     * Gets the update queue.
3936
     *
3937
     * @return array Array containing IDs of items to be updated by JavaScript
3938
     */
3939
    public function get_update_queue()
3940
    {
3941
        if ($this->debug > 0) {
3942
            error_log('In learnpath::get_update_queue()', 0);
3943
        }
3944
3945
        return $this->update_queue;
3946
    }
3947
3948
    /**
3949
     * Gets the user ID.
3950
     *
3951
     * @return int User ID
3952
     */
3953
    public function get_user_id()
3954
    {
3955
        if ($this->debug > 0) {
3956
            error_log('In learnpath::get_user_id()', 0);
3957
        }
3958
        if (!empty($this->user_id)) {
3959
            return $this->user_id;
3960
        } else {
3961
            return false;
3962
        }
3963
    }
3964
3965
    /**
3966
     * Checks if any of the items has an audio element attached.
3967
     *
3968
     * @return bool True or false
3969
     */
3970
    public function has_audio()
3971
    {
3972
        if ($this->debug > 1) {
3973
            error_log('In learnpath::has_audio()', 0);
3974
        }
3975
        $has = false;
3976
        foreach ($this->items as $i => $item) {
3977
            if (!empty($this->items[$i]->audio)) {
3978
                $has = true;
3979
                break;
3980
            }
3981
        }
3982
3983
        return $has;
3984
    }
3985
3986
    /**
3987
     * Moves an item up and down at its level.
3988
     *
3989
     * @param int    $id        Item to move up and down
3990
     * @param string $direction Direction 'up' or 'down'
3991
     *
3992
     * @return bool|int
3993
     */
3994
    public function move_item($id, $direction)
3995
    {
3996
        $course_id = api_get_course_int_id();
3997
        if ($this->debug > 0) {
3998
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
3999
        }
4000
        if (empty($id) || empty($direction)) {
4001
            return false;
4002
        }
4003
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4004
        $sql_sel = "SELECT *
4005
                    FROM $tbl_lp_item
4006
                    WHERE  
4007
                        iid = $id
4008
                    ";
4009
        $res_sel = Database::query($sql_sel);
4010
        // Check if elem exists.
4011
        if (Database::num_rows($res_sel) < 1) {
4012
            return false;
4013
        }
4014
        // Gather data.
4015
        $row = Database::fetch_array($res_sel);
4016
        $previous = $row['previous_item_id'];
4017
        $next = $row['next_item_id'];
4018
        $display = $row['display_order'];
4019
        $parent = $row['parent_item_id'];
4020
        $lp = $row['lp_id'];
4021
        // Update the item (switch with previous/next one).
4022
        switch ($direction) {
4023
            case 'up':
4024
                if ($this->debug > 2) {
4025
                    error_log('Movement up detected', 0);
4026
                }
4027
                if ($display > 1) {
4028
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4029
                                 WHERE iid = $previous";
4030
4031
                    if ($this->debug > 2) {
4032
                        error_log('Selecting previous: '.$sql_sel2, 0);
4033
                    }
4034
                    $res_sel2 = Database::query($sql_sel2);
4035
                    if (Database::num_rows($res_sel2) < 1) {
4036
                        $previous_previous = 0;
4037
                    }
4038
                    // Gather data.
4039
                    $row2 = Database::fetch_array($res_sel2);
4040
                    $previous_previous = $row2['previous_item_id'];
4041
                    // Update previous_previous item (switch "next" with current).
4042
                    if ($previous_previous != 0) {
4043
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4044
                                        next_item_id = $id
4045
                                    WHERE iid = $previous_previous";
4046
                        if ($this->debug > 2) {
4047
                            error_log($sql_upd2, 0);
4048
                        }
4049
                        Database::query($sql_upd2);
4050
                    }
4051
                    // Update previous item (switch with current).
4052
                    if ($previous != 0) {
4053
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4054
                                    next_item_id = $next,
4055
                                    previous_item_id = $id,
4056
                                    display_order = display_order +1
4057
                                    WHERE iid = $previous";
4058
                        if ($this->debug > 2) {
4059
                            error_log($sql_upd2, 0);
4060
                        }
4061
                        Database::query($sql_upd2);
4062
                    }
4063
4064
                    // Update current item (switch with previous).
4065
                    if ($id != 0) {
4066
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4067
                                        next_item_id = $previous,
4068
                                        previous_item_id = $previous_previous,
4069
                                        display_order = display_order-1
4070
                                    WHERE c_id = ".$course_id." AND id = $id";
4071
                        if ($this->debug > 2) {
4072
                            error_log($sql_upd2, 0);
4073
                        }
4074
                        Database::query($sql_upd2);
4075
                    }
4076
                    // Update next item (new previous item).
4077
                    if ($next != 0) {
4078
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4079
                                     WHERE iid = $next";
4080
                        if ($this->debug > 2) {
4081
                            error_log($sql_upd2, 0);
4082
                        }
4083
                        Database::query($sql_upd2);
4084
                    }
4085
                    $display = $display - 1;
4086
                }
4087
                break;
4088
            case 'down':
4089
                if ($this->debug > 2) {
4090
                    error_log('Movement down detected', 0);
4091
                }
4092
                if ($next != 0) {
4093
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4094
                                 WHERE iid = $next";
4095
                    if ($this->debug > 2) {
4096
                        error_log('Selecting next: '.$sql_sel2, 0);
4097
                    }
4098
                    $res_sel2 = Database::query($sql_sel2);
4099
                    if (Database::num_rows($res_sel2) < 1) {
4100
                        $next_next = 0;
4101
                    }
4102
                    // Gather data.
4103
                    $row2 = Database::fetch_array($res_sel2);
4104
                    $next_next = $row2['next_item_id'];
4105
                    // Update previous item (switch with current).
4106
                    if ($previous != 0) {
4107
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4108
                                     SET next_item_id = $next
4109
                                     WHERE iid = $previous";
4110
                        Database::query($sql_upd2);
4111
                    }
4112
                    // Update current item (switch with previous).
4113
                    if ($id != 0) {
4114
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4115
                                     previous_item_id = $next, 
4116
                                     next_item_id = $next_next, 
4117
                                     display_order = display_order + 1
4118
                                     WHERE iid = $id";
4119
                        Database::query($sql_upd2);
4120
                    }
4121
4122
                    // Update next item (new previous item).
4123
                    if ($next != 0) {
4124
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4125
                                     previous_item_id = $previous, 
4126
                                     next_item_id = $id, 
4127
                                     display_order = display_order-1
4128
                                     WHERE iid = $next";
4129
                        Database::query($sql_upd2);
4130
                    }
4131
4132
                    // Update next_next item (switch "previous" with current).
4133
                    if ($next_next != 0) {
4134
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4135
                                     previous_item_id = $id
4136
                                     WHERE iid = $next_next";
4137
                        Database::query($sql_upd2);
4138
                    }
4139
                    $display = $display + 1;
4140
                }
4141
                break;
4142
            default:
4143
                return false;
4144
        }
4145
4146
        return $display;
4147
    }
4148
4149
    /**
4150
     * Move a LP up (display_order).
4151
     *
4152
     * @param int $lp_id      Learnpath ID
4153
     * @param int $categoryId Category ID
4154
     *
4155
     * @return bool
4156
     */
4157
    public static function move_up($lp_id, $categoryId = 0)
4158
    {
4159
        $courseId = api_get_course_int_id();
4160
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4161
4162
        $categoryCondition = '';
4163
        if (!empty($categoryId)) {
4164
            $categoryId = (int) $categoryId;
4165
            $categoryCondition = " AND category_id = $categoryId";
4166
        }
4167
        $sql = "SELECT * FROM $lp_table
4168
                WHERE c_id = $courseId
4169
                $categoryCondition
4170
                ORDER BY display_order";
4171
        $res = Database::query($sql);
4172
        if ($res === false) {
4173
            return false;
4174
        }
4175
4176
        $lps = [];
4177
        $lp_order = [];
4178
        $num = Database::num_rows($res);
4179
        // First check the order is correct, globally (might be wrong because
4180
        // of versions < 1.8.4)
4181
        if ($num > 0) {
4182
            $i = 1;
4183
            while ($row = Database::fetch_array($res)) {
4184
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4185
                    $sql = "UPDATE $lp_table SET display_order = $i
4186
                            WHERE iid = ".$row['iid'];
4187
                    Database::query($sql);
4188
                }
4189
                $row['display_order'] = $i;
4190
                $lps[$row['iid']] = $row;
4191
                $lp_order[$i] = $row['iid'];
4192
                $i++;
4193
            }
4194
        }
4195
        if ($num > 1) { // If there's only one element, no need to sort.
4196
            $order = $lps[$lp_id]['display_order'];
4197
            if ($order > 1) { // If it's the first element, no need to move up.
4198
                $sql = "UPDATE $lp_table SET display_order = $order
4199
                        WHERE iid = ".$lp_order[$order - 1];
4200
                Database::query($sql);
4201
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4202
                        WHERE iid = $lp_id";
4203
                Database::query($sql);
4204
            }
4205
        }
4206
4207
        return true;
4208
    }
4209
4210
    /**
4211
     * Move a learnpath down (display_order).
4212
     *
4213
     * @param int $lp_id      Learnpath ID
4214
     * @param int $categoryId Category ID
4215
     *
4216
     * @return bool
4217
     */
4218
    public static function move_down($lp_id, $categoryId = 0)
4219
    {
4220
        $courseId = api_get_course_int_id();
4221
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4222
4223
        $categoryCondition = '';
4224
        if (!empty($categoryId)) {
4225
            $categoryId = (int) $categoryId;
4226
            $categoryCondition = " AND category_id = $categoryId";
4227
        }
4228
4229
        $sql = "SELECT * FROM $lp_table
4230
                WHERE c_id = $courseId
4231
                $categoryCondition
4232
                ORDER BY display_order";
4233
        $res = Database::query($sql);
4234
        if ($res === false) {
4235
            return false;
4236
        }
4237
        $lps = [];
4238
        $lp_order = [];
4239
        $num = Database::num_rows($res);
4240
        $max = 0;
4241
        // First check the order is correct, globally (might be wrong because
4242
        // of versions < 1.8.4).
4243
        if ($num > 0) {
4244
            $i = 1;
4245
            while ($row = Database::fetch_array($res)) {
4246
                $max = $i;
4247
                if ($row['display_order'] != $i) {
4248
                    // If we find a gap in the order, we need to fix it.
4249
                    $sql = "UPDATE $lp_table SET display_order = $i
4250
                              WHERE iid = ".$row['iid'];
4251
                    Database::query($sql);
4252
                }
4253
                $row['display_order'] = $i;
4254
                $lps[$row['iid']] = $row;
4255
                $lp_order[$i] = $row['iid'];
4256
                $i++;
4257
            }
4258
        }
4259
        if ($num > 1) { // If there's only one element, no need to sort.
4260
            $order = $lps[$lp_id]['display_order'];
4261
            if ($order < $max) { // If it's the first element, no need to move up.
4262
                $sql = "UPDATE $lp_table SET display_order = $order
4263
                        WHERE iid = ".$lp_order[$order + 1];
4264
                Database::query($sql);
4265
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4266
                        WHERE iid = $lp_id";
4267
                Database::query($sql);
4268
            }
4269
        }
4270
4271
        return true;
4272
    }
4273
4274
    /**
4275
     * Updates learnpath attributes to point to the next element
4276
     * The last part is similar to set_current_item but processing the other way around.
4277
     */
4278
    public function next()
4279
    {
4280
        if ($this->debug > 0) {
4281
            error_log('In learnpath::next()', 0);
4282
        }
4283
        $this->last = $this->get_current_item_id();
4284
        $this->items[$this->last]->save(
4285
            false,
4286
            $this->prerequisites_match($this->last)
4287
        );
4288
        $this->autocomplete_parents($this->last);
4289
        $new_index = $this->get_next_index();
4290
        if ($this->debug > 2) {
4291
            error_log('New index: '.$new_index, 0);
4292
        }
4293
        $this->index = $new_index;
4294
        if ($this->debug > 2) {
4295
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4296
        }
4297
        $this->current = $this->ordered_items[$new_index];
4298
        if ($this->debug > 2) {
4299
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4300
        }
4301
    }
4302
4303
    /**
4304
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4305
     * class, this might be redefined to allow several behaviours depending on the document type.
4306
     *
4307
     * @param int $id Resource ID
4308
     */
4309
    public function open($id)
4310
    {
4311
        if ($this->debug > 0) {
4312
            error_log('In learnpath::open()', 0);
4313
        }
4314
        // TODO:
4315
        // set the current resource attribute to this resource
4316
        // switch on element type (redefine in child class?)
4317
        // set status for this item to "opened"
4318
        // start timer
4319
        // initialise score
4320
        $this->index = 0; //or = the last item seen (see $this->last)
4321
    }
4322
4323
    /**
4324
     * Check that all prerequisites are fulfilled. Returns true and an
4325
     * empty string on success, returns false
4326
     * and the prerequisite string on error.
4327
     * This function is based on the rules for aicc_script language as
4328
     * described in the SCORM 1.2 CAM documentation page 108.
4329
     *
4330
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4331
     *
4332
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4333
     *              string otherwise
4334
     */
4335
    public function prerequisites_match($itemId = null)
4336
    {
4337
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4338
        if ($allow) {
4339
            if (api_is_allowed_to_edit() ||
4340
                api_is_platform_admin(true) ||
4341
                api_is_drh() ||
4342
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4343
            ) {
4344
                return true;
4345
            }
4346
        }
4347
4348
        $debug = $this->debug;
4349
        if ($debug > 0) {
4350
            error_log('In learnpath::prerequisites_match()', 0);
4351
        }
4352
4353
        if (empty($itemId)) {
4354
            $itemId = $this->current;
4355
        }
4356
4357
        $currentItem = $this->getItem($itemId);
4358
4359
        if ($currentItem) {
4360
            if ($this->type == 2) {
4361
                // Getting prereq from scorm
4362
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4363
            } else {
4364
                $prereq_string = $currentItem->get_prereq_string();
4365
            }
4366
4367
            if (empty($prereq_string)) {
4368
                if ($debug > 0) {
4369
                    error_log('Found prereq_string is empty return true');
4370
                }
4371
4372
                return true;
4373
            }
4374
            // Clean spaces.
4375
            $prereq_string = str_replace(' ', '', $prereq_string);
4376
            if ($debug > 0) {
4377
                error_log('Found prereq_string: '.$prereq_string, 0);
4378
            }
4379
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4380
            $result = $currentItem->parse_prereq(
4381
                $prereq_string,
4382
                $this->items,
4383
                $this->refs_list,
4384
                $this->get_user_id()
4385
            );
4386
4387
            if ($result === false) {
4388
                $this->set_error_msg($currentItem->prereq_alert);
4389
            }
4390
        } else {
4391
            $result = true;
4392
            if ($debug > 1) {
4393
                error_log('$this->items['.$itemId.'] was not an object', 0);
4394
            }
4395
        }
4396
4397
        if ($debug > 1) {
4398
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4399
        }
4400
4401
        return $result;
4402
    }
4403
4404
    /**
4405
     * Updates learnpath attributes to point to the previous element
4406
     * The last part is similar to set_current_item but processing the other way around.
4407
     */
4408
    public function previous()
4409
    {
4410
        if ($this->debug > 0) {
4411
            error_log('In learnpath::previous()', 0);
4412
        }
4413
        $this->last = $this->get_current_item_id();
4414
        $this->items[$this->last]->save(
4415
            false,
4416
            $this->prerequisites_match($this->last)
4417
        );
4418
        $this->autocomplete_parents($this->last);
4419
        $new_index = $this->get_previous_index();
4420
        $this->index = $new_index;
4421
        $this->current = $this->ordered_items[$new_index];
4422
    }
4423
4424
    /**
4425
     * Publishes a learnpath. This basically means show or hide the learnpath
4426
     * to normal users.
4427
     * Can be used as abstract.
4428
     *
4429
     * @param int $lp_id          Learnpath ID
4430
     * @param int $set_visibility New visibility
4431
     *
4432
     * @return bool
4433
     */
4434
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4435
    {
4436
        $action = 'visible';
4437
        if ($set_visibility != 1) {
4438
            $action = 'invisible';
4439
            self::toggle_publish($lp_id, 'i');
4440
        }
4441
4442
        return api_item_property_update(
4443
            api_get_course_info(),
4444
            TOOL_LEARNPATH,
4445
            $lp_id,
4446
            $action,
4447
            api_get_user_id()
4448
        );
4449
    }
4450
4451
    /**
4452
     * Publishes a learnpath category.
4453
     * This basically means show or hide the learnpath category to normal users.
4454
     *
4455
     * @param int $id
4456
     * @param int $visibility
4457
     *
4458
     * @throws \Doctrine\ORM\NonUniqueResultException
4459
     * @throws \Doctrine\ORM\ORMException
4460
     * @throws \Doctrine\ORM\OptimisticLockException
4461
     * @throws \Doctrine\ORM\TransactionRequiredException
4462
     *
4463
     * @return bool
4464
     */
4465
    public static function toggleCategoryVisibility($id, $visibility = 1)
4466
    {
4467
        $action = 'visible';
4468
4469
        if ($visibility != 1) {
4470
            $action = 'invisible';
4471
            $list = new LearnpathList(
4472
                api_get_user_id(),
4473
                null,
4474
                null,
4475
                null,
4476
                false,
4477
                $id
4478
            );
4479
4480
            $lpList = $list->get_flat_list();
4481
            foreach ($lpList as $lp) {
4482
                learnpath::toggle_visibility($lp['iid'], 0);
4483
            }
4484
4485
            learnpath::toggleCategoryPublish($id, 0);
4486
        }
4487
4488
        return api_item_property_update(
4489
            api_get_course_info(),
4490
            TOOL_LEARNPATH_CATEGORY,
4491
            $id,
4492
            $action,
4493
            api_get_user_id()
4494
        );
4495
    }
4496
4497
    /**
4498
     * Publishes a learnpath. This basically means show or hide the learnpath
4499
     * on the course homepage
4500
     * Can be used as abstract.
4501
     *
4502
     * @param int    $lp_id          Learnpath id
4503
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4504
     *
4505
     * @return bool
4506
     */
4507
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4508
    {
4509
        $course_id = api_get_course_int_id();
4510
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4511
        $lp_id = (int) $lp_id;
4512
        $sql = "SELECT * FROM $tbl_lp
4513
                WHERE iid = $lp_id";
4514
        $result = Database::query($sql);
4515
        if (Database::num_rows($result)) {
4516
            $row = Database::fetch_array($result);
4517
            $name = Database::escape_string($row['name']);
4518
            if ($set_visibility == 'i') {
4519
                $v = 0;
4520
            }
4521
            if ($set_visibility == 'v') {
4522
                $v = 1;
4523
            }
4524
4525
            $session_id = api_get_session_id();
4526
            $session_condition = api_get_session_condition($session_id);
4527
4528
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4529
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4530
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4531
4532
            $sql = "SELECT * FROM $tbl_tool
4533
                    WHERE
4534
                        c_id = $course_id AND
4535
                        (link = '$link' OR link = '$oldLink') AND
4536
                        image = 'scormbuilder.gif' AND
4537
                        (
4538
                            link LIKE '$link%' OR
4539
                            link LIKE '$oldLink%'
4540
                        )
4541
                        $session_condition
4542
                    ";
4543
4544
            $result = Database::query($sql);
4545
            $num = Database::num_rows($result);
4546
            if ($set_visibility == 'i' && $num > 0) {
4547
                $sql = "DELETE FROM $tbl_tool
4548
                        WHERE 
4549
                            c_id = $course_id AND 
4550
                            (link = '$link' OR link = '$oldLink') AND 
4551
                            image='scormbuilder.gif' 
4552
                            $session_condition";
4553
                Database::query($sql);
4554
            } elseif ($set_visibility == 'v' && $num == 0) {
4555
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4556
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4557
                Database::query($sql);
4558
                $insertId = Database::insert_id();
4559
                if ($insertId) {
4560
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4561
                    Database::query($sql);
4562
                }
4563
            } elseif ($set_visibility == 'v' && $num > 0) {
4564
                $sql = "UPDATE $tbl_tool SET
4565
                            c_id = $course_id,
4566
                            name = '$name',
4567
                            link = '$link',
4568
                            image = 'scormbuilder.gif',
4569
                            visibility = '$v',
4570
                            admin = '0',
4571
                            address = 'pastillegris.gif',
4572
                            added_tool = 0,
4573
                            session_id = $session_id
4574
                        WHERE
4575
                            c_id = ".$course_id." AND
4576
                            (link = '$link' OR link = '$oldLink') AND 
4577
                            image='scormbuilder.gif' 
4578
                            $session_condition
4579
                        ";
4580
                Database::query($sql);
4581
            } else {
4582
                // Parameter and database incompatible, do nothing, exit.
4583
                return false;
4584
            }
4585
        } else {
4586
            return false;
4587
        }
4588
    }
4589
4590
    /**
4591
     * Publishes a learnpath.
4592
     * Show or hide the learnpath category on the course homepage.
4593
     *
4594
     * @param int $id
4595
     * @param int $setVisibility
4596
     *
4597
     * @throws \Doctrine\ORM\NonUniqueResultException
4598
     * @throws \Doctrine\ORM\ORMException
4599
     * @throws \Doctrine\ORM\OptimisticLockException
4600
     * @throws \Doctrine\ORM\TransactionRequiredException
4601
     *
4602
     * @return bool
4603
     */
4604
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4605
    {
4606
        $courseId = api_get_course_int_id();
4607
        $sessionId = api_get_session_id();
4608
        $sessionCondition = api_get_session_condition(
4609
            $sessionId,
4610
            true,
4611
            false,
4612
            't.sessionId'
4613
        );
4614
4615
        $em = Database::getManager();
4616
4617
        /** @var CLpCategory $category */
4618
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4619
4620
        if (!$category) {
4621
            return false;
4622
        }
4623
4624
        $link = self::getCategoryLinkForTool($id);
4625
4626
        /** @var CTool $tool */
4627
        $tool = $em->createQuery("
4628
                SELECT t FROM ChamiloCourseBundle:CTool t
4629
                WHERE
4630
                    t.cId = :course AND
4631
                    t.link = :link1 AND
4632
                    t.image = 'lp_category.gif' AND
4633
                    t.link LIKE :link2
4634
                    $sessionCondition
4635
            ")
4636
            ->setParameters([
4637
                'course' => (int) $courseId,
4638
                'link1' => $link,
4639
                'link2' => "$link%",
4640
            ])
4641
            ->getOneOrNullResult();
4642
4643
        if ($setVisibility == 0 && $tool) {
4644
            $em->remove($tool);
4645
            $em->flush();
4646
4647
            return true;
4648
        }
4649
4650
        if ($setVisibility == 1 && !$tool) {
4651
            $tool = new CTool();
4652
            $tool
4653
                ->setCategory('authoring')
4654
                ->setCId($courseId)
4655
                ->setName(strip_tags($category->getName()))
4656
                ->setLink($link)
4657
                ->setImage('lp_category.gif')
4658
                ->setVisibility(1)
4659
                ->setAdmin(0)
4660
                ->setAddress('pastillegris.gif')
4661
                ->setAddedTool(0)
4662
                ->setSessionId($sessionId)
4663
                ->setTarget('_self');
4664
4665
            $em->persist($tool);
4666
            $em->flush();
4667
4668
            $tool->setId($tool->getIid());
4669
4670
            $em->persist($tool);
4671
            $em->flush();
4672
4673
            return true;
4674
        }
4675
4676
        if ($setVisibility == 1 && $tool) {
4677
            $tool
4678
                ->setName(strip_tags($category->getName()))
4679
                ->setVisibility(1);
4680
4681
            $em->persist($tool);
4682
            $em->flush();
4683
4684
            return true;
4685
        }
4686
4687
        return false;
4688
    }
4689
4690
    /**
4691
     * Check if the learnpath category is visible for a user.
4692
     *
4693
     * @param CLpCategory $category
4694
     * @param User        $user
4695
     *
4696
     * @return bool
4697
     */
4698
    public static function categoryIsVisibleForStudent(
4699
        CLpCategory $category,
4700
        User $user
4701
    ) {
4702
        $subscriptionSettings = learnpath::getSubscriptionSettings();
4703
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4704
            return true;
4705
        }
4706
4707
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4708
4709
        if ($isAllowedToEdit) {
4710
            return true;
4711
        }
4712
4713
        if (empty($category)) {
4714
            return false;
4715
        }
4716
4717
        $users = $category->getUsers();
4718
4719
        if (empty($users) || !$users->count()) {
4720
            return true;
4721
        }
4722
4723
        if ($category->hasUserAdded($user)) {
4724
            return true;
4725
        }
4726
4727
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4728
        if (!empty($groups)) {
4729
            $em = Database::getManager();
4730
4731
            /** @var ItemPropertyRepository $itemRepo */
4732
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4733
4734
            /** @var CourseRepository $courseRepo */
4735
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4736
            $sessionId = api_get_session_id();
4737
            $session = null;
4738
            if (!empty($sessionId)) {
4739
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4740
            }
4741
4742
            $course = $courseRepo->find(api_get_course_int_id());
4743
4744
            // Subscribed groups to a LP
4745
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4746
                TOOL_LEARNPATH_CATEGORY,
4747
                $category->getId(),
4748
                $course,
4749
                $session
4750
            );
4751
4752
            if (!empty($subscribedGroupsInLp)) {
4753
                $groups = array_column($groups, 'iid');
4754
                /** @var CItemProperty $item */
4755
                foreach ($subscribedGroupsInLp as $item) {
4756
                    if ($item->getGroup() &&
4757
                        in_array($item->getGroup()->getId(), $groups)
4758
                    ) {
4759
                        return true;
4760
                    }
4761
                }
4762
            }
4763
        }
4764
4765
        return false;
4766
    }
4767
4768
    /**
4769
     * Check if a learnpath category is published as course tool.
4770
     *
4771
     * @param CLpCategory $category
4772
     * @param int         $courseId
4773
     *
4774
     * @return bool
4775
     */
4776
    public static function categoryIsPublished(
4777
        CLpCategory $category,
4778
        $courseId
4779
    ) {
4780
        $link = self::getCategoryLinkForTool($category->getId());
4781
        $em = Database::getManager();
4782
4783
        $tools = $em
4784
            ->createQuery("
4785
                SELECT t FROM ChamiloCourseBundle:CTool t
4786
                WHERE t.cId = :course AND 
4787
                    t.name = :name AND
4788
                    t.image = 'lp_category.gif' AND
4789
                    t.link LIKE :link
4790
            ")
4791
            ->setParameters([
4792
                'course' => $courseId,
4793
                'name' => strip_tags($category->getName()),
4794
                'link' => "$link%",
4795
            ])
4796
            ->getResult();
4797
4798
        /** @var CTool $tool */
4799
        $tool = current($tools);
4800
4801
        return $tool ? $tool->getVisibility() : false;
4802
    }
4803
4804
    /**
4805
     * Restart the whole learnpath. Return the URL of the first element.
4806
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4807
     * To use a similar method  statically, use the create_new_attempt() method.
4808
     *
4809
     * @return bool
4810
     */
4811
    public function restart()
4812
    {
4813
        if ($this->debug > 0) {
4814
            error_log('In learnpath::restart()', 0);
4815
        }
4816
        // TODO
4817
        // Call autosave method to save the current progress.
4818
        //$this->index = 0;
4819
        if (api_is_invitee()) {
4820
            return false;
4821
        }
4822
        $session_id = api_get_session_id();
4823
        $course_id = api_get_course_int_id();
4824
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4825
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4826
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4827
        if ($this->debug > 2) {
4828
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4829
        }
4830
        Database::query($sql);
4831
        $view_id = Database::insert_id();
4832
4833
        if ($view_id) {
4834
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4835
            Database::query($sql);
4836
            $this->lp_view_id = $view_id;
4837
            $this->attempt = $this->attempt + 1;
4838
        } else {
4839
            $this->error = 'Could not insert into item_view table...';
4840
4841
            return false;
4842
        }
4843
        $this->autocomplete_parents($this->current);
4844
        foreach ($this->items as $index => $dummy) {
4845
            $this->items[$index]->restart();
4846
            $this->items[$index]->set_lp_view($this->lp_view_id);
4847
        }
4848
        $this->first();
4849
4850
        return true;
4851
    }
4852
4853
    /**
4854
     * Saves the current item.
4855
     *
4856
     * @return bool
4857
     */
4858
    public function save_current()
4859
    {
4860
        $debug = $this->debug;
4861
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4862
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4863
        if ($debug) {
4864
            error_log('save_current() saving item '.$this->current, 0);
4865
            error_log(''.print_r($this->items, true), 0);
4866
        }
4867
        if (isset($this->items[$this->current]) &&
4868
            is_object($this->items[$this->current])
4869
        ) {
4870
            if ($debug) {
4871
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4872
            }
4873
4874
            $res = $this->items[$this->current]->save(
4875
                false,
4876
                $this->prerequisites_match($this->current)
4877
            );
4878
            $this->autocomplete_parents($this->current);
4879
            $status = $this->items[$this->current]->get_status();
4880
            $this->update_queue[$this->current] = $status;
4881
4882
            if ($debug) {
4883
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4884
            }
4885
4886
            return $res;
4887
        }
4888
4889
        return false;
4890
    }
4891
4892
    /**
4893
     * Saves the given item.
4894
     *
4895
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4896
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4897
     *
4898
     * @return bool
4899
     */
4900
    public function save_item($item_id = null, $from_outside = true)
4901
    {
4902
        $debug = $this->debug;
4903
        if ($debug) {
4904
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4905
        }
4906
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4907
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4908
        if (empty($item_id)) {
4909
            $item_id = intval($_REQUEST['id']);
4910
        }
4911
        if (empty($item_id)) {
4912
            $item_id = $this->get_current_item_id();
4913
        }
4914
        if (isset($this->items[$item_id]) &&
4915
            is_object($this->items[$item_id])
4916
        ) {
4917
            if ($debug) {
4918
                error_log('Object exists');
4919
            }
4920
4921
            // Saving the item.
4922
            $res = $this->items[$item_id]->save(
4923
                $from_outside,
4924
                $this->prerequisites_match($item_id)
4925
            );
4926
4927
            if ($debug) {
4928
                error_log('update_queue before:');
4929
                error_log(print_r($this->update_queue, 1));
4930
            }
4931
            $this->autocomplete_parents($item_id);
4932
4933
            $status = $this->items[$item_id]->get_status();
4934
            $this->update_queue[$item_id] = $status;
4935
4936
            if ($debug) {
4937
                error_log('get_status(): '.$status);
4938
                error_log('update_queue after:');
4939
                error_log(print_r($this->update_queue, 1));
4940
            }
4941
4942
            return $res;
4943
        }
4944
4945
        return false;
4946
    }
4947
4948
    /**
4949
     * Saves the last item seen's ID only in case.
4950
     */
4951
    public function save_last()
4952
    {
4953
        $course_id = api_get_course_int_id();
4954
        $debug = $this->debug;
4955
        if ($debug) {
4956
            error_log('In learnpath::save_last()', 0);
4957
        }
4958
        $session_condition = api_get_session_condition(
4959
            api_get_session_id(),
4960
            true,
4961
            false
4962
        );
4963
        $table = Database::get_course_table(TABLE_LP_VIEW);
4964
4965
        if (isset($this->current) && !api_is_invitee()) {
4966
            if ($debug) {
4967
                error_log('Saving current item ('.$this->current.') for later review', 0);
4968
            }
4969
            $sql = "UPDATE $table SET
4970
                        last_item = ".intval($this->get_current_item_id())."
4971
                    WHERE
4972
                        c_id = $course_id AND
4973
                        lp_id = ".$this->get_id()." AND
4974
                        user_id = ".$this->get_user_id()." ".$session_condition;
4975
4976
            if ($debug) {
4977
                error_log('Saving last item seen : '.$sql, 0);
4978
            }
4979
            Database::query($sql);
4980
        }
4981
4982
        if (!api_is_invitee()) {
4983
            // Save progress.
4984
            list($progress) = $this->get_progress_bar_text('%');
4985
            if ($progress >= 0 && $progress <= 100) {
4986
                $progress = (int) $progress;
4987
                $sql = "UPDATE $table SET
4988
                            progress = $progress
4989
                        WHERE
4990
                            c_id = $course_id AND
4991
                            lp_id = ".$this->get_id()." AND
4992
                            user_id = ".$this->get_user_id()." ".$session_condition;
4993
                // Ignore errors as some tables might not have the progress field just yet.
4994
                Database::query($sql);
4995
                if ($debug) {
4996
                    error_log($sql);
4997
                }
4998
                $this->progress_db = $progress;
4999
            }
5000
        }
5001
    }
5002
5003
    /**
5004
     * Sets the current item ID (checks if valid and authorized first).
5005
     *
5006
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5007
     */
5008
    public function set_current_item($item_id = null)
5009
    {
5010
        $debug = $this->debug;
5011
        if ($debug) {
5012
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5013
        }
5014
        if (empty($item_id)) {
5015
            if ($debug) {
5016
                error_log('No new current item given, ignore...', 0);
5017
            }
5018
            // Do nothing.
5019
        } else {
5020
            if ($debug) {
5021
                error_log('New current item given is '.$item_id.'...', 0);
5022
            }
5023
            if (is_numeric($item_id)) {
5024
                $item_id = intval($item_id);
5025
                // TODO: Check in database here.
5026
                $this->last = $this->current;
5027
                $this->current = $item_id;
5028
                // TODO: Update $this->index as well.
5029
                foreach ($this->ordered_items as $index => $item) {
5030
                    if ($item == $this->current) {
5031
                        $this->index = $index;
5032
                        break;
5033
                    }
5034
                }
5035
                if ($debug) {
5036
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5037
                }
5038
            } else {
5039
                if ($debug) {
5040
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5041
                }
5042
            }
5043
        }
5044
    }
5045
5046
    /**
5047
     * Sets the encoding.
5048
     *
5049
     * @param string $enc New encoding
5050
     *
5051
     * @return bool
5052
     *
5053
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5054
     */
5055
    public function set_encoding($enc = 'UTF-8')
5056
    {
5057
        if ($this->debug > 0) {
5058
            error_log('In learnpath::set_encoding()', 0);
5059
        }
5060
5061
        $enc = api_refine_encoding_id($enc);
5062
        if (empty($enc)) {
5063
            $enc = api_get_system_encoding();
5064
        }
5065
        if (api_is_encoding_supported($enc)) {
5066
            $lp = $this->get_id();
5067
            if ($lp != 0) {
5068
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5069
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5070
                        WHERE iid = ".$lp;
5071
                $res = Database::query($sql);
5072
5073
                return $res;
5074
            }
5075
        }
5076
5077
        return false;
5078
    }
5079
5080
    /**
5081
     * Sets the JS lib setting in the database directly.
5082
     * This is the JavaScript library file this lp needs to load on startup.
5083
     *
5084
     * @param string $lib Proximity setting
5085
     *
5086
     * @return bool True on update success. False otherwise.
5087
     */
5088
    public function set_jslib($lib = '')
5089
    {
5090
        if ($this->debug > 0) {
5091
            error_log('In learnpath::set_jslib()', 0);
5092
        }
5093
        $lp = $this->get_id();
5094
5095
        if ($lp != 0) {
5096
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5097
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5098
                    WHERE iid = $lp";
5099
            $res = Database::query($sql);
5100
5101
            return $res;
5102
        } else {
5103
            return false;
5104
        }
5105
    }
5106
5107
    /**
5108
     * Sets the name of the LP maker (publisher) (and save).
5109
     *
5110
     * @param string $name Optional string giving the new content_maker of this learnpath
5111
     *
5112
     * @return bool True
5113
     */
5114
    public function set_maker($name = '')
5115
    {
5116
        if ($this->debug > 0) {
5117
            error_log('In learnpath::set_maker()', 0);
5118
        }
5119
        if (empty($name)) {
5120
            return false;
5121
        }
5122
        $this->maker = $name;
5123
        $table = Database::get_course_table(TABLE_LP_MAIN);
5124
        $lp_id = $this->get_id();
5125
        $sql = "UPDATE $table SET
5126
                content_maker = '".Database::escape_string($this->maker)."'
5127
                WHERE iid = $lp_id";
5128
        if ($this->debug > 2) {
5129
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5130
        }
5131
        Database::query($sql);
5132
5133
        return true;
5134
    }
5135
5136
    /**
5137
     * Sets the name of the current learnpath (and save).
5138
     *
5139
     * @param string $name Optional string giving the new name of this learnpath
5140
     *
5141
     * @return bool True/False
5142
     */
5143
    public function set_name($name = null)
5144
    {
5145
        if ($this->debug > 0) {
5146
            error_log('In learnpath::set_name()', 0);
5147
        }
5148
        if (empty($name)) {
5149
            return false;
5150
        }
5151
        $this->name = Database::escape_string($name);
5152
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5153
        $lp_id = $this->get_id();
5154
        $course_id = $this->course_info['real_id'];
5155
        $sql = "UPDATE $lp_table SET
5156
                name = '".Database::escape_string($this->name)."'
5157
                WHERE iid = $lp_id";
5158
        if ($this->debug > 2) {
5159
            error_log('lp updated with new name : '.$this->name, 0);
5160
        }
5161
        $result = Database::query($sql);
5162
        // If the lp is visible on the homepage, change his name there.
5163
        if (Database::affected_rows($result)) {
5164
            $session_id = api_get_session_id();
5165
            $session_condition = api_get_session_condition($session_id);
5166
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5167
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5168
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5169
            	    WHERE
5170
            	        c_id = $course_id AND
5171
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5172
            Database::query($sql);
5173
5174
            return true;
5175
        } else {
5176
            return false;
5177
        }
5178
    }
5179
5180
    /**
5181
     * Set index specified prefix terms for all items in this path.
5182
     *
5183
     * @param string $terms_string Comma-separated list of terms
5184
     * @param string $prefix       Xapian term prefix
5185
     *
5186
     * @return bool False on error, true otherwise
5187
     */
5188
    public function set_terms_by_prefix($terms_string, $prefix)
5189
    {
5190
        $course_id = api_get_course_int_id();
5191
        if (api_get_setting('search_enabled') !== 'true') {
5192
            return false;
5193
        }
5194
5195
        if (!extension_loaded('xapian')) {
5196
            return false;
5197
        }
5198
5199
        $terms_string = trim($terms_string);
5200
        $terms = explode(',', $terms_string);
5201
        array_walk($terms, 'trim_value');
5202
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5203
5204
        // Don't do anything if no change, verify only at DB, not the search engine.
5205
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5206
            return false;
5207
        }
5208
5209
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5210
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5211
5212
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5213
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5214
        $lp_id = intval($_POST['lp_id']);
5215
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5216
        $result = Database::query($sql);
5217
        $di = new ChamiloIndexer();
5218
5219
        while ($lp_item = Database::fetch_array($result)) {
5220
            // Get search_did.
5221
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5222
            $sql = 'SELECT * FROM %s
5223
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5224
                    LIMIT 1';
5225
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5226
5227
            //echo $sql; echo '<br>';
5228
            $res = Database::query($sql);
5229
            if (Database::num_rows($res) > 0) {
5230
                $se_ref = Database::fetch_array($res);
5231
                // Compare terms.
5232
                $doc = $di->get_document($se_ref['search_did']);
5233
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5234
                $xterms = [];
5235
                foreach ($xapian_terms as $xapian_term) {
5236
                    $xterms[] = substr($xapian_term['name'], 1);
5237
                }
5238
5239
                $dterms = $terms;
5240
                $missing_terms = array_diff($dterms, $xterms);
5241
                $deprecated_terms = array_diff($xterms, $dterms);
5242
5243
                // Save it to search engine.
5244
                foreach ($missing_terms as $term) {
5245
                    $doc->add_term($prefix.$term, 1);
5246
                }
5247
                foreach ($deprecated_terms as $term) {
5248
                    $doc->remove_term($prefix.$term);
5249
                }
5250
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5251
                $di->getDb()->flush();
5252
            }
5253
        }
5254
5255
        return true;
5256
    }
5257
5258
    /**
5259
     * Sets the theme of the LP (local/remote) (and save).
5260
     *
5261
     * @param string $name Optional string giving the new theme of this learnpath
5262
     *
5263
     * @return bool Returns true if theme name is not empty
5264
     */
5265
    public function set_theme($name = '')
5266
    {
5267
        if ($this->debug > 0) {
5268
            error_log('In learnpath::set_theme()', 0);
5269
        }
5270
        $this->theme = $name;
5271
        $table = Database::get_course_table(TABLE_LP_MAIN);
5272
        $lp_id = $this->get_id();
5273
        $sql = "UPDATE $table 
5274
                SET theme = '".Database::escape_string($this->theme)."'
5275
                WHERE iid = $lp_id";
5276
        if ($this->debug > 2) {
5277
            error_log('lp updated with new theme : '.$this->theme, 0);
5278
        }
5279
        Database::query($sql);
5280
5281
        return true;
5282
    }
5283
5284
    /**
5285
     * Sets the image of an LP (and save).
5286
     *
5287
     * @param string $name Optional string giving the new image of this learnpath
5288
     *
5289
     * @return bool Returns true if theme name is not empty
5290
     */
5291
    public function set_preview_image($name = '')
5292
    {
5293
        if ($this->debug > 0) {
5294
            error_log('In learnpath::set_preview_image()', 0);
5295
        }
5296
5297
        $this->preview_image = $name;
5298
        $table = Database::get_course_table(TABLE_LP_MAIN);
5299
        $lp_id = $this->get_id();
5300
        $sql = "UPDATE $table SET
5301
                preview_image = '".Database::escape_string($this->preview_image)."'
5302
                WHERE iid = $lp_id";
5303
        if ($this->debug > 2) {
5304
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5305
        }
5306
        Database::query($sql);
5307
5308
        return true;
5309
    }
5310
5311
    /**
5312
     * Sets the author of a LP (and save).
5313
     *
5314
     * @param string $name Optional string giving the new author of this learnpath
5315
     *
5316
     * @return bool Returns true if author's name is not empty
5317
     */
5318
    public function set_author($name = '')
5319
    {
5320
        if ($this->debug > 0) {
5321
            error_log('In learnpath::set_author()', 0);
5322
        }
5323
        $this->author = $name;
5324
        $table = Database::get_course_table(TABLE_LP_MAIN);
5325
        $lp_id = $this->get_id();
5326
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5327
                WHERE iid = $lp_id";
5328
        if ($this->debug > 2) {
5329
            error_log('lp updated with new preview author : '.$this->author, 0);
5330
        }
5331
        Database::query($sql);
5332
5333
        return true;
5334
    }
5335
5336
    /**
5337
     * Sets the hide_toc_frame parameter of a LP (and save).
5338
     *
5339
     * @param int $hide 1 if frame is hidden 0 then else
5340
     *
5341
     * @return bool Returns true if author's name is not empty
5342
     */
5343
    public function set_hide_toc_frame($hide)
5344
    {
5345
        if ($this->debug > 0) {
5346
            error_log('In learnpath::set_hide_toc_frame()', 0);
5347
        }
5348
        if (intval($hide) == $hide) {
5349
            $this->hide_toc_frame = $hide;
5350
            $table = Database::get_course_table(TABLE_LP_MAIN);
5351
            $lp_id = $this->get_id();
5352
            $sql = "UPDATE $table SET
5353
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5354
                    WHERE iid = $lp_id";
5355
            if ($this->debug > 2) {
5356
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5357
            }
5358
            Database::query($sql);
5359
5360
            return true;
5361
        } else {
5362
            return false;
5363
        }
5364
    }
5365
5366
    /**
5367
     * Sets the prerequisite of a LP (and save).
5368
     *
5369
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5370
     *
5371
     * @return bool returns true if prerequisite is not empty
5372
     */
5373
    public function set_prerequisite($prerequisite)
5374
    {
5375
        if ($this->debug > 0) {
5376
            error_log('In learnpath::set_prerequisite()', 0);
5377
        }
5378
        $this->prerequisite = intval($prerequisite);
5379
        $table = Database::get_course_table(TABLE_LP_MAIN);
5380
        $lp_id = $this->get_id();
5381
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5382
                WHERE iid = $lp_id";
5383
        if ($this->debug > 2) {
5384
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5385
        }
5386
        Database::query($sql);
5387
5388
        return true;
5389
    }
5390
5391
    /**
5392
     * Sets the location/proximity of the LP (local/remote) (and save).
5393
     *
5394
     * @param string $name Optional string giving the new location of this learnpath
5395
     *
5396
     * @return bool True on success / False on error
5397
     */
5398
    public function set_proximity($name = '')
5399
    {
5400
        if ($this->debug > 0) {
5401
            error_log('In learnpath::set_proximity()', 0);
5402
        }
5403
        if (empty($name)) {
5404
            return false;
5405
        }
5406
5407
        $this->proximity = $name;
5408
        $table = Database::get_course_table(TABLE_LP_MAIN);
5409
        $lp_id = $this->get_id();
5410
        $sql = "UPDATE $table SET
5411
                    content_local = '".Database::escape_string($name)."'
5412
                WHERE iid = $lp_id";
5413
        if ($this->debug > 2) {
5414
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5415
        }
5416
        Database::query($sql);
5417
5418
        return true;
5419
    }
5420
5421
    /**
5422
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5423
     *
5424
     * @param int $id DB ID of the item
5425
     */
5426
    public function set_previous_item($id)
5427
    {
5428
        if ($this->debug > 0) {
5429
            error_log('In learnpath::set_previous_item()', 0);
5430
        }
5431
        $this->last = $id;
5432
    }
5433
5434
    /**
5435
     * Sets use_max_score.
5436
     *
5437
     * @param int $use_max_score Optional string giving the new location of this learnpath
5438
     *
5439
     * @return bool True on success / False on error
5440
     */
5441
    public function set_use_max_score($use_max_score = 1)
5442
    {
5443
        if ($this->debug > 0) {
5444
            error_log('In learnpath::set_use_max_score()', 0);
5445
        }
5446
        $use_max_score = intval($use_max_score);
5447
        $this->use_max_score = $use_max_score;
5448
        $table = Database::get_course_table(TABLE_LP_MAIN);
5449
        $lp_id = $this->get_id();
5450
        $sql = "UPDATE $table SET
5451
                    use_max_score = '".$this->use_max_score."'
5452
                WHERE iid = $lp_id";
5453
5454
        if ($this->debug > 2) {
5455
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5456
        }
5457
        Database::query($sql);
5458
5459
        return true;
5460
    }
5461
5462
    /**
5463
     * Sets and saves the expired_on date.
5464
     *
5465
     * @param string $expired_on Optional string giving the new author of this learnpath
5466
     *
5467
     * @throws \Doctrine\ORM\OptimisticLockException
5468
     *
5469
     * @return bool Returns true if author's name is not empty
5470
     */
5471
    public function set_expired_on($expired_on)
5472
    {
5473
        if ($this->debug > 0) {
5474
            error_log('In learnpath::set_expired_on()', 0);
5475
        }
5476
5477
        $em = Database::getManager();
5478
        /** @var CLp $lp */
5479
        $lp = $em
5480
            ->getRepository('ChamiloCourseBundle:CLp')
5481
            ->findOneBy(
5482
                [
5483
                    'iid' => $this->get_id(),
5484
                ]
5485
            );
5486
5487
        if (!$lp) {
5488
            return false;
5489
        }
5490
5491
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5492
5493
        $lp->setExpiredOn($this->expired_on);
5494
        $em->persist($lp);
5495
        $em->flush();
5496
5497
        if ($this->debug > 2) {
5498
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5499
        }
5500
5501
        return true;
5502
    }
5503
5504
    /**
5505
     * Sets and saves the publicated_on date.
5506
     *
5507
     * @param string $publicated_on Optional string giving the new author of this learnpath
5508
     *
5509
     * @throws \Doctrine\ORM\OptimisticLockException
5510
     *
5511
     * @return bool Returns true if author's name is not empty
5512
     */
5513
    public function set_publicated_on($publicated_on)
5514
    {
5515
        if ($this->debug > 0) {
5516
            error_log('In learnpath::set_expired_on()', 0);
5517
        }
5518
5519
        $em = Database::getManager();
5520
        /** @var CLp $lp */
5521
        $lp = $em
5522
            ->getRepository('ChamiloCourseBundle:CLp')
5523
            ->findOneBy(
5524
                [
5525
                    'iid' => $this->get_id(),
5526
                ]
5527
            );
5528
5529
        if (!$lp) {
5530
            return false;
5531
        }
5532
5533
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5534
        $lp->setPublicatedOn($this->publicated_on);
5535
        $em->persist($lp);
5536
        $em->flush();
5537
5538
        if ($this->debug > 2) {
5539
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5540
        }
5541
5542
        return true;
5543
    }
5544
5545
    /**
5546
     * Sets and saves the expired_on date.
5547
     *
5548
     * @return bool Returns true if author's name is not empty
5549
     */
5550
    public function set_modified_on()
5551
    {
5552
        if ($this->debug > 0) {
5553
            error_log('In learnpath::set_expired_on()', 0);
5554
        }
5555
        $this->modified_on = api_get_utc_datetime();
5556
        $table = Database::get_course_table(TABLE_LP_MAIN);
5557
        $lp_id = $this->get_id();
5558
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5559
                WHERE iid = $lp_id";
5560
        if ($this->debug > 2) {
5561
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5562
        }
5563
        Database::query($sql);
5564
5565
        return true;
5566
    }
5567
5568
    /**
5569
     * Sets the object's error message.
5570
     *
5571
     * @param string $error Error message. If empty, reinits the error string
5572
     */
5573
    public function set_error_msg($error = '')
5574
    {
5575
        if ($this->debug > 0) {
5576
            error_log('In learnpath::set_error_msg()', 0);
5577
        }
5578
        if (empty($error)) {
5579
            $this->error = '';
5580
        } else {
5581
            $this->error .= $error;
5582
        }
5583
    }
5584
5585
    /**
5586
     * Launches the current item if not 'sco'
5587
     * (starts timer and make sure there is a record ready in the DB).
5588
     *
5589
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5590
     *
5591
     * @return bool
5592
     */
5593
    public function start_current_item($allow_new_attempt = false)
5594
    {
5595
        $debug = $this->debug;
5596
        if ($debug) {
5597
            error_log('In learnpath::start_current_item()');
5598
            error_log('current: '.$this->current);
5599
        }
5600
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5601
            $type = $this->get_type();
5602
            $item_type = $this->items[$this->current]->get_type();
5603
            if (($type == 2 && $item_type != 'sco') ||
5604
                ($type == 3 && $item_type != 'au') ||
5605
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5606
            ) {
5607
                if ($debug) {
5608
                    error_log('item type: '.$item_type);
5609
                    error_log('lp type: '.$type);
5610
                }
5611
                $this->items[$this->current]->open($allow_new_attempt);
5612
                $this->autocomplete_parents($this->current);
5613
                $prereq_check = $this->prerequisites_match($this->current);
5614
                if ($debug) {
5615
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5616
                }
5617
                $this->items[$this->current]->save(false, $prereq_check);
5618
            }
5619
            // If sco, then it is supposed to have been updated by some other call.
5620
            if ($item_type == 'sco') {
5621
                $this->items[$this->current]->restart();
5622
            }
5623
        }
5624
        if ($debug) {
5625
            error_log('lp_view_session_id');
5626
            error_log($this->lp_view_session_id);
5627
            error_log('api session id');
5628
            error_log(api_get_session_id());
5629
            error_log('End of learnpath::start_current_item()');
5630
        }
5631
5632
        return true;
5633
    }
5634
5635
    /**
5636
     * Stops the processing and counters for the old item (as held in $this->last).
5637
     *
5638
     * @return bool True/False
5639
     */
5640
    public function stop_previous_item()
5641
    {
5642
        $debug = $this->debug;
5643
        if ($debug) {
5644
            error_log('In learnpath::stop_previous_item()', 0);
5645
        }
5646
5647
        if ($this->last != 0 && $this->last != $this->current &&
5648
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5649
        ) {
5650
            if ($debug) {
5651
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5652
            }
5653
            switch ($this->get_type()) {
5654
                case '3':
5655
                    if ($this->items[$this->last]->get_type() != 'au') {
5656
                        if ($debug) {
5657
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5658
                        }
5659
                        $this->items[$this->last]->close();
5660
                    } else {
5661
                        if ($debug) {
5662
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5663
                        }
5664
                    }
5665
                    break;
5666
                case '2':
5667
                    if ($this->items[$this->last]->get_type() != 'sco') {
5668
                        if ($debug) {
5669
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5670
                        }
5671
                        $this->items[$this->last]->close();
5672
                    } else {
5673
                        if ($debug) {
5674
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5675
                        }
5676
                    }
5677
                    break;
5678
                case '1':
5679
                default:
5680
                    if ($debug) {
5681
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5682
                    }
5683
                    $this->items[$this->last]->close();
5684
                    break;
5685
            }
5686
        } else {
5687
            if ($debug) {
5688
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5689
            }
5690
5691
            return false;
5692
        }
5693
5694
        return true;
5695
    }
5696
5697
    /**
5698
     * Updates the default view mode from fullscreen to embedded and inversely.
5699
     *
5700
     * @return string The current default view mode ('fullscreen' or 'embedded')
5701
     */
5702
    public function update_default_view_mode()
5703
    {
5704
        if ($this->debug > 0) {
5705
            error_log('In learnpath::update_default_view_mode()', 0);
5706
        }
5707
        $table = Database::get_course_table(TABLE_LP_MAIN);
5708
        $sql = "SELECT * FROM $table
5709
                WHERE iid = ".$this->get_id();
5710
        $res = Database::query($sql);
5711
        if (Database::num_rows($res) > 0) {
5712
            $row = Database::fetch_array($res);
5713
            $default_view_mode = $row['default_view_mod'];
5714
            $view_mode = $default_view_mode;
5715
            switch ($default_view_mode) {
5716
                case 'fullscreen': // default with popup
5717
                    $view_mode = 'embedded';
5718
                    break;
5719
                case 'embedded': // default view with left menu
5720
                    $view_mode = 'embedframe';
5721
                    break;
5722
                case 'embedframe': //folded menu
5723
                    $view_mode = 'impress';
5724
                    break;
5725
                case 'impress':
5726
                    $view_mode = 'fullscreen';
5727
                    break;
5728
            }
5729
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5730
                    WHERE iid = ".$this->get_id();
5731
            Database::query($sql);
5732
            $this->mode = $view_mode;
5733
5734
            return $view_mode;
5735
        } else {
5736
            if ($this->debug > 2) {
5737
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5738
            }
5739
        }
5740
5741
        return -1;
5742
    }
5743
5744
    /**
5745
     * Updates the default behaviour about auto-commiting SCORM updates.
5746
     *
5747
     * @return bool True if auto-commit has been set to 'on', false otherwise
5748
     */
5749
    public function update_default_scorm_commit()
5750
    {
5751
        if ($this->debug > 0) {
5752
            error_log('In learnpath::update_default_scorm_commit()', 0);
5753
        }
5754
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5755
        $sql = "SELECT * FROM $lp_table
5756
                WHERE iid = ".$this->get_id();
5757
        $res = Database::query($sql);
5758
        if (Database::num_rows($res) > 0) {
5759
            $row = Database::fetch_array($res);
5760
            $force = $row['force_commit'];
5761
            if ($force == 1) {
5762
                $force = 0;
5763
                $force_return = false;
5764
            } elseif ($force == 0) {
5765
                $force = 1;
5766
                $force_return = true;
5767
            }
5768
            $sql = "UPDATE $lp_table SET force_commit = $force
5769
                    WHERE iid = ".$this->get_id();
5770
            Database::query($sql);
5771
            $this->force_commit = $force_return;
5772
5773
            return $force_return;
5774
        } else {
5775
            if ($this->debug > 2) {
5776
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5777
            }
5778
        }
5779
5780
        return -1;
5781
    }
5782
5783
    /**
5784
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5785
     *
5786
     * @return bool True on success, false on failure
5787
     */
5788
    public function update_display_order()
5789
    {
5790
        $course_id = api_get_course_int_id();
5791
        $table = Database::get_course_table(TABLE_LP_MAIN);
5792
        $sql = "SELECT * FROM $table 
5793
                WHERE c_id = $course_id 
5794
                ORDER BY display_order";
5795
        $res = Database::query($sql);
5796
        if ($res === false) {
5797
            return false;
5798
        }
5799
5800
        $num = Database::num_rows($res);
5801
        // First check the order is correct, globally (might be wrong because
5802
        // of versions < 1.8.4).
5803
        if ($num > 0) {
5804
            $i = 1;
5805
            while ($row = Database::fetch_array($res)) {
5806
                if ($row['display_order'] != $i) {
5807
                    // If we find a gap in the order, we need to fix it.
5808
                    $sql = "UPDATE $table SET display_order = $i
5809
                            WHERE iid = ".$row['iid'];
5810
                    Database::query($sql);
5811
                }
5812
                $i++;
5813
            }
5814
        }
5815
5816
        return true;
5817
    }
5818
5819
    /**
5820
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5821
     *
5822
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5823
     */
5824
    public function update_reinit()
5825
    {
5826
        if ($this->debug > 0) {
5827
            error_log('In learnpath::update_reinit()', 0);
5828
        }
5829
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5830
        $sql = "SELECT * FROM $lp_table
5831
                WHERE iid = ".$this->get_id();
5832
        $res = Database::query($sql);
5833
        if (Database::num_rows($res) > 0) {
5834
            $row = Database::fetch_array($res);
5835
            $force = $row['prevent_reinit'];
5836
            if ($force == 1) {
5837
                $force = 0;
5838
            } elseif ($force == 0) {
5839
                $force = 1;
5840
            }
5841
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5842
                    WHERE iid = ".$this->get_id();
5843
            Database::query($sql);
5844
            $this->prevent_reinit = $force;
5845
5846
            return $force;
5847
        } else {
5848
            if ($this->debug > 2) {
5849
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5850
            }
5851
        }
5852
5853
        return -1;
5854
    }
5855
5856
    /**
5857
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5858
     *
5859
     * @return string 'single', 'multi' or 'seriousgame'
5860
     *
5861
     * @author ndiechburg <[email protected]>
5862
     */
5863
    public function get_attempt_mode()
5864
    {
5865
        //Set default value for seriousgame_mode
5866
        if (!isset($this->seriousgame_mode)) {
5867
            $this->seriousgame_mode = 0;
5868
        }
5869
        // Set default value for prevent_reinit
5870
        if (!isset($this->prevent_reinit)) {
5871
            $this->prevent_reinit = 1;
5872
        }
5873
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5874
            return 'seriousgame';
5875
        }
5876
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5877
            return 'single';
5878
        }
5879
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5880
            return 'multiple';
5881
        }
5882
5883
        return 'single';
5884
    }
5885
5886
    /**
5887
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5888
     *
5889
     * @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...
5890
     *
5891
     * @return bool
5892
     *
5893
     * @author ndiechburg <[email protected]>
5894
     */
5895
    public function set_attempt_mode($mode)
5896
    {
5897
        switch ($mode) {
5898
            case 'seriousgame':
5899
                $sg_mode = 1;
5900
                $prevent_reinit = 1;
5901
                break;
5902
            case 'single':
5903
                $sg_mode = 0;
5904
                $prevent_reinit = 1;
5905
                break;
5906
            case 'multiple':
5907
                $sg_mode = 0;
5908
                $prevent_reinit = 0;
5909
                break;
5910
            default:
5911
                $sg_mode = 0;
5912
                $prevent_reinit = 0;
5913
                break;
5914
        }
5915
        $this->prevent_reinit = $prevent_reinit;
5916
        $this->seriousgame_mode = $sg_mode;
5917
        $table = Database::get_course_table(TABLE_LP_MAIN);
5918
        $sql = "UPDATE $table SET
5919
                prevent_reinit = $prevent_reinit ,
5920
                seriousgame_mode = $sg_mode
5921
                WHERE iid = ".$this->get_id();
5922
        $res = Database::query($sql);
5923
        if ($res) {
5924
            return true;
5925
        } else {
5926
            return false;
5927
        }
5928
    }
5929
5930
    /**
5931
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5932
     *
5933
     * @author ndiechburg <[email protected]>
5934
     */
5935
    public function switch_attempt_mode()
5936
    {
5937
        if ($this->debug > 0) {
5938
            error_log('In learnpath::switch_attempt_mode()', 0);
5939
        }
5940
        $mode = $this->get_attempt_mode();
5941
        switch ($mode) {
5942
            case 'single':
5943
                $next_mode = 'multiple';
5944
                break;
5945
            case 'multiple':
5946
                $next_mode = 'seriousgame';
5947
                break;
5948
            case 'seriousgame':
5949
                $next_mode = 'single';
5950
                break;
5951
            default:
5952
                $next_mode = 'single';
5953
                break;
5954
        }
5955
        $this->set_attempt_mode($next_mode);
5956
    }
5957
5958
    /**
5959
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5960
     * but possibility to do again a completed item.
5961
     *
5962
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5963
     *
5964
     * @author ndiechburg <[email protected]>
5965
     */
5966
    public function set_seriousgame_mode()
5967
    {
5968
        if ($this->debug > 0) {
5969
            error_log('In learnpath::set_seriousgame_mode()', 0);
5970
        }
5971
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5972
        $sql = "SELECT * FROM $lp_table 
5973
                WHERE iid = ".$this->get_id();
5974
        $res = Database::query($sql);
5975
        if (Database::num_rows($res) > 0) {
5976
            $row = Database::fetch_array($res);
5977
            $force = $row['seriousgame_mode'];
5978
            if ($force == 1) {
5979
                $force = 0;
5980
            } elseif ($force == 0) {
5981
                $force = 1;
5982
            }
5983
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5984
			        WHERE iid = ".$this->get_id();
5985
            Database::query($sql);
5986
            $this->seriousgame_mode = $force;
5987
5988
            return $force;
5989
        } else {
5990
            if ($this->debug > 2) {
5991
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
5992
            }
5993
        }
5994
5995
        return -1;
5996
    }
5997
5998
    /**
5999
     * Updates the "scorm_debug" value that shows or hide the debug window.
6000
     *
6001
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6002
     */
6003
    public function update_scorm_debug()
6004
    {
6005
        if ($this->debug > 0) {
6006
            error_log('In learnpath::update_scorm_debug()', 0);
6007
        }
6008
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6009
        $sql = "SELECT * FROM $lp_table
6010
                WHERE iid = ".$this->get_id();
6011
        $res = Database::query($sql);
6012
        if (Database::num_rows($res) > 0) {
6013
            $row = Database::fetch_array($res);
6014
            $force = $row['debug'];
6015
            if ($force == 1) {
6016
                $force = 0;
6017
            } elseif ($force == 0) {
6018
                $force = 1;
6019
            }
6020
            $sql = "UPDATE $lp_table SET debug = $force
6021
                    WHERE iid = ".$this->get_id();
6022
            Database::query($sql);
6023
            $this->scorm_debug = $force;
6024
6025
            return $force;
6026
        } else {
6027
            if ($this->debug > 2) {
6028
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6029
            }
6030
        }
6031
6032
        return -1;
6033
    }
6034
6035
    /**
6036
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6037
     *
6038
     * @author Kevin Van Den Haute
6039
     *
6040
     * @param  array
6041
     */
6042
    public function tree_array($array)
6043
    {
6044
        if ($this->debug > 1) {
6045
            error_log('In learnpath::tree_array()', 0);
6046
        }
6047
        $array = $this->sort_tree_array($array);
6048
        $this->create_tree_array($array);
6049
    }
6050
6051
    /**
6052
     * Creates an array with the elements of the learning path tree in it.
6053
     *
6054
     * @author Kevin Van Den Haute
6055
     *
6056
     * @param array $array
6057
     * @param int   $parent
6058
     * @param int   $depth
6059
     * @param array $tmp
6060
     */
6061
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6062
    {
6063
        if ($this->debug > 1) {
6064
            error_log('In learnpath::create_tree_array())', 0);
6065
        }
6066
6067
        if (is_array($array)) {
6068
            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...
6069
                if ($array[$i]['parent_item_id'] == $parent) {
6070
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6071
                        $tmp[] = $array[$i]['parent_item_id'];
6072
                        $depth++;
6073
                    }
6074
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6075
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6076
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6077
6078
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6079
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6080
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6081
                    $this->arrMenu[] = [
6082
                        'id' => $array[$i]['id'],
6083
                        'ref' => $ref,
6084
                        'item_type' => $array[$i]['item_type'],
6085
                        'title' => $array[$i]['title'],
6086
                        'path' => $path,
6087
                        'description' => $array[$i]['description'],
6088
                        'parent_item_id' => $array[$i]['parent_item_id'],
6089
                        'previous_item_id' => $array[$i]['previous_item_id'],
6090
                        'next_item_id' => $array[$i]['next_item_id'],
6091
                        'min_score' => $array[$i]['min_score'],
6092
                        'max_score' => $array[$i]['max_score'],
6093
                        'mastery_score' => $array[$i]['mastery_score'],
6094
                        'display_order' => $array[$i]['display_order'],
6095
                        'prerequisite' => $preq,
6096
                        'depth' => $depth,
6097
                        'audio' => $audio,
6098
                        'prerequisite_min_score' => $prerequisiteMinScore,
6099
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6100
                    ];
6101
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6102
                }
6103
            }
6104
        }
6105
    }
6106
6107
    /**
6108
     * Sorts a multi dimensional array by parent id and display order.
6109
     *
6110
     * @author Kevin Van Den Haute
6111
     *
6112
     * @param array $array (array with al the learning path items in it)
6113
     *
6114
     * @return array
6115
     */
6116
    public function sort_tree_array($array)
6117
    {
6118
        foreach ($array as $key => $row) {
6119
            $parent[$key] = $row['parent_item_id'];
6120
            $position[$key] = $row['display_order'];
6121
        }
6122
6123
        if (count($array) > 0) {
6124
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6125
        }
6126
6127
        return $array;
6128
    }
6129
6130
    /**
6131
     * Function that creates a html list of learning path items so that we can add audio files to them.
6132
     *
6133
     * @author Kevin Van Den Haute
6134
     *
6135
     * @return string
6136
     */
6137
    public function overview()
6138
    {
6139
        if ($this->debug > 0) {
6140
            error_log('In learnpath::overview()', 0);
6141
        }
6142
6143
        $return = '';
6144
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6145
6146
        // we need to start a form when we want to update all the mp3 files
6147
        if ($update_audio == 'true') {
6148
            $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">';
6149
        }
6150
        $return .= '<div id="message"></div>';
6151
        if (count($this->items) == 0) {
6152
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6153
        } else {
6154
            $return_audio = '<table class="data_table">';
6155
            $return_audio .= '<tr>';
6156
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6157
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6158
            $return_audio .= '</tr>';
6159
6160
            if ($update_audio != 'true') {
6161
                $return .= '<div class="col-md-12">';
6162
                $return .= self::return_new_tree($update_audio);
6163
                $return .= '</div>';
6164
                $return .= Display::div(
6165
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6166
                    ['style' => 'float:left; margin-top:15px;width:100%']
6167
                );
6168
            } else {
6169
                $return_audio .= self::return_new_tree($update_audio);
6170
                $return .= $return_audio.'</table>';
6171
            }
6172
6173
            // We need to close the form when we are updating the mp3 files.
6174
            if ($update_audio == 'true') {
6175
                $return .= '<div class="footer-audio">';
6176
                $return .= Display::button(
6177
                    'save_audio',
6178
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6179
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6180
                );
6181
                $return .= '</div>';
6182
            }
6183
        }
6184
6185
        // We need to close the form when we are updating the mp3 files.
6186
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6187
            $return .= '</form>';
6188
        }
6189
6190
        return $return;
6191
    }
6192
6193
    /**
6194
     * @param string $update_audio
6195
     *
6196
     * @return array
6197
     */
6198
    public function processBuildMenuElements($update_audio = 'false')
6199
    {
6200
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6201
        $course_id = api_get_course_int_id();
6202
        $table = Database::get_course_table(TABLE_LP_ITEM);
6203
6204
        $sql = "SELECT * FROM $table
6205
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6206
6207
        $result = Database::query($sql);
6208
        $arrLP = [];
6209
        while ($row = Database::fetch_array($result)) {
6210
            $arrLP[] = [
6211
                'id' => $row['iid'],
6212
                'item_type' => $row['item_type'],
6213
                'title' => Security::remove_XSS($row['title']),
6214
                'path' => $row['path'],
6215
                'description' => Security::remove_XSS($row['description']),
6216
                'parent_item_id' => $row['parent_item_id'],
6217
                'previous_item_id' => $row['previous_item_id'],
6218
                'next_item_id' => $row['next_item_id'],
6219
                'max_score' => $row['max_score'],
6220
                'min_score' => $row['min_score'],
6221
                'mastery_score' => $row['mastery_score'],
6222
                'prerequisite' => $row['prerequisite'],
6223
                'display_order' => $row['display_order'],
6224
                'audio' => $row['audio'],
6225
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6226
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6227
            ];
6228
        }
6229
6230
        $this->tree_array($arrLP);
6231
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6232
        unset($this->arrMenu);
6233
        $default_data = null;
6234
        $default_content = null;
6235
        $elements = [];
6236
        $return_audio = null;
6237
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6238
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6239
6240
        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...
6241
            $title = $arrLP[$i]['title'];
6242
            $title_cut = cut($arrLP[$i]['title'], 25);
6243
6244
            // Link for the documents
6245
            if ($arrLP[$i]['item_type'] == 'document') {
6246
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6247
                $title_cut = Display::url(
6248
                    $title_cut,
6249
                    $url,
6250
                    [
6251
                        'class' => 'ajax moved',
6252
                        'data-title' => $title,
6253
                        'title' => $title,
6254
                    ]
6255
                );
6256
            }
6257
6258
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6259
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6260
                Session::write('pathItem', $arrLP[$i]['path']);
6261
            }
6262
6263
            if (($i % 2) == 0) {
6264
                $oddClass = 'row_odd';
6265
            } else {
6266
                $oddClass = 'row_even';
6267
            }
6268
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6269
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6270
6271
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6272
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6273
            } else {
6274
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6275
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6276
                } else {
6277
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6278
                        $icon = Display::return_icon('certificate.png');
6279
                    } else {
6280
                        $icon = Display::return_icon('folder_document.gif');
6281
                    }
6282
                }
6283
            }
6284
6285
            // The audio column.
6286
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6287
            $audio = '';
6288
            if (!$update_audio || $update_audio != 'true') {
6289
                if (empty($arrLP[$i]['audio'])) {
6290
                    $audio .= '';
6291
                }
6292
            } else {
6293
                $types = self::getChapterTypes();
6294
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6295
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6296
                    if (!empty($arrLP[$i]['audio'])) {
6297
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6298
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6299
                    }
6300
                }
6301
            }
6302
6303
            $return_audio .= Display::span($icon.' '.$title).
6304
                Display::tag(
6305
                    'td',
6306
                    $audio,
6307
                    ['style' => '']
6308
                );
6309
            $return_audio .= '</td>';
6310
            $move_icon = '';
6311
            $move_item_icon = '';
6312
            $edit_icon = '';
6313
            $delete_icon = '';
6314
            $audio_icon = '';
6315
            $prerequisities_icon = '';
6316
            $forumIcon = '';
6317
            $previewIcon = '';
6318
6319
            if ($is_allowed_to_edit) {
6320
                if (!$update_audio || $update_audio != 'true') {
6321
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6322
                        $move_icon .= '<a class="moved" href="#">';
6323
                        $move_icon .= Display::return_icon(
6324
                            'move_everywhere.png',
6325
                            get_lang('Move'),
6326
                            [],
6327
                            ICON_SIZE_TINY
6328
                        );
6329
                        $move_icon .= '</a>';
6330
                    }
6331
                }
6332
6333
                // No edit for this item types
6334
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6335
                    if ($arrLP[$i]['item_type'] != 'dir') {
6336
                        $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">';
6337
                        $edit_icon .= Display::return_icon(
6338
                            'edit.png',
6339
                            get_lang('LearnpathEditModule'),
6340
                            [],
6341
                            ICON_SIZE_TINY
6342
                        );
6343
                        $edit_icon .= '</a>';
6344
6345
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6346
                            $forumThread = null;
6347
                            if (isset($this->items[$arrLP[$i]['id']])) {
6348
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6349
                                    $this->course_int_id,
6350
                                    $this->lp_session_id
6351
                                );
6352
                            }
6353
                            if ($forumThread) {
6354
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6355
                                        'action' => 'dissociate_forum',
6356
                                        'id' => $arrLP[$i]['id'],
6357
                                        'lp_id' => $this->lp_id,
6358
                                    ]);
6359
                                $forumIcon = Display::url(
6360
                                    Display::return_icon(
6361
                                        'forum.png',
6362
                                        get_lang('DissociateForumToLPItem'),
6363
                                        [],
6364
                                        ICON_SIZE_TINY
6365
                                    ),
6366
                                    $forumIconUrl,
6367
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6368
                                );
6369
                            } else {
6370
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6371
                                        'action' => 'create_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('AssociateForumToLPItem'),
6379
                                        [],
6380
                                        ICON_SIZE_TINY
6381
                                    ),
6382
                                    $forumIconUrl,
6383
                                    ['class' => "btn btn-default lp-btn-associate-forum"]
6384
                                );
6385
                            }
6386
                        }
6387
                    } else {
6388
                        $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">';
6389
                        $edit_icon .= Display::return_icon(
6390
                            'edit.png',
6391
                            get_lang('LearnpathEditModule'),
6392
                            [],
6393
                            ICON_SIZE_TINY
6394
                        );
6395
                        $edit_icon .= '</a>';
6396
                    }
6397
                } else {
6398
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6399
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6400
                        $edit_icon .= Display::return_icon(
6401
                            'edit.png',
6402
                            get_lang('Edit'),
6403
                            [],
6404
                            ICON_SIZE_TINY
6405
                        );
6406
                        $edit_icon .= '</a>';
6407
                    }
6408
                }
6409
6410
                $delete_icon .= ' <a 
6411
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6412
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6413
                    class="btn btn-default">';
6414
                $delete_icon .= Display::return_icon(
6415
                    'delete.png',
6416
                    get_lang('LearnpathDeleteModule'),
6417
                    [],
6418
                    ICON_SIZE_TINY
6419
                );
6420
                $delete_icon .= '</a>';
6421
6422
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6423
                $previewImage = Display::return_icon(
6424
                    'preview_view.png',
6425
                    get_lang('Preview'),
6426
                    [],
6427
                    ICON_SIZE_TINY
6428
                );
6429
6430
                switch ($arrLP[$i]['item_type']) {
6431
                    case TOOL_DOCUMENT:
6432
                    case TOOL_LP_FINAL_ITEM:
6433
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6434
                        $previewIcon = Display::url(
6435
                            $previewImage,
6436
                            $urlPreviewLink,
6437
                            [
6438
                                'target' => '_blank',
6439
                                'class' => 'btn btn-default',
6440
                                'data-title' => $arrLP[$i]['title'],
6441
                                'title' => $arrLP[$i]['title'],
6442
                            ]
6443
                        );
6444
                        break;
6445
                    case TOOL_THREAD:
6446
                    case TOOL_FORUM:
6447
                    case TOOL_QUIZ:
6448
                    case TOOL_STUDENTPUBLICATION:
6449
                    case TOOL_LP_FINAL_ITEM:
6450
                    case TOOL_LINK:
6451
                        //$target = '';
6452
                        //$class = 'btn btn-default ajax';
6453
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6454
                        $class = 'btn btn-default';
6455
                        $target = '_blank';
6456
                        //}
6457
6458
                        $link = self::rl_get_resource_link_for_learnpath(
6459
                            $this->course_int_id,
6460
                            $this->lp_id,
6461
                            $arrLP[$i]['id'],
6462
                            0
6463
                        );
6464
                        $previewIcon = Display::url(
6465
                            $previewImage,
6466
                            $link,
6467
                            [
6468
                                'class' => $class,
6469
                                'data-title' => $arrLP[$i]['title'],
6470
                                'title' => $arrLP[$i]['title'],
6471
                                'target' => $target,
6472
                            ]
6473
                        );
6474
                        break;
6475
                    default:
6476
                        $previewIcon = Display::url(
6477
                            $previewImage,
6478
                            $url.'&action=view_item',
6479
                            ['class' => 'btn btn-default', 'target' => '_blank']
6480
                        );
6481
                        break;
6482
                }
6483
6484
                if ($arrLP[$i]['item_type'] != 'dir') {
6485
                    $prerequisities_icon = Display::url(
6486
                        Display::return_icon(
6487
                            'accept.png',
6488
                            get_lang('LearnpathPrerequisites'),
6489
                            [],
6490
                            ICON_SIZE_TINY
6491
                        ),
6492
                        $url.'&action=edit_item_prereq',
6493
                        ['class' => 'btn btn-default']
6494
                    );
6495
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6496
                        $move_item_icon = Display::url(
6497
                            Display::return_icon(
6498
                                'move.png',
6499
                                get_lang('Move'),
6500
                                [],
6501
                                ICON_SIZE_TINY
6502
                            ),
6503
                            $url.'&action=move_item',
6504
                            ['class' => 'btn btn-default']
6505
                        );
6506
                    }
6507
                    $audio_icon = Display::url(
6508
                        Display::return_icon(
6509
                            'audio.png',
6510
                            get_lang('UplUpload'),
6511
                            [],
6512
                            ICON_SIZE_TINY
6513
                        ),
6514
                        $url.'&action=add_audio',
6515
                        ['class' => 'btn btn-default']
6516
                    );
6517
                }
6518
            }
6519
            if ($update_audio != 'true') {
6520
                $row = $move_icon.' '.$icon.
6521
                    Display::span($title_cut).
6522
                    Display::tag(
6523
                        'div',
6524
                        "<div class=\"btn-group btn-group-xs\">
6525
                                    $previewIcon 
6526
                                    $audio 
6527
                                    $edit_icon 
6528
                                    $forumIcon 
6529
                                    $prerequisities_icon 
6530
                                    $move_item_icon 
6531
                                    $audio_icon 
6532
                                    $delete_icon
6533
                                </div>",
6534
                        ['class' => 'btn-toolbar button_actions']
6535
                    );
6536
            } else {
6537
                $row =
6538
                    Display::span($title.$icon).
6539
                    Display::span($audio, ['class' => 'button_actions']);
6540
            }
6541
6542
            $parent_id = $arrLP[$i]['parent_item_id'];
6543
            $default_data[$arrLP[$i]['id']] = $row;
6544
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6545
6546
            if (empty($parent_id)) {
6547
                $elements[$arrLP[$i]['id']]['data'] = $row;
6548
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6549
            } else {
6550
                $parent_arrays = [];
6551
                if ($arrLP[$i]['depth'] > 1) {
6552
                    //Getting list of parents
6553
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6554
                        foreach ($arrLP as $item) {
6555
                            if ($item['id'] == $parent_id) {
6556
                                if ($item['parent_item_id'] == 0) {
6557
                                    $parent_id = $item['id'];
6558
                                    break;
6559
                                } else {
6560
                                    $parent_id = $item['parent_item_id'];
6561
                                    if (empty($parent_arrays)) {
6562
                                        $parent_arrays[] = intval($item['id']);
6563
                                    }
6564
                                    $parent_arrays[] = $parent_id;
6565
                                    break;
6566
                                }
6567
                            }
6568
                        }
6569
                    }
6570
                }
6571
6572
                if (!empty($parent_arrays)) {
6573
                    $parent_arrays = array_reverse($parent_arrays);
6574
                    $val = '$elements';
6575
                    $x = 0;
6576
                    foreach ($parent_arrays as $item) {
6577
                        if ($x != count($parent_arrays) - 1) {
6578
                            $val .= '["'.$item.'"]["children"]';
6579
                        } else {
6580
                            $val .= '["'.$item.'"]["children"]';
6581
                        }
6582
                        $x++;
6583
                    }
6584
                    $val .= "";
6585
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6586
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6587
                } else {
6588
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6589
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6590
                }
6591
            }
6592
        }
6593
6594
        return [
6595
            'elements' => $elements,
6596
            'default_data' => $default_data,
6597
            'default_content' => $default_content,
6598
            'return_audio' => $return_audio,
6599
        ];
6600
    }
6601
6602
    /**
6603
     * @param string $updateAudio true/false strings
6604
     *
6605
     * @return string
6606
     */
6607
    public function returnLpItemList($updateAudio)
6608
    {
6609
        $result = $this->processBuildMenuElements($updateAudio);
6610
6611
        $html = self::print_recursive(
6612
            $result['elements'],
6613
            $result['default_data'],
6614
            $result['default_content']
6615
        );
6616
6617
        if (!empty($html)) {
6618
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6619
        }
6620
6621
        return $html;
6622
    }
6623
6624
    /**
6625
     * @param string $update_audio
6626
     * @param bool   $drop_element_here
6627
     *
6628
     * @return string
6629
     */
6630
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6631
    {
6632
        $return = '';
6633
        $result = $this->processBuildMenuElements($update_audio);
6634
6635
        $list = '<ul id="lp_item_list">';
6636
        $tree = self::print_recursive(
6637
            $result['elements'],
6638
            $result['default_data'],
6639
            $result['default_content']
6640
        );
6641
6642
        if (!empty($tree)) {
6643
            $list .= $tree;
6644
        } else {
6645
            if ($drop_element_here) {
6646
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6647
            }
6648
        }
6649
        $list .= '</ul>';
6650
6651
        $return .= Display::panelCollapse(
6652
            $this->name,
6653
            $list,
6654
            'scorm-list',
6655
            null,
6656
            'scorm-list-accordion',
6657
            'scorm-list-collapse'
6658
        );
6659
6660
        if ($update_audio == 'true') {
6661
            $return = $result['return_audio'];
6662
        }
6663
6664
        return $return;
6665
    }
6666
6667
    /**
6668
     * @param array $elements
6669
     * @param array $default_data
6670
     * @param array $default_content
6671
     *
6672
     * @return string
6673
     */
6674
    public function print_recursive($elements, $default_data, $default_content)
6675
    {
6676
        $return = '';
6677
        foreach ($elements as $key => $item) {
6678
            if (isset($item['load_data']) || empty($item['data'])) {
6679
                $item['data'] = $default_data[$item['load_data']];
6680
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6681
            }
6682
            $sub_list = '';
6683
            if (isset($item['type']) && $item['type'] == 'dir') {
6684
                // empty value
6685
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6686
            }
6687
            if (empty($item['children'])) {
6688
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6689
                $active = null;
6690
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6691
                    $active = 'active';
6692
                }
6693
                $return .= Display::tag(
6694
                    'li',
6695
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6696
                    ['id' => $key, 'class' => 'record li_container']
6697
                );
6698
            } else {
6699
                // Sections
6700
                $data = '';
6701
                if (isset($item['children'])) {
6702
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6703
                }
6704
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6705
                $return .= Display::tag(
6706
                    'li',
6707
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6708
                    ['id' => $key, 'class' => 'record li_container']
6709
                );
6710
            }
6711
        }
6712
6713
        return $return;
6714
    }
6715
6716
    /**
6717
     * This function builds the action menu.
6718
     *
6719
     * @param bool $returnContent          Optional
6720
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6721
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6722
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6723
     *
6724
     * @return string
6725
     */
6726
    public function build_action_menu(
6727
        $returnContent = false,
6728
        $showRequirementButtons = true,
6729
        $isConfigPage = false,
6730
        $allowExpand = true
6731
    ) {
6732
        $actionsLeft = '';
6733
        $actionsRight = '';
6734
        $actionsLeft .= Display::url(
6735
            Display::return_icon(
6736
                'preview_view.png',
6737
                get_lang('Preview'),
6738
                '',
6739
                ICON_SIZE_MEDIUM
6740
            ),
6741
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6742
                'action' => 'view',
6743
                'lp_id' => $this->lp_id,
6744
                'isStudentView' => 'true',
6745
            ])
6746
        );
6747
6748
        $actionsLeft .= Display::url(
6749
            Display::return_icon(
6750
                'upload_audio.png',
6751
                get_lang('UpdateAllAudioFragments'),
6752
                '',
6753
                ICON_SIZE_MEDIUM
6754
            ),
6755
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6756
                'action' => 'admin_view',
6757
                'lp_id' => $this->lp_id,
6758
                'updateaudio' => 'true',
6759
            ])
6760
        );
6761
6762
        if (!$isConfigPage) {
6763
            $actionsLeft .= Display::url(
6764
                Display::return_icon(
6765
                    'settings.png',
6766
                    get_lang('CourseSettings'),
6767
                    '',
6768
                    ICON_SIZE_MEDIUM
6769
                ),
6770
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6771
                    'action' => 'edit',
6772
                    'lp_id' => $this->lp_id,
6773
                ])
6774
            );
6775
        } else {
6776
            $actionsLeft .= Display::url(
6777
                Display::return_icon(
6778
                    'edit.png',
6779
                    get_lang('Edit'),
6780
                    '',
6781
                    ICON_SIZE_MEDIUM
6782
                ),
6783
                'lp_controller.php?'.http_build_query([
6784
                    'action' => 'build',
6785
                    'lp_id' => $this->lp_id,
6786
                ]).'&'.api_get_cidreq()
6787
            );
6788
        }
6789
6790
        if ($allowExpand) {
6791
            $actionsLeft .= Display::url(
6792
                Display::return_icon(
6793
                    'expand.png',
6794
                    get_lang('Expand'),
6795
                    ['id' => 'expand'],
6796
                    ICON_SIZE_MEDIUM
6797
                ).
6798
                Display::return_icon(
6799
                    'contract.png',
6800
                    get_lang('Collapse'),
6801
                    ['id' => 'contract', 'class' => 'hide'],
6802
                    ICON_SIZE_MEDIUM
6803
                ),
6804
                '#',
6805
                ['role' => 'button', 'id' => 'hide_bar_template']
6806
            );
6807
        }
6808
6809
        if ($showRequirementButtons) {
6810
            $buttons = [
6811
                [
6812
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6813
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6814
                        'action' => 'set_previous_step_as_prerequisite',
6815
                        'lp_id' => $this->lp_id,
6816
                    ]),
6817
                ],
6818
                [
6819
                    'title' => get_lang('ClearAllPrerequisites'),
6820
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6821
                        'action' => 'clear_prerequisites',
6822
                        'lp_id' => $this->lp_id,
6823
                    ]),
6824
                ],
6825
            ];
6826
            $actionsRight = Display::groupButtonWithDropDown(
6827
                get_lang('PrerequisitesOptions'),
6828
                $buttons,
6829
                true
6830
            );
6831
        }
6832
6833
        $toolbar = Display::toolbarAction(
6834
            'actions-lp-controller',
6835
            [$actionsLeft, $actionsRight]
6836
        );
6837
6838
        if ($returnContent) {
6839
            return $toolbar;
6840
        }
6841
6842
        echo $toolbar;
6843
    }
6844
6845
    /**
6846
     * Creates the default learning path folder.
6847
     *
6848
     * @param array $course
6849
     * @param int   $creatorId
6850
     *
6851
     * @return bool
6852
     */
6853
    public static function generate_learning_path_folder($course, $creatorId = 0)
6854
    {
6855
        // Creating learning_path folder
6856
        $dir = '/learning_path';
6857
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6858
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6859
6860
        $folder = false;
6861
        if (!is_dir($filepath.'/'.$dir)) {
6862
            $folderData = create_unexisting_directory(
6863
                $course,
6864
                $creatorId,
6865
                0,
6866
                null,
6867
                0,
6868
                $filepath,
6869
                $dir,
6870
                get_lang('LearningPaths'),
6871
                0
6872
            );
6873
            if (!empty($folderData)) {
6874
                $folder = true;
6875
            }
6876
        } else {
6877
            $folder = true;
6878
        }
6879
6880
        return $folder;
6881
    }
6882
6883
    /**
6884
     * @param array  $course
6885
     * @param string $lp_name
6886
     * @param int    $creatorId
6887
     *
6888
     * @return array
6889
     */
6890
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6891
    {
6892
        $filepath = '';
6893
        $dir = '/learning_path/';
6894
6895
        if (empty($lp_name)) {
6896
            $lp_name = $this->name;
6897
        }
6898
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6899
6900
        $folder = self::generate_learning_path_folder($course, $creatorId);
6901
6902
        // Limits title size
6903
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6904
        $dir = $dir.$title;
6905
6906
        // Creating LP folder
6907
        $documentId = null;
6908
        if ($folder) {
6909
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6910
            if (!is_dir($filepath.'/'.$dir)) {
6911
                $folderData = create_unexisting_directory(
6912
                    $course,
6913
                    $creatorId,
6914
                    0,
6915
                    0,
6916
                    0,
6917
                    $filepath,
6918
                    $dir,
6919
                    $lp_name
6920
                );
6921
                if (!empty($folderData)) {
6922
                    $folder = true;
6923
                }
6924
6925
                $documentId = $folderData['id'];
6926
            } else {
6927
                $folder = true;
6928
            }
6929
            $dir = $dir.'/';
6930
            if ($folder) {
6931
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6932
            }
6933
        }
6934
6935
        if (empty($documentId)) {
6936
            $dir = api_remove_trailing_slash($dir);
6937
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6938
        }
6939
6940
        $array = [
6941
            'dir' => $dir,
6942
            'filepath' => $filepath,
6943
            'folder' => $folder,
6944
            'id' => $documentId,
6945
        ];
6946
6947
        return $array;
6948
    }
6949
6950
    /**
6951
     * Create a new document //still needs some finetuning.
6952
     *
6953
     * @param array  $courseInfo
6954
     * @param string $content
6955
     * @param string $title
6956
     * @param string $extension
6957
     * @param int    $parentId
6958
     * @param int    $creatorId  creator id
6959
     *
6960
     * @return int
6961
     */
6962
    public function create_document(
6963
        $courseInfo,
6964
        $content = '',
6965
        $title = '',
6966
        $extension = 'html',
6967
        $parentId = 0,
6968
        $creatorId = 0
6969
    ) {
6970
        if (!empty($courseInfo)) {
6971
            $course_id = $courseInfo['real_id'];
6972
        } else {
6973
            $course_id = api_get_course_int_id();
6974
        }
6975
6976
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6977
        $sessionId = api_get_session_id();
6978
6979
        // Generates folder
6980
        $result = $this->generate_lp_folder($courseInfo);
6981
        $dir = $result['dir'];
6982
6983
        if (empty($parentId) || $parentId == '/') {
6984
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6985
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6986
6987
            if ($parentId === '/') {
6988
                $dir = '/';
6989
            }
6990
6991
            // Please, do not modify this dirname formatting.
6992
            if (strstr($dir, '..')) {
6993
                $dir = '/';
6994
            }
6995
6996
            if (!empty($dir[0]) && $dir[0] == '.') {
6997
                $dir = substr($dir, 1);
6998
            }
6999
            if (!empty($dir[0]) && $dir[0] != '/') {
7000
                $dir = '/'.$dir;
7001
            }
7002
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7003
                $dir .= '/';
7004
            }
7005
        } else {
7006
            $parentInfo = DocumentManager::get_document_data_by_id(
7007
                $parentId,
7008
                $courseInfo['code']
7009
            );
7010
            if (!empty($parentInfo)) {
7011
                $dir = $parentInfo['path'].'/';
7012
            }
7013
        }
7014
7015
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7016
        if (!is_dir($filepath)) {
7017
            $dir = '/';
7018
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7019
        }
7020
7021
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7022
        // is already escaped twice when it gets here.
7023
7024
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7025
        if (!empty($title)) {
7026
            $title = api_replace_dangerous_char(stripslashes($title));
7027
        } else {
7028
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7029
        }
7030
7031
        $title = disable_dangerous_file($title);
7032
        $filename = $title;
7033
        $content = !empty($content) ? $content : $_POST['content_lp'];
7034
        $tmp_filename = $filename;
7035
7036
        $i = 0;
7037
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7038
            $tmp_filename = $filename.'_'.++$i;
7039
        }
7040
7041
        $filename = $tmp_filename.'.'.$extension;
7042
        if ($extension == 'html') {
7043
            $content = stripslashes($content);
7044
            $content = str_replace(
7045
                api_get_path(WEB_COURSE_PATH),
7046
                api_get_path(REL_PATH).'courses/',
7047
                $content
7048
            );
7049
7050
            // Change the path of mp3 to absolute.
7051
7052
            // The first regexp deals with :// urls.
7053
            $content = preg_replace(
7054
                "|(flashvars=\"file=)([^:/]+)/|",
7055
                "$1".api_get_path(
7056
                    REL_COURSE_PATH
7057
                ).$courseInfo['path'].'/document/',
7058
                $content
7059
            );
7060
            // The second regexp deals with audio/ urls.
7061
            $content = preg_replace(
7062
                "|(flashvars=\"file=)([^/]+)/|",
7063
                "$1".api_get_path(
7064
                    REL_COURSE_PATH
7065
                ).$courseInfo['path'].'/document/$2/',
7066
                $content
7067
            );
7068
            // For flv player: To prevent edition problem with firefox,
7069
            // we have to use a strange tip (don't blame me please).
7070
            $content = str_replace(
7071
                '</body>',
7072
                '<style type="text/css">body{}</style></body>',
7073
                $content
7074
            );
7075
        }
7076
7077
        if (!file_exists($filepath.$filename)) {
7078
            if ($fp = @fopen($filepath.$filename, 'w')) {
7079
                fputs($fp, $content);
7080
                fclose($fp);
7081
7082
                $file_size = filesize($filepath.$filename);
7083
                $save_file_path = $dir.$filename;
7084
7085
                $document_id = add_document(
7086
                    $courseInfo,
7087
                    $save_file_path,
7088
                    'file',
7089
                    $file_size,
7090
                    $tmp_filename,
7091
                    '',
7092
                    0, //readonly
7093
                    true,
7094
                    null,
7095
                    $sessionId,
7096
                    $creatorId
7097
                );
7098
7099
                if ($document_id) {
7100
                    api_item_property_update(
7101
                        $courseInfo,
7102
                        TOOL_DOCUMENT,
7103
                        $document_id,
7104
                        'DocumentAdded',
7105
                        $creatorId,
7106
                        null,
7107
                        null,
7108
                        null,
7109
                        null,
7110
                        $sessionId
7111
                    );
7112
7113
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7114
                    $new_title = $originalTitle;
7115
7116
                    if ($new_comment || $new_title) {
7117
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7118
                        $ct = '';
7119
                        if ($new_comment) {
7120
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7121
                        }
7122
                        if ($new_title) {
7123
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7124
                        }
7125
7126
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7127
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7128
                        Database::query($sql);
7129
                    }
7130
                }
7131
7132
                return $document_id;
7133
            }
7134
        }
7135
    }
7136
7137
    /**
7138
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7139
     *
7140
     * @param array $_course array
7141
     */
7142
    public function edit_document($_course)
7143
    {
7144
        $course_id = api_get_course_int_id();
7145
        $urlAppend = api_get_configuration_value('url_append');
7146
        // Please, do not modify this dirname formatting.
7147
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7148
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7149
7150
        if (strstr($dir, '..')) {
7151
            $dir = '/';
7152
        }
7153
7154
        if (isset($dir[0]) && $dir[0] == '.') {
7155
            $dir = substr($dir, 1);
7156
        }
7157
7158
        if (isset($dir[0]) && $dir[0] != '/') {
7159
            $dir = '/'.$dir;
7160
        }
7161
7162
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7163
            $dir .= '/';
7164
        }
7165
7166
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7167
7168
        if (!is_dir($filepath)) {
7169
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7170
        }
7171
7172
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7173
7174
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7175
            $document_id = intval($_POST['path']);
7176
            $sql = "SELECT path FROM ".$table_doc."
7177
                    WHERE c_id = $course_id AND id = ".$document_id;
7178
            $res = Database::query($sql);
7179
            $row = Database::fetch_array($res);
7180
            $content = stripslashes($_POST['content_lp']);
7181
            $file = $filepath.$row['path'];
7182
7183
            if ($fp = @fopen($file, 'w')) {
7184
                $content = str_replace(
7185
                    api_get_path(WEB_COURSE_PATH),
7186
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7187
                    $content
7188
                );
7189
                // Change the path of mp3 to absolute.
7190
                // The first regexp deals with :// urls.
7191
                $content = preg_replace(
7192
                    "|(flashvars=\"file=)([^:/]+)/|",
7193
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7194
                    $content
7195
                );
7196
                // The second regexp deals with audio/ urls.
7197
                $content = preg_replace(
7198
                    "|(flashvars=\"file=)([^:/]+)/|",
7199
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7200
                    $content
7201
                );
7202
                fputs($fp, $content);
7203
                fclose($fp);
7204
7205
                $sql = "UPDATE ".$table_doc." SET
7206
                            title='".Database::escape_string($_POST['title'])."'
7207
                        WHERE c_id = ".$course_id." AND id = ".$document_id;
7208
                Database::query($sql);
7209
            }
7210
        }
7211
    }
7212
7213
    /**
7214
     * Displays the selected item, with a panel for manipulating the item.
7215
     *
7216
     * @param int    $item_id
7217
     * @param string $msg
7218
     * @param bool   $show_actions
7219
     *
7220
     * @return string
7221
     */
7222
    public function display_item($item_id, $msg = null, $show_actions = true)
7223
    {
7224
        $course_id = api_get_course_int_id();
7225
        $return = '';
7226
        if (is_numeric($item_id)) {
7227
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7228
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7229
                    WHERE lp.iid = ".intval($item_id);
7230
            $result = Database::query($sql);
7231
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7232
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7233
7234
                // Prevents wrong parent selection for document, see Bug#1251.
7235
                if ($row['item_type'] != 'dir') {
7236
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7237
                }
7238
7239
                if ($show_actions) {
7240
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7241
                }
7242
                $return .= '<div style="padding:10px;">';
7243
7244
                if ($msg != '') {
7245
                    $return .= $msg;
7246
                }
7247
7248
                $return .= '<h3>'.$row['title'].'</h3>';
7249
7250
                switch ($row['item_type']) {
7251
                    case TOOL_THREAD:
7252
                        $link = $this->rl_get_resource_link_for_learnpath(
7253
                            $course_id,
7254
                            $row['lp_id'],
7255
                            $item_id,
7256
                            0
7257
                        );
7258
                        $return .= Display::url(
7259
                            get_lang('GoToThread'),
7260
                            $link,
7261
                            ['class' => 'btn btn-primary']
7262
                        );
7263
                        break;
7264
                    case TOOL_FORUM:
7265
                        $return .= Display::url(
7266
                            get_lang('GoToForum'),
7267
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7268
                            ['class' => 'btn btn-primary']
7269
                        );
7270
                        break;
7271
                    case TOOL_QUIZ:
7272
                        if (!empty($row['path'])) {
7273
                            $exercise = new Exercise();
7274
                            $exercise->read($row['path']);
7275
                            $return .= $exercise->description.'<br />';
7276
                            $return .= Display::url(
7277
                                get_lang('GoToExercise'),
7278
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7279
                                ['class' => 'btn btn-primary']
7280
                            );
7281
                        }
7282
                        break;
7283
                    case TOOL_LP_FINAL_ITEM:
7284
                        $return .= $this->getSavedFinalItem();
7285
                        break;
7286
                    case TOOL_DOCUMENT:
7287
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7288
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
7289
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
7290
                        $result = Database::query($sql_doc);
7291
                        $path_file = Database::result($result, 0, 0);
7292
                        $path_parts = pathinfo($path_file);
7293
                        // TODO: Correct the following naive comparisons.
7294
                        if (in_array($path_parts['extension'], [
7295
                            'html',
7296
                            'txt',
7297
                            'png',
7298
                            'jpg',
7299
                            'JPG',
7300
                            'jpeg',
7301
                            'JPEG',
7302
                            'gif',
7303
                            'swf',
7304
                            'pdf',
7305
                            'htm',
7306
                        ])) {
7307
                            $return .= $this->display_document($row['path'], true, true);
7308
                        }
7309
                        break;
7310
                    case TOOL_HOTPOTATOES:
7311
                        $return .= $this->display_document($row['path'], false, true);
7312
                        break;
7313
                }
7314
                $return .= '</div>';
7315
            }
7316
        }
7317
7318
        return $return;
7319
    }
7320
7321
    /**
7322
     * Shows the needed forms for editing a specific item.
7323
     *
7324
     * @param int $item_id
7325
     *
7326
     * @throws Exception
7327
     * @throws HTML_QuickForm_Error
7328
     *
7329
     * @return string
7330
     */
7331
    public function display_edit_item($item_id)
7332
    {
7333
        $course_id = api_get_course_int_id();
7334
        $return = '';
7335
        $item_id = (int) $item_id;
7336
7337
        if (empty($item_id)) {
7338
            return '';
7339
        }
7340
7341
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7342
        $sql = "SELECT * FROM $tbl_lp_item
7343
                WHERE iid = ".$item_id;
7344
        $res = Database::query($sql);
7345
        $row = Database::fetch_array($res);
7346
        switch ($row['item_type']) {
7347
            case 'dir':
7348
            case 'asset':
7349
            case 'sco':
7350
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7351
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7352
                    $return .= $this->display_item_form(
7353
                        $row['item_type'],
7354
                        get_lang('EditCurrentChapter').' :',
7355
                        'edit',
7356
                        $item_id,
7357
                        $row
7358
                    );
7359
                } else {
7360
                    $return .= $this->display_item_small_form(
7361
                        $row['item_type'],
7362
                        get_lang('EditCurrentChapter').' :',
7363
                        $row
7364
                    );
7365
                }
7366
                break;
7367
            case TOOL_DOCUMENT:
7368
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7369
                $sql = "SELECT lp.*, doc.path as dir
7370
                        FROM $tbl_lp_item as lp
7371
                        LEFT JOIN $tbl_doc as doc
7372
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7373
                        WHERE
7374
                            doc.c_id = $course_id AND
7375
                            lp.iid = ".$item_id;
7376
                $res_step = Database::query($sql);
7377
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7378
                $return .= $this->display_manipulate(
7379
                    $item_id,
7380
                    $row['item_type']
7381
                );
7382
                $return .= $this->display_document_form(
7383
                    'edit',
7384
                    $item_id,
7385
                    $row_step
7386
                );
7387
                break;
7388
            case TOOL_LINK:
7389
                $link_id = (string) $row['path'];
7390
                if (ctype_digit($link_id)) {
7391
                    $tbl_link = Database::get_course_table(TABLE_LINK);
7392
                    $sql_select = 'SELECT url FROM '.$tbl_link.'
7393
                                   WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7394
                    $res_link = Database::query($sql_select);
7395
                    $row_link = Database::fetch_array($res_link);
7396
                    if (is_array($row_link)) {
7397
                        $row['url'] = $row_link['url'];
7398
                    }
7399
                }
7400
                $return .= $this->display_manipulate(
7401
                    $item_id,
7402
                    $row['item_type']
7403
                );
7404
                $return .= $this->display_link_form('edit', $item_id, $row);
7405
                break;
7406
            case TOOL_LP_FINAL_ITEM:
7407
                Session::write('finalItem', true);
7408
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7409
                $sql = "SELECT lp.*, doc.path as dir
7410
                        FROM $tbl_lp_item as lp
7411
                        LEFT JOIN $tbl_doc as doc
7412
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7413
                        WHERE
7414
                            doc.c_id = $course_id AND
7415
                            lp.iid = ".$item_id;
7416
                $res_step = Database::query($sql);
7417
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7418
                $return .= $this->display_manipulate(
7419
                    $item_id,
7420
                    $row['item_type']
7421
                );
7422
                $return .= $this->display_document_form(
7423
                    'edit',
7424
                    $item_id,
7425
                    $row_step
7426
                );
7427
                break;
7428
            case TOOL_QUIZ:
7429
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7430
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7431
                break;
7432
            case TOOL_HOTPOTATOES:
7433
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7434
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7435
                break;
7436
            case TOOL_STUDENTPUBLICATION:
7437
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7438
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7439
                break;
7440
            case TOOL_FORUM:
7441
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7442
                $return .= $this->display_forum_form('edit', $item_id, $row);
7443
                break;
7444
            case TOOL_THREAD:
7445
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7446
                $return .= $this->display_thread_form('edit', $item_id, $row);
7447
                break;
7448
        }
7449
7450
        return $return;
7451
    }
7452
7453
    /**
7454
     * Function that displays a list with al the resources that
7455
     * could be added to the learning path.
7456
     *
7457
     * @throws Exception
7458
     * @throws HTML_QuickForm_Error
7459
     *
7460
     * @return bool
7461
     */
7462
    public function display_resources()
7463
    {
7464
        $course_code = api_get_course_id();
7465
7466
        // Get all the docs.
7467
        $documents = $this->get_documents(true);
7468
7469
        // Get all the exercises.
7470
        $exercises = $this->get_exercises();
7471
7472
        // Get all the links.
7473
        $links = $this->get_links();
7474
7475
        // Get all the student publications.
7476
        $works = $this->get_student_publications();
7477
7478
        // Get all the forums.
7479
        $forums = $this->get_forums(null, $course_code);
7480
7481
        // Get the final item form (see BT#11048) .
7482
        $finish = $this->getFinalItemForm();
7483
7484
        $headers = [
7485
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7486
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7487
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7488
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7489
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7490
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7491
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7492
        ];
7493
7494
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7495
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7496
        echo Display::tabs(
7497
            $headers,
7498
            [
7499
                $documents,
7500
                $exercises,
7501
                $links,
7502
                $works,
7503
                $forums,
7504
                $dir,
7505
                $finish,
7506
            ],
7507
            'resource_tab'
7508
        );
7509
7510
        return true;
7511
    }
7512
7513
    /**
7514
     * Returns the extension of a document.
7515
     *
7516
     * @param string $filename
7517
     *
7518
     * @return string Extension (part after the last dot)
7519
     */
7520
    public function get_extension($filename)
7521
    {
7522
        $explode = explode('.', $filename);
7523
7524
        return $explode[count($explode) - 1];
7525
    }
7526
7527
    /**
7528
     * Displays a document by id.
7529
     *
7530
     * @param int  $id
7531
     * @param bool $show_title
7532
     * @param bool $iframe
7533
     * @param bool $edit_link
7534
     *
7535
     * @return string
7536
     */
7537
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7538
    {
7539
        $_course = api_get_course_info();
7540
        $course_id = api_get_course_int_id();
7541
        $id = (int) $id;
7542
        $return = '';
7543
        $table = Database::get_course_table(TABLE_DOCUMENT);
7544
        $sql_doc = "SELECT * FROM $table
7545
                    WHERE c_id = $course_id AND iid = $id";
7546
        $res_doc = Database::query($sql_doc);
7547
        $row_doc = Database::fetch_array($res_doc);
7548
7549
        // TODO: Add a path filter.
7550
        if ($iframe) {
7551
            $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>';
7552
        } else {
7553
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7554
        }
7555
7556
        return $return;
7557
    }
7558
7559
    /**
7560
     * Return HTML form to add/edit a quiz.
7561
     *
7562
     * @param string $action     Action (add/edit)
7563
     * @param int    $id         Item ID if already exists
7564
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7565
     *
7566
     * @throws Exception
7567
     *
7568
     * @return string HTML form
7569
     */
7570
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7571
    {
7572
        $course_id = api_get_course_int_id();
7573
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7574
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7575
7576
        if ($id != 0 && is_array($extra_info)) {
7577
            $item_title = $extra_info['title'];
7578
            $item_description = $extra_info['description'];
7579
        } elseif (is_numeric($extra_info)) {
7580
            $sql = "SELECT title, description
7581
                    FROM $tbl_quiz
7582
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7583
7584
            $result = Database::query($sql);
7585
            $row = Database::fetch_array($result);
7586
            $item_title = $row['title'];
7587
            $item_description = $row['description'];
7588
        } else {
7589
            $item_title = '';
7590
            $item_description = '';
7591
        }
7592
        $item_title = Security::remove_XSS($item_title);
7593
        $item_description = Security::remove_XSS($item_description);
7594
7595
        if ($id != 0 && is_array($extra_info)) {
7596
            $parent = $extra_info['parent_item_id'];
7597
        } else {
7598
            $parent = 0;
7599
        }
7600
7601
        $sql = "SELECT * FROM $tbl_lp_item 
7602
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7603
7604
        $result = Database::query($sql);
7605
        $arrLP = [];
7606
        while ($row = Database::fetch_array($result)) {
7607
            $arrLP[] = [
7608
                'id' => $row['iid'],
7609
                'item_type' => $row['item_type'],
7610
                'title' => $row['title'],
7611
                'path' => $row['path'],
7612
                'description' => $row['description'],
7613
                'parent_item_id' => $row['parent_item_id'],
7614
                'previous_item_id' => $row['previous_item_id'],
7615
                'next_item_id' => $row['next_item_id'],
7616
                'display_order' => $row['display_order'],
7617
                'max_score' => $row['max_score'],
7618
                'min_score' => $row['min_score'],
7619
                'mastery_score' => $row['mastery_score'],
7620
                'prerequisite' => $row['prerequisite'],
7621
                'max_time_allowed' => $row['max_time_allowed'],
7622
            ];
7623
        }
7624
7625
        $this->tree_array($arrLP);
7626
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7627
        unset($this->arrMenu);
7628
7629
        $form = new FormValidator(
7630
            'quiz_form',
7631
            'POST',
7632
            $this->getCurrentBuildingModeURL()
7633
        );
7634
        $defaults = [];
7635
7636
        if ($action == 'add') {
7637
            $legend = get_lang('CreateTheExercise');
7638
        } elseif ($action == 'move') {
7639
            $legend = get_lang('MoveTheCurrentExercise');
7640
        } else {
7641
            $legend = get_lang('EditCurrentExecice');
7642
        }
7643
7644
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7645
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7646
        }
7647
7648
        $form->addHeader($legend);
7649
7650
        if ($action != 'move') {
7651
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7652
            $defaults['title'] = $item_title;
7653
        }
7654
7655
        // Select for Parent item, root or chapter
7656
        $selectParent = $form->addSelect(
7657
            'parent',
7658
            get_lang('Parent'),
7659
            [],
7660
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7661
        );
7662
        $selectParent->addOption($this->name, 0);
7663
7664
        $arrHide = [
7665
            $id,
7666
        ];
7667
        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...
7668
            if ($action != 'add') {
7669
                if (
7670
                    ($arrLP[$i]['item_type'] == 'dir') &&
7671
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7672
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7673
                ) {
7674
                    $selectParent->addOption(
7675
                        $arrLP[$i]['title'],
7676
                        $arrLP[$i]['id'],
7677
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7678
                    );
7679
7680
                    if ($parent == $arrLP[$i]['id']) {
7681
                        $selectParent->setSelected($arrLP[$i]['id']);
7682
                    }
7683
                } else {
7684
                    $arrHide[] = $arrLP[$i]['id'];
7685
                }
7686
            } else {
7687
                if ($arrLP[$i]['item_type'] == 'dir') {
7688
                    $selectParent->addOption(
7689
                        $arrLP[$i]['title'],
7690
                        $arrLP[$i]['id'],
7691
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7692
                    );
7693
7694
                    if ($parent == $arrLP[$i]['id']) {
7695
                        $selectParent->setSelected($arrLP[$i]['id']);
7696
                    }
7697
                }
7698
            }
7699
        }
7700
        if (is_array($arrLP)) {
7701
            reset($arrLP);
7702
        }
7703
7704
        $selectPrevious = $form->addSelect(
7705
            'previous',
7706
            get_lang('Position'),
7707
            [],
7708
            ['id' => 'previous']
7709
        );
7710
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7711
7712
        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...
7713
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7714
                $arrLP[$i]['id'] != $id
7715
            ) {
7716
                $selectPrevious->addOption(
7717
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7718
                    $arrLP[$i]['id']
7719
                );
7720
7721
                if (is_array($extra_info)) {
7722
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7723
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7724
                    }
7725
                } elseif ($action == 'add') {
7726
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7727
                }
7728
            }
7729
        }
7730
7731
        if ($action != 'move') {
7732
            $id_prerequisite = 0;
7733
            if (is_array($arrLP)) {
7734
                foreach ($arrLP as $key => $value) {
7735
                    if ($value['id'] == $id) {
7736
                        $id_prerequisite = $value['prerequisite'];
7737
                        break;
7738
                    }
7739
                }
7740
            }
7741
            $arrHide = [];
7742
            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...
7743
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7744
                    if (is_array($extra_info)) {
7745
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7746
                            $s_selected_position = $arrLP[$i]['id'];
7747
                        }
7748
                    } elseif ($action == 'add') {
7749
                        $s_selected_position = 0;
7750
                    }
7751
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7752
                }
7753
            }
7754
        }
7755
7756
        if ($action == 'add') {
7757
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7758
        } else {
7759
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7760
        }
7761
7762
        if ($action == 'move') {
7763
            $form->addHidden('title', $item_title);
7764
            $form->addHidden('description', $item_description);
7765
        }
7766
7767
        if (is_numeric($extra_info)) {
7768
            $form->addHidden('path', $extra_info);
7769
        } elseif (is_array($extra_info)) {
7770
            $form->addHidden('path', $extra_info['path']);
7771
        }
7772
7773
        $form->addHidden('type', TOOL_QUIZ);
7774
        $form->addHidden('post_time', time());
7775
        $form->setDefaults($defaults);
7776
7777
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7778
    }
7779
7780
    /**
7781
     * Addition of Hotpotatoes tests.
7782
     *
7783
     * @param string $action
7784
     * @param int    $id         Internal ID of the item
7785
     * @param string $extra_info
7786
     *
7787
     * @return string HTML structure to display the hotpotatoes addition formular
7788
     */
7789
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7790
    {
7791
        $course_id = api_get_course_int_id();
7792
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7793
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7794
7795
        if ($id != 0 && is_array($extra_info)) {
7796
            $item_title = stripslashes($extra_info['title']);
7797
            $item_description = stripslashes($extra_info['description']);
7798
        } elseif (is_numeric($extra_info)) {
7799
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7800
7801
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7802
                    WHERE
7803
                        c_id = ".$course_id." AND
7804
                        path LIKE '".$uploadPath."/%/%htm%' AND
7805
                        iid = ".(int) $extra_info."
7806
                    ORDER BY iid ASC";
7807
7808
            $res_hot = Database::query($sql);
7809
            $row = Database::fetch_array($res_hot);
7810
7811
            $item_title = $row['title'];
7812
            $item_description = $row['description'];
7813
7814
            if (!empty($row['comment'])) {
7815
                $item_title = $row['comment'];
7816
            }
7817
        } else {
7818
            $item_title = '';
7819
            $item_description = '';
7820
        }
7821
7822
        if ($id != 0 && is_array($extra_info)) {
7823
            $parent = $extra_info['parent_item_id'];
7824
        } else {
7825
            $parent = 0;
7826
        }
7827
7828
        $sql = "SELECT * FROM $tbl_lp_item
7829
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7830
        $result = Database::query($sql);
7831
        $arrLP = [];
7832
        while ($row = Database::fetch_array($result)) {
7833
            $arrLP[] = [
7834
                'id' => $row['id'],
7835
                'item_type' => $row['item_type'],
7836
                'title' => $row['title'],
7837
                'path' => $row['path'],
7838
                'description' => $row['description'],
7839
                'parent_item_id' => $row['parent_item_id'],
7840
                'previous_item_id' => $row['previous_item_id'],
7841
                'next_item_id' => $row['next_item_id'],
7842
                'display_order' => $row['display_order'],
7843
                'max_score' => $row['max_score'],
7844
                'min_score' => $row['min_score'],
7845
                'mastery_score' => $row['mastery_score'],
7846
                'prerequisite' => $row['prerequisite'],
7847
                'max_time_allowed' => $row['max_time_allowed'],
7848
            ];
7849
        }
7850
7851
        $legend = '<legend>';
7852
        if ($action == 'add') {
7853
            $legend .= get_lang('CreateTheExercise');
7854
        } elseif ($action == 'move') {
7855
            $legend .= get_lang('MoveTheCurrentExercise');
7856
        } else {
7857
            $legend .= get_lang('EditCurrentExecice');
7858
        }
7859
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7860
            $legend .= Display:: return_message(
7861
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7862
            );
7863
        }
7864
        $legend .= '</legend>';
7865
7866
        $return = '<form method="POST">';
7867
        $return .= $legend;
7868
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7869
        $return .= '<tr>';
7870
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7871
        $return .= '<td class="input">';
7872
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7873
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7874
        $arrHide = [
7875
            $id,
7876
        ];
7877
7878
        if (count($arrLP) > 0) {
7879
            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...
7880
                if ($action != 'add') {
7881
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7882
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7883
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7884
                    ) {
7885
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7886
                    } else {
7887
                        $arrHide[] = $arrLP[$i]['id'];
7888
                    }
7889
                } else {
7890
                    if ($arrLP[$i]['item_type'] == 'dir') {
7891
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7892
                    }
7893
                }
7894
            }
7895
            reset($arrLP);
7896
        }
7897
7898
        $return .= '</select>';
7899
        $return .= '</td>';
7900
        $return .= '</tr>';
7901
        $return .= '<tr>';
7902
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7903
        $return .= '<td class="input">';
7904
        $return .= '<select id="previous" name="previous" size="1">';
7905
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7906
7907
        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...
7908
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7909
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7910
                    $selected = 'selected="selected" ';
7911
                } elseif ($action == 'add') {
7912
                    $selected = 'selected="selected" ';
7913
                } else {
7914
                    $selected = '';
7915
                }
7916
7917
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7918
            }
7919
        }
7920
7921
        $return .= '</select>';
7922
        $return .= '</td>';
7923
        $return .= '</tr>';
7924
7925
        if ($action != 'move') {
7926
            $return .= '<tr>';
7927
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7928
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7929
            $return .= '</tr>';
7930
            $id_prerequisite = 0;
7931
            if (is_array($arrLP) && count($arrLP) > 0) {
7932
                foreach ($arrLP as $key => $value) {
7933
                    if ($value['id'] == $id) {
7934
                        $id_prerequisite = $value['prerequisite'];
7935
                        break;
7936
                    }
7937
                }
7938
7939
                $arrHide = [];
7940
                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...
7941
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7942
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7943
                            $s_selected_position = $arrLP[$i]['id'];
7944
                        } elseif ($action == 'add') {
7945
                            $s_selected_position = 0;
7946
                        }
7947
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7948
                    }
7949
                }
7950
            }
7951
        }
7952
7953
        $return .= '<tr>';
7954
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.get_lang('SaveHotpotatoes').'</button></td>';
7955
        $return .= '</tr>';
7956
        $return .= '</table>';
7957
7958
        if ($action == 'move') {
7959
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7960
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7961
        }
7962
7963
        if (is_numeric($extra_info)) {
7964
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7965
        } elseif (is_array($extra_info)) {
7966
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7967
        }
7968
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7969
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7970
        $return .= '</form>';
7971
7972
        return $return;
7973
    }
7974
7975
    /**
7976
     * Return the form to display the forum edit/add option.
7977
     *
7978
     * @param string $action
7979
     * @param int    $id         ID of the lp_item if already exists
7980
     * @param string $extra_info
7981
     *
7982
     * @throws Exception
7983
     *
7984
     * @return string HTML form
7985
     */
7986
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7987
    {
7988
        $course_id = api_get_course_int_id();
7989
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7990
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7991
7992
        if ($id != 0 && is_array($extra_info)) {
7993
            $item_title = stripslashes($extra_info['title']);
7994
        } elseif (is_numeric($extra_info)) {
7995
            $sql = "SELECT forum_title as title, forum_comment as comment
7996
                    FROM ".$tbl_forum."
7997
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
7998
7999
            $result = Database::query($sql);
8000
            $row = Database::fetch_array($result);
8001
8002
            $item_title = $row['title'];
8003
            $item_description = $row['comment'];
8004
        } else {
8005
            $item_title = '';
8006
            $item_description = '';
8007
        }
8008
8009
        if ($id != 0 && is_array($extra_info)) {
8010
            $parent = $extra_info['parent_item_id'];
8011
        } else {
8012
            $parent = 0;
8013
        }
8014
8015
        $sql = "SELECT * FROM $tbl_lp_item
8016
                WHERE
8017
                    c_id = $course_id AND
8018
                    lp_id = ".$this->lp_id;
8019
        $result = Database::query($sql);
8020
        $arrLP = [];
8021
        while ($row = Database::fetch_array($result)) {
8022
            $arrLP[] = [
8023
                'id' => $row['iid'],
8024
                'item_type' => $row['item_type'],
8025
                'title' => $row['title'],
8026
                'path' => $row['path'],
8027
                'description' => $row['description'],
8028
                'parent_item_id' => $row['parent_item_id'],
8029
                'previous_item_id' => $row['previous_item_id'],
8030
                'next_item_id' => $row['next_item_id'],
8031
                'display_order' => $row['display_order'],
8032
                'max_score' => $row['max_score'],
8033
                'min_score' => $row['min_score'],
8034
                'mastery_score' => $row['mastery_score'],
8035
                'prerequisite' => $row['prerequisite'],
8036
            ];
8037
        }
8038
8039
        $this->tree_array($arrLP);
8040
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8041
        unset($this->arrMenu);
8042
8043
        if ($action == 'add') {
8044
            $legend = get_lang('CreateTheForum');
8045
        } elseif ($action == 'move') {
8046
            $legend = get_lang('MoveTheCurrentForum');
8047
        } else {
8048
            $legend = get_lang('EditCurrentForum');
8049
        }
8050
8051
        $form = new FormValidator(
8052
            'forum_form',
8053
            'POST',
8054
            $this->getCurrentBuildingModeURL()
8055
        );
8056
        $defaults = [];
8057
8058
        $form->addHeader($legend);
8059
8060
        if ($action != 'move') {
8061
            $form->addText(
8062
                'title',
8063
                get_lang('Title'),
8064
                true,
8065
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8066
            );
8067
            $defaults['title'] = $item_title;
8068
        }
8069
8070
        $selectParent = $form->addSelect(
8071
            'parent',
8072
            get_lang('Parent'),
8073
            [],
8074
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8075
        );
8076
        $selectParent->addOption($this->name, 0);
8077
        $arrHide = [
8078
            $id,
8079
        ];
8080
        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...
8081
            if ($action != 'add') {
8082
                if ($arrLP[$i]['item_type'] == 'dir' &&
8083
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8084
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8085
                ) {
8086
                    $selectParent->addOption(
8087
                        $arrLP[$i]['title'],
8088
                        $arrLP[$i]['id'],
8089
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8090
                    );
8091
8092
                    if ($parent == $arrLP[$i]['id']) {
8093
                        $selectParent->setSelected($arrLP[$i]['id']);
8094
                    }
8095
                } else {
8096
                    $arrHide[] = $arrLP[$i]['id'];
8097
                }
8098
            } else {
8099
                if ($arrLP[$i]['item_type'] == 'dir') {
8100
                    $selectParent->addOption(
8101
                        $arrLP[$i]['title'],
8102
                        $arrLP[$i]['id'],
8103
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8104
                    );
8105
8106
                    if ($parent == $arrLP[$i]['id']) {
8107
                        $selectParent->setSelected($arrLP[$i]['id']);
8108
                    }
8109
                }
8110
            }
8111
        }
8112
8113
        if (is_array($arrLP)) {
8114
            reset($arrLP);
8115
        }
8116
8117
        $selectPrevious = $form->addSelect(
8118
            'previous',
8119
            get_lang('Position'),
8120
            [],
8121
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8122
        );
8123
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8124
8125
        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...
8126
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8127
                $arrLP[$i]['id'] != $id
8128
            ) {
8129
                $selectPrevious->addOption(
8130
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8131
                    $arrLP[$i]['id']
8132
                );
8133
8134
                if (isset($extra_info['previous_item_id']) &&
8135
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8136
                ) {
8137
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8138
                } elseif ($action == 'add') {
8139
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8140
                }
8141
            }
8142
        }
8143
8144
        if ($action != 'move') {
8145
            $id_prerequisite = 0;
8146
            if (is_array($arrLP)) {
8147
                foreach ($arrLP as $key => $value) {
8148
                    if ($value['id'] == $id) {
8149
                        $id_prerequisite = $value['prerequisite'];
8150
                        break;
8151
                    }
8152
                }
8153
            }
8154
8155
            $arrHide = [];
8156
            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...
8157
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8158
                    if (isset($extra_info['previous_item_id']) &&
8159
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8160
                    ) {
8161
                        $s_selected_position = $arrLP[$i]['id'];
8162
                    } elseif ($action == 'add') {
8163
                        $s_selected_position = 0;
8164
                    }
8165
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8166
                }
8167
            }
8168
        }
8169
8170
        if ($action == 'add') {
8171
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8172
        } else {
8173
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8174
        }
8175
8176
        if ($action == 'move') {
8177
            $form->addHidden('title', $item_title);
8178
            $form->addHidden('description', $item_description);
8179
        }
8180
8181
        if (is_numeric($extra_info)) {
8182
            $form->addHidden('path', $extra_info);
8183
        } elseif (is_array($extra_info)) {
8184
            $form->addHidden('path', $extra_info['path']);
8185
        }
8186
        $form->addHidden('type', TOOL_FORUM);
8187
        $form->addHidden('post_time', time());
8188
        $form->setDefaults($defaults);
8189
8190
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8191
    }
8192
8193
    /**
8194
     * Return HTML form to add/edit forum threads.
8195
     *
8196
     * @param string $action
8197
     * @param int    $id         Item ID if already exists in learning path
8198
     * @param string $extra_info
8199
     *
8200
     * @throws Exception
8201
     *
8202
     * @return string HTML form
8203
     */
8204
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8205
    {
8206
        $course_id = api_get_course_int_id();
8207
        if (empty($course_id)) {
8208
            return null;
8209
        }
8210
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8211
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8212
8213
        if ($id != 0 && is_array($extra_info)) {
8214
            $item_title = stripslashes($extra_info['title']);
8215
        } elseif (is_numeric($extra_info)) {
8216
            $sql = "SELECT thread_title as title FROM $tbl_forum
8217
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8218
8219
            $result = Database::query($sql);
8220
            $row = Database::fetch_array($result);
8221
8222
            $item_title = $row['title'];
8223
            $item_description = '';
8224
        } else {
8225
            $item_title = '';
8226
            $item_description = '';
8227
        }
8228
8229
        if ($id != 0 && is_array($extra_info)) {
8230
            $parent = $extra_info['parent_item_id'];
8231
        } else {
8232
            $parent = 0;
8233
        }
8234
8235
        $sql = "SELECT * FROM $tbl_lp_item
8236
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8237
        $result = Database::query($sql);
8238
8239
        $arrLP = [];
8240
        while ($row = Database::fetch_array($result)) {
8241
            $arrLP[] = [
8242
                'id' => $row['iid'],
8243
                'item_type' => $row['item_type'],
8244
                'title' => $row['title'],
8245
                'path' => $row['path'],
8246
                'description' => $row['description'],
8247
                'parent_item_id' => $row['parent_item_id'],
8248
                'previous_item_id' => $row['previous_item_id'],
8249
                'next_item_id' => $row['next_item_id'],
8250
                'display_order' => $row['display_order'],
8251
                'max_score' => $row['max_score'],
8252
                'min_score' => $row['min_score'],
8253
                'mastery_score' => $row['mastery_score'],
8254
                'prerequisite' => $row['prerequisite'],
8255
            ];
8256
        }
8257
8258
        $this->tree_array($arrLP);
8259
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8260
        unset($this->arrMenu);
8261
8262
        $form = new FormValidator(
8263
            'thread_form',
8264
            'POST',
8265
            $this->getCurrentBuildingModeURL()
8266
        );
8267
        $defaults = [];
8268
8269
        if ($action == 'add') {
8270
            $legend = get_lang('CreateTheForum');
8271
        } elseif ($action == 'move') {
8272
            $legend = get_lang('MoveTheCurrentForum');
8273
        } else {
8274
            $legend = get_lang('EditCurrentForum');
8275
        }
8276
8277
        $form->addHeader($legend);
8278
        $selectParent = $form->addSelect(
8279
            'parent',
8280
            get_lang('Parent'),
8281
            [],
8282
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8283
        );
8284
        $selectParent->addOption($this->name, 0);
8285
8286
        $arrHide = [
8287
            $id,
8288
        ];
8289
8290
        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...
8291
            if ($action != 'add') {
8292
                if (
8293
                    ($arrLP[$i]['item_type'] == 'dir') &&
8294
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8295
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8296
                ) {
8297
                    $selectParent->addOption(
8298
                        $arrLP[$i]['title'],
8299
                        $arrLP[$i]['id'],
8300
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8301
                    );
8302
8303
                    if ($parent == $arrLP[$i]['id']) {
8304
                        $selectParent->setSelected($arrLP[$i]['id']);
8305
                    }
8306
                } else {
8307
                    $arrHide[] = $arrLP[$i]['id'];
8308
                }
8309
            } else {
8310
                if ($arrLP[$i]['item_type'] == 'dir') {
8311
                    $selectParent->addOption(
8312
                        $arrLP[$i]['title'],
8313
                        $arrLP[$i]['id'],
8314
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8315
                    );
8316
8317
                    if ($parent == $arrLP[$i]['id']) {
8318
                        $selectParent->setSelected($arrLP[$i]['id']);
8319
                    }
8320
                }
8321
            }
8322
        }
8323
8324
        if ($arrLP != null) {
8325
            reset($arrLP);
8326
        }
8327
8328
        $selectPrevious = $form->addSelect(
8329
            'previous',
8330
            get_lang('Position'),
8331
            [],
8332
            ['id' => 'previous']
8333
        );
8334
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8335
8336
        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...
8337
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8338
                $selectPrevious->addOption(
8339
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8340
                    $arrLP[$i]['id']
8341
                );
8342
8343
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8344
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8345
                } elseif ($action == 'add') {
8346
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8347
                }
8348
            }
8349
        }
8350
8351
        if ($action != 'move') {
8352
            $form->addText(
8353
                'title',
8354
                get_lang('Title'),
8355
                true,
8356
                ['id' => 'idTitle']
8357
            );
8358
            $defaults['title'] = $item_title;
8359
8360
            $id_prerequisite = 0;
8361
            if ($arrLP != null) {
8362
                foreach ($arrLP as $key => $value) {
8363
                    if ($value['id'] == $id) {
8364
                        $id_prerequisite = $value['prerequisite'];
8365
                        break;
8366
                    }
8367
                }
8368
            }
8369
8370
            $arrHide = [];
8371
            $s_selected_position = 0;
8372
            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...
8373
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8374
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8375
                        $s_selected_position = $arrLP[$i]['id'];
8376
                    } elseif ($action == 'add') {
8377
                        $s_selected_position = 0;
8378
                    }
8379
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8380
                }
8381
            }
8382
8383
            $selectPrerequisites = $form->addSelect(
8384
                'prerequisites',
8385
                get_lang('LearnpathPrerequisites'),
8386
                [],
8387
                ['id' => 'prerequisites']
8388
            );
8389
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8390
8391
            foreach ($arrHide as $key => $value) {
8392
                $selectPrerequisites->addOption($value['value'], $key);
8393
8394
                if ($key == $s_selected_position && $action == 'add') {
8395
                    $selectPrerequisites->setSelected($key);
8396
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8397
                    $selectPrerequisites->setSelected($key);
8398
                }
8399
            }
8400
        }
8401
8402
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8403
8404
        if ($action == 'move') {
8405
            $form->addHidden('title', $item_title);
8406
            $form->addHidden('description', $item_description);
8407
        }
8408
8409
        if (is_numeric($extra_info)) {
8410
            $form->addHidden('path', $extra_info);
8411
        } elseif (is_array($extra_info)) {
8412
            $form->addHidden('path', $extra_info['path']);
8413
        }
8414
8415
        $form->addHidden('type', TOOL_THREAD);
8416
        $form->addHidden('post_time', time());
8417
        $form->setDefaults($defaults);
8418
8419
        return $form->returnForm();
8420
    }
8421
8422
    /**
8423
     * Return the HTML form to display an item (generally a dir item).
8424
     *
8425
     * @param string $item_type
8426
     * @param string $title
8427
     * @param string $action
8428
     * @param int    $id
8429
     * @param string $extra_info
8430
     *
8431
     * @throws Exception
8432
     * @throws HTML_QuickForm_Error
8433
     *
8434
     * @return string HTML form
8435
     */
8436
    public function display_item_form(
8437
        $item_type,
8438
        $title = '',
8439
        $action = 'add_item',
8440
        $id = 0,
8441
        $extra_info = 'new'
8442
    ) {
8443
        $_course = api_get_course_info();
8444
8445
        global $charset;
8446
8447
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8448
8449
        if ($id != 0 && is_array($extra_info)) {
8450
            $item_title = $extra_info['title'];
8451
            $item_description = $extra_info['description'];
8452
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8453
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8454
        } else {
8455
            $item_title = '';
8456
            $item_description = '';
8457
            $item_path_fck = '';
8458
        }
8459
8460
        if ($id != 0 && is_array($extra_info)) {
8461
            $parent = $extra_info['parent_item_id'];
8462
        } else {
8463
            $parent = 0;
8464
        }
8465
8466
        $id = intval($id);
8467
        $sql = "SELECT * FROM $tbl_lp_item
8468
                WHERE
8469
                    lp_id = ".$this->lp_id." AND
8470
                    iid != $id";
8471
8472
        if ($item_type == 'dir') {
8473
            $sql .= " AND parent_item_id = 0";
8474
        }
8475
8476
        $result = Database::query($sql);
8477
        $arrLP = [];
8478
        while ($row = Database::fetch_array($result)) {
8479
            $arrLP[] = [
8480
                'id' => $row['iid'],
8481
                'item_type' => $row['item_type'],
8482
                'title' => $row['title'],
8483
                'path' => $row['path'],
8484
                'description' => $row['description'],
8485
                'parent_item_id' => $row['parent_item_id'],
8486
                'previous_item_id' => $row['previous_item_id'],
8487
                'next_item_id' => $row['next_item_id'],
8488
                'max_score' => $row['max_score'],
8489
                'min_score' => $row['min_score'],
8490
                'mastery_score' => $row['mastery_score'],
8491
                'prerequisite' => $row['prerequisite'],
8492
                'display_order' => $row['display_order'],
8493
            ];
8494
        }
8495
8496
        $this->tree_array($arrLP);
8497
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8498
        unset($this->arrMenu);
8499
8500
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8501
8502
        $form = new FormValidator('form', 'POST', $url);
8503
        $defaults['title'] = api_html_entity_decode(
8504
            $item_title,
8505
            ENT_QUOTES,
8506
            $charset
8507
        );
8508
        $defaults['description'] = $item_description;
8509
8510
        $form->addElement('header', $title);
8511
8512
        //$arrHide = array($id);
8513
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8514
        $arrHide[0]['padding'] = 20;
8515
        $charset = api_get_system_encoding();
8516
8517
        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...
8518
            if ($action != 'add') {
8519
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8520
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8521
                ) {
8522
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8523
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8524
                    if ($parent == $arrLP[$i]['id']) {
8525
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8526
                    }
8527
                }
8528
            } else {
8529
                if ($arrLP[$i]['item_type'] == 'dir') {
8530
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8531
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8532
                    if ($parent == $arrLP[$i]['id']) {
8533
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8534
                    }
8535
                }
8536
            }
8537
        }
8538
8539
        if ($action != 'move') {
8540
            $form->addElement('text', 'title', get_lang('Title'));
8541
            $form->applyFilter('title', 'html_filter');
8542
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8543
        } else {
8544
            $form->addElement('hidden', 'title');
8545
        }
8546
8547
        $parent_select = $form->addElement(
8548
            'select',
8549
            'parent',
8550
            get_lang('Parent'),
8551
            '',
8552
            [
8553
                'id' => 'idParent',
8554
                'onchange' => "javascript: load_cbo(this.value);",
8555
            ]
8556
        );
8557
8558
        foreach ($arrHide as $key => $value) {
8559
            $parent_select->addOption(
8560
                $value['value'],
8561
                $key,
8562
                'style="padding-left:'.$value['padding'].'px;"'
8563
            );
8564
        }
8565
        if (!empty($s_selected_parent)) {
8566
            $parent_select->setSelected($s_selected_parent);
8567
        }
8568
8569
        if (is_array($arrLP)) {
8570
            reset($arrLP);
8571
        }
8572
        $arrHide = [];
8573
        // POSITION
8574
        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...
8575
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8576
                //this is the same!
8577
                if (isset($extra_info['previous_item_id']) &&
8578
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8579
                ) {
8580
                    $s_selected_position = $arrLP[$i]['id'];
8581
                } elseif ($action == 'add') {
8582
                    $s_selected_position = $arrLP[$i]['id'];
8583
                }
8584
8585
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8586
            }
8587
        }
8588
8589
        $position = $form->addElement(
8590
            'select',
8591
            'previous',
8592
            get_lang('Position'),
8593
            '',
8594
            ['id' => 'previous']
8595
        );
8596
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8597
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8598
8599
        foreach ($arrHide as $key => $value) {
8600
            $position->addOption($value['value'].'"', $key, 'style="padding-left:'.$padding.'px;"');
8601
        }
8602
8603
        if (!empty($s_selected_position)) {
8604
            $position->setSelected($s_selected_position);
8605
        }
8606
8607
        if (is_array($arrLP)) {
8608
            reset($arrLP);
8609
        }
8610
8611
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8612
8613
        //fix in order to use the tab
8614
        if ($item_type == 'dir') {
8615
            $form->addElement('hidden', 'type', 'dir');
8616
        }
8617
8618
        $extension = null;
8619
        if (!empty($item_path)) {
8620
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8621
        }
8622
8623
        //assets can't be modified
8624
        //$item_type == 'asset' ||
8625
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8626
            if ($item_type == 'sco') {
8627
                $form->addElement(
8628
                    'html',
8629
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8630
                );
8631
            }
8632
            $renderer = $form->defaultRenderer();
8633
            $renderer->setElementTemplate(
8634
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8635
                'content_lp'
8636
            );
8637
8638
            $relative_prefix = '';
8639
8640
            $editor_config = [
8641
                'ToolbarSet' => 'LearningPathDocuments',
8642
                'Width' => '100%',
8643
                'Height' => '500',
8644
                'FullPage' => true,
8645
                'CreateDocumentDir' => $relative_prefix,
8646
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8647
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8648
            ];
8649
8650
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8651
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8652
            $defaults['content_lp'] = file_get_contents($content_path);
8653
        }
8654
8655
        $form->addElement('hidden', 'type', $item_type);
8656
        $form->addElement('hidden', 'post_time', time());
8657
        $form->setDefaults($defaults);
8658
8659
        return $form->returnForm();
8660
    }
8661
8662
    /**
8663
     * @return string
8664
     */
8665
    public function getCurrentBuildingModeURL()
8666
    {
8667
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8668
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8669
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8670
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8671
8672
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8673
8674
        return $currentUrl;
8675
    }
8676
8677
    /**
8678
     * Returns the form to update or create a document.
8679
     *
8680
     * @param string $action     (add/edit)
8681
     * @param int    $id         ID of the lp_item (if already exists)
8682
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8683
     *
8684
     * @throws Exception
8685
     * @throws HTML_QuickForm_Error
8686
     *
8687
     * @return string HTML form
8688
     */
8689
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8690
    {
8691
        $course_id = api_get_course_int_id();
8692
        $_course = api_get_course_info();
8693
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8694
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8695
8696
        $no_display_edit_textarea = false;
8697
        $item_description = '';
8698
        //If action==edit document
8699
        //We don't display the document form if it's not an editable document (html or txt file)
8700
        if ($action == 'edit') {
8701
            if (is_array($extra_info)) {
8702
                $path_parts = pathinfo($extra_info['dir']);
8703
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8704
                    $no_display_edit_textarea = true;
8705
                }
8706
            }
8707
        }
8708
        $no_display_add = false;
8709
8710
        // If action==add an existing document
8711
        // We don't display the document form if it's not an editable document (html or txt file).
8712
        if ($action == 'add') {
8713
            if (is_numeric($extra_info)) {
8714
                $sql_doc = "SELECT path FROM $tbl_doc 
8715
                            WHERE c_id = $course_id AND iid = ".intval($extra_info);
8716
                $result = Database::query($sql_doc);
8717
                $path_file = Database::result($result, 0, 0);
8718
                $path_parts = pathinfo($path_file);
8719
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8720
                    $no_display_add = true;
8721
                }
8722
            }
8723
        }
8724
        if ($id != 0 && is_array($extra_info)) {
8725
            $item_title = stripslashes($extra_info['title']);
8726
            $item_description = stripslashes($extra_info['description']);
8727
            $item_terms = stripslashes($extra_info['terms']);
8728
            if (empty($item_title)) {
8729
                $path_parts = pathinfo($extra_info['path']);
8730
                $item_title = stripslashes($path_parts['filename']);
8731
            }
8732
        } elseif (is_numeric($extra_info)) {
8733
            $sql = "SELECT path, title FROM $tbl_doc
8734
                    WHERE
8735
                        c_id = ".$course_id." AND
8736
                        iid = ".intval($extra_info);
8737
            $result = Database::query($sql);
8738
            $row = Database::fetch_array($result);
8739
            $item_title = $row['title'];
8740
            $item_title = str_replace('_', ' ', $item_title);
8741
            if (empty($item_title)) {
8742
                $path_parts = pathinfo($row['path']);
8743
                $item_title = stripslashes($path_parts['filename']);
8744
            }
8745
        } else {
8746
            $item_title = '';
8747
            $item_description = '';
8748
        }
8749
        $return = '<legend>';
8750
8751
        if ($id != 0 && is_array($extra_info)) {
8752
            $parent = $extra_info['parent_item_id'];
8753
        } else {
8754
            $parent = 0;
8755
        }
8756
8757
        $sql = "SELECT * FROM $tbl_lp_item
8758
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8759
        $result = Database::query($sql);
8760
        $arrLP = [];
8761
8762
        while ($row = Database::fetch_array($result)) {
8763
            $arrLP[] = [
8764
                'id' => $row['iid'],
8765
                'item_type' => $row['item_type'],
8766
                'title' => $row['title'],
8767
                'path' => $row['path'],
8768
                'description' => $row['description'],
8769
                'parent_item_id' => $row['parent_item_id'],
8770
                'previous_item_id' => $row['previous_item_id'],
8771
                'next_item_id' => $row['next_item_id'],
8772
                'display_order' => $row['display_order'],
8773
                'max_score' => $row['max_score'],
8774
                'min_score' => $row['min_score'],
8775
                'mastery_score' => $row['mastery_score'],
8776
                'prerequisite' => $row['prerequisite'],
8777
            ];
8778
        }
8779
8780
        $this->tree_array($arrLP);
8781
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8782
        unset($this->arrMenu);
8783
8784
        if ($action == 'add') {
8785
            $return .= get_lang('CreateTheDocument');
8786
        } elseif ($action == 'move') {
8787
            $return .= get_lang('MoveTheCurrentDocument');
8788
        } else {
8789
            $return .= get_lang('EditTheCurrentDocument');
8790
        }
8791
        $return .= '</legend>';
8792
8793
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8794
            $return .= Display::return_message(
8795
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8796
                false
8797
            );
8798
        }
8799
        $form = new FormValidator(
8800
            'form',
8801
            'POST',
8802
            $this->getCurrentBuildingModeURL(),
8803
            '',
8804
            ['enctype' => 'multipart/form-data']
8805
        );
8806
        $defaults['title'] = Security::remove_XSS($item_title);
8807
        if (empty($item_title)) {
8808
            $defaults['title'] = Security::remove_XSS($item_title);
8809
        }
8810
        $defaults['description'] = $item_description;
8811
        $form->addElement('html', $return);
8812
8813
        if ($action != 'move') {
8814
            $data = $this->generate_lp_folder($_course);
8815
            if ($action != 'edit') {
8816
                $folders = DocumentManager::get_all_document_folders(
8817
                    $_course,
8818
                    0,
8819
                    true
8820
                );
8821
                DocumentManager::build_directory_selector(
8822
                    $folders,
8823
                    '',
8824
                    [],
8825
                    true,
8826
                    $form,
8827
                    'directory_parent_id'
8828
                );
8829
            }
8830
8831
            if (isset($data['id'])) {
8832
                $defaults['directory_parent_id'] = $data['id'];
8833
            }
8834
8835
            $form->addElement(
8836
                'text',
8837
                'title',
8838
                get_lang('Title'),
8839
                ['id' => 'idTitle', 'class' => 'col-md-4']
8840
            );
8841
            $form->applyFilter('title', 'html_filter');
8842
        }
8843
8844
        $arrHide[0]['value'] = $this->name;
8845
        $arrHide[0]['padding'] = 20;
8846
8847
        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...
8848
            if ($action != 'add') {
8849
                if ($arrLP[$i]['item_type'] == 'dir' &&
8850
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8851
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8852
                ) {
8853
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8854
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8855
                    if ($parent == $arrLP[$i]['id']) {
8856
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8857
                    }
8858
                }
8859
            } else {
8860
                if ($arrLP[$i]['item_type'] == 'dir') {
8861
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8862
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8863
                    if ($parent == $arrLP[$i]['id']) {
8864
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8865
                    }
8866
                }
8867
            }
8868
        }
8869
8870
        $parent_select = $form->addSelect(
8871
            'parent',
8872
            get_lang('Parent'),
8873
            [],
8874
            [
8875
                'id' => 'idParent',
8876
                'onchange' => 'javascript: load_cbo(this.value);',
8877
            ]
8878
        );
8879
8880
        $my_count = 0;
8881
        foreach ($arrHide as $key => $value) {
8882
            if ($my_count != 0) {
8883
                // The LP name is also the first section and is not in the same charset like the other sections.
8884
                $value['value'] = Security::remove_XSS($value['value']);
8885
                $parent_select->addOption(
8886
                    $value['value'],
8887
                    $key,
8888
                    'style="padding-left:'.$value['padding'].'px;"'
8889
                );
8890
            } else {
8891
                $value['value'] = Security::remove_XSS($value['value']);
8892
                $parent_select->addOption(
8893
                    $value['value'],
8894
                    $key,
8895
                    'style="padding-left:'.$value['padding'].'px;"'
8896
                );
8897
            }
8898
            $my_count++;
8899
        }
8900
8901
        if (!empty($id)) {
8902
            $parent_select->setSelected($parent);
8903
        } else {
8904
            $parent_item_id = Session::read('parent_item_id', 0);
8905
            $parent_select->setSelected($parent_item_id);
8906
        }
8907
8908
        if (is_array($arrLP)) {
8909
            reset($arrLP);
8910
        }
8911
8912
        $arrHide = [];
8913
        $s_selected_position = null;
8914
8915
        // POSITION
8916
        $lastPosition = null;
8917
8918
        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...
8919
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8920
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8921
            ) {
8922
                if ((isset($extra_info['previous_item_id']) &&
8923
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8924
                ) {
8925
                    $s_selected_position = $arrLP[$i]['id'];
8926
                }
8927
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8928
            }
8929
            $lastPosition = $arrLP[$i]['id'];
8930
        }
8931
8932
        if (empty($s_selected_position)) {
8933
            $s_selected_position = $lastPosition;
8934
        }
8935
8936
        $position = $form->addSelect(
8937
            'previous',
8938
            get_lang('Position'),
8939
            [],
8940
            ['id' => 'previous']
8941
        );
8942
        $position->addOption(get_lang('FirstPosition'), 0);
8943
8944
        foreach ($arrHide as $key => $value) {
8945
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8946
            $position->addOption(
8947
                $value['value'],
8948
                $key,
8949
                'style="padding-left:'.$padding.'px;"'
8950
            );
8951
        }
8952
        $position->setSelected($s_selected_position);
8953
8954
        if (is_array($arrLP)) {
8955
            reset($arrLP);
8956
        }
8957
8958
        if ($action != 'move') {
8959
            $id_prerequisite = 0;
8960
            if (is_array($arrLP)) {
8961
                foreach ($arrLP as $key => $value) {
8962
                    if ($value['id'] == $id) {
8963
                        $id_prerequisite = $value['prerequisite'];
8964
                        break;
8965
                    }
8966
                }
8967
            }
8968
8969
            $arrHide = [];
8970
            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...
8971
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8972
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8973
                ) {
8974
                    if (isset($extra_info['previous_item_id']) &&
8975
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8976
                    ) {
8977
                        $s_selected_position = $arrLP[$i]['id'];
8978
                    } elseif ($action == 'add') {
8979
                        $s_selected_position = $arrLP[$i]['id'];
8980
                    }
8981
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8982
                }
8983
            }
8984
8985
            if (!$no_display_add) {
8986
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8987
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8988
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8989
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8990
                ) {
8991
                    if (isset($_POST['content'])) {
8992
                        $content = stripslashes($_POST['content']);
8993
                    } elseif (is_array($extra_info)) {
8994
                        //If it's an html document or a text file
8995
                        if (!$no_display_edit_textarea) {
8996
                            $content = $this->display_document(
8997
                                $extra_info['path'],
8998
                                false,
8999
                                false
9000
                            );
9001
                        }
9002
                    } elseif (is_numeric($extra_info)) {
9003
                        $content = $this->display_document(
9004
                            $extra_info,
9005
                            false,
9006
                            false
9007
                        );
9008
                    } else {
9009
                        $content = '';
9010
                    }
9011
9012
                    if (!$no_display_edit_textarea) {
9013
                        // We need to calculate here some specific settings for the online editor.
9014
                        // The calculated settings work for documents in the Documents tool
9015
                        // (on the root or in subfolders).
9016
                        // For documents in native scorm packages it is unclear whether the
9017
                        // online editor should be activated or not.
9018
9019
                        // A new document, it is in the root of the repository.
9020
                        $relative_path = '';
9021
                        $relative_prefix = '';
9022
                        if (is_array($extra_info) && $extra_info != 'new') {
9023
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9024
                            $relative_path = explode('/', $extra_info['dir']);
9025
                            $cnt = count($relative_path) - 2;
9026
                            if ($cnt < 0) {
9027
                                $cnt = 0;
9028
                            }
9029
                            $relative_prefix = str_repeat('../', $cnt);
9030
                            $relative_path = array_slice($relative_path, 1, $cnt);
9031
                            $relative_path = implode('/', $relative_path);
9032
                            if (strlen($relative_path) > 0) {
9033
                                $relative_path = $relative_path.'/';
9034
                            }
9035
                        } else {
9036
                            $result = $this->generate_lp_folder($_course);
9037
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9038
                            $relative_prefix = '../../';
9039
                        }
9040
9041
                        $editor_config = [
9042
                            'ToolbarSet' => 'LearningPathDocuments',
9043
                            'Width' => '100%',
9044
                            'Height' => '500',
9045
                            'FullPage' => true,
9046
                            'CreateDocumentDir' => $relative_prefix,
9047
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9048
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9049
                        ];
9050
9051
                        if ($_GET['action'] == 'add_item') {
9052
                            $class = 'add';
9053
                            $text = get_lang('LPCreateDocument');
9054
                        } else {
9055
                            if ($_GET['action'] == 'edit_item') {
9056
                                $class = 'save';
9057
                                $text = get_lang('SaveDocument');
9058
                            }
9059
                        }
9060
9061
                        $form->addButtonSave($text, 'submit_button');
9062
                        $renderer = $form->defaultRenderer();
9063
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9064
                        $form->addElement('html', '<div class="editor-lp">');
9065
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9066
                        $form->addElement('html', '</div>');
9067
                        $defaults['content_lp'] = $content;
9068
                    }
9069
                } elseif (is_numeric($extra_info)) {
9070
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9071
9072
                    $return = $this->display_document($extra_info, true, true, true);
9073
                    $form->addElement('html', $return);
9074
                }
9075
            }
9076
        }
9077
        if (isset($extra_info['item_type']) &&
9078
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9079
        ) {
9080
            $parent_select->freeze();
9081
            $position->freeze();
9082
        }
9083
9084
        if ($action == 'move') {
9085
            $form->addElement('hidden', 'title', $item_title);
9086
            $form->addElement('hidden', 'description', $item_description);
9087
        }
9088
        if (is_numeric($extra_info)) {
9089
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9090
            $form->addElement('hidden', 'path', $extra_info);
9091
        } elseif (is_array($extra_info)) {
9092
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9093
            $form->addElement('hidden', 'path', $extra_info['path']);
9094
        }
9095
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9096
        $form->addElement('hidden', 'post_time', time());
9097
        $form->setDefaults($defaults);
9098
9099
        return $form->returnForm();
9100
    }
9101
9102
    /**
9103
     * Return HTML form to add/edit a link item.
9104
     *
9105
     * @param string $action     (add/edit)
9106
     * @param int    $id         Item ID if exists
9107
     * @param mixed  $extra_info
9108
     *
9109
     * @throws Exception
9110
     * @throws HTML_QuickForm_Error
9111
     *
9112
     * @return string HTML form
9113
     */
9114
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9115
    {
9116
        $course_id = api_get_course_int_id();
9117
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9118
        $tbl_link = Database::get_course_table(TABLE_LINK);
9119
9120
        if ($id != 0 && is_array($extra_info)) {
9121
            $item_title = stripslashes($extra_info['title']);
9122
            $item_description = stripslashes($extra_info['description']);
9123
            $item_url = stripslashes($extra_info['url']);
9124
        } elseif (is_numeric($extra_info)) {
9125
            $extra_info = intval($extra_info);
9126
            $sql = "SELECT title, description, url FROM ".$tbl_link."
9127
                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
9128
            $result = Database::query($sql);
9129
            $row = Database::fetch_array($result);
9130
            $item_title = $row['title'];
9131
            $item_description = $row['description'];
9132
            $item_url = $row['url'];
9133
        } else {
9134
            $item_title = '';
9135
            $item_description = '';
9136
            $item_url = '';
9137
        }
9138
9139
        $form = new FormValidator(
9140
            'edit_link',
9141
            'POST',
9142
            $this->getCurrentBuildingModeURL()
9143
        );
9144
        $defaults = [];
9145
        if ($id != 0 && is_array($extra_info)) {
9146
            $parent = $extra_info['parent_item_id'];
9147
        } else {
9148
            $parent = 0;
9149
        }
9150
9151
        $sql = "SELECT * FROM $tbl_lp_item
9152
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9153
        $result = Database::query($sql);
9154
        $arrLP = [];
9155
9156
        while ($row = Database::fetch_array($result)) {
9157
            $arrLP[] = [
9158
                'id' => $row['id'],
9159
                'item_type' => $row['item_type'],
9160
                'title' => $row['title'],
9161
                'path' => $row['path'],
9162
                'description' => $row['description'],
9163
                'parent_item_id' => $row['parent_item_id'],
9164
                'previous_item_id' => $row['previous_item_id'],
9165
                'next_item_id' => $row['next_item_id'],
9166
                'display_order' => $row['display_order'],
9167
                'max_score' => $row['max_score'],
9168
                'min_score' => $row['min_score'],
9169
                'mastery_score' => $row['mastery_score'],
9170
                'prerequisite' => $row['prerequisite'],
9171
            ];
9172
        }
9173
9174
        $this->tree_array($arrLP);
9175
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9176
        unset($this->arrMenu);
9177
9178
        if ($action == 'add') {
9179
            $legend = get_lang('CreateTheLink');
9180
        } elseif ($action == 'move') {
9181
            $legend = get_lang('MoveCurrentLink');
9182
        } else {
9183
            $legend = get_lang('EditCurrentLink');
9184
        }
9185
9186
        $form->addHeader($legend);
9187
9188
        if ($action != 'move') {
9189
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9190
            $defaults['title'] = $item_title;
9191
        }
9192
9193
        $selectParent = $form->addSelect(
9194
            'parent',
9195
            get_lang('Parent'),
9196
            [],
9197
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9198
        );
9199
        $selectParent->addOption($this->name, 0);
9200
        $arrHide = [
9201
            $id,
9202
        ];
9203
9204
        $parent_item_id = Session::read('parent_item_id', 0);
9205
9206
        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...
9207
            if ($action != 'add') {
9208
                if (
9209
                    ($arrLP[$i]['item_type'] == 'dir') &&
9210
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9211
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9212
                ) {
9213
                    $selectParent->addOption(
9214
                        $arrLP[$i]['title'],
9215
                        $arrLP[$i]['id'],
9216
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9217
                    );
9218
9219
                    if ($parent == $arrLP[$i]['id']) {
9220
                        $selectParent->setSelected($arrLP[$i]['id']);
9221
                    }
9222
                } else {
9223
                    $arrHide[] = $arrLP[$i]['id'];
9224
                }
9225
            } else {
9226
                if ($arrLP[$i]['item_type'] == 'dir') {
9227
                    $selectParent->addOption(
9228
                        $arrLP[$i]['title'],
9229
                        $arrLP[$i]['id'],
9230
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9231
                    );
9232
9233
                    if ($parent_item_id == $arrLP[$i]['id']) {
9234
                        $selectParent->setSelected($arrLP[$i]['id']);
9235
                    }
9236
                }
9237
            }
9238
        }
9239
9240
        if (is_array($arrLP)) {
9241
            reset($arrLP);
9242
        }
9243
9244
        $selectPrevious = $form->addSelect(
9245
            'previous',
9246
            get_lang('Position'),
9247
            [],
9248
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9249
        );
9250
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9251
9252
        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...
9253
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9254
                $selectPrevious->addOption(
9255
                    $arrLP[$i]['title'],
9256
                    $arrLP[$i]['id']
9257
                );
9258
9259
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9260
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9261
                } elseif ($action == 'add') {
9262
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9263
                }
9264
            }
9265
        }
9266
9267
        if ($action != 'move') {
9268
            $urlAttributes = ['class' => 'learnpath_item_form'];
9269
9270
            if (is_numeric($extra_info)) {
9271
                $urlAttributes['disabled'] = 'disabled';
9272
            }
9273
9274
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9275
            $defaults['url'] = $item_url;
9276
9277
            $id_prerequisite = 0;
9278
            if (is_array($arrLP)) {
9279
                foreach ($arrLP as $key => $value) {
9280
                    if ($value['id'] == $id) {
9281
                        $id_prerequisite = $value['prerequisite'];
9282
                        break;
9283
                    }
9284
                }
9285
            }
9286
9287
            $arrHide = [];
9288
            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...
9289
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9290
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9291
                        $s_selected_position = $arrLP[$i]['id'];
9292
                    } elseif ($action == 'add') {
9293
                        $s_selected_position = 0;
9294
                    }
9295
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9296
                }
9297
            }
9298
        }
9299
9300
        if ($action == 'add') {
9301
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9302
        } else {
9303
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9304
        }
9305
9306
        if ($action == 'move') {
9307
            $form->addHidden('title', $item_title);
9308
            $form->addHidden('description', $item_description);
9309
        }
9310
9311
        if (is_numeric($extra_info)) {
9312
            $form->addHidden('path', $extra_info);
9313
        } elseif (is_array($extra_info)) {
9314
            $form->addHidden('path', $extra_info['path']);
9315
        }
9316
        $form->addHidden('type', TOOL_LINK);
9317
        $form->addHidden('post_time', time());
9318
        $form->setDefaults($defaults);
9319
9320
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9321
    }
9322
9323
    /**
9324
     * Return HTML form to add/edit a student publication (work).
9325
     *
9326
     * @param string $action
9327
     * @param int    $id         Item ID if already exists
9328
     * @param string $extra_info
9329
     *
9330
     * @throws Exception
9331
     *
9332
     * @return string HTML form
9333
     */
9334
    public function display_student_publication_form(
9335
        $action = 'add',
9336
        $id = 0,
9337
        $extra_info = ''
9338
    ) {
9339
        $course_id = api_get_course_int_id();
9340
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9341
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9342
9343
        if ($id != 0 && is_array($extra_info)) {
9344
            $item_title = stripslashes($extra_info['title']);
9345
            $item_description = stripslashes($extra_info['description']);
9346
        } elseif (is_numeric($extra_info)) {
9347
            $extra_info = intval($extra_info);
9348
            $sql = "SELECT title, description
9349
                    FROM $tbl_publication
9350
                    WHERE c_id = $course_id AND id = ".$extra_info;
9351
9352
            $result = Database::query($sql);
9353
            $row = Database::fetch_array($result);
9354
9355
            $item_title = $row['title'];
9356
        } else {
9357
            $item_title = get_lang('Student_publication');
9358
        }
9359
9360
        if ($id != 0 && is_array($extra_info)) {
9361
            $parent = $extra_info['parent_item_id'];
9362
        } else {
9363
            $parent = 0;
9364
        }
9365
9366
        $sql = "SELECT * FROM $tbl_lp_item 
9367
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9368
        $result = Database::query($sql);
9369
        $arrLP = [];
9370
9371
        while ($row = Database::fetch_array($result)) {
9372
            $arrLP[] = [
9373
                'id' => $row['iid'],
9374
                'item_type' => $row['item_type'],
9375
                'title' => $row['title'],
9376
                'path' => $row['path'],
9377
                'description' => $row['description'],
9378
                'parent_item_id' => $row['parent_item_id'],
9379
                'previous_item_id' => $row['previous_item_id'],
9380
                'next_item_id' => $row['next_item_id'],
9381
                'display_order' => $row['display_order'],
9382
                'max_score' => $row['max_score'],
9383
                'min_score' => $row['min_score'],
9384
                'mastery_score' => $row['mastery_score'],
9385
                'prerequisite' => $row['prerequisite'],
9386
            ];
9387
        }
9388
9389
        $this->tree_array($arrLP);
9390
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9391
        unset($this->arrMenu);
9392
9393
        $form = new FormValidator('frm_student_publication', 'post', '#');
9394
9395
        if ($action == 'add') {
9396
            $form->addHeader(get_lang('Student_publication'));
9397
        } elseif ($action == 'move') {
9398
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9399
        } else {
9400
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9401
        }
9402
9403
        if ($action != 'move') {
9404
            $form->addText(
9405
                'title',
9406
                get_lang('Title'),
9407
                true,
9408
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9409
            );
9410
        }
9411
9412
        $parentSelect = $form->addSelect(
9413
            'parent',
9414
            get_lang('Parent'),
9415
            ['0' => $this->name],
9416
            [
9417
                'onchange' => 'javascript: load_cbo(this.value);',
9418
                'class' => 'learnpath_item_form',
9419
                'id' => 'idParent',
9420
            ]
9421
        );
9422
9423
        $arrHide = [$id];
9424
        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...
9425
            if ($action != 'add') {
9426
                if (
9427
                    ($arrLP[$i]['item_type'] == 'dir') &&
9428
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9429
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9430
                ) {
9431
                    $parentSelect->addOption(
9432
                        $arrLP[$i]['title'],
9433
                        $arrLP[$i]['id'],
9434
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9435
                    );
9436
9437
                    if ($parent == $arrLP[$i]['id']) {
9438
                        $parentSelect->setSelected($arrLP[$i]['id']);
9439
                    }
9440
                } else {
9441
                    $arrHide[] = $arrLP[$i]['id'];
9442
                }
9443
            } else {
9444
                if ($arrLP[$i]['item_type'] == 'dir') {
9445
                    $parentSelect->addOption(
9446
                        $arrLP[$i]['title'],
9447
                        $arrLP[$i]['id'],
9448
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9449
                    );
9450
9451
                    if ($parent == $arrLP[$i]['id']) {
9452
                        $parentSelect->setSelected($arrLP[$i]['id']);
9453
                    }
9454
                }
9455
            }
9456
        }
9457
9458
        if (is_array($arrLP)) {
9459
            reset($arrLP);
9460
        }
9461
9462
        $previousSelect = $form->addSelect(
9463
            'previous',
9464
            get_lang('Position'),
9465
            ['0' => get_lang('FirstPosition')],
9466
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9467
        );
9468
9469
        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...
9470
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9471
                $previousSelect->addOption(
9472
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9473
                    $arrLP[$i]['id']
9474
                );
9475
9476
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9477
                    $previousSelect->setSelected($arrLP[$i]['id']);
9478
                } elseif ($action == 'add') {
9479
                    $previousSelect->setSelected($arrLP[$i]['id']);
9480
                }
9481
            }
9482
        }
9483
9484
        if ($action != 'move') {
9485
            $id_prerequisite = 0;
9486
            if (is_array($arrLP)) {
9487
                foreach ($arrLP as $key => $value) {
9488
                    if ($value['id'] == $id) {
9489
                        $id_prerequisite = $value['prerequisite'];
9490
                        break;
9491
                    }
9492
                }
9493
            }
9494
            $arrHide = [];
9495
            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...
9496
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9497
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9498
                        $s_selected_position = $arrLP[$i]['id'];
9499
                    } elseif ($action == 'add') {
9500
                        $s_selected_position = 0;
9501
                    }
9502
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9503
                }
9504
            }
9505
        }
9506
9507
        if ($action == 'add') {
9508
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9509
        } else {
9510
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9511
        }
9512
9513
        if ($action == 'move') {
9514
            $form->addHidden('title', $item_title);
9515
            $form->addHidden('description', $item_description);
9516
        }
9517
9518
        if (is_numeric($extra_info)) {
9519
            $form->addHidden('path', $extra_info);
9520
        } elseif (is_array($extra_info)) {
9521
            $form->addHidden('path', $extra_info['path']);
9522
        }
9523
9524
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9525
        $form->addHidden('post_time', time());
9526
        $form->setDefaults(['title' => $item_title]);
9527
9528
        $return = '<div class="sectioncomment">';
9529
        $return .= $form->returnForm();
9530
        $return .= '</div>';
9531
9532
        return $return;
9533
    }
9534
9535
    /**
9536
     * Displays the menu for manipulating a step.
9537
     *
9538
     * @param id     $item_id
9539
     * @param string $item_type
9540
     *
9541
     * @return string
9542
     */
9543
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9544
    {
9545
        $_course = api_get_course_info();
9546
        $course_code = api_get_course_id();
9547
        $return = '<div class="actions">';
9548
        switch ($item_type) {
9549
            case 'dir':
9550
                // Commented the message cause should not show it.
9551
                //$lang = get_lang('TitleManipulateChapter');
9552
                break;
9553
            case TOOL_LP_FINAL_ITEM:
9554
            case TOOL_DOCUMENT:
9555
                // Commented the message cause should not show it.
9556
                //$lang = get_lang('TitleManipulateDocument');
9557
                break;
9558
            case TOOL_LINK:
9559
            case 'link':
9560
                // Commented the message cause should not show it.
9561
                //$lang = get_lang('TitleManipulateLink');
9562
                break;
9563
            case TOOL_QUIZ:
9564
                // Commented the message cause should not show it.
9565
                //$lang = get_lang('TitleManipulateQuiz');
9566
                break;
9567
            case TOOL_STUDENTPUBLICATION:
9568
                // Commented the message cause should not show it.
9569
                //$lang = get_lang('TitleManipulateStudentPublication');
9570
                break;
9571
        }
9572
9573
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9574
        $item_id = intval($item_id);
9575
        $sql = "SELECT * FROM $tbl_lp_item 
9576
                WHERE iid = ".$item_id;
9577
        $result = Database::query($sql);
9578
        $row = Database::fetch_assoc($result);
9579
9580
        $audio_player = null;
9581
        // We display an audio player if needed.
9582
        if (!empty($row['audio'])) {
9583
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9584
                              <a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.
9585
                              </div>';
9586
            $audio_player .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>';
9587
            $audio_player .= '<script>
9588
                var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF");
9589
                s1.addParam("allowscriptaccess","always");
9590
                s1.addParam("flashvars","file=../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'].'&autostart=true");
9591
                s1.write("container");
9592
            </script>';
9593
        }
9594
9595
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9596
9597
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9598
            $return .= Display::url(
9599
                Display::return_icon(
9600
                    'edit.png',
9601
                    get_lang('Edit'),
9602
                    [],
9603
                    ICON_SIZE_SMALL
9604
                ),
9605
                $url.'&action=edit_item&path_item='.$row['path']
9606
            );
9607
9608
            $return .= Display::url(
9609
                Display::return_icon(
9610
                    'move.png',
9611
                    get_lang('Move'),
9612
                    [],
9613
                    ICON_SIZE_SMALL
9614
                ),
9615
                $url.'&action=move_item'
9616
            );
9617
        }
9618
9619
        // Commented for now as prerequisites cannot be added to chapters.
9620
        if ($item_type != 'dir') {
9621
            $return .= Display::url(
9622
                Display::return_icon(
9623
                    'accept.png',
9624
                    get_lang('LearnpathPrerequisites'),
9625
                    [],
9626
                    ICON_SIZE_SMALL
9627
                ),
9628
                $url.'&action=edit_item_prereq'
9629
            );
9630
        }
9631
        $return .= Display::url(
9632
            Display::return_icon(
9633
                'delete.png',
9634
                get_lang('Delete'),
9635
                [],
9636
                ICON_SIZE_SMALL
9637
            ),
9638
            $url.'&action=delete_item'
9639
        );
9640
9641
        if ($item_type == TOOL_HOTPOTATOES) {
9642
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9643
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9644
        }
9645
9646
        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
9647
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9648
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9649
        }
9650
9651
        $return .= '</div>';
9652
9653
        if (!empty($audio_player)) {
9654
            $return .= '<br />'.$audio_player;
9655
        }
9656
9657
        return $return;
9658
    }
9659
9660
    /**
9661
     * Creates the javascript needed for filling up the checkboxes without page reload.
9662
     *
9663
     * @return string
9664
     */
9665
    public function get_js_dropdown_array()
9666
    {
9667
        $course_id = api_get_course_int_id();
9668
        $return = 'var child_name = new Array();'."\n";
9669
        $return .= 'var child_value = new Array();'."\n\n";
9670
        $return .= 'child_name[0] = new Array();'."\n";
9671
        $return .= 'child_value[0] = new Array();'."\n\n";
9672
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9673
        $sql = "SELECT * FROM ".$tbl_lp_item."
9674
                WHERE 
9675
                    c_id = $course_id AND 
9676
                    lp_id = ".$this->lp_id." AND 
9677
                    parent_item_id = 0
9678
                ORDER BY display_order ASC";
9679
        $res_zero = Database::query($sql);
9680
        $i = 0;
9681
9682
        while ($row_zero = Database::fetch_array($res_zero)) {
9683
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9684
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9685
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9686
                }
9687
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9688
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9689
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9690
            }
9691
        }
9692
        $return .= "\n";
9693
        $sql = "SELECT * FROM $tbl_lp_item
9694
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9695
        $res = Database::query($sql);
9696
        while ($row = Database::fetch_array($res)) {
9697
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9698
                           WHERE
9699
                                c_id = ".$course_id." AND
9700
                                parent_item_id = ".$row['iid']."
9701
                           ORDER BY display_order ASC";
9702
            $res_parent = Database::query($sql_parent);
9703
            $i = 0;
9704
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9705
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9706
9707
            while ($row_parent = Database::fetch_array($res_parent)) {
9708
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
9709
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9710
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9711
            }
9712
            $return .= "\n";
9713
        }
9714
9715
        return $return;
9716
    }
9717
9718
    /**
9719
     * Display the form to allow moving an item.
9720
     *
9721
     * @param int $item_id Item ID
9722
     *
9723
     * @throws Exception
9724
     * @throws HTML_QuickForm_Error
9725
     *
9726
     * @return string HTML form
9727
     */
9728
    public function display_move_item($item_id)
9729
    {
9730
        $return = '';
9731
        if (is_numeric($item_id)) {
9732
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9733
9734
            $sql = "SELECT * FROM $tbl_lp_item
9735
                    WHERE iid = $item_id";
9736
            $res = Database::query($sql);
9737
            $row = Database::fetch_array($res);
9738
9739
            switch ($row['item_type']) {
9740
                case 'dir':
9741
                case 'asset':
9742
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9743
                    $return .= $this->display_item_form(
9744
                        $row['item_type'],
9745
                        get_lang('MoveCurrentChapter'),
9746
                        'move',
9747
                        $item_id,
9748
                        $row
9749
                    );
9750
                    break;
9751
                case TOOL_DOCUMENT:
9752
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9753
                    $return .= $this->display_document_form('move', $item_id, $row);
9754
                    break;
9755
                case TOOL_LINK:
9756
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9757
                    $return .= $this->display_link_form('move', $item_id, $row);
9758
                    break;
9759
                case TOOL_HOTPOTATOES:
9760
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9761
                    $return .= $this->display_link_form('move', $item_id, $row);
9762
                    break;
9763
                case TOOL_QUIZ:
9764
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9765
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9766
                    break;
9767
                case TOOL_STUDENTPUBLICATION:
9768
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9769
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9770
                    break;
9771
                case TOOL_FORUM:
9772
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9773
                    $return .= $this->display_forum_form('move', $item_id, $row);
9774
                    break;
9775
                case TOOL_THREAD:
9776
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9777
                    $return .= $this->display_forum_form('move', $item_id, $row);
9778
                    break;
9779
            }
9780
        }
9781
9782
        return $return;
9783
    }
9784
9785
    /**
9786
     * Displays a basic form on the overview page for changing the item title and the item description.
9787
     *
9788
     * @param string $item_type
9789
     * @param string $title
9790
     * @param array  $data
9791
     *
9792
     * @throws Exception
9793
     * @throws HTML_QuickForm_Error
9794
     *
9795
     * @return string
9796
     */
9797
    public function display_item_small_form($item_type, $title = '', $data = [])
9798
    {
9799
        $url = api_get_self().'?'.api_get_cidreq().'&action=edit_item&lp_id='.$this->lp_id;
9800
        $form = new FormValidator('small_form', 'post', $url);
9801
        $form->addElement('header', $title);
9802
        $form->addElement('text', 'title', get_lang('Title'));
9803
        $form->addButtonSave(get_lang('Save'), 'submit_button');
9804
        $form->addElement('hidden', 'id', $data['id']);
9805
        $form->addElement('hidden', 'parent', $data['parent_item_id']);
9806
        $form->addElement('hidden', 'previous', $data['previous_item_id']);
9807
        $form->setDefaults(['title' => $data['title']]);
9808
9809
        return $form->toHtml();
9810
    }
9811
9812
    /**
9813
     * Return HTML form to allow prerequisites selection.
9814
     *
9815
     * @todo use FormValidator
9816
     *
9817
     * @param int Item ID
9818
     *
9819
     * @return string HTML form
9820
     */
9821
    public function display_item_prerequisites_form($item_id = 0)
9822
    {
9823
        $course_id = api_get_course_int_id();
9824
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9825
        $item_id = intval($item_id);
9826
        /* Current prerequisite */
9827
        $sql = "SELECT * FROM $tbl_lp_item
9828
                WHERE iid = $item_id";
9829
        $result = Database::query($sql);
9830
        $row = Database::fetch_array($result);
9831
        $prerequisiteId = $row['prerequisite'];
9832
        $return = '<legend>';
9833
        $return .= get_lang('AddEditPrerequisites');
9834
        $return .= '</legend>';
9835
        $return .= '<form method="POST">';
9836
        $return .= '<div class="table-responsive">';
9837
        $return .= '<table class="table table-hover">';
9838
        $return .= '<thead>';
9839
        $return .= '<tr>';
9840
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9841
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9842
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9843
        $return .= '</tr>';
9844
        $return .= '</thead>';
9845
9846
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9847
        $return .= '<tbody>';
9848
        $return .= '<tr>';
9849
        $return .= '<td colspan="3">';
9850
        $return .= '<div class="radio learnpath"><label for="idNone">';
9851
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9852
        $return .= get_lang('None').'</label>';
9853
        $return .= '</div>';
9854
        $return .= '</tr>';
9855
9856
        $sql = "SELECT * FROM $tbl_lp_item
9857
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9858
        $result = Database::query($sql);
9859
        $arrLP = [];
9860
9861
        $selectedMinScore = [];
9862
        $selectedMaxScore = [];
9863
        while ($row = Database::fetch_array($result)) {
9864
            if ($row['id'] == $item_id) {
9865
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9866
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9867
            }
9868
            $arrLP[] = [
9869
                'id' => $row['iid'],
9870
                'item_type' => $row['item_type'],
9871
                'title' => $row['title'],
9872
                'ref' => $row['ref'],
9873
                'description' => $row['description'],
9874
                'parent_item_id' => $row['parent_item_id'],
9875
                'previous_item_id' => $row['previous_item_id'],
9876
                'next_item_id' => $row['next_item_id'],
9877
                'max_score' => $row['max_score'],
9878
                'min_score' => $row['min_score'],
9879
                'mastery_score' => $row['mastery_score'],
9880
                'prerequisite' => $row['prerequisite'],
9881
                'display_order' => $row['display_order'],
9882
                'prerequisite_min_score' => $row['prerequisite_min_score'],
9883
                'prerequisite_max_score' => $row['prerequisite_max_score'],
9884
            ];
9885
        }
9886
9887
        $this->tree_array($arrLP);
9888
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9889
        unset($this->arrMenu);
9890
9891
        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...
9892
            $item = $arrLP[$i];
9893
9894
            if ($item['id'] == $item_id) {
9895
                break;
9896
            }
9897
9898
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9899
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9900
9901
            $return .= '<tr>';
9902
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9903
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9904
            $return .= '<label for="id'.$item['id'].'">';
9905
            $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'].'" />';
9906
9907
            $icon_name = str_replace(' ', '', $item['item_type']);
9908
9909
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9910
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9911
            } else {
9912
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9913
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9914
                } else {
9915
                    $return .= Display::return_icon('folder_document.png');
9916
                }
9917
            }
9918
9919
            $return .= $item['title'].'</label>';
9920
            $return .= '</div>';
9921
            $return .= '</td>';
9922
9923
            if ($item['item_type'] == TOOL_QUIZ) {
9924
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9925
                $lpItemObj = new LpItem($course_id, $item['id']);
9926
                $exercise = new Exercise($course_id);
9927
                $exercise->read($lpItemObj->path);
9928
                $lpItemObj->max_score = $exercise->get_max_score();
9929
                $lpItemObj->update();
9930
                $item['max_score'] = $lpItemObj->max_score;
9931
9932
                $return .= '<td>';
9933
                $return .= '<input 
9934
                    class="form-control" 
9935
                    size="4" maxlength="3" 
9936
                    name="min_'.$item['id'].'" 
9937
                    type="number" 
9938
                    min="0" 
9939
                    step="1" 
9940
                    max="'.$item['max_score'].'" 
9941
                    value="'.$selectedMinScoreValue.'" 
9942
                />';
9943
                $return .= '</td>';
9944
                $return .= '<td>';
9945
                $return .= '<input 
9946
                    class="form-control" 
9947
                    size="4" 
9948
                    maxlength="3" 
9949
                    name="max_'.$item['id'].'" 
9950
                    type="number" 
9951
                    min="0" 
9952
                    step="1" 
9953
                    max="'.$item['max_score'].'" 
9954
                    value="'.$selectedMaxScoreValue.'" 
9955
                />';
9956
                $return .= '</td>';
9957
            }
9958
9959
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9960
                $return .= '<td>';
9961
                $return .= '<input 
9962
                    size="4" 
9963
                    maxlength="3" 
9964
                    name="min_'.$item['id'].'" 
9965
                    type="number" 
9966
                    min="0" 
9967
                    step="1" 
9968
                    max="'.$item['max_score'].'" 
9969
                    value="'.$selectedMinScoreValue.'" 
9970
                />';
9971
                $return .= '</td>';
9972
                $return .= '<td>';
9973
                $return .= '<input 
9974
                    size="4" 
9975
                    maxlength="3" 
9976
                    name="max_'.$item['id'].'" 
9977
                    type="number" 
9978
                    min="0" 
9979
                    step="1" 
9980
                    max="'.$item['max_score'].'" 
9981
                    value="'.$selectedMaxScoreValue.'" 
9982
                />';
9983
                $return .= '</td>';
9984
            }
9985
            $return .= '</tr>';
9986
        }
9987
        $return .= '<tr>';
9988
        $return .= '</tr>';
9989
        $return .= '</tbody>';
9990
        $return .= '</table>';
9991
        $return .= '</div>';
9992
        $return .= '<div class="form-group">';
9993
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9994
            get_lang('ModifyPrerequisites').'</button>';
9995
        $return .= '</form>';
9996
9997
        return $return;
9998
    }
9999
10000
    /**
10001
     * Return HTML list to allow prerequisites selection for lp.
10002
     *
10003
     * @return string HTML form
10004
     */
10005
    public function display_lp_prerequisites_list()
10006
    {
10007
        $course_id = api_get_course_int_id();
10008
        $lp_id = $this->lp_id;
10009
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10010
10011
        // get current prerequisite
10012
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10013
        $result = Database::query($sql);
10014
        $row = Database::fetch_array($result);
10015
        $prerequisiteId = $row['prerequisite'];
10016
        $session_id = api_get_session_id();
10017
        $session_condition = api_get_session_condition($session_id, true, true);
10018
        $sql = "SELECT * FROM $tbl_lp
10019
                WHERE c_id = $course_id $session_condition
10020
                ORDER BY display_order ";
10021
        $rs = Database::query($sql);
10022
        $return = '';
10023
        $return .= '<select name="prerequisites" class="form-control">';
10024
        $return .= '<option value="0">'.get_lang('None').'</option>';
10025
        if (Database::num_rows($rs) > 0) {
10026
            while ($row = Database::fetch_array($rs)) {
10027
                if ($row['id'] == $lp_id) {
10028
                    continue;
10029
                }
10030
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10031
            }
10032
        }
10033
        $return .= '</select>';
10034
10035
        return $return;
10036
    }
10037
10038
    /**
10039
     * Creates a list with all the documents in it.
10040
     *
10041
     * @param bool $showInvisibleFiles
10042
     *
10043
     * @throws Exception
10044
     * @throws HTML_QuickForm_Error
10045
     *
10046
     * @return string
10047
     */
10048
    public function get_documents($showInvisibleFiles = false)
10049
    {
10050
        $course_info = api_get_course_info();
10051
        $sessionId = api_get_session_id();
10052
        $documentTree = DocumentManager::get_document_preview(
10053
            $course_info,
10054
            $this->lp_id,
10055
            null,
10056
            $sessionId,
10057
            true,
10058
            null,
10059
            null,
10060
            $showInvisibleFiles,
10061
            true
10062
        );
10063
10064
        $headers = [
10065
            get_lang('Files'),
10066
            get_lang('CreateTheDocument'),
10067
            get_lang('Upload'),
10068
        ];
10069
10070
        $form = new FormValidator(
10071
            'form_upload',
10072
            'POST',
10073
            $this->getCurrentBuildingModeURL(),
10074
            '',
10075
            ['enctype' => 'multipart/form-data']
10076
        );
10077
10078
        $folders = DocumentManager::get_all_document_folders(
10079
            api_get_course_info(),
10080
            0,
10081
            true
10082
        );
10083
10084
        DocumentManager::build_directory_selector(
10085
            $folders,
10086
            '',
10087
            [],
10088
            true,
10089
            $form,
10090
            'directory_parent_id'
10091
        );
10092
10093
        $group = [
10094
            $form->createElement(
10095
                'radio',
10096
                'if_exists',
10097
                get_lang("UplWhatIfFileExists"),
10098
                get_lang('UplDoNothing'),
10099
                'nothing'
10100
            ),
10101
            $form->createElement(
10102
                'radio',
10103
                'if_exists',
10104
                null,
10105
                get_lang('UplOverwriteLong'),
10106
                'overwrite'
10107
            ),
10108
            $form->createElement(
10109
                'radio',
10110
                'if_exists',
10111
                null,
10112
                get_lang('UplRenameLong'),
10113
                'rename'
10114
            ),
10115
        ];
10116
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10117
10118
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10119
        $defaultFileExistsOption = 'rename';
10120
        if (!empty($fileExistsOption)) {
10121
            $defaultFileExistsOption = $fileExistsOption;
10122
        }
10123
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10124
10125
        // Check box options
10126
        $form->addElement(
10127
            'checkbox',
10128
            'unzip',
10129
            get_lang('Options'),
10130
            get_lang('Uncompress')
10131
        );
10132
10133
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10134
        $form->addMultipleUpload($url);
10135
        $new = $this->display_document_form('add', 0);
10136
        $tabs = Display::tabs(
10137
            $headers,
10138
            [$documentTree, $new, $form->returnForm()],
10139
            'subtab'
10140
        );
10141
10142
        return $tabs;
10143
    }
10144
10145
    /**
10146
     * Creates a list with all the exercises (quiz) in it.
10147
     *
10148
     * @return string
10149
     */
10150
    public function get_exercises()
10151
    {
10152
        $course_id = api_get_course_int_id();
10153
        $session_id = api_get_session_id();
10154
        $userInfo = api_get_user_info();
10155
10156
        // New for hotpotatoes.
10157
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10158
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10159
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10160
        $condition_session = api_get_session_condition($session_id, true, true);
10161
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10162
10163
        $activeCondition = ' active <> -1 ';
10164
        if ($setting) {
10165
            $activeCondition = ' active = 1 ';
10166
        }
10167
10168
        $sql_quiz = "SELECT * FROM $tbl_quiz
10169
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10170
                     ORDER BY title ASC";
10171
10172
        $sql_hot = "SELECT * FROM $tbl_doc
10173
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10174
                     ORDER BY id ASC";
10175
10176
        $res_quiz = Database::query($sql_quiz);
10177
        $res_hot = Database::query($sql_hot);
10178
10179
        $return = '<ul class="lp_resource">';
10180
        $return .= '<li class="lp_resource_element">';
10181
        $return .= Display::return_icon('new_exercice.png');
10182
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10183
            get_lang('NewExercise').'</a>';
10184
        $return .= '</li>';
10185
10186
        $previewIcon = Display::return_icon(
10187
            'preview_view.png',
10188
            get_lang('Preview')
10189
        );
10190
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10191
10192
        // Display hotpotatoes
10193
        while ($row_hot = Database::fetch_array($res_hot)) {
10194
            $link = Display::url(
10195
                $previewIcon,
10196
                $exerciseUrl.'&file='.$row_hot['path'],
10197
                ['target' => '_blank']
10198
            );
10199
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10200
            $return .= '<a class="moved" href="#">';
10201
            $return .= Display::return_icon(
10202
                'move_everywhere.png',
10203
                get_lang('Move'),
10204
                [],
10205
                ICON_SIZE_TINY
10206
            );
10207
            $return .= '</a> ';
10208
            $return .= Display::return_icon('hotpotatoes_s.png');
10209
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10210
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10211
            $return .= '</li>';
10212
        }
10213
10214
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10215
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10216
            $title = strip_tags(
10217
                api_html_entity_decode($row_quiz['title'])
10218
            );
10219
10220
            $link = Display::url(
10221
                $previewIcon,
10222
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10223
                ['target' => '_blank']
10224
            );
10225
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10226
            $return .= '<a class="moved" href="#">';
10227
            $return .= Display::return_icon(
10228
                'move_everywhere.png',
10229
                get_lang('Move'),
10230
                [],
10231
                ICON_SIZE_TINY
10232
            );
10233
            $return .= '</a> ';
10234
            $return .= Display::return_icon(
10235
                'quiz.png',
10236
                '',
10237
                [],
10238
                ICON_SIZE_TINY
10239
            );
10240
            $sessionStar = api_get_session_image(
10241
                $row_quiz['session_id'],
10242
                $userInfo['status']
10243
            );
10244
            $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.'">'.
10245
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
10246
                '</a>';
10247
10248
            $return .= '</li>';
10249
        }
10250
10251
        $return .= '</ul>';
10252
10253
        return $return;
10254
    }
10255
10256
    /**
10257
     * Creates a list with all the links in it.
10258
     *
10259
     * @return string
10260
     */
10261
    public function get_links()
10262
    {
10263
        $selfUrl = api_get_self();
10264
        $courseIdReq = api_get_cidreq();
10265
        $course = api_get_course_info();
10266
        $userInfo = api_get_user_info();
10267
10268
        $course_id = $course['real_id'];
10269
        $tbl_link = Database::get_course_table(TABLE_LINK);
10270
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10271
        $moveEverywhereIcon = Display::return_icon(
10272
            'move_everywhere.png',
10273
            get_lang('Move'),
10274
            [],
10275
            ICON_SIZE_TINY
10276
        );
10277
10278
        $session_id = api_get_session_id();
10279
        $condition_session = api_get_session_condition(
10280
            $session_id,
10281
            true,
10282
            true,
10283
            "link.session_id"
10284
        );
10285
10286
        $sql = "SELECT 
10287
                    link.id as link_id,
10288
                    link.title as link_title,
10289
                    link.session_id as link_session_id,
10290
                    link.category_id as category_id,
10291
                    link_category.category_title as category_title
10292
                FROM $tbl_link as link
10293
                LEFT JOIN $linkCategoryTable as link_category
10294
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10295
                WHERE link.c_id = ".$course_id." $condition_session
10296
                ORDER BY link_category.category_title ASC, link.title ASC";
10297
        $result = Database::query($sql);
10298
        $categorizedLinks = [];
10299
        $categories = [];
10300
10301
        while ($link = Database::fetch_array($result)) {
10302
            if (!$link['category_id']) {
10303
                $link['category_title'] = get_lang('Uncategorized');
10304
            }
10305
            $categories[$link['category_id']] = $link['category_title'];
10306
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10307
        }
10308
10309
        $linksHtmlCode =
10310
            '<script>
10311
            function toggle_tool(tool, id) {
10312
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10313
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10314
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10315
                } else {
10316
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10317
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10318
                }
10319
            }
10320
        </script>
10321
10322
        <ul class="lp_resource">
10323
            <li class="lp_resource_element">
10324
                '.Display::return_icon('linksnew.gif').'
10325
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10326
                get_lang('LinkAdd').'
10327
                </a>
10328
            </li>';
10329
10330
        foreach ($categorizedLinks as $categoryId => $links) {
10331
            $linkNodes = null;
10332
            foreach ($links as $key => $linkInfo) {
10333
                $title = $linkInfo['link_title'];
10334
                $linkSessionId = $linkInfo['link_session_id'];
10335
10336
                $link = Display::url(
10337
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10338
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10339
                    ['target' => '_blank']
10340
                );
10341
10342
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10343
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10344
                    $linkNodes .=
10345
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10346
                        <a class="moved" href="#">'.
10347
                            $moveEverywhereIcon.
10348
                        '</a>
10349
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10350
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10351
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10352
                        Security::remove_XSS($title).$sessionStar.$link.
10353
                        '</a>
10354
                    </li>';
10355
                }
10356
            }
10357
            $linksHtmlCode .=
10358
                '<li>
10359
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10360
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10361
                    align="absbottom" />
10362
                </a>
10363
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10364
            </li>
10365
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10366
        }
10367
        $linksHtmlCode .= '</ul>';
10368
10369
        return $linksHtmlCode;
10370
    }
10371
10372
    /**
10373
     * Creates a list with all the student publications in it.
10374
     *
10375
     * @return string
10376
     */
10377
    public function get_student_publications()
10378
    {
10379
        $return = '<ul class="lp_resource">';
10380
        $return .= '<li class="lp_resource_element">';
10381
        $return .= Display::return_icon('works_new.gif');
10382
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10383
            get_lang('AddAssignmentPage').'</a>';
10384
        $return .= '</li>';
10385
10386
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10387
        $works = getWorkListTeacher(0, 100, null, null, null);
10388
        if (!empty($works)) {
10389
            foreach ($works as $work) {
10390
                $link = Display::url(
10391
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10392
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10393
                    ['target' => '_blank']
10394
                );
10395
10396
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10397
                $return .= '<a class="moved" href="#">';
10398
                $return .= Display::return_icon(
10399
                    'move_everywhere.png',
10400
                    get_lang('Move'),
10401
                    [],
10402
                    ICON_SIZE_TINY
10403
                );
10404
                $return .= '</a> ';
10405
10406
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10407
                $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.'">'.
10408
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10409
                </a>';
10410
10411
                $return .= '</li>';
10412
            }
10413
        }
10414
10415
        $return .= '</ul>';
10416
10417
        return $return;
10418
    }
10419
10420
    /**
10421
     * Creates a list with all the forums in it.
10422
     *
10423
     * @return string
10424
     */
10425
    public function get_forums()
10426
    {
10427
        require_once '../forum/forumfunction.inc.php';
10428
        require_once '../forum/forumconfig.inc.php';
10429
10430
        $forumCategories = get_forum_categories();
10431
        $forumsInNoCategory = get_forums_in_category(0);
10432
        if (!empty($forumsInNoCategory)) {
10433
            $forumCategories = array_merge(
10434
                $forumCategories,
10435
                [
10436
                    [
10437
                        'cat_id' => 0,
10438
                        'session_id' => 0,
10439
                        'visibility' => 1,
10440
                        'cat_comment' => null,
10441
                    ],
10442
                ]
10443
            );
10444
        }
10445
10446
        $forumList = get_forums();
10447
        $a_forums = [];
10448
        foreach ($forumCategories as $forumCategory) {
10449
            // The forums in this category.
10450
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10451
            if (!empty($forumsInCategory)) {
10452
                foreach ($forumList as $forum) {
10453
                    if (isset($forum['forum_category']) &&
10454
                        $forum['forum_category'] == $forumCategory['cat_id']
10455
                    ) {
10456
                        $a_forums[] = $forum;
10457
                    }
10458
                }
10459
            }
10460
        }
10461
10462
        $return = '<ul class="lp_resource">';
10463
10464
        // First add link
10465
        $return .= '<li class="lp_resource_element">';
10466
        $return .= Display::return_icon('new_forum.png');
10467
        $return .= Display::url(
10468
            get_lang('CreateANewForum'),
10469
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10470
                'action' => 'add',
10471
                'content' => 'forum',
10472
                'lp_id' => $this->lp_id,
10473
            ]),
10474
            ['title' => get_lang('CreateANewForum')]
10475
        );
10476
        $return .= '</li>';
10477
10478
        $return .= '<script>
10479
            function toggle_forum(forum_id) {
10480
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10481
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10482
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10483
                } else {
10484
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10485
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10486
                }
10487
            }
10488
        </script>';
10489
10490
        foreach ($a_forums as $forum) {
10491
            if (!empty($forum['forum_id'])) {
10492
                $link = Display::url(
10493
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10494
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10495
                    ['target' => '_blank']
10496
                );
10497
10498
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10499
                $return .= '<a class="moved" href="#">';
10500
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10501
                $return .= ' </a>';
10502
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10503
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10504
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10505
                            </a>
10506
                            <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">'.
10507
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10508
10509
                $return .= '</li>';
10510
10511
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10512
                $a_threads = get_threads($forum['forum_id']);
10513
                if (is_array($a_threads)) {
10514
                    foreach ($a_threads as $thread) {
10515
                        $link = Display::url(
10516
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10517
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10518
                            ['target' => '_blank']
10519
                        );
10520
10521
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10522
                        $return .= '&nbsp;<a class="moved" href="#">';
10523
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10524
                        $return .= ' </a>';
10525
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10526
                        $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.'">'.
10527
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10528
                        $return .= '</li>';
10529
                    }
10530
                }
10531
                $return .= '</div>';
10532
            }
10533
        }
10534
        $return .= '</ul>';
10535
10536
        return $return;
10537
    }
10538
10539
    /**
10540
     * // TODO: The output encoding should be equal to the system encoding.
10541
     *
10542
     * Exports the learning path as a SCORM package. This is the main function that
10543
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10544
     * whole thing and returns the zip.
10545
     *
10546
     * This method needs to be called in PHP5, as it will fail with non-adequate
10547
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10548
     * you need to call it on a learnpath object.
10549
     *
10550
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10551
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10552
     * path has been modified, it should use the generic method here below.
10553
     *
10554
     * @return string Returns the zip package string, or null if error
10555
     */
10556
    public function scorm_export()
10557
    {
10558
        $_course = api_get_course_info();
10559
        $course_id = $_course['real_id'];
10560
10561
        // Remove memory and time limits as much as possible as this might be a long process...
10562
        if (function_exists('ini_set')) {
10563
            api_set_memory_limit('256M');
10564
            ini_set('max_execution_time', 600);
10565
        }
10566
10567
        // Create the zip handler (this will remain available throughout the method).
10568
        $archive_path = api_get_path(SYS_ARCHIVE_PATH);
10569
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10570
        $temp_dir_short = uniqid();
10571
        $temp_zip_dir = $archive_path.'/'.$temp_dir_short;
10572
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10573
        $zip_folder = new PclZip($temp_zip_file);
10574
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10575
        $root_path = $main_path = api_get_path(SYS_PATH);
10576
        $files_cleanup = [];
10577
10578
        // Place to temporarily stash the zip file.
10579
        // create the temp dir if it doesn't exist
10580
        // or do a cleanup before creating the zip file.
10581
        if (!is_dir($temp_zip_dir)) {
10582
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10583
        } else {
10584
            // Cleanup: Check the temp dir for old files and delete them.
10585
            $handle = opendir($temp_zip_dir);
10586
            while (false !== ($file = readdir($handle))) {
10587
                if ($file != '.' && $file != '..') {
10588
                    unlink("$temp_zip_dir/$file");
10589
                }
10590
            }
10591
            closedir($handle);
10592
        }
10593
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10594
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10595
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10596
        ) {
10597
            // Remove the possible . at the end of the path.
10598
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10599
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10600
            mkdir(
10601
                $dest_path_to_scorm_folder,
10602
                api_get_permissions_for_new_directories(),
10603
                true
10604
            );
10605
            copyr(
10606
                $current_course_path.'/scorm/'.$this->path,
10607
                $dest_path_to_scorm_folder,
10608
                ['imsmanifest'],
10609
                $zip_files
10610
            );
10611
        }
10612
10613
        // Build a dummy imsmanifest structure.
10614
        // Do not add to the zip yet (we still need it).
10615
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10616
        // Aggregation Model official document, section "2.3 Content Packaging".
10617
        // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding.
10618
        $xmldoc = new DOMDocument('1.0');
10619
        $root = $xmldoc->createElement('manifest');
10620
        $root->setAttribute('identifier', 'SingleCourseManifest');
10621
        $root->setAttribute('version', '1.1');
10622
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10623
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10624
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10625
        $root->setAttribute('xsi:schemaLocation', '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');
10626
        // Build mandatory sub-root container elements.
10627
        $metadata = $xmldoc->createElement('metadata');
10628
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10629
        $metadata->appendChild($md_schema);
10630
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10631
        $metadata->appendChild($md_schemaversion);
10632
        $root->appendChild($metadata);
10633
10634
        $organizations = $xmldoc->createElement('organizations');
10635
        $resources = $xmldoc->createElement('resources');
10636
10637
        // Build the only organization we will use in building our learnpaths.
10638
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10639
        $organization = $xmldoc->createElement('organization');
10640
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10641
        // To set the title of the SCORM entity (=organization), we take the name given
10642
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10643
        // learning path charset) as it is the encoding that defines how it is stored
10644
        // in the database. Then we convert it to HTML entities again as the "&" character
10645
        // alone is not authorized in XML (must be &amp;).
10646
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10647
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10648
        $organization->appendChild($org_title);
10649
10650
        $folder_name = 'document';
10651
10652
        // Removes the learning_path/scorm_folder path when exporting see #4841
10653
        $path_to_remove = null;
10654
        $result = $this->generate_lp_folder($_course);
10655
10656
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10657
            $path_to_remove = 'document'.$result['dir'];
10658
            $path_to_replace = $folder_name.'/';
10659
        }
10660
10661
        // Fixes chamilo scorm exports
10662
        if ($this->ref === 'chamilo_scorm_export') {
10663
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10664
        }
10665
10666
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10667
        // Always call the learnpathItem->scorm_export() method to change it to the SCORM format.
10668
        $link_updates = [];
10669
        $links_to_create = [];
10670
        //foreach ($this->items as $index => $item) {
10671
        foreach ($this->ordered_items as $index => $itemId) {
10672
            $item = $this->items[$itemId];
10673
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10674
                // Get included documents from this item.
10675
                if ($item->type === 'sco') {
10676
                    $inc_docs = $item->get_resources_from_source(
10677
                        null,
10678
                        api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path()
10679
                    );
10680
                } else {
10681
                    $inc_docs = $item->get_resources_from_source();
10682
                }
10683
                // Give a child element <item> to the <organization> element.
10684
                $my_item_id = $item->get_id();
10685
                $my_item = $xmldoc->createElement('item');
10686
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10687
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10688
                $my_item->setAttribute('isvisible', 'true');
10689
                // Give a child element <title> to the <item> element.
10690
                $my_title = $xmldoc->createElement(
10691
                    'title',
10692
                    htmlspecialchars(
10693
                        api_utf8_encode($item->get_title()),
10694
                        ENT_QUOTES,
10695
                        'UTF-8'
10696
                    )
10697
                );
10698
                $my_item->appendChild($my_title);
10699
                // Give a child element <adlcp:prerequisites> to the <item> element.
10700
                $my_prereqs = $xmldoc->createElement(
10701
                    'adlcp:prerequisites',
10702
                    $this->get_scorm_prereq_string($my_item_id)
10703
                );
10704
                $my_prereqs->setAttribute('type', 'aicc_script');
10705
                $my_item->appendChild($my_prereqs);
10706
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10707
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10708
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10709
                //$xmldoc->createElement('adlcp:timelimitaction','');
10710
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10711
                //$xmldoc->createElement('adlcp:datafromlms','');
10712
                // Give a child element <adlcp:masteryscore> to the <item> element.
10713
                $my_masteryscore = $xmldoc->createElement(
10714
                    'adlcp:masteryscore',
10715
                    $item->get_mastery_score()
10716
                );
10717
                $my_item->appendChild($my_masteryscore);
10718
10719
                // Attach this item to the organization element or hits parent if there is one.
10720
                if (!empty($item->parent) && $item->parent != 0) {
10721
                    $children = $organization->childNodes;
10722
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10723
                    if (is_object($possible_parent)) {
10724
                        $possible_parent->appendChild($my_item);
10725
                    } else {
10726
                        if ($this->debug > 0) {
10727
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10728
                        }
10729
                    }
10730
                } else {
10731
                    if ($this->debug > 0) {
10732
                        error_log('No parent');
10733
                    }
10734
                    $organization->appendChild($my_item);
10735
                }
10736
10737
                // Get the path of the file(s) from the course directory root.
10738
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10739
10740
                if (!empty($path_to_remove)) {
10741
                    //From docs
10742
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10743
10744
                    //From quiz
10745
                    if ($this->ref === 'chamilo_scorm_export') {
10746
                        $path_to_remove = 'scorm/'.$this->path.'/';
10747
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10748
                    }
10749
                } else {
10750
                    $my_xml_file_path = $my_file_path;
10751
                }
10752
10753
                $my_sub_dir = dirname($my_file_path);
10754
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10755
                $my_xml_sub_dir = $my_sub_dir;
10756
                // Give a <resource> child to the <resources> element
10757
                $my_resource = $xmldoc->createElement('resource');
10758
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10759
                $my_resource->setAttribute('type', 'webcontent');
10760
                $my_resource->setAttribute('href', $my_xml_file_path);
10761
                // adlcp:scormtype can be either 'sco' or 'asset'.
10762
                if ($item->type === 'sco') {
10763
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10764
                } else {
10765
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10766
                }
10767
                // xml:base is the base directory to find the files declared in this resource.
10768
                $my_resource->setAttribute('xml:base', '');
10769
                // Give a <file> child to the <resource> element.
10770
                $my_file = $xmldoc->createElement('file');
10771
                $my_file->setAttribute('href', $my_xml_file_path);
10772
                $my_resource->appendChild($my_file);
10773
10774
                // Dependency to other files - not yet supported.
10775
                $i = 1;
10776
                if ($inc_docs) {
10777
                    foreach ($inc_docs as $doc_info) {
10778
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10779
                            continue;
10780
                        }
10781
                        $my_dep = $xmldoc->createElement('resource');
10782
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10783
                        $my_dep->setAttribute('identifier', $res_id);
10784
                        $my_dep->setAttribute('type', 'webcontent');
10785
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10786
                        $my_dep_file = $xmldoc->createElement('file');
10787
                        // Check type of URL.
10788
                        if ($doc_info[1] == 'remote') {
10789
                            // Remote file. Save url as is.
10790
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10791
                            $my_dep->setAttribute('xml:base', '');
10792
                        } elseif ($doc_info[1] === 'local') {
10793
                            switch ($doc_info[2]) {
10794
                                case 'url': // Local URL - save path as url for now, don't zip file.
10795
                                    $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10796
                                    $current_dir = dirname($abs_path);
10797
                                    $current_dir = str_replace('\\', '/', $current_dir);
10798
                                    $file_path = realpath($abs_path);
10799
                                    $file_path = str_replace('\\', '/', $file_path);
10800
                                    $my_dep_file->setAttribute('href', $file_path);
10801
                                    $my_dep->setAttribute('xml:base', '');
10802
                                    if (strstr($file_path, $main_path) !== false) {
10803
                                        // The calculated real path is really inside Chamilo's root path.
10804
                                        // Reduce file path to what's under the DocumentRoot.
10805
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10806
                                        //echo $file_path;echo '<br /><br />';
10807
                                        //error_log(__LINE__.'Reduced url path: '.$file_path, 0);
10808
                                        $zip_files_abs[] = $file_path;
10809
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10810
                                        $my_dep_file->setAttribute('href', $file_path);
10811
                                        $my_dep->setAttribute('xml:base', '');
10812
                                    } elseif (empty($file_path)) {
10813
                                        /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
10814
                                        if (strpos($document_root, -1) == '/') {
10815
                                            $document_root = substr(0, -1, $document_root);
10816
                                        }*/
10817
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10818
                                        $file_path = str_replace('//', '/', $file_path);
10819
                                        if (file_exists($file_path)) {
10820
                                            $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10821
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10822
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10823
                                            $my_dep_file->setAttribute('href', $file_path);
10824
                                            $my_dep->setAttribute('xml:base', '');
10825
                                        }
10826
                                    }
10827
                                    break;
10828
                                case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10829
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10830
                                    $my_dep->setAttribute('xml:base', '');
10831
10832
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10833
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10834
                                    $abs_img_path_without_subdir = $doc_info[0];
10835
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10836
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10837
                                    if ($pos === 0) {
10838
                                        $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp));
10839
                                    }
10840
10841
                                    //$file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir);
10842
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10843
10844
                                    $file_path = str_replace('\\', '/', $file_path);
10845
                                    $file_path = str_replace('//', '/', $file_path);
10846
10847
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10848
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10849
                                    // Check if the current document is in that path.
10850
                                    if (strstr($file_path, $cur_path) !== false) {
10851
                                        // The document is in that path, now get the relative path
10852
                                        // to the containing document.
10853
                                        $orig_file_path = dirname($cur_path.$my_file_path).'/';
10854
                                        $orig_file_path = str_replace('\\', '/', $orig_file_path);
10855
                                        $relative_path = '';
10856
                                        if (strstr($file_path, $cur_path) !== false) {
10857
                                            //$relative_path = substr($file_path, strlen($orig_file_path));
10858
                                            $relative_path = str_replace($cur_path, '', $file_path);
10859
                                            $file_path = substr($file_path, strlen($cur_path));
10860
                                        } else {
10861
                                            // This case is still a problem as it's difficult to calculate a relative path easily
10862
                                            // might still generate wrong links.
10863
                                            //$file_path = substr($file_path,strlen($cur_path));
10864
                                            // Calculate the directory path to the current file (without trailing slash).
10865
                                            $my_relative_path = dirname($file_path);
10866
                                            $my_relative_path = str_replace('\\', '/', $my_relative_path);
10867
                                            $my_relative_file = basename($file_path);
10868
                                            // Calculate the directory path to the containing file (without trailing slash).
10869
                                            $my_orig_file_path = substr($orig_file_path, 0, -1);
10870
                                            $dotdot = '';
10871
                                            $subdir = '';
10872
                                            while (strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) {
10873
                                                $my_relative_path2 = dirname($my_relative_path);
10874
                                                $my_relative_path2 = str_replace('\\', '/', $my_relative_path2);
10875
                                                $my_orig_file_path = dirname($my_orig_file_path);
10876
                                                $my_orig_file_path = str_replace('\\', '/', $my_orig_file_path);
10877
                                                $subdir = substr($my_relative_path, strlen($my_relative_path2) + 1).'/'.$subdir;
10878
                                                $dotdot += '../';
10879
                                                $my_relative_path = $my_relative_path2;
10880
                                            }
10881
                                            $relative_path = $dotdot.$subdir.$my_relative_file;
10882
                                        }
10883
                                        // Put the current document in the zip (this array is the array
10884
                                        // that will manage documents already in the course folder - relative).
10885
                                        $zip_files[] = $file_path;
10886
                                        // Update the links to the current document in the containing document (make them relative).
10887
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $relative_path];
10888
                                        $my_dep_file->setAttribute('href', $file_path);
10889
                                        $my_dep->setAttribute('xml:base', '');
10890
                                    } elseif (strstr($file_path, $main_path) !== false) {
10891
                                        // The calculated real path is really inside Chamilo's root path.
10892
                                        // Reduce file path to what's under the DocumentRoot.
10893
                                        $file_path = substr($file_path, strlen($root_path));
10894
                                        //echo $file_path;echo '<br /><br />';
10895
                                        //error_log('Reduced path: '.$file_path, 0);
10896
                                        $zip_files_abs[] = $file_path;
10897
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10898
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10899
                                        $my_dep->setAttribute('xml:base', '');
10900
                                    } elseif (empty($file_path)) {
10901
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
10902
                                        $file_path = str_replace('//', '/', $file_path);
10903
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10904
                                        $current_dir = dirname($abs_path);
10905
                                        $current_dir = str_replace('\\', '/', $current_dir);
10906
10907
                                        if (file_exists($file_path)) {
10908
                                            // We get the relative path.
10909
                                            $file_path = substr($file_path, strlen($current_dir));
10910
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10911
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10912
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10913
                                            $my_dep->setAttribute('xml:base', '');
10914
                                        }
10915
                                    }
10916
                                    break;
10917
                                case 'rel':
10918
                                    // Path relative to the current document.
10919
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10920
                                    if (substr($doc_info[0], 0, 2) == '..') {
10921
                                        // Relative path going up.
10922
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10923
                                        $current_dir = str_replace('\\', '/', $current_dir);
10924
                                        $file_path = realpath($current_dir.$doc_info[0]);
10925
                                        $file_path = str_replace('\\', '/', $file_path);
10926
                                        if (strstr($file_path, $main_path) !== false) {
10927
                                            // The calculated real path is really inside Chamilo's root path.
10928
                                            // Reduce file path to what's under the DocumentRoot.
10929
                                            $file_path = substr($file_path, strlen($root_path));
10930
                                            $zip_files_abs[] = $file_path;
10931
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10932
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10933
                                            $my_dep->setAttribute('xml:base', '');
10934
                                        }
10935
                                    } else {
10936
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10937
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10938
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10939
                                    }
10940
                                    break;
10941
                                default:
10942
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10943
                                    $my_dep->setAttribute('xml:base', '');
10944
                                    break;
10945
                            }
10946
                        }
10947
                        $my_dep->appendChild($my_dep_file);
10948
                        $resources->appendChild($my_dep);
10949
                        $dependency = $xmldoc->createElement('dependency');
10950
                        $dependency->setAttribute('identifierref', $res_id);
10951
                        $my_resource->appendChild($dependency);
10952
                        $i++;
10953
                    }
10954
                }
10955
                $resources->appendChild($my_resource);
10956
                $zip_files[] = $my_file_path;
10957
            } else {
10958
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10959
                switch ($item->type) {
10960
                    case TOOL_LINK:
10961
                        $my_item = $xmldoc->createElement('item');
10962
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10963
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10964
                        $my_item->setAttribute('isvisible', 'true');
10965
                        // Give a child element <title> to the <item> element.
10966
                        $my_title = $xmldoc->createElement(
10967
                            'title',
10968
                            htmlspecialchars(
10969
                                api_utf8_encode($item->get_title()),
10970
                                ENT_QUOTES,
10971
                                'UTF-8'
10972
                            )
10973
                        );
10974
                        $my_item->appendChild($my_title);
10975
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10976
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10977
                        $my_prereqs->setAttribute('type', 'aicc_script');
10978
                        $my_item->appendChild($my_prereqs);
10979
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10980
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10981
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10982
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10983
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10984
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10985
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10986
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10987
                        $my_item->appendChild($my_masteryscore);
10988
10989
                        // Attach this item to the organization element or its parent if there is one.
10990
                        if (!empty($item->parent) && $item->parent != 0) {
10991
                            $children = $organization->childNodes;
10992
                            for ($i = 0; $i < $children->length; $i++) {
10993
                                $item_temp = $children->item($i);
10994
                                if ($item_temp->nodeName == 'item') {
10995
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10996
                                        $item_temp->appendChild($my_item);
10997
                                    }
10998
                                }
10999
                            }
11000
                        } else {
11001
                            $organization->appendChild($my_item);
11002
                        }
11003
11004
                        $my_file_path = 'link_'.$item->get_id().'.html';
11005
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11006
                                WHERE c_id = '.$course_id.' AND id='.$item->path;
11007
                        $rs = Database::query($sql);
11008
                        if ($link = Database::fetch_array($rs)) {
11009
                            $url = $link['url'];
11010
                            $title = stripslashes($link['title']);
11011
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11012
                            $my_xml_file_path = $my_file_path;
11013
                            $my_sub_dir = dirname($my_file_path);
11014
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11015
                            $my_xml_sub_dir = $my_sub_dir;
11016
                            // Give a <resource> child to the <resources> element.
11017
                            $my_resource = $xmldoc->createElement('resource');
11018
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11019
                            $my_resource->setAttribute('type', 'webcontent');
11020
                            $my_resource->setAttribute('href', $my_xml_file_path);
11021
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11022
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11023
                            // xml:base is the base directory to find the files declared in this resource.
11024
                            $my_resource->setAttribute('xml:base', '');
11025
                            // give a <file> child to the <resource> element.
11026
                            $my_file = $xmldoc->createElement('file');
11027
                            $my_file->setAttribute('href', $my_xml_file_path);
11028
                            $my_resource->appendChild($my_file);
11029
                            $resources->appendChild($my_resource);
11030
                        }
11031
                        break;
11032
                    case TOOL_QUIZ:
11033
                        $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard.
11034
                        $exe = new Exercise();
11035
                        $exe->read($exe_id);
11036
                        $my_item = $xmldoc->createElement('item');
11037
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11038
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11039
                        $my_item->setAttribute('isvisible', 'true');
11040
                        // Give a child element <title> to the <item> element.
11041
                        $my_title = $xmldoc->createElement(
11042
                            'title',
11043
                            htmlspecialchars(
11044
                                api_utf8_encode($item->get_title()),
11045
                                ENT_QUOTES,
11046
                                'UTF-8'
11047
                            )
11048
                        );
11049
                        $my_item->appendChild($my_title);
11050
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11051
                        //$my_item->appendChild($my_max_score);
11052
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11053
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11054
                        $my_prereqs->setAttribute('type', 'aicc_script');
11055
                        $my_item->appendChild($my_prereqs);
11056
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11057
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11058
                        $my_item->appendChild($my_masteryscore);
11059
11060
                        // Attach this item to the organization element or hits parent if there is one.
11061
                        if (!empty($item->parent) && $item->parent != 0) {
11062
                            $children = $organization->childNodes;
11063
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11064
                            if ($possible_parent) {
11065
                                if ($possible_parent->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11066
                                    $possible_parent->appendChild($my_item);
11067
                                }
11068
                            }
11069
                        } else {
11070
                            $organization->appendChild($my_item);
11071
                        }
11072
11073
                        // Get the path of the file(s) from the course directory root
11074
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11075
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11076
                        // Write the contents of the exported exercise into a (big) html file
11077
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11078
                        $contents = ScormSection::export_exercise_to_scorm(
11079
                            $exe,
11080
                            true
11081
                        );
11082
11083
                        $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path;
11084
                        $res = file_put_contents($tmp_file_path, $contents);
11085
                        if ($res === false) {
11086
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11087
                        }
11088
                        $files_cleanup[] = $tmp_file_path;
11089
                        $my_xml_file_path = $my_file_path;
11090
                        $my_sub_dir = dirname($my_file_path);
11091
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11092
                        $my_xml_sub_dir = $my_sub_dir;
11093
                        // Give a <resource> child to the <resources> element.
11094
                        $my_resource = $xmldoc->createElement('resource');
11095
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11096
                        $my_resource->setAttribute('type', 'webcontent');
11097
                        $my_resource->setAttribute('href', $my_xml_file_path);
11098
11099
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11100
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11101
                        // xml:base is the base directory to find the files declared in this resource.
11102
                        $my_resource->setAttribute('xml:base', '');
11103
                        // Give a <file> child to the <resource> element.
11104
                        $my_file = $xmldoc->createElement('file');
11105
                        $my_file->setAttribute('href', $my_xml_file_path);
11106
                        $my_resource->appendChild($my_file);
11107
11108
                        // Get included docs.
11109
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11110
                        // Dependency to other files - not yet supported.
11111
                        $i = 1;
11112
                        foreach ($inc_docs as $doc_info) {
11113
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11114
                                continue;
11115
                            }
11116
                            $my_dep = $xmldoc->createElement('resource');
11117
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11118
                            $my_dep->setAttribute('identifier', $res_id);
11119
                            $my_dep->setAttribute('type', 'webcontent');
11120
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11121
                            $my_dep_file = $xmldoc->createElement('file');
11122
                            // Check type of URL.
11123
                            if ($doc_info[1] == 'remote') {
11124
                                // Remote file. Save url as is.
11125
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11126
                                $my_dep->setAttribute('xml:base', '');
11127
                            } elseif ($doc_info[1] == 'local') {
11128
                                switch ($doc_info[2]) {
11129
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11130
                                        // Save file but as local file (retrieve from URL).
11131
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11132
                                        $current_dir = dirname($abs_path);
11133
                                        $current_dir = str_replace('\\', '/', $current_dir);
11134
                                        $file_path = realpath($abs_path);
11135
                                        $file_path = str_replace('\\', '/', $file_path);
11136
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11137
                                        $my_dep->setAttribute('xml:base', '');
11138
                                        if (strstr($file_path, $main_path) !== false) {
11139
                                            // The calculated real path is really inside the chamilo root path.
11140
                                            // Reduce file path to what's under the DocumentRoot.
11141
                                            $file_path = substr($file_path, strlen($root_path));
11142
                                            $zip_files_abs[] = $file_path;
11143
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => 'document/'.$file_path];
11144
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11145
                                            $my_dep->setAttribute('xml:base', '');
11146
                                        } elseif (empty($file_path)) {
11147
                                            /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH)));
11148
                                            if (strpos($document_root,-1) == '/') {
11149
                                                $document_root = substr(0, -1, $document_root);
11150
                                            }*/
11151
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11152
                                            $file_path = str_replace('//', '/', $file_path);
11153
                                            if (file_exists($file_path)) {
11154
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
11155
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11156
                                                $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => 'document/'.$file_path];
11157
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11158
                                                $my_dep->setAttribute('xml:base', '');
11159
                                            }
11160
                                        }
11161
                                        break;
11162
                                    case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11163
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11164
                                        $current_dir = str_replace('\\', '/', $current_dir);
11165
                                        $file_path = realpath($doc_info[0]);
11166
                                        $file_path = str_replace('\\', '/', $file_path);
11167
                                        $my_dep_file->setAttribute('href', $file_path);
11168
                                        $my_dep->setAttribute('xml:base', '');
11169
11170
                                        if (strstr($file_path, $main_path) !== false) {
11171
                                            // The calculated real path is really inside the chamilo root path.
11172
                                            // Reduce file path to what's under the DocumentRoot.
11173
                                            $file_path = substr($file_path, strlen($root_path));
11174
                                            $zip_files_abs[] = $file_path;
11175
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11176
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11177
                                            $my_dep->setAttribute('xml:base', '');
11178
                                        } elseif (empty($file_path)) {
11179
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
11180
                                            $file_path = str_replace('//', '/', $file_path);
11181
                                            if (file_exists($file_path)) {
11182
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
11183
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11184
                                                $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11185
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11186
                                                $my_dep->setAttribute('xml:base', '');
11187
                                            }
11188
                                        }
11189
                                        break;
11190
                                    case 'rel':
11191
                                        // Path relative to the current document. Save xml:base as current document's
11192
                                        // directory and save file in zip as subdir.file_path
11193
                                        if (substr($doc_info[0], 0, 2) == '..') {
11194
                                            // Relative path going up.
11195
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11196
                                            $current_dir = str_replace('\\', '/', $current_dir);
11197
                                            $file_path = realpath($current_dir.$doc_info[0]);
11198
                                            $file_path = str_replace('\\', '/', $file_path);
11199
                                            //error_log($file_path.' <-> '.$main_path, 0);
11200
                                            if (strstr($file_path, $main_path) !== false) {
11201
                                                // The calculated real path is really inside Chamilo's root path.
11202
                                                // Reduce file path to what's under the DocumentRoot.
11203
11204
                                                $file_path = substr($file_path, strlen($root_path));
11205
                                                $file_path_dest = $file_path;
11206
11207
                                                // File path is courses/CHAMILO/document/....
11208
                                                $info_file_path = explode('/', $file_path);
11209
                                                if ($info_file_path[0] == 'courses') {
11210
                                                    // Add character "/" in file path.
11211
                                                    $file_path_dest = 'document/'.$file_path;
11212
                                                }
11213
11214
                                                //error_log('Reduced path: '.$file_path, 0);
11215
                                                $zip_files_abs[] = $file_path;
11216
11217
                                                $link_updates[$my_file_path][] = [
11218
                                                    'orig' => $doc_info[0],
11219
                                                    'dest' => $file_path_dest,
11220
                                                ];
11221
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11222
                                                $my_dep->setAttribute('xml:base', '');
11223
                                            }
11224
                                        } else {
11225
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11226
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11227
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11228
                                        }
11229
                                        break;
11230
                                    default:
11231
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11232
                                        $my_dep->setAttribute('xml:base', '');
11233
                                        break;
11234
                                }
11235
                            }
11236
                            $my_dep->appendChild($my_dep_file);
11237
                            $resources->appendChild($my_dep);
11238
                            $dependency = $xmldoc->createElement('dependency');
11239
                            $dependency->setAttribute('identifierref', $res_id);
11240
                            $my_resource->appendChild($dependency);
11241
                            $i++;
11242
                        }
11243
                        $resources->appendChild($my_resource);
11244
                        $zip_files[] = $my_file_path;
11245
                        break;
11246
                    default:
11247
                        // Get the path of the file(s) from the course directory root
11248
                        $my_file_path = 'non_exportable.html';
11249
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11250
                        $my_xml_file_path = $my_file_path;
11251
                        $my_sub_dir = dirname($my_file_path);
11252
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11253
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11254
                        $my_xml_sub_dir = $my_sub_dir;
11255
                        // Give a <resource> child to the <resources> element.
11256
                        $my_resource = $xmldoc->createElement('resource');
11257
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11258
                        $my_resource->setAttribute('type', 'webcontent');
11259
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11260
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11261
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11262
                        // xml:base is the base directory to find the files declared in this resource.
11263
                        $my_resource->setAttribute('xml:base', '');
11264
                        // Give a <file> child to the <resource> element.
11265
                        $my_file = $xmldoc->createElement('file');
11266
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11267
                        $my_resource->appendChild($my_file);
11268
                        $resources->appendChild($my_resource);
11269
11270
                        break;
11271
                }
11272
            }
11273
        }
11274
        $organizations->appendChild($organization);
11275
        $root->appendChild($organizations);
11276
        $root->appendChild($resources);
11277
        $xmldoc->appendChild($root);
11278
11279
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11280
11281
        // TODO: Add a readme file here, with a short description and a link to the Reload player
11282
        // then add the file to the zip, then destroy the file (this is done automatically).
11283
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11284
        foreach ($zip_files as $file_path) {
11285
            if (empty($file_path)) {
11286
                continue;
11287
            }
11288
11289
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11290
            $dest_file = $archive_path.$temp_dir_short.'/'.$file_path;
11291
11292
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11293
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11294
            }
11295
            $this->create_path($dest_file);
11296
            @copy($filePath, $dest_file);
11297
11298
            // Check if the file needs a link update.
11299
            if (in_array($file_path, array_keys($link_updates))) {
11300
                $string = file_get_contents($dest_file);
11301
                unlink($dest_file);
11302
                foreach ($link_updates[$file_path] as $old_new) {
11303
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11304
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11305
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11306
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11307
                    if (substr($old_new['dest'], -3) == 'flv' &&
11308
                        substr($old_new['dest'], 0, 5) == 'main/'
11309
                    ) {
11310
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11311
                    } elseif (substr($old_new['dest'], -3) == 'flv' &&
11312
                        substr($old_new['dest'], 0, 6) == 'video/'
11313
                    ) {
11314
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11315
                    }
11316
                    //Fix to avoid problems with default_course_document
11317
                    if (strpos("main/default_course_document", $old_new['dest'] === false)) {
11318
                        //$newDestination = str_replace('document/', $mult.'document/', $old_new['dest']);
11319
                        $newDestination = $old_new['dest'];
11320
                    } else {
11321
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11322
                    }
11323
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11324
11325
                    // Add files inside the HTMLs
11326
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11327
                    $destinationFile = $archive_path.$temp_dir_short.'/'.$old_new['dest'];
11328
                    if (file_exists($sys_course_path.$new_path)) {
11329
                        copy($sys_course_path.$new_path, $destinationFile);
11330
                    }
11331
                }
11332
                file_put_contents($dest_file, $string);
11333
            }
11334
11335
            if (file_exists($filePath) && $copyAll) {
11336
                $extension = $this->get_extension($filePath);
11337
                if (in_array($extension, ['html', 'html'])) {
11338
                    $containerOrigin = dirname($filePath);
11339
                    $containerDestination = dirname($dest_file);
11340
11341
                    $finder = new Finder();
11342
                    $finder->files()->in($containerOrigin)
11343
                        ->notName('*_DELETED_*')
11344
                        ->exclude('share_folder')
11345
                        ->exclude('chat_files')
11346
                        ->exclude('certificates')
11347
                    ;
11348
11349
                    if (is_dir($containerOrigin) &&
11350
                        is_dir($containerDestination)
11351
                    ) {
11352
                        $fs = new Filesystem();
11353
                        $fs->mirror(
11354
                            $containerOrigin,
11355
                            $containerDestination,
11356
                            $finder
11357
                        );
11358
                    }
11359
                }
11360
            }
11361
        }
11362
11363
        foreach ($zip_files_abs as $file_path) {
11364
            if (empty($file_path)) {
11365
                continue;
11366
            }
11367
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11368
                continue;
11369
            }
11370
11371
            $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path;
11372
            $this->create_path($dest_file);
11373
            copy($main_path.$file_path, $dest_file);
11374
            // Check if the file needs a link update.
11375
            if (in_array($file_path, array_keys($link_updates))) {
11376
                $string = file_get_contents($dest_file);
11377
                unlink($dest_file);
11378
                foreach ($link_updates[$file_path] as $old_new) {
11379
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11380
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11381
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11382
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11383
                    if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
11384
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11385
                    }
11386
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11387
                }
11388
                file_put_contents($dest_file, $string);
11389
            }
11390
        }
11391
11392
        if (is_array($links_to_create)) {
11393
            foreach ($links_to_create as $file => $link) {
11394
                $file_content = '<!DOCTYPE html><head>
11395
                                <meta charset="'.api_get_language_isocode().'" />
11396
                                <title>'.$link['title'].'</title>
11397
                                </head>
11398
                                <body dir="'.api_get_text_direction().'">
11399
                                <div style="text-align:center">
11400
                                <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11401
                                </body>
11402
                                </html>';
11403
                file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content);
11404
            }
11405
        }
11406
11407
        // Add non exportable message explanation.
11408
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11409
        $file_content = '<!DOCTYPE html><head>
11410
                        <meta charset="'.api_get_language_isocode().'" />
11411
                        <title>'.$lang_not_exportable.'</title>
11412
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11413
                        </head>
11414
                        <body dir="'.api_get_text_direction().'">';
11415
        $file_content .=
11416
            <<<EOD
11417
                    <style>
11418
            .error-message {
11419
                font-family: arial, verdana, helvetica, sans-serif;
11420
                border-width: 1px;
11421
                border-style: solid;
11422
                left: 50%;
11423
                margin: 10px auto;
11424
                min-height: 30px;
11425
                padding: 5px;
11426
                right: 50%;
11427
                width: 500px;
11428
                background-color: #FFD1D1;
11429
                border-color: #FF0000;
11430
                color: #000;
11431
            }
11432
        </style>
11433
    <body>
11434
        <div class="error-message">
11435
            $lang_not_exportable
11436
        </div>
11437
    </body>
11438
</html>
11439
EOD;
11440
        if (!is_dir($archive_path.$temp_dir_short.'/document')) {
11441
            @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11442
        }
11443
        file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content);
11444
11445
        // Add the extra files that go along with a SCORM package.
11446
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11447
11448
        $fs = new Filesystem();
11449
        $fs->mirror($main_code_path, $archive_path.$temp_dir_short);
11450
11451
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11452
        $manifest = @$xmldoc->saveXML();
11453
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11454
        file_put_contents($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11455
        $zip_folder->add(
11456
            $archive_path.'/'.$temp_dir_short,
11457
            PCLZIP_OPT_REMOVE_PATH,
11458
            $archive_path.'/'.$temp_dir_short.'/'
11459
        );
11460
11461
        // Clean possible temporary files.
11462
        foreach ($files_cleanup as $file) {
11463
            $res = unlink($file);
11464
            if ($res === false) {
11465
                error_log(
11466
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11467
                    0
11468
                );
11469
            }
11470
        }
11471
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11472
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11473
    }
11474
11475
    /**
11476
     * @param int $lp_id
11477
     *
11478
     * @return bool
11479
     */
11480
    public function scorm_export_to_pdf($lp_id)
11481
    {
11482
        $lp_id = intval($lp_id);
11483
        $files_to_export = [];
11484
        $course_data = api_get_course_info($this->cc);
11485
        if (!empty($course_data)) {
11486
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11487
            $list = self::get_flat_ordered_items_list($lp_id);
11488
            if (!empty($list)) {
11489
                foreach ($list as $item_id) {
11490
                    $item = $this->items[$item_id];
11491
                    switch ($item->type) {
11492
                        case 'document':
11493
                            //Getting documents from a LP with chamilo documents
11494
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11495
                            // Try loading document from the base course.
11496
                            if (empty($file_data) && !empty($sessionId)) {
11497
                                $file_data = DocumentManager::get_document_data_by_id(
11498
                                    $item->path,
11499
                                    $this->cc,
11500
                                    false,
11501
                                    0
11502
                                );
11503
                            }
11504
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11505
                            if (file_exists($file_path)) {
11506
                                $files_to_export[] = [
11507
                                    'title' => $item->get_title(),
11508
                                    'path' => $file_path,
11509
                                ];
11510
                            }
11511
                            break;
11512
                        case 'asset': //commes from a scorm package generated by chamilo
11513
                        case 'sco':
11514
                            $file_path = $scorm_path.'/'.$item->path;
11515
                            if (file_exists($file_path)) {
11516
                                $files_to_export[] = [
11517
                                    'title' => $item->get_title(),
11518
                                    'path' => $file_path,
11519
                                ];
11520
                            }
11521
                            break;
11522
                        case 'dir':
11523
                            $files_to_export[] = [
11524
                                'title' => $item->get_title(),
11525
                                'path' => null,
11526
                            ];
11527
                            break;
11528
                    }
11529
                }
11530
            }
11531
            $pdf = new PDF();
11532
            $result = $pdf->html_to_pdf(
11533
                $files_to_export,
11534
                $this->name,
11535
                $this->cc,
11536
                true
11537
            );
11538
11539
            return $result;
11540
        }
11541
11542
        return false;
11543
    }
11544
11545
    /**
11546
     * Temp function to be moved in main_api or the best place around for this.
11547
     * Creates a file path if it doesn't exist.
11548
     *
11549
     * @param string $path
11550
     */
11551
    public function create_path($path)
11552
    {
11553
        $path_bits = explode('/', dirname($path));
11554
11555
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11556
        $path_built = IS_WINDOWS_OS ? '' : '/';
11557
        foreach ($path_bits as $bit) {
11558
            if (!empty($bit)) {
11559
                $new_path = $path_built.$bit;
11560
                if (is_dir($new_path)) {
11561
                    $path_built = $new_path.'/';
11562
                } else {
11563
                    mkdir($new_path, api_get_permissions_for_new_directories());
11564
                    $path_built = $new_path.'/';
11565
                }
11566
            }
11567
        }
11568
    }
11569
11570
    /**
11571
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11572
     *
11573
     * @return bool The results of the unlink function, or false if there was no image to start with
11574
     */
11575
    public function delete_lp_image()
11576
    {
11577
        $img = $this->get_preview_image();
11578
        if ($img != '') {
11579
            $del_file = $this->get_preview_image_path(null, 'sys');
11580
            if (isset($del_file) && file_exists($del_file)) {
11581
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11582
                if (file_exists($del_file_2)) {
11583
                    unlink($del_file_2);
11584
                }
11585
                $this->set_preview_image('');
11586
11587
                return @unlink($del_file);
11588
            }
11589
        }
11590
11591
        return false;
11592
    }
11593
11594
    /**
11595
     * Uploads an author image to the upload/learning_path/images directory.
11596
     *
11597
     * @param array    The image array, coming from the $_FILES superglobal
11598
     *
11599
     * @return bool True on success, false on error
11600
     */
11601
    public function upload_image($image_array)
11602
    {
11603
        if (!empty($image_array['name'])) {
11604
            $upload_ok = process_uploaded_file($image_array);
11605
            $has_attachment = true;
11606
        }
11607
11608
        if ($upload_ok && $has_attachment) {
11609
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11610
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11611
            $updir = $sys_course_path.$courseDir;
11612
            // Try to add an extension to the file if it hasn't one.
11613
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11614
11615
            if (filter_extension($new_file_name)) {
11616
                $file_extension = explode('.', $image_array['name']);
11617
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
11618
                $filename = uniqid('');
11619
                $new_file_name = $filename.'.'.$file_extension;
11620
                $new_path = $updir.'/'.$new_file_name;
11621
11622
                // Resize the image.
11623
                $temp = new Image($image_array['tmp_name']);
11624
                $temp->resize(104);
11625
                $result = $temp->send_image($new_path);
11626
11627
                // Storing the image filename.
11628
                if ($result) {
11629
                    $this->set_preview_image($new_file_name);
11630
11631
                    //Resize to 64px to use on course homepage
11632
                    $temp->resize(64);
11633
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11634
11635
                    return true;
11636
                }
11637
            }
11638
        }
11639
11640
        return false;
11641
    }
11642
11643
    /**
11644
     * @param int    $lp_id
11645
     * @param string $status
11646
     */
11647
    public function set_autolaunch($lp_id, $status)
11648
    {
11649
        $course_id = api_get_course_int_id();
11650
        $lp_id = intval($lp_id);
11651
        $status = intval($status);
11652
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11653
11654
        // Setting everything to autolaunch = 0
11655
        $attributes['autolaunch'] = 0;
11656
        $where = [
11657
            'session_id = ? AND c_id = ? ' => [
11658
                api_get_session_id(),
11659
                $course_id,
11660
            ],
11661
        ];
11662
        Database::update($lp_table, $attributes, $where);
11663
        if ($status == 1) {
11664
            //Setting my lp_id to autolaunch = 1
11665
            $attributes['autolaunch'] = 1;
11666
            $where = [
11667
                'iid = ? AND session_id = ? AND c_id = ?' => [
11668
                    $lp_id,
11669
                    api_get_session_id(),
11670
                    $course_id,
11671
                ],
11672
            ];
11673
            Database::update($lp_table, $attributes, $where);
11674
        }
11675
    }
11676
11677
    /**
11678
     * Gets previous_item_id for the next element of the lp_item table.
11679
     *
11680
     * @author Isaac flores paz
11681
     *
11682
     * @return int Previous item ID
11683
     */
11684
    public function select_previous_item_id()
11685
    {
11686
        $course_id = api_get_course_int_id();
11687
        if ($this->debug > 0) {
11688
            error_log('In learnpath::select_previous_item_id()', 0);
11689
        }
11690
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11691
11692
        // Get the max order of the items
11693
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11694
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11695
        $rs_max_order = Database::query($sql);
11696
        $row_max_order = Database::fetch_object($rs_max_order);
11697
        $max_order = $row_max_order->display_order;
11698
        // Get the previous item ID
11699
        $sql = "SELECT iid as previous FROM $table_lp_item
11700
                WHERE 
11701
                    c_id = $course_id AND 
11702
                    lp_id = ".$this->lp_id." AND 
11703
                    display_order = '$max_order' ";
11704
        $rs_max = Database::query($sql);
11705
        $row_max = Database::fetch_object($rs_max);
11706
11707
        // Return the previous item ID
11708
        return $row_max->previous;
11709
    }
11710
11711
    /**
11712
     * Copies an LP.
11713
     */
11714
    public function copy()
11715
    {
11716
        // Course builder
11717
        $cb = new CourseBuilder();
11718
11719
        //Setting tools that will be copied
11720
        $cb->set_tools_to_build(['learnpaths']);
11721
11722
        //Setting elements that will be copied
11723
        $cb->set_tools_specific_id_list(
11724
            ['learnpaths' => [$this->lp_id]]
11725
        );
11726
11727
        $course = $cb->build();
11728
11729
        //Course restorer
11730
        $course_restorer = new CourseRestorer($course);
11731
        $course_restorer->set_add_text_in_items(true);
11732
        $course_restorer->set_tool_copy_settings(
11733
            ['learnpaths' => ['reset_dates' => true]]
11734
        );
11735
        $course_restorer->restore(
11736
            api_get_course_id(),
11737
            api_get_session_id(),
11738
            false,
11739
            false
11740
        );
11741
    }
11742
11743
    /**
11744
     * Verify document size.
11745
     *
11746
     * @param string $s
11747
     *
11748
     * @return bool
11749
     */
11750
    public static function verify_document_size($s)
11751
    {
11752
        $post_max = ini_get('post_max_size');
11753
        if (substr($post_max, -1, 1) == 'M') {
11754
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11755
        } elseif (substr($post_max, -1, 1) == 'G') {
11756
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11757
        }
11758
        $upl_max = ini_get('upload_max_filesize');
11759
        if (substr($upl_max, -1, 1) == 'M') {
11760
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11761
        } elseif (substr($upl_max, -1, 1) == 'G') {
11762
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11763
        }
11764
        $documents_total_space = DocumentManager::documents_total_space();
11765
        $course_max_space = DocumentManager::get_course_quota();
11766
        $total_size = filesize($s) + $documents_total_space;
11767
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11768
            return true;
11769
        } else {
11770
            return false;
11771
        }
11772
    }
11773
11774
    /**
11775
     * Clear LP prerequisites.
11776
     */
11777
    public function clear_prerequisites()
11778
    {
11779
        $course_id = $this->get_course_int_id();
11780
        if ($this->debug > 0) {
11781
            error_log('In learnpath::clear_prerequisites()', 0);
11782
        }
11783
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11784
        $lp_id = $this->get_id();
11785
        //Cleaning prerequisites
11786
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11787
                WHERE c_id = $course_id AND lp_id = $lp_id";
11788
        Database::query($sql);
11789
11790
        //Cleaning mastery score for exercises
11791
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11792
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11793
        Database::query($sql);
11794
    }
11795
11796
    public function set_previous_step_as_prerequisite_for_all_items()
11797
    {
11798
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11799
        $course_id = $this->get_course_int_id();
11800
        $lp_id = $this->get_id();
11801
11802
        if (!empty($this->items)) {
11803
            $previous_item_id = null;
11804
            $previous_item_max = 0;
11805
            $previous_item_type = null;
11806
            $last_item_not_dir = null;
11807
            $last_item_not_dir_type = null;
11808
            $last_item_not_dir_max = null;
11809
11810
            foreach ($this->ordered_items as $itemId) {
11811
                $item = $this->getItem($itemId);
11812
                // if there was a previous item... (otherwise jump to set it)
11813
                if (!empty($previous_item_id)) {
11814
                    $current_item_id = $item->get_id(); //save current id
11815
                    if ($item->get_type() != 'dir') {
11816
                        // Current item is not a folder, so it qualifies to get a prerequisites
11817
                        if ($last_item_not_dir_type == 'quiz') {
11818
                            // if previous is quiz, mark its max score as default score to be achieved
11819
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11820
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11821
                            Database::query($sql);
11822
                        }
11823
                        // now simply update the prerequisite to set it to the last non-chapter item
11824
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11825
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11826
                        Database::query($sql);
11827
                        // record item as 'non-chapter' reference
11828
                        $last_item_not_dir = $item->get_id();
11829
                        $last_item_not_dir_type = $item->get_type();
11830
                        $last_item_not_dir_max = $item->get_max();
11831
                    }
11832
                } else {
11833
                    if ($item->get_type() != 'dir') {
11834
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11835
                        $last_item_not_dir = $item->get_id();
11836
                        $last_item_not_dir_type = $item->get_type();
11837
                        $last_item_not_dir_max = $item->get_max();
11838
                    }
11839
                }
11840
                // Saving the item as "previous item" for the next loop
11841
                $previous_item_id = $item->get_id();
11842
                $previous_item_max = $item->get_max();
11843
                $previous_item_type = $item->get_type();
11844
            }
11845
        }
11846
    }
11847
11848
    /**
11849
     * @param array $params
11850
     *
11851
     * @throws \Doctrine\ORM\OptimisticLockException
11852
     */
11853
    public static function createCategory($params)
11854
    {
11855
        $em = Database::getManager();
11856
        $item = new CLpCategory();
11857
        $item->setName($params['name']);
11858
        $item->setCId($params['c_id']);
11859
        $em->persist($item);
11860
        $em->flush();
11861
11862
        api_item_property_update(
11863
            api_get_course_info(),
11864
            TOOL_LEARNPATH_CATEGORY,
11865
            $item->getId(),
11866
            'visible',
11867
            api_get_user_id()
11868
        );
11869
    }
11870
11871
    /**
11872
     * @param array $params
11873
     *
11874
     * @throws \Doctrine\ORM\ORMException
11875
     * @throws \Doctrine\ORM\OptimisticLockException
11876
     * @throws \Doctrine\ORM\TransactionRequiredException
11877
     */
11878
    public static function updateCategory($params)
11879
    {
11880
        $em = Database::getManager();
11881
        /** @var CLpCategory $item */
11882
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11883
        if ($item) {
11884
            $item->setName($params['name']);
11885
            $em->merge($item);
11886
            $em->flush();
11887
        }
11888
    }
11889
11890
    /**
11891
     * @param int $id
11892
     *
11893
     * @throws \Doctrine\ORM\ORMException
11894
     * @throws \Doctrine\ORM\OptimisticLockException
11895
     * @throws \Doctrine\ORM\TransactionRequiredException
11896
     */
11897
    public static function moveUpCategory($id)
11898
    {
11899
        $id = (int) $id;
11900
        $em = Database::getManager();
11901
        /** @var CLpCategory $item */
11902
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11903
        if ($item) {
11904
            $position = $item->getPosition() - 1;
11905
            $item->setPosition($position);
11906
            $em->persist($item);
11907
            $em->flush();
11908
        }
11909
    }
11910
11911
    /**
11912
     * @param int $id
11913
     *
11914
     * @throws \Doctrine\ORM\ORMException
11915
     * @throws \Doctrine\ORM\OptimisticLockException
11916
     * @throws \Doctrine\ORM\TransactionRequiredException
11917
     */
11918
    public static function moveDownCategory($id)
11919
    {
11920
        $id = (int) $id;
11921
        $em = Database::getManager();
11922
        /** @var CLpCategory $item */
11923
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11924
        if ($item) {
11925
            $position = $item->getPosition() + 1;
11926
            $item->setPosition($position);
11927
            $em->persist($item);
11928
            $em->flush();
11929
        }
11930
    }
11931
11932
    /**
11933
     * @param int $courseId
11934
     *
11935
     * @throws \Doctrine\ORM\Query\QueryException
11936
     *
11937
     * @return int|mixed
11938
     */
11939
    public static function getCountCategories($courseId)
11940
    {
11941
        if (empty($course_id)) {
11942
            return 0;
11943
        }
11944
        $em = Database::getManager();
11945
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11946
        $query->setParameter('id', $courseId);
11947
11948
        return $query->getSingleScalarResult();
11949
    }
11950
11951
    /**
11952
     * @param int $courseId
11953
     *
11954
     * @return mixed
11955
     */
11956
    public static function getCategories($courseId)
11957
    {
11958
        $em = Database::getManager();
11959
        //Default behaviour
11960
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11961
            array('cId' => $course_id),
11962
            array('name' => 'ASC')
11963
        );*/
11964
11965
        // Using doctrine extensions
11966
        /** @var SortableRepository $repo */
11967
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11968
        $items = $repo
11969
            ->getBySortableGroupsQuery(['cId' => $courseId])
11970
            ->getResult();
11971
11972
        return $items;
11973
    }
11974
11975
    /**
11976
     * @param int $id
11977
     *
11978
     * @throws \Doctrine\ORM\ORMException
11979
     * @throws \Doctrine\ORM\OptimisticLockException
11980
     * @throws \Doctrine\ORM\TransactionRequiredException
11981
     *
11982
     * @return CLpCategory
11983
     */
11984
    public static function getCategory($id)
11985
    {
11986
        $id = (int) $id;
11987
        $em = Database::getManager();
11988
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11989
11990
        return $item;
11991
    }
11992
11993
    /**
11994
     * @param int $courseId
11995
     *
11996
     * @return array
11997
     */
11998
    public static function getCategoryByCourse($courseId)
11999
    {
12000
        $em = Database::getManager();
12001
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12002
            ['cId' => $courseId]
12003
        );
12004
12005
        return $items;
12006
    }
12007
12008
    /**
12009
     * @param int $id
12010
     *
12011
     * @throws \Doctrine\ORM\ORMException
12012
     * @throws \Doctrine\ORM\OptimisticLockException
12013
     * @throws \Doctrine\ORM\TransactionRequiredException
12014
     *
12015
     * @return mixed
12016
     */
12017
    public static function deleteCategory($id)
12018
    {
12019
        $em = Database::getManager();
12020
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12021
        if ($item) {
12022
            $courseId = $item->getCId();
12023
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12024
            $query->setParameter('id', $courseId);
12025
            $query->setParameter('catId', $item->getId());
12026
            $lps = $query->getResult();
12027
12028
            // Setting category = 0.
12029
            if ($lps) {
12030
                foreach ($lps as $lpItem) {
12031
                    $lpItem->setCategoryId(0);
12032
                }
12033
            }
12034
12035
            // Removing category.
12036
            $em->remove($item);
12037
            $em->flush();
12038
12039
            $courseInfo = api_get_course_info_by_id($courseId);
12040
            $sessionId = api_get_session_id();
12041
12042
            // Delete link tool
12043
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12044
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12045
            // Delete tools
12046
            $sql = "DELETE FROM $tbl_tool
12047
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12048
            Database::query($sql);
12049
        }
12050
    }
12051
12052
    /**
12053
     * @param int  $courseId
12054
     * @param bool $addSelectOption
12055
     *
12056
     * @return mixed
12057
     */
12058
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12059
    {
12060
        $items = self::getCategoryByCourse($courseId);
12061
        $cats = [];
12062
        if ($addSelectOption) {
12063
            $cats = [get_lang('SelectACategory')];
12064
        }
12065
12066
        if (!empty($items)) {
12067
            foreach ($items as $cat) {
12068
                $cats[$cat->getId()] = $cat->getName();
12069
            }
12070
        }
12071
12072
        return $cats;
12073
    }
12074
12075
    /**
12076
     * @param string $courseCode
12077
     * @param int    $lpId
12078
     * @param int    $user_id
12079
     *
12080
     * @return learnpath
12081
     */
12082
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12083
    {
12084
        $debug = 0;
12085
        $learnPath = null;
12086
        $lpObject = Session::read('lpobject');
12087
        if ($lpObject !== null) {
12088
            $learnPath = unserialize($lpObject);
12089
            if ($debug) {
12090
                error_log('getLpFromSession: unserialize');
12091
                error_log('------getLpFromSession------');
12092
                error_log('------unserialize------');
12093
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12094
                error_log("api_get_sessionid: ".api_get_session_id());
12095
            }
12096
        }
12097
12098
        if (!is_object($learnPath)) {
12099
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12100
            if ($debug) {
12101
                error_log('------getLpFromSession------');
12102
                error_log('getLpFromSession: create new learnpath');
12103
                error_log("create new LP with $courseCode - $lpId - $user_id");
12104
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12105
                error_log("api_get_sessionid: ".api_get_session_id());
12106
            }
12107
        }
12108
12109
        return $learnPath;
12110
    }
12111
12112
    /**
12113
     * @param int $itemId
12114
     *
12115
     * @return learnpathItem|false
12116
     */
12117
    public function getItem($itemId)
12118
    {
12119
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12120
            return $this->items[$itemId];
12121
        }
12122
12123
        return false;
12124
    }
12125
12126
    /**
12127
     * @return int
12128
     */
12129
    public function getCategoryId()
12130
    {
12131
        return $this->categoryId;
12132
    }
12133
12134
    /**
12135
     * @param int $categoryId
12136
     *
12137
     * @return bool
12138
     */
12139
    public function setCategoryId($categoryId)
12140
    {
12141
        $this->categoryId = intval($categoryId);
12142
        $table = Database::get_course_table(TABLE_LP_MAIN);
12143
        $lp_id = $this->get_id();
12144
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12145
                WHERE iid = $lp_id";
12146
        Database::query($sql);
12147
12148
        return true;
12149
    }
12150
12151
    /**
12152
     * Get whether this is a learning path with the possibility to subscribe
12153
     * users or not.
12154
     *
12155
     * @return int
12156
     */
12157
    public function getSubscribeUsers()
12158
    {
12159
        return $this->subscribeUsers;
12160
    }
12161
12162
    /**
12163
     * Set whether this is a learning path with the possibility to subscribe
12164
     * users or not.
12165
     *
12166
     * @param int $value (0 = false, 1 = true)
12167
     *
12168
     * @return bool
12169
     */
12170
    public function setSubscribeUsers($value)
12171
    {
12172
        if ($this->debug > 0) {
12173
            error_log('In learnpath::set_subscribe_users()', 0);
12174
        }
12175
        $this->subscribeUsers = (int) $value;
12176
        $table = Database::get_course_table(TABLE_LP_MAIN);
12177
        $lp_id = $this->get_id();
12178
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12179
                WHERE iid = $lp_id";
12180
        Database::query($sql);
12181
12182
        return true;
12183
    }
12184
12185
    /**
12186
     * Calculate the count of stars for a user in this LP
12187
     * This calculation is based on the following rules:
12188
     * - the student gets one star when he gets to 50% of the learning path
12189
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12190
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12191
     * - the student gets the final star when the score for the *last* test is >= 80%.
12192
     *
12193
     * @param int $sessionId Optional. The session ID
12194
     *
12195
     * @return int The count of stars
12196
     */
12197
    public function getCalculateStars($sessionId = 0)
12198
    {
12199
        $stars = 0;
12200
        $progress = self::getProgress(
12201
            $this->lp_id,
12202
            $this->user_id,
12203
            $this->course_int_id,
12204
            $sessionId
12205
        );
12206
12207
        if ($progress >= 50) {
12208
            $stars++;
12209
        }
12210
12211
        // Calculate stars chapters evaluation
12212
        $exercisesItems = $this->getExercisesItems();
12213
12214
        if (!empty($exercisesItems)) {
12215
            $totalResult = 0;
12216
12217
            foreach ($exercisesItems as $exerciseItem) {
12218
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12219
                    $this->user_id,
12220
                    $exerciseItem->path,
12221
                    $this->course_int_id,
12222
                    $sessionId,
12223
                    $this->lp_id,
12224
                    $exerciseItem->db_id
12225
                );
12226
12227
                $exerciseResultInfo = end($exerciseResultInfo);
12228
12229
                if (!$exerciseResultInfo) {
12230
                    continue;
12231
                }
12232
12233
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12234
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12235
                } else {
12236
                    $exerciseResult = 0;
12237
                }
12238
                $totalResult += $exerciseResult;
12239
            }
12240
12241
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12242
12243
            if ($totalExerciseAverage >= 50) {
12244
                $stars++;
12245
            }
12246
12247
            if ($totalExerciseAverage >= 80) {
12248
                $stars++;
12249
            }
12250
        }
12251
12252
        // Calculate star for final evaluation
12253
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12254
12255
        if (!empty($finalEvaluationItem)) {
12256
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12257
                $this->user_id,
12258
                $finalEvaluationItem->path,
12259
                $this->course_int_id,
12260
                $sessionId,
12261
                $this->lp_id,
12262
                $finalEvaluationItem->db_id
12263
            );
12264
12265
            $evaluationResultInfo = end($evaluationResultInfo);
12266
12267
            if ($evaluationResultInfo) {
12268
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12269
12270
                if ($evaluationResult >= 80) {
12271
                    $stars++;
12272
                }
12273
            }
12274
        }
12275
12276
        return $stars;
12277
    }
12278
12279
    /**
12280
     * Get the items of exercise type.
12281
     *
12282
     * @return array The items. Otherwise return false
12283
     */
12284
    public function getExercisesItems()
12285
    {
12286
        $exercises = [];
12287
        foreach ($this->items as $item) {
12288
            if ($item->type != 'quiz') {
12289
                continue;
12290
            }
12291
            $exercises[] = $item;
12292
        }
12293
12294
        array_pop($exercises);
12295
12296
        return $exercises;
12297
    }
12298
12299
    /**
12300
     * Get the item of exercise type (evaluation type).
12301
     *
12302
     * @return array The final evaluation. Otherwise return false
12303
     */
12304
    public function getFinalEvaluationItem()
12305
    {
12306
        $exercises = [];
12307
        foreach ($this->items as $item) {
12308
            if ($item->type != 'quiz') {
12309
                continue;
12310
            }
12311
12312
            $exercises[] = $item;
12313
        }
12314
12315
        return array_pop($exercises);
12316
    }
12317
12318
    /**
12319
     * Calculate the total points achieved for the current user in this learning path.
12320
     *
12321
     * @param int $sessionId Optional. The session Id
12322
     *
12323
     * @return int
12324
     */
12325
    public function getCalculateScore($sessionId = 0)
12326
    {
12327
        // Calculate stars chapters evaluation
12328
        $exercisesItems = $this->getExercisesItems();
12329
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12330
        $totalExercisesResult = 0;
12331
        $totalEvaluationResult = 0;
12332
12333
        if ($exercisesItems !== false) {
12334
            foreach ($exercisesItems as $exerciseItem) {
12335
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12336
                    $this->user_id,
12337
                    $exerciseItem->path,
12338
                    $this->course_int_id,
12339
                    $sessionId,
12340
                    $this->lp_id,
12341
                    $exerciseItem->db_id
12342
                );
12343
12344
                $exerciseResultInfo = end($exerciseResultInfo);
12345
12346
                if (!$exerciseResultInfo) {
12347
                    continue;
12348
                }
12349
12350
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12351
            }
12352
        }
12353
12354
        if (!empty($finalEvaluationItem)) {
12355
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12356
                $this->user_id,
12357
                $finalEvaluationItem->path,
12358
                $this->course_int_id,
12359
                $sessionId,
12360
                $this->lp_id,
12361
                $finalEvaluationItem->db_id
12362
            );
12363
12364
            $evaluationResultInfo = end($evaluationResultInfo);
12365
12366
            if ($evaluationResultInfo) {
12367
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12368
            }
12369
        }
12370
12371
        return $totalExercisesResult + $totalEvaluationResult;
12372
    }
12373
12374
    /**
12375
     * Check if URL is not allowed to be show in a iframe.
12376
     *
12377
     * @param string $src
12378
     *
12379
     * @return string
12380
     */
12381
    public function fixBlockedLinks($src)
12382
    {
12383
        $urlInfo = parse_url($src);
12384
12385
        $platformProtocol = 'https';
12386
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12387
            $platformProtocol = 'http';
12388
        }
12389
12390
        $protocolFixApplied = false;
12391
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12392
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12393
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12394
12395
        if ($platformProtocol != $scheme) {
12396
            Session::write('x_frame_source', $src);
12397
            $src = 'blank.php?error=x_frames_options';
12398
            $protocolFixApplied = true;
12399
        }
12400
12401
        if ($protocolFixApplied == false) {
12402
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12403
                // Check X-Frame-Options
12404
                $ch = curl_init();
12405
                $options = [
12406
                    CURLOPT_URL => $src,
12407
                    CURLOPT_RETURNTRANSFER => true,
12408
                    CURLOPT_HEADER => true,
12409
                    CURLOPT_FOLLOWLOCATION => true,
12410
                    CURLOPT_ENCODING => "",
12411
                    CURLOPT_AUTOREFERER => true,
12412
                    CURLOPT_CONNECTTIMEOUT => 120,
12413
                    CURLOPT_TIMEOUT => 120,
12414
                    CURLOPT_MAXREDIRS => 10,
12415
                ];
12416
12417
                $proxySettings = api_get_configuration_value('proxy_settings');
12418
                if (!empty($proxySettings) &&
12419
                    isset($proxySettings['curl_setopt_array'])
12420
                ) {
12421
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12422
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12423
                }
12424
12425
                curl_setopt_array($ch, $options);
12426
                $response = curl_exec($ch);
12427
                $httpCode = curl_getinfo($ch);
12428
                $headers = substr($response, 0, $httpCode['header_size']);
12429
12430
                $error = false;
12431
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12432
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12433
                ) {
12434
                    $error = true;
12435
                }
12436
12437
                if ($error) {
12438
                    Session::write('x_frame_source', $src);
12439
                    $src = 'blank.php?error=x_frames_options';
12440
                }
12441
            }
12442
        }
12443
12444
        return $src;
12445
    }
12446
12447
    /**
12448
     * Check if this LP has a created forum in the basis course.
12449
     *
12450
     * @return bool
12451
     */
12452
    public function lpHasForum()
12453
    {
12454
        $forumTable = Database::get_course_table(TABLE_FORUM);
12455
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12456
12457
        $fakeFrom = "
12458
            $forumTable f
12459
            INNER JOIN $itemProperty ip
12460
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12461
        ";
12462
12463
        $resultData = Database::select(
12464
            'COUNT(f.iid) AS qty',
12465
            $fakeFrom,
12466
            [
12467
                'where' => [
12468
                    'ip.visibility != ? AND ' => 2,
12469
                    'ip.tool = ? AND ' => TOOL_FORUM,
12470
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12471
                    'f.lp_id = ?' => intval($this->lp_id),
12472
                ],
12473
            ],
12474
            'first'
12475
        );
12476
12477
        return $resultData['qty'] > 0;
12478
    }
12479
12480
    /**
12481
     * Get the forum for this learning path.
12482
     *
12483
     * @param int $sessionId
12484
     *
12485
     * @return bool
12486
     */
12487
    public function getForum($sessionId = 0)
12488
    {
12489
        $forumTable = Database::get_course_table(TABLE_FORUM);
12490
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12491
12492
        $fakeFrom = "$forumTable f
12493
            INNER JOIN $itemProperty ip ";
12494
12495
        if ($this->lp_session_id == 0) {
12496
            $fakeFrom .= "
12497
                ON (
12498
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12499
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12500
                    )
12501
                )
12502
            ";
12503
        } else {
12504
            $fakeFrom .= "
12505
                ON (
12506
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12507
                )
12508
            ";
12509
        }
12510
12511
        $resultData = Database::select(
12512
            'f.*',
12513
            $fakeFrom,
12514
            [
12515
                'where' => [
12516
                    'ip.visibility != ? AND ' => 2,
12517
                    'ip.tool = ? AND ' => TOOL_FORUM,
12518
                    'f.session_id = ? AND ' => $sessionId,
12519
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12520
                    'f.lp_id = ?' => intval($this->lp_id),
12521
                ],
12522
            ],
12523
            'first'
12524
        );
12525
12526
        if (empty($resultData)) {
12527
            return false;
12528
        }
12529
12530
        return $resultData;
12531
    }
12532
12533
    /**
12534
     * Create a forum for this learning path.
12535
     *
12536
     * @param int $forumCategoryId
12537
     *
12538
     * @return int The forum ID if was created. Otherwise return false
12539
     */
12540
    public function createForum($forumCategoryId)
12541
    {
12542
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12543
12544
        $forumId = store_forum(
12545
            [
12546
                'lp_id' => $this->lp_id,
12547
                'forum_title' => $this->name,
12548
                'forum_comment' => null,
12549
                'forum_category' => intval($forumCategoryId),
12550
                'students_can_edit_group' => ['students_can_edit' => 0],
12551
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12552
                'default_view_type_group' => ['default_view_type' => 'flat'],
12553
                'group_forum' => 0,
12554
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12555
            ],
12556
            [],
12557
            true
12558
        );
12559
12560
        return $forumId;
12561
    }
12562
12563
    /**
12564
     * Get the LP Final Item form.
12565
     *
12566
     * @throws Exception
12567
     * @throws HTML_QuickForm_Error
12568
     *
12569
     * @return string
12570
     */
12571
    public function getFinalItemForm()
12572
    {
12573
        $finalItem = $this->getFinalItem();
12574
        $title = '';
12575
12576
        if ($finalItem) {
12577
            $title = $finalItem->get_title();
12578
            $buttonText = get_lang('Save');
12579
            $content = $this->getSavedFinalItem();
12580
        } else {
12581
            $buttonText = get_lang('LPCreateDocument');
12582
            $content = $this->getFinalItemTemplate();
12583
        }
12584
12585
        $courseInfo = api_get_course_info();
12586
        $result = $this->generate_lp_folder($courseInfo);
12587
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12588
        $relative_prefix = '../../';
12589
12590
        $editorConfig = [
12591
            'ToolbarSet' => 'LearningPathDocuments',
12592
            'Width' => '100%',
12593
            'Height' => '500',
12594
            'FullPage' => true,
12595
            'CreateDocumentDir' => $relative_prefix,
12596
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12597
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12598
        ];
12599
12600
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12601
            'type' => 'document',
12602
            'lp_id' => $this->lp_id,
12603
        ]);
12604
12605
        $form = new FormValidator('final_item', 'POST', $url);
12606
        $form->addText('title', get_lang('Title'));
12607
        $form->addButtonSave($buttonText);
12608
        $form->addHtml(
12609
            Display::return_message(
12610
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12611
                'normal',
12612
                false
12613
            )
12614
        );
12615
12616
        $renderer = $form->defaultRenderer();
12617
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12618
12619
        $form->addHtmlEditor(
12620
            'content_lp_certificate',
12621
            null,
12622
            true,
12623
            false,
12624
            $editorConfig,
12625
            true
12626
        );
12627
        $form->addHidden('action', 'add_final_item');
12628
        $form->addHidden('path', Session::read('pathItem'));
12629
        $form->addHidden('previous', $this->get_last());
12630
        $form->setDefaults(
12631
            ['title' => $title, 'content_lp_certificate' => $content]
12632
        );
12633
12634
        if ($form->validate()) {
12635
            $values = $form->exportValues();
12636
            $lastItemId = $this->get_last();
12637
12638
            if (!$finalItem) {
12639
                $documentId = $this->create_document(
12640
                    $this->course_info,
12641
                    $values['content_lp_certificate'],
12642
                    $values['title']
12643
                );
12644
                $this->add_item(
12645
                    0,
12646
                    $lastItemId,
12647
                    'final_item',
12648
                    $documentId,
12649
                    $values['title'],
12650
                    ''
12651
                );
12652
12653
                Display::addFlash(
12654
                    Display::return_message(get_lang('Added'))
12655
                );
12656
            } else {
12657
                $this->edit_document($this->course_info);
12658
            }
12659
        }
12660
12661
        return $form->returnForm();
12662
    }
12663
12664
    /**
12665
     * Check if the current lp item is first, both, last or none from lp list.
12666
     *
12667
     * @param int $currentItemId
12668
     *
12669
     * @return string
12670
     */
12671
    public function isFirstOrLastItem($currentItemId)
12672
    {
12673
        if ($this->debug > 0) {
12674
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
12675
        }
12676
12677
        $lpItemId = [];
12678
        $typeListNotToVerify = self::getChapterTypes();
12679
12680
        // Using get_toc() function instead $this->items because returns the correct order of the items
12681
        foreach ($this->get_toc() as $item) {
12682
            if (!in_array($item['type'], $typeListNotToVerify)) {
12683
                $lpItemId[] = $item['id'];
12684
            }
12685
        }
12686
12687
        $lastLpItemIndex = count($lpItemId) - 1;
12688
        $position = array_search($currentItemId, $lpItemId);
12689
12690
        switch ($position) {
12691
            case 0:
12692
                if (!$lastLpItemIndex) {
12693
                    $answer = 'both';
12694
                    break;
12695
                }
12696
12697
                $answer = 'first';
12698
                break;
12699
            case $lastLpItemIndex:
12700
                $answer = 'last';
12701
                break;
12702
            default:
12703
                $answer = 'none';
12704
        }
12705
12706
        return $answer;
12707
    }
12708
12709
    /**
12710
     * Get whether this is a learning path with the accumulated SCORM time or not.
12711
     *
12712
     * @return int
12713
     */
12714
    public function getAccumulateScormTime()
12715
    {
12716
        return $this->accumulateScormTime;
12717
    }
12718
12719
    /**
12720
     * Set whether this is a learning path with the accumulated SCORM time or not.
12721
     *
12722
     * @param int $value (0 = false, 1 = true)
12723
     *
12724
     * @return bool Always returns true
12725
     */
12726
    public function setAccumulateScormTime($value)
12727
    {
12728
        if ($this->debug > 0) {
12729
            error_log('In learnpath::setAccumulateScormTime()', 0);
12730
        }
12731
        $this->accumulateScormTime = intval($value);
12732
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12733
        $lp_id = $this->get_id();
12734
        $sql = "UPDATE $lp_table 
12735
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12736
                WHERE iid = $lp_id";
12737
        Database::query($sql);
12738
12739
        return true;
12740
    }
12741
12742
    /**
12743
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12744
     * the new learning path tool.
12745
     *
12746
     * The function is a big switch on tool type.
12747
     * In each case, we query the corresponding table for information and build the link
12748
     * with that information.
12749
     *
12750
     * @author Yannick Warnier <[email protected]> - rebranding based on
12751
     * previous work (display_addedresource_link_in_learnpath())
12752
     *
12753
     * @param int $course_id      Course code
12754
     * @param int $learningPathId The learning path ID (in lp table)
12755
     * @param int $id_in_path     the unique index in the items table
12756
     * @param int $lpViewId
12757
     *
12758
     * @return string
12759
     */
12760
    public static function rl_get_resource_link_for_learnpath(
12761
        $course_id,
12762
        $learningPathId,
12763
        $id_in_path,
12764
        $lpViewId
12765
    ) {
12766
        $session_id = api_get_session_id();
12767
        $course_info = api_get_course_info_by_id($course_id);
12768
12769
        $learningPathId = intval($learningPathId);
12770
        $id_in_path = intval($id_in_path);
12771
        $lpViewId = intval($lpViewId);
12772
12773
        $em = Database::getManager();
12774
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12775
        /** @var CLpItem $rowItem */
12776
        $rowItem = $lpItemRepo->findOneBy([
12777
            'cId' => $course_id,
12778
            'lpId' => $learningPathId,
12779
            'id' => $id_in_path,
12780
        ]);
12781
12782
        if (!$rowItem) {
12783
            // Try one more time with iid
12784
            /** @var CLpItem $rowItem */
12785
            $rowItem = $lpItemRepo->findOneBy([
12786
                'cId' => $course_id,
12787
                'lpId' => $learningPathId,
12788
                'iid' => $id_in_path,
12789
            ]);
12790
12791
            if (!$rowItem) {
12792
                return -1;
12793
            }
12794
        }
12795
12796
        $course_code = $course_info['code'];
12797
        $type = $rowItem->getItemType();
12798
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12799
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12800
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12801
        $link = '';
12802
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12803
12804
        switch ($type) {
12805
            case 'dir':
12806
                return $main_dir_path.'lp/blank.php';
12807
            case TOOL_CALENDAR_EVENT:
12808
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12809
            case TOOL_ANNOUNCEMENT:
12810
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12811
            case TOOL_LINK:
12812
                $linkInfo = Link::getLinkInfo($id);
12813
                if (isset($linkInfo['url'])) {
12814
                    return $linkInfo['url'];
12815
                }
12816
12817
                return '';
12818
            case TOOL_QUIZ:
12819
                if (empty($id)) {
12820
                    return '';
12821
                }
12822
12823
                // Get the lp_item_view with the highest view_count.
12824
                $learnpathItemViewResult = $em
12825
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12826
                    ->findBy(
12827
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12828
                        ['viewCount' => 'DESC'],
12829
                        1
12830
                    );
12831
                /** @var CLpItemView $learnpathItemViewData */
12832
                $learnpathItemViewData = current($learnpathItemViewResult);
12833
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12834
12835
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12836
                    .http_build_query([
12837
                        'lp_init' => 1,
12838
                        'learnpath_item_view_id' => $learnpathItemViewId,
12839
                        'learnpath_id' => $learningPathId,
12840
                        'learnpath_item_id' => $id_in_path,
12841
                        'exerciseId' => $id,
12842
                    ]);
12843
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12844
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12845
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12846
                $myrow = Database::fetch_array($result);
12847
                $path = $myrow['path'];
12848
12849
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12850
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12851
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12852
            case TOOL_FORUM:
12853
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12854
            case TOOL_THREAD:
12855
                // forum post
12856
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12857
                if (empty($id)) {
12858
                    return '';
12859
                }
12860
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12861
                $result = Database::query($sql);
12862
                $myrow = Database::fetch_array($result);
12863
12864
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12865
                    .$extraParams;
12866
            case TOOL_POST:
12867
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12868
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12869
                $myrow = Database::fetch_array($result);
12870
12871
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12872
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12873
            case TOOL_DOCUMENT:
12874
                $document = $em
12875
                    ->getRepository('ChamiloCourseBundle:CDocument')
12876
                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
12877
12878
                if (empty($document)) {
12879
                    // Try with normal id
12880
                    $document = $em
12881
                        ->getRepository('ChamiloCourseBundle:CDocument')
12882
                        ->findOneBy(['cId' => $course_id, 'id' => $id]);
12883
12884
                    if (empty($document)) {
12885
                        return '';
12886
                    }
12887
                }
12888
12889
                $documentPathInfo = pathinfo($document->getPath());
12890
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
12891
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12892
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
12893
12894
                $openmethod = 2;
12895
                $officedoc = false;
12896
                Session::write('openmethod', $openmethod);
12897
                Session::write('officedoc', $officedoc);
12898
12899
                if ($showDirectUrl) {
12900
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12901
                }
12902
12903
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12904
            case TOOL_LP_FINAL_ITEM:
12905
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12906
                    .$extraParams;
12907
            case 'assignments':
12908
                return $main_dir_path.'work/work.php?'.$extraParams;
12909
            case TOOL_DROPBOX:
12910
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12911
            case 'introduction_text': //DEPRECATED
12912
                return '';
12913
            case TOOL_COURSE_DESCRIPTION:
12914
                return $main_dir_path.'course_description?'.$extraParams;
12915
            case TOOL_GROUP:
12916
                return $main_dir_path.'group/group.php?'.$extraParams;
12917
            case TOOL_USER:
12918
                return $main_dir_path.'user/user.php?'.$extraParams;
12919
            case TOOL_STUDENTPUBLICATION:
12920
                if (!empty($rowItem->getPath())) {
12921
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12922
                }
12923
12924
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12925
        } //end switch
12926
12927
        return $link;
12928
    }
12929
12930
    /**
12931
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12932
     *
12933
     * @author Yannick Warnier <[email protected]>
12934
     *
12935
     * @param string $course_code    Course code
12936
     * @param string $learningPathId The tool type (using constants declared in main_api.lib.php)
12937
     * @param int    $id_in_path     The resource ID
12938
     *
12939
     * @return string
12940
     */
12941
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12942
    {
12943
        $_course = api_get_course_info($course_code);
12944
        $course_id = $_course['real_id'];
12945
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12946
        $learningPathId = intval($learningPathId);
12947
        $id_in_path = intval($id_in_path);
12948
12949
        $sql_item = "SELECT item_type, title, ref FROM $tbl_lp_item
12950
                     WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12951
        $res_item = Database::query($sql_item);
12952
12953
        if (Database::num_rows($res_item) < 1) {
12954
            return '';
12955
        }
12956
        $row_item = Database::fetch_array($res_item);
12957
        $type = strtolower($row_item['item_type']);
12958
        $id = $row_item['ref'];
12959
        $output = '';
12960
12961
        switch ($type) {
12962
            case TOOL_CALENDAR_EVENT:
12963
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12964
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12965
                $myrow = Database::fetch_array($result);
12966
                $output = $myrow['title'];
12967
                break;
12968
            case TOOL_ANNOUNCEMENT:
12969
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12970
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12971
                $myrow = Database::fetch_array($result);
12972
                $output = $myrow['title'];
12973
                break;
12974
            case TOOL_LINK:
12975
                // Doesn't take $target into account.
12976
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12977
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12978
                $myrow = Database::fetch_array($result);
12979
                $output = $myrow['title'];
12980
                break;
12981
            case TOOL_QUIZ:
12982
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12983
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id=$id");
12984
                $myrow = Database::fetch_array($result);
12985
                $output = $myrow['title'];
12986
                break;
12987
            case TOOL_FORUM:
12988
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12989
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id=$id");
12990
                $myrow = Database::fetch_array($result);
12991
                $output = $myrow['forum_name'];
12992
                break;
12993
            case TOOL_THREAD:  //=topics
12994
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12995
                // Grabbing the title of the post.
12996
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12997
                $result_title = Database::query($sql_title);
12998
                $myrow_title = Database::fetch_array($result_title);
12999
                $output = $myrow_title['post_title'];
13000
                break;
13001
            case TOOL_POST:
13002
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13003
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
13004
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13005
                $result = Database::query($sql);
13006
                $post = Database::fetch_array($result);
13007
                $output = $post['post_title'];
13008
                break;
13009
            case 'dir':
13010
                $title = $row_item['title'];
13011
                if (!empty($title)) {
13012
                    $output = $title;
13013
                } else {
13014
                    $output = '-';
13015
                }
13016
                break;
13017
            case TOOL_DOCUMENT:
13018
                $title = $row_item['title'];
13019
                if (!empty($title)) {
13020
                    $output = $title;
13021
                } else {
13022
                    $output = '-';
13023
                }
13024
                break;
13025
            case 'hotpotatoes':
13026
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13027
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13028
                $myrow = Database::fetch_array($result);
13029
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13030
                $last = count($pathname) - 1; // Making a correct name for the link.
13031
                $filename = $pathname[$last]; // Making a correct name for the link.
13032
                $ext = explode('.', $filename);
13033
                $ext = strtolower($ext[sizeof($ext) - 1]);
13034
                $myrow['path'] = rawurlencode($myrow['path']);
13035
                $output = $filename;
13036
                break;
13037
        }
13038
13039
        return stripslashes($output);
13040
    }
13041
13042
    /**
13043
     * Get the parent names for the current item.
13044
     *
13045
     * @param int $newItemId Optional. The item ID
13046
     *
13047
     * @return array
13048
     */
13049
    public function getCurrentItemParentNames($newItemId = 0)
13050
    {
13051
        $newItemId = $newItemId ?: $this->get_current_item_id();
13052
        $return = [];
13053
        $item = $this->getItem($newItemId);
13054
        $parent = $this->getItem($item->get_parent());
13055
13056
        while ($parent) {
13057
            $return[] = $parent->get_title();
13058
13059
            $parent = $this->getItem($parent->get_parent());
13060
        }
13061
13062
        return array_reverse($return);
13063
    }
13064
13065
    /**
13066
     * Reads and process "lp_subscription_settings" setting.
13067
     *
13068
     * @return array
13069
     */
13070
    public static function getSubscriptionSettings()
13071
    {
13072
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13073
        if (empty($subscriptionSettings)) {
13074
            // By default allow both settings
13075
            $subscriptionSettings = [
13076
                'allow_add_users_to_lp' => true,
13077
                'allow_add_users_to_lp_category' => true,
13078
            ];
13079
        } else {
13080
            $subscriptionSettings = $subscriptionSettings['options'];
13081
        }
13082
13083
        return $subscriptionSettings;
13084
    }
13085
13086
    /**
13087
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13088
     */
13089
    public function exportToCourseBuildFormat()
13090
    {
13091
        if (!api_is_allowed_to_edit()) {
13092
            return false;
13093
        }
13094
13095
        $courseBuilder = new CourseBuilder();
13096
        $itemList = [];
13097
        /** @var learnpathItem $item */
13098
        foreach ($this->items as $item) {
13099
            $itemList[$item->get_type()][] = $item->get_path();
13100
        }
13101
13102
        if (empty($itemList)) {
13103
            return false;
13104
        }
13105
13106
        if (isset($itemList['document'])) {
13107
            // Get parents
13108
            foreach ($itemList['document'] as $documentId) {
13109
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13110
                if (!empty($documentInfo['parents'])) {
13111
                    foreach ($documentInfo['parents'] as $parentInfo) {
13112
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13113
                            continue;
13114
                        }
13115
                        $itemList['document'][] = $parentInfo['iid'];
13116
                    }
13117
                }
13118
            }
13119
13120
            $courseInfo = api_get_course_info();
13121
            foreach ($itemList['document'] as $documentId) {
13122
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13123
                $items = DocumentManager::get_resources_from_source_html(
13124
                    $documentInfo['absolute_path'],
13125
                    true,
13126
                    TOOL_DOCUMENT
13127
                );
13128
13129
                if (!empty($items)) {
13130
                    foreach ($items as $item) {
13131
                        // Get information about source url
13132
                        $url = $item[0]; // url
13133
                        $scope = $item[1]; // scope (local, remote)
13134
                        $type = $item[2]; // type (rel, abs, url)
13135
13136
                        $origParseUrl = parse_url($url);
13137
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13138
13139
                        if ($scope == 'local') {
13140
                            if ($type == 'abs' || $type == 'rel') {
13141
                                $documentFile = strstr($realOrigPath, 'document');
13142
                                if (strpos($realOrigPath, $documentFile) !== false) {
13143
                                    $documentFile = str_replace('document', '', $documentFile);
13144
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13145
                                    // Document found! Add it to the list
13146
                                    if ($itemDocumentId) {
13147
                                        $itemList['document'][] = $itemDocumentId;
13148
                                    }
13149
                                }
13150
                            }
13151
                        }
13152
                    }
13153
                }
13154
            }
13155
13156
            $courseBuilder->build_documents(
13157
                api_get_session_id(),
13158
                $this->get_course_int_id(),
13159
                true,
13160
                $itemList['document']
13161
            );
13162
        }
13163
13164
        if (isset($itemList['quiz'])) {
13165
            $courseBuilder->build_quizzes(
13166
                api_get_session_id(),
13167
                $this->get_course_int_id(),
13168
                true,
13169
                $itemList['quiz']
13170
            );
13171
        }
13172
13173
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13174
13175
        /*if (!empty($itemList['thread'])) {
13176
            $postList = [];
13177
            foreach ($itemList['thread'] as $postId) {
13178
                $post = get_post_information($postId);
13179
                if ($post) {
13180
                    if (!isset($itemList['forum'])) {
13181
                        $itemList['forum'] = [];
13182
                    }
13183
                    $itemList['forum'][] = $post['forum_id'];
13184
                    $postList[] = $postId;
13185
                }
13186
            }
13187
13188
            if (!empty($postList)) {
13189
                $courseBuilder->build_forum_posts(
13190
                    $this->get_course_int_id(),
13191
                    null,
13192
                    null,
13193
                    $postList
13194
                );
13195
            }
13196
        }*/
13197
13198
        if (!empty($itemList['thread'])) {
13199
            $threadList = [];
13200
            $em = Database::getManager();
13201
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13202
            foreach ($itemList['thread'] as $threadId) {
13203
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13204
                $thread = $repo->find($threadId);
13205
                if ($thread) {
13206
                    $itemList['forum'][] = $thread->getForumId();
13207
                    $threadList[] = $thread->getIid();
13208
                }
13209
            }
13210
13211
            if (!empty($threadList)) {
13212
                $courseBuilder->build_forum_topics(
13213
                    api_get_session_id(),
13214
                    $this->get_course_int_id(),
13215
                    null,
13216
                    $threadList
13217
                );
13218
            }
13219
        }
13220
13221
        $forumCategoryList = [];
13222
        if (isset($itemList['forum'])) {
13223
            foreach ($itemList['forum'] as $forumId) {
13224
                $forumInfo = get_forums($forumId);
13225
                $forumCategoryList[] = $forumInfo['forum_category'];
13226
            }
13227
        }
13228
13229
        if (!empty($forumCategoryList)) {
13230
            $courseBuilder->build_forum_category(
13231
                api_get_session_id(),
13232
                $this->get_course_int_id(),
13233
                true,
13234
                $forumCategoryList
13235
            );
13236
        }
13237
13238
        if (!empty($itemList['forum'])) {
13239
            $courseBuilder->build_forums(
13240
                api_get_session_id(),
13241
                $this->get_course_int_id(),
13242
                true,
13243
                $itemList['forum']
13244
            );
13245
        }
13246
13247
        if (isset($itemList['link'])) {
13248
            $courseBuilder->build_links(
13249
                api_get_session_id(),
13250
                $this->get_course_int_id(),
13251
                true,
13252
                $itemList['link']
13253
            );
13254
        }
13255
13256
        if (!empty($itemList['student_publication'])) {
13257
            $courseBuilder->build_works(
13258
                api_get_session_id(),
13259
                $this->get_course_int_id(),
13260
                true,
13261
                $itemList['student_publication']
13262
            );
13263
        }
13264
13265
        $courseBuilder->build_learnpaths(
13266
            api_get_session_id(),
13267
            $this->get_course_int_id(),
13268
            true,
13269
            [$this->get_id()],
13270
            false
13271
        );
13272
13273
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13274
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13275
        $result = DocumentManager::file_send_for_download(
13276
            $zipPath,
13277
            true,
13278
            $this->get_name().'.zip'
13279
        );
13280
13281
        if ($result) {
13282
            api_not_allowed();
13283
        }
13284
13285
        return true;
13286
    }
13287
13288
    /**
13289
     * Get the depth level of LP item.
13290
     *
13291
     * @param array $items
13292
     * @param int   $currentItemId
13293
     *
13294
     * @return int
13295
     */
13296
    private static function get_level_for_item($items, $currentItemId)
13297
    {
13298
        $parentItemId = 0;
13299
        if (isset($items[$currentItemId])) {
13300
            $parentItemId = $items[$currentItemId]->parent;
13301
        }
13302
13303
        if ($parentItemId == 0) {
13304
            return 0;
13305
        } else {
13306
            return self::get_level_for_item($items, $parentItemId) + 1;
13307
        }
13308
    }
13309
13310
    /**
13311
     * Generate the link for a learnpath category as course tool.
13312
     *
13313
     * @param int $categoryId
13314
     *
13315
     * @return string
13316
     */
13317
    private static function getCategoryLinkForTool($categoryId)
13318
    {
13319
        $categoryId = (int) $categoryId;
13320
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13321
            .http_build_query(
13322
                [
13323
                    'action' => 'view_category',
13324
                    'id' => $categoryId,
13325
                ]
13326
            );
13327
13328
        return $link;
13329
    }
13330
13331
    /**
13332
     * Return the scorm item type object with spaces replaced with _
13333
     * The return result is use to build a css classname like scorm_type_$return.
13334
     *
13335
     * @param $in_type
13336
     *
13337
     * @return mixed
13338
     */
13339
    private static function format_scorm_type_item($in_type)
13340
    {
13341
        return str_replace(' ', '_', $in_type);
13342
    }
13343
13344
    /**
13345
     * Check and obtain the lp final item if exist.
13346
     *
13347
     * @return learnpathItem
13348
     */
13349
    private function getFinalItem()
13350
    {
13351
        if (empty($this->items)) {
13352
            return null;
13353
        }
13354
13355
        foreach ($this->items as $item) {
13356
            if ($item->type !== 'final_item') {
13357
                continue;
13358
            }
13359
13360
            return $item;
13361
        }
13362
    }
13363
13364
    /**
13365
     * Get the LP Final Item Template.
13366
     *
13367
     * @return string
13368
     */
13369
    private function getFinalItemTemplate()
13370
    {
13371
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13372
    }
13373
13374
    /**
13375
     * Get the LP Final Item Url.
13376
     *
13377
     * @return string
13378
     */
13379
    private function getSavedFinalItem()
13380
    {
13381
        $finalItem = $this->getFinalItem();
13382
        $doc = DocumentManager::get_document_data_by_id(
13383
            $finalItem->path,
13384
            $this->cc
13385
        );
13386
        if ($doc && file_exists($doc['absolute_path'])) {
13387
            return file_get_contents($doc['absolute_path']);
13388
        }
13389
13390
        return '';
13391
    }
13392
}
13393