Passed
Pull Request — 1.11.x (#3859)
by Angel Fernando Quiroz
09:57
created

learnpath::display_document()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 4
dl 0
loc 20
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
6
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CItemProperty;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CTool;
17
use Chamilo\PluginBundle\Entity\XApi\ToolLaunch;
18
use Chamilo\UserBundle\Entity\User;
19
use ChamiloSession as Session;
20
use Gedmo\Sortable\Entity\Repository\SortableRepository;
21
use Symfony\Component\Filesystem\Filesystem;
22
use Symfony\Component\Finder\Finder;
23
24
/**
25
 * Class learnpath
26
 * This class defines the parent attributes and methods for Chamilo learnpaths
27
 * and SCORM learnpaths. It is used by the scorm class.
28
 *
29
 * @todo decouple class
30
 *
31
 * @author  Yannick Warnier <[email protected]>
32
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
33
 */
34
class learnpath
35
{
36
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
37
    public const STATUS_CSS_CLASS_NAME = [
38
        'not attempted' => 'scorm_not_attempted',
39
        'incomplete' => 'scorm_not_attempted',
40
        'failed' => 'scorm_failed',
41
        'completed' => 'scorm_completed',
42
        'passed' => 'scorm_completed',
43
        'succeeded' => 'scorm_completed',
44
        'browsed' => 'scorm_completed',
45
    ];
46
47
    public $attempt = 0; // The number for the current ID view.
48
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
49
    public $current; // Id of the current item the user is viewing.
50
    public $current_score; // The score of the current item.
51
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
52
    public $current_time_stop; // The time the user closed this resource.
53
    public $default_status = 'not attempted';
54
    public $encoding = 'UTF-8';
55
    public $error = '';
56
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
57
    public $index; // The index of the active learnpath_item in $ordered_items array.
58
    /** @var learnpathItem[] */
59
    public $items = [];
60
    public $last; // item_id of last item viewed in the learning path.
61
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
62
    public $license; // Which license this course has been given - not used yet on 20060522.
63
    public $lp_id; // DB iid for this learnpath.
64
    public $lp_view_id; // DB ID for lp_view
65
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
66
    public $message = '';
67
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
68
    public $name; // Learnpath name (they generally have one).
69
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
70
    public $path = ''; // Path inside the scorm directory (if scorm).
71
    public $theme; // The current theme of the learning path.
72
    public $preview_image; // The current image of the learning path.
73
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
74
    public $accumulateWorkTime; // The min time of learnpath
75
76
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
77
    public $prevent_reinit = 1;
78
79
    // Describes the mode of progress bar display.
80
    public $seriousgame_mode = 0;
81
    public $progress_bar_mode = '%';
82
83
    // Percentage progress as saved in the db.
84
    public $progress_db = 0;
85
    public $proximity; // Wether the content is distant or local or unknown.
86
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
87
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
88
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
89
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
90
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
91
    public $user_id; //ID of the user that is viewing/using the course
92
    public $update_queue = [];
93
    public $scorm_debug = 0;
94
    public $arrMenu = []; // Array for the menu items.
95
    public $debug = 0; // Logging level.
96
    public $lp_session_id = 0;
97
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
98
    public $prerequisite = 0;
99
    public $use_max_score = 1; // 1 or 0
100
    public $subscribeUsers = 0; // Subscribe users or not
101
    public $created_on = '';
102
    public $modified_on = '';
103
    public $publicated_on = '';
104
    public $expired_on = '';
105
    public $ref = null;
106
    public $course_int_id;
107
    public $course_info = [];
108
    public $categoryId;
109
110
    /**
111
     * Constructor.
112
     * Needs a database handler, a course code and a learnpath id from the database.
113
     * Also builds the list of items into $this->items.
114
     *
115
     * @param string $course  Course code
116
     * @param int    $lp_id   c_lp.iid
117
     * @param int    $user_id
118
     */
119
    public function __construct($course, $lp_id, $user_id)
120
    {
121
        $debug = $this->debug;
122
        $this->encoding = api_get_system_encoding();
123
        if (empty($course)) {
124
            $course = api_get_course_id();
125
        }
126
        $course_info = api_get_course_info($course);
127
        if (!empty($course_info)) {
128
            $this->cc = $course_info['code'];
129
            $this->course_info = $course_info;
130
            $course_id = $course_info['real_id'];
131
        } else {
132
            $this->error = 'Course code does not exist in database.';
133
        }
134
135
        $lp_id = (int) $lp_id;
136
        $course_id = (int) $course_id;
137
        $this->set_course_int_id($course_id);
138
        // Check learnpath ID.
139
        if (empty($lp_id) || empty($course_id)) {
140
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
141
        } else {
142
            // TODO: Make it flexible to use any course_code (still using env course code here).
143
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
144
            $sql = "SELECT * FROM $lp_table
145
                    WHERE iid = $lp_id";
146
            if ($debug) {
147
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
148
            }
149
            $res = Database::query($sql);
150
            if (Database::num_rows($res) > 0) {
151
                $this->lp_id = $lp_id;
152
                $row = Database::fetch_array($res);
153
                $this->type = $row['lp_type'];
154
                $this->name = stripslashes($row['name']);
155
                $this->proximity = $row['content_local'];
156
                $this->theme = $row['theme'];
157
                $this->maker = $row['content_maker'];
158
                $this->prevent_reinit = $row['prevent_reinit'];
159
                $this->seriousgame_mode = $row['seriousgame_mode'];
160
                $this->license = $row['content_license'];
161
                $this->scorm_debug = $row['debug'];
162
                $this->js_lib = $row['js_lib'];
163
                $this->path = $row['path'];
164
                $this->preview_image = $row['preview_image'];
165
                $this->author = $row['author'];
166
                $this->hide_toc_frame = $row['hide_toc_frame'];
167
                $this->lp_session_id = $row['session_id'];
168
                $this->use_max_score = $row['use_max_score'];
169
                $this->subscribeUsers = $row['subscribe_users'];
170
                $this->created_on = $row['created_on'];
171
                $this->modified_on = $row['modified_on'];
172
                $this->ref = $row['ref'];
173
                $this->categoryId = $row['category_id'];
174
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
175
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
176
177
                if (!empty($row['publicated_on'])) {
178
                    $this->publicated_on = $row['publicated_on'];
179
                }
180
181
                if (!empty($row['expired_on'])) {
182
                    $this->expired_on = $row['expired_on'];
183
                }
184
                if ($this->type == 2) {
185
                    if ($row['force_commit'] == 1) {
186
                        $this->force_commit = true;
187
                    }
188
                }
189
                $this->mode = $row['default_view_mod'];
190
191
                // Check user ID.
192
                if (empty($user_id)) {
193
                    $this->error = 'User ID is empty';
194
                } else {
195
                    $userInfo = api_get_user_info($user_id);
196
                    if (!empty($userInfo)) {
197
                        $this->user_id = $userInfo['user_id'];
198
                    } else {
199
                        $this->error = 'User ID does not exist in database #'.$user_id;
200
                    }
201
                }
202
203
                // End of variables checking.
204
                $session_id = api_get_session_id();
205
                //  Get the session condition for learning paths of the base + session.
206
                $session = api_get_session_condition($session_id);
207
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
208
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
209
210
                // Selecting by view_count descending allows to get the highest view_count first.
211
                $sql = "SELECT * FROM $lp_table
212
                        WHERE
213
                            c_id = $course_id AND
214
                            lp_id = $lp_id AND
215
                            user_id = $user_id
216
                            $session
217
                        ORDER BY view_count DESC";
218
                $res = Database::query($sql);
219
                if ($debug) {
220
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
221
                }
222
223
                if (Database::num_rows($res) > 0) {
224
                    if ($debug) {
225
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
226
                    }
227
                    $row = Database::fetch_array($res);
228
                    $this->attempt = $row['view_count'];
229
                    $this->lp_view_id = $row['id'];
230
                    $this->last_item_seen = $row['last_item'];
231
                    $this->progress_db = $row['progress'];
232
                    $this->lp_view_session_id = $row['session_id'];
233
                } elseif (!api_is_invitee()) {
234
                    if ($debug) {
235
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
236
                    }
237
                    $this->attempt = 1;
238
                    $params = [
239
                        'c_id' => $course_id,
240
                        'lp_id' => $lp_id,
241
                        'user_id' => $user_id,
242
                        'view_count' => 1,
243
                        'session_id' => $session_id,
244
                        'last_item' => 0,
245
                    ];
246
                    $this->last_item_seen = 0;
247
                    $this->lp_view_session_id = $session_id;
248
                    $this->lp_view_id = Database::insert($lp_table, $params);
249
                    if (!empty($this->lp_view_id)) {
250
                        $sql = "UPDATE $lp_table SET id = iid
251
                                WHERE iid = ".$this->lp_view_id;
252
                        Database::query($sql);
253
                    }
254
                }
255
256
                // Initialise items.
257
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
258
                $sql = "SELECT * FROM $lp_item_table
259
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
260
                        ORDER BY parent_item_id, display_order";
261
                $res = Database::query($sql);
262
263
                $lp_item_id_list = [];
264
                while ($row = Database::fetch_array($res)) {
265
                    $lp_item_id_list[] = $row['iid'];
266
                    switch ($this->type) {
267
                        case 3: //aicc
268
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
269
                            if (is_object($oItem)) {
270
                                $my_item_id = $oItem->get_id();
271
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
272
                                $oItem->set_prevent_reinit($this->prevent_reinit);
273
                                // Don't use reference here as the next loop will make the pointed object change.
274
                                $this->items[$my_item_id] = $oItem;
275
                                $this->refs_list[$oItem->ref] = $my_item_id;
276
                                if ($debug) {
277
                                    error_log(
278
                                        'learnpath::__construct() - '.
279
                                        'aicc object with id '.$my_item_id.
280
                                        ' set in items[]',
281
                                        0
282
                                    );
283
                                }
284
                            }
285
                            break;
286
                        case 2:
287
                            $oItem = new scormItem('db', $row['iid'], $course_id);
288
                            if (is_object($oItem)) {
289
                                $my_item_id = $oItem->get_id();
290
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
291
                                $oItem->set_prevent_reinit($this->prevent_reinit);
292
                                // Don't use reference here as the next loop will make the pointed object change.
293
                                $this->items[$my_item_id] = $oItem;
294
                                $this->refs_list[$oItem->ref] = $my_item_id;
295
                                if ($debug) {
296
                                    error_log('object with id '.$my_item_id.' set in items[]');
297
                                }
298
                            }
299
                            break;
300
                        case 1:
301
                        default:
302
                            if ($debug) {
303
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
304
                            }
305
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
306
307
                            if ($debug) {
308
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
309
                            }
310
                            if (is_object($oItem)) {
311
                                $my_item_id = $oItem->get_id();
312
                                // Moved down to when we are sure the item_view exists.
313
                                //$oItem->set_lp_view($this->lp_view_id);
314
                                $oItem->set_prevent_reinit($this->prevent_reinit);
315
                                // Don't use reference here as the next loop will make the pointed object change.
316
                                $this->items[$my_item_id] = $oItem;
317
                                $this->refs_list[$my_item_id] = $my_item_id;
318
                                if ($debug) {
319
                                    error_log(
320
                                        'learnpath::__construct() '.__LINE__.
321
                                        ' - object with id '.$my_item_id.' set in items[]'
322
                                    );
323
                                }
324
                            }
325
                            break;
326
                    }
327
328
                    // Setting the object level with variable $this->items[$i][parent]
329
                    foreach ($this->items as $itemLPObject) {
330
                        $level = self::get_level_for_item(
331
                            $this->items,
332
                            $itemLPObject->db_id
333
                        );
334
                        $itemLPObject->level = $level;
335
                    }
336
337
                    // Setting the view in the item object.
338
                    if (is_object($this->items[$row['iid']])) {
339
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
340
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
341
                            $this->items[$row['iid']]->current_start_time = 0;
342
                            $this->items[$row['iid']]->current_stop_time = 0;
343
                        }
344
                    }
345
                }
346
347
                if (!empty($lp_item_id_list)) {
348
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
349
                    if (!empty($lp_item_id_list_to_string)) {
350
                        // Get last viewing vars.
351
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
352
                        // This query should only return one or zero result.
353
                        $sql = "SELECT lp_item_id, status
354
                                FROM $itemViewTable
355
                                WHERE
356
                                    c_id = $course_id AND
357
                                    lp_view_id = ".$this->get_view_id()." AND
358
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
359
                                ORDER BY view_count DESC ";
360
                        $status_list = [];
361
                        $res = Database::query($sql);
362
                        while ($row = Database:: fetch_array($res)) {
363
                            $status_list[$row['lp_item_id']] = $row['status'];
364
                        }
365
366
                        foreach ($lp_item_id_list as $item_id) {
367
                            if (isset($status_list[$item_id])) {
368
                                $status = $status_list[$item_id];
369
                                if (is_object($this->items[$item_id])) {
370
                                    $this->items[$item_id]->set_status($status);
371
                                    if (empty($status)) {
372
                                        $this->items[$item_id]->set_status(
373
                                            $this->default_status
374
                                        );
375
                                    }
376
                                }
377
                            } else {
378
                                if (!api_is_invitee()) {
379
                                    if (is_object($this->items[$item_id])) {
380
                                        $this->items[$item_id]->set_status(
381
                                            $this->default_status
382
                                        );
383
                                    }
384
385
                                    if (!empty($this->lp_view_id)) {
386
                                        // Add that row to the lp_item_view table so that
387
                                        // we have something to show in the stats page.
388
                                        $params = [
389
                                            'c_id' => $course_id,
390
                                            'lp_item_id' => $item_id,
391
                                            'lp_view_id' => $this->lp_view_id,
392
                                            'view_count' => 1,
393
                                            'status' => 'not attempted',
394
                                            'start_time' => time(),
395
                                            'total_time' => 0,
396
                                            'score' => 0,
397
                                        ];
398
                                        $insertId = Database::insert($itemViewTable, $params);
399
400
                                        if ($insertId) {
401
                                            $sql = "UPDATE $itemViewTable SET id = iid
402
                                                    WHERE iid = $insertId";
403
                                            Database::query($sql);
404
                                        }
405
406
                                        $this->items[$item_id]->set_lp_view(
407
                                            $this->lp_view_id,
408
                                            $course_id
409
                                        );
410
                                    }
411
                                }
412
                            }
413
                        }
414
                    }
415
                }
416
417
                $this->ordered_items = self::get_flat_ordered_items_list(
418
                    $this->get_id(),
419
                    0,
420
                    $course_id
421
                );
422
                $this->max_ordered_items = 0;
423
                foreach ($this->ordered_items as $index => $dummy) {
424
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
425
                        $this->max_ordered_items = $index;
426
                    }
427
                }
428
                // TODO: Define the current item better.
429
                $this->first();
430
                if ($debug) {
431
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
432
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
433
                }
434
            } else {
435
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
436
            }
437
        }
438
    }
439
440
    /**
441
     * @return string
442
     */
443
    public function getCourseCode()
444
    {
445
        return $this->cc;
446
    }
447
448
    /**
449
     * @return int
450
     */
451
    public function get_course_int_id()
452
    {
453
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
454
    }
455
456
    /**
457
     * @param $course_id
458
     *
459
     * @return int
460
     */
461
    public function set_course_int_id($course_id)
462
    {
463
        return $this->course_int_id = (int) $course_id;
464
    }
465
466
    /**
467
     * Function rewritten based on old_add_item() from Yannick Warnier.
468
     * Due the fact that users can decide where the item should come, I had to overlook this function and
469
     * I found it better to rewrite it. Old function is still available.
470
     * Added also the possibility to add a description.
471
     *
472
     * @param int    $parent
473
     * @param int    $previous
474
     * @param string $type
475
     * @param int    $id               resource ID (ref)
476
     * @param string $title
477
     * @param string $description
478
     * @param int    $prerequisites
479
     * @param int    $max_time_allowed
480
     * @param int    $userId
481
     *
482
     * @return int
483
     */
484
    public function add_item(
485
        $parent,
486
        $previous,
487
        $type,
488
        $id,
489
        $title,
490
        $description,
491
        $prerequisites = 0,
492
        $max_time_allowed = 0,
493
        $userId = 0
494
    ) {
495
        $course_id = $this->course_info['real_id'];
496
        if (empty($course_id)) {
497
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
498
            $this->course_info = api_get_course_info($this->cc);
499
            $course_id = $this->course_info['real_id'];
500
        }
501
        $userId = empty($userId) ? api_get_user_id() : $userId;
502
        $sessionId = api_get_session_id();
503
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
504
        $_course = $this->course_info;
505
        $parent = (int) $parent;
506
        $previous = (int) $previous;
507
        $id = (int) $id;
508
        $max_time_allowed = htmlentities($max_time_allowed);
509
        if (empty($max_time_allowed)) {
510
            $max_time_allowed = 0;
511
        }
512
        $sql = "SELECT COUNT(iid) AS num
513
                FROM $tbl_lp_item
514
                WHERE
515
                    c_id = $course_id AND
516
                    lp_id = ".$this->get_id()." AND
517
                    parent_item_id = $parent ";
518
519
        $res_count = Database::query($sql);
520
        $row = Database::fetch_array($res_count);
521
        $num = $row['num'];
522
523
        $tmp_previous = 0;
524
        $display_order = 0;
525
        $next = 0;
526
        if ($num > 0) {
527
            if (empty($previous)) {
528
                $sql = "SELECT iid, next_item_id, display_order
529
                        FROM $tbl_lp_item
530
                        WHERE
531
                            c_id = $course_id AND
532
                            lp_id = ".$this->get_id()." AND
533
                            parent_item_id = $parent AND
534
                            previous_item_id = 0 OR
535
                            previous_item_id = $parent";
536
                $result = Database::query($sql);
537
                $row = Database::fetch_array($result);
538
                if ($row) {
539
                    $next = $row['iid'];
540
                }
541
            } else {
542
                $previous = (int) $previous;
543
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
544
						FROM $tbl_lp_item
545
                        WHERE
546
                            c_id = $course_id AND
547
                            lp_id = ".$this->get_id()." AND
548
                            id = $previous";
549
                $result = Database::query($sql);
550
                $row = Database::fetch_array($result);
551
                if ($row) {
552
                    $tmp_previous = $row['iid'];
553
                    $next = $row['next_item_id'];
554
                    $display_order = $row['display_order'];
555
                }
556
            }
557
        }
558
559
        $id = (int) $id;
560
        $typeCleaned = Database::escape_string($type);
561
        $max_score = 100;
562
        if ($type === 'quiz' && $id) {
563
            $sql = 'SELECT SUM(ponderation)
564
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
565
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
566
                    ON
567
                        quiz_question.id = quiz_rel_question.question_id AND
568
                        quiz_question.c_id = quiz_rel_question.c_id
569
                    WHERE
570
                        quiz_rel_question.exercice_id = '.$id." AND
571
                        quiz_question.c_id = $course_id AND
572
                        quiz_rel_question.c_id = $course_id ";
573
            $rsQuiz = Database::query($sql);
574
            $max_score = Database::result($rsQuiz, 0, 0);
575
576
            // Disabling the exercise if we add it inside a LP
577
            $exercise = new Exercise($course_id);
578
            $exercise->read($id);
579
            $exercise->disable();
580
            $exercise->save();
581
        }
582
583
        $params = [
584
            'c_id' => $course_id,
585
            'lp_id' => $this->get_id(),
586
            'item_type' => $typeCleaned,
587
            'ref' => '',
588
            'title' => $title,
589
            'description' => $description,
590
            'path' => $id,
591
            'max_score' => $max_score,
592
            'parent_item_id' => $parent,
593
            'previous_item_id' => $previous,
594
            'next_item_id' => (int) $next,
595
            'display_order' => $display_order + 1,
596
            'prerequisite' => $prerequisites,
597
            'max_time_allowed' => $max_time_allowed,
598
            'min_score' => 0,
599
            'launch_data' => '',
600
        ];
601
602
        if ($prerequisites != 0) {
603
            $params['prerequisite'] = $prerequisites;
604
        }
605
606
        $new_item_id = Database::insert($tbl_lp_item, $params);
607
        if ($new_item_id) {
608
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
609
            Database::query($sql);
610
611
            if (!empty($next)) {
612
                $sql = "UPDATE $tbl_lp_item
613
                        SET previous_item_id = $new_item_id
614
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
615
                Database::query($sql);
616
            }
617
618
            // Update the item that should be before the new item.
619
            if (!empty($tmp_previous)) {
620
                $sql = "UPDATE $tbl_lp_item
621
                        SET next_item_id = $new_item_id
622
                        WHERE c_id = $course_id AND id = $tmp_previous";
623
                Database::query($sql);
624
            }
625
626
            // Update all the items after the new item.
627
            $sql = "UPDATE $tbl_lp_item
628
                        SET display_order = display_order + 1
629
                    WHERE
630
                        c_id = $course_id AND
631
                        lp_id = ".$this->get_id()." AND
632
                        iid <> $new_item_id AND
633
                        parent_item_id = $parent AND
634
                        display_order > $display_order";
635
            Database::query($sql);
636
637
            // Update the item that should come after the new item.
638
            $sql = "UPDATE $tbl_lp_item
639
                    SET ref = $new_item_id
640
                    WHERE c_id = $course_id AND iid = $new_item_id";
641
            Database::query($sql);
642
643
            $sql = "UPDATE $tbl_lp_item
644
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
645
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
646
            Database::query($sql);
647
648
            // Upload audio.
649
            if (!empty($_FILES['mp3']['name'])) {
650
                // Create the audio folder if it does not exist yet.
651
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
652
                if (!is_dir($filepath.'audio')) {
653
                    mkdir(
654
                        $filepath.'audio',
655
                        api_get_permissions_for_new_directories()
656
                    );
657
                    $audio_id = add_document(
658
                        $_course,
659
                        '/audio',
660
                        'folder',
661
                        0,
662
                        'audio',
663
                        '',
664
                        0,
665
                        true,
666
                        null,
667
                        $sessionId,
668
                        $userId
669
                    );
670
                    api_item_property_update(
671
                        $_course,
672
                        TOOL_DOCUMENT,
673
                        $audio_id,
674
                        'FolderCreated',
675
                        $userId,
676
                        null,
677
                        null,
678
                        null,
679
                        null,
680
                        $sessionId
681
                    );
682
                    api_item_property_update(
683
                        $_course,
684
                        TOOL_DOCUMENT,
685
                        $audio_id,
686
                        'invisible',
687
                        $userId,
688
                        null,
689
                        null,
690
                        null,
691
                        null,
692
                        $sessionId
693
                    );
694
                }
695
696
                $file_path = handle_uploaded_document(
697
                    $_course,
698
                    $_FILES['mp3'],
699
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
700
                    '/audio',
701
                    $userId,
702
                    '',
703
                    '',
704
                    '',
705
                    '',
706
                    false
707
                );
708
709
                // Store the mp3 file in the lp_item table.
710
                $sql = "UPDATE $tbl_lp_item SET
711
                          audio = '".Database::escape_string($file_path)."'
712
                        WHERE iid = '".intval($new_item_id)."'";
713
                Database::query($sql);
714
            }
715
        }
716
717
        return $new_item_id;
718
    }
719
720
    /**
721
     * Static admin function allowing addition of a learnpath to a course.
722
     *
723
     * @param string $courseCode
724
     * @param string $name
725
     * @param string $description
726
     * @param string $learnpath
727
     * @param string $origin
728
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
729
     * @param string $publicated_on
730
     * @param string $expired_on
731
     * @param int    $categoryId
732
     * @param int    $userId
733
     *
734
     * @return int The new learnpath ID on success, 0 on failure
735
     */
736
    public static function add_lp(
737
        $courseCode,
738
        $name,
739
        $description = '',
740
        $learnpath = 'guess',
741
        $origin = 'zip',
742
        $zipname = '',
743
        $publicated_on = '',
744
        $expired_on = '',
745
        $categoryId = 0,
746
        $userId = 0
747
    ) {
748
        global $charset;
749
750
        if (!empty($courseCode)) {
751
            $courseInfo = api_get_course_info($courseCode);
752
            $course_id = $courseInfo['real_id'];
753
        } else {
754
            $course_id = api_get_course_int_id();
755
            $courseInfo = api_get_course_info();
756
        }
757
758
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
759
        // Check course code exists.
760
        // Check lp_name doesn't exist, otherwise append something.
761
        $i = 0;
762
        $categoryId = (int) $categoryId;
763
        // Session id.
764
        $session_id = api_get_session_id();
765
        $userId = empty($userId) ? api_get_user_id() : $userId;
766
767
        if (empty($publicated_on)) {
768
            $publicated_on = null;
769
        } else {
770
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
771
        }
772
773
        if (empty($expired_on)) {
774
            $expired_on = null;
775
        } else {
776
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
777
        }
778
779
        $check_name = "SELECT * FROM $tbl_lp
780
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
781
        $res_name = Database::query($check_name);
782
783
        while (Database::num_rows($res_name)) {
784
            // There is already one such name, update the current one a bit.
785
            $i++;
786
            $name = $name.' - '.$i;
787
            $check_name = "SELECT * FROM $tbl_lp
788
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
789
            $res_name = Database::query($check_name);
790
        }
791
        // New name does not exist yet; keep it.
792
        // Escape description.
793
        // Kevin: added htmlentities().
794
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
795
        $type = 1;
796
        switch ($learnpath) {
797
            case 'guess':
798
                break;
799
            case 'dokeos':
800
            case 'chamilo':
801
                $type = 1;
802
                break;
803
            case 'aicc':
804
                break;
805
        }
806
807
        switch ($origin) {
808
            case 'zip':
809
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
810
                break;
811
            case 'manual':
812
            default:
813
                $get_max = "SELECT MAX(display_order)
814
                            FROM $tbl_lp WHERE c_id = $course_id";
815
                $res_max = Database::query($get_max);
816
                if (Database::num_rows($res_max) < 1) {
817
                    $dsp = 1;
818
                } else {
819
                    $row = Database::fetch_array($res_max);
820
                    $dsp = $row[0] + 1;
821
                }
822
823
                $params = [
824
                    'c_id' => $course_id,
825
                    'lp_type' => $type,
826
                    'name' => $name,
827
                    'description' => $description,
828
                    'path' => '',
829
                    'default_view_mod' => 'embedded',
830
                    'default_encoding' => 'UTF-8',
831
                    'display_order' => $dsp,
832
                    'content_maker' => 'Chamilo',
833
                    'content_local' => 'local',
834
                    'js_lib' => '',
835
                    'session_id' => $session_id,
836
                    'created_on' => api_get_utc_datetime(),
837
                    'modified_on' => api_get_utc_datetime(),
838
                    'publicated_on' => $publicated_on,
839
                    'expired_on' => $expired_on,
840
                    'category_id' => $categoryId,
841
                    'force_commit' => 0,
842
                    'content_license' => '',
843
                    'debug' => 0,
844
                    'theme' => '',
845
                    'preview_image' => '',
846
                    'author' => '',
847
                    'prerequisite' => 0,
848
                    'hide_toc_frame' => 0,
849
                    'seriousgame_mode' => 0,
850
                    'autolaunch' => 0,
851
                    'max_attempts' => 0,
852
                    'subscribe_users' => 0,
853
                    'accumulate_scorm_time' => 1,
854
                ];
855
                $id = Database::insert($tbl_lp, $params);
856
857
                if ($id > 0) {
858
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
859
                    Database::query($sql);
860
861
                    // Insert into item_property.
862
                    api_item_property_update(
863
                        $courseInfo,
864
                        TOOL_LEARNPATH,
865
                        $id,
866
                        'LearnpathAdded',
867
                        $userId
868
                    );
869
                    api_set_default_visibility(
870
                        $id,
871
                        TOOL_LEARNPATH,
872
                        0,
873
                        $courseInfo,
874
                        $session_id,
875
                        $userId
876
                    );
877
878
                    return $id;
879
                }
880
                break;
881
        }
882
    }
883
884
    /**
885
     * Auto completes the parents of an item in case it's been completed or passed.
886
     *
887
     * @param int $item Optional ID of the item from which to look for parents
888
     */
889
    public function autocomplete_parents($item)
890
    {
891
        $debug = $this->debug;
892
893
        if (empty($item)) {
894
            $item = $this->current;
895
        }
896
897
        $currentItem = $this->getItem($item);
898
        if ($currentItem) {
899
            $parent_id = $currentItem->get_parent();
900
            $parent = $this->getItem($parent_id);
901
            if ($parent) {
902
                // if $item points to an object and there is a parent.
903
                if ($debug) {
904
                    error_log(
905
                        'Autocompleting parent of item '.$item.' '.
906
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
907
                        0
908
                    );
909
                }
910
911
                // New experiment including failed and browsed in completed status.
912
                //$current_status = $currentItem->get_status();
913
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
914
                // Fixes chapter auto complete
915
                if (true) {
916
                    // If the current item is completed or passes or succeeded.
917
                    $updateParentStatus = true;
918
                    if ($debug) {
919
                        error_log('Status of current item is alright');
920
                    }
921
922
                    foreach ($parent->get_children() as $childItemId) {
923
                        $childItem = $this->getItem($childItemId);
924
925
                        // If children was not set try to get the info
926
                        if (empty($childItem->db_item_view_id)) {
927
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
928
                        }
929
930
                        // Check all his brothers (parent's children) for completion status.
931
                        if ($childItemId != $item) {
932
                            if ($debug) {
933
                                error_log(
934
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
935
                                    0
936
                                );
937
                            }
938
                            // Trying completing parents of failed and browsed items as well.
939
                            if ($childItem->status_is(
940
                                [
941
                                    'completed',
942
                                    'passed',
943
                                    'succeeded',
944
                                    'browsed',
945
                                    'failed',
946
                                ]
947
                            )
948
                            ) {
949
                                // Keep completion status to true.
950
                                continue;
951
                            } else {
952
                                if ($debug > 2) {
953
                                    error_log(
954
                                        '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,
955
                                        0
956
                                    );
957
                                }
958
                                $updateParentStatus = false;
959
                                break;
960
                            }
961
                        }
962
                    }
963
964
                    if ($updateParentStatus) {
965
                        // If all the children were completed:
966
                        $parent->set_status('completed');
967
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
968
                        // Force the status to "completed"
969
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
970
                        $this->update_queue[$parent->get_id()] = 'completed';
971
                        if ($debug) {
972
                            error_log(
973
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
974
                                print_r($this->update_queue, 1),
975
                                0
976
                            );
977
                        }
978
                        // Recursive call.
979
                        $this->autocomplete_parents($parent->get_id());
980
                    }
981
                }
982
            } else {
983
                if ($debug) {
984
                    error_log("Parent #$parent_id does not exists");
985
                }
986
            }
987
        } else {
988
            if ($debug) {
989
                error_log("#$item is an item that doesn't have parents");
990
            }
991
        }
992
    }
993
994
    /**
995
     * Closes the current resource.
996
     *
997
     * Stops the timer
998
     * Saves into the database if required
999
     * Clears the current resource data from this object
1000
     *
1001
     * @return bool True on success, false on failure
1002
     */
1003
    public function close()
1004
    {
1005
        if (empty($this->lp_id)) {
1006
            $this->error = 'Trying to close this learnpath but no ID is set';
1007
1008
            return false;
1009
        }
1010
        $this->current_time_stop = time();
1011
        $this->ordered_items = [];
1012
        $this->index = 0;
1013
        unset($this->lp_id);
1014
        //unset other stuff
1015
        return true;
1016
    }
1017
1018
    /**
1019
     * Static admin function allowing removal of a learnpath.
1020
     *
1021
     * @param array  $courseInfo
1022
     * @param int    $id         Learnpath ID
1023
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1024
     *
1025
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1026
     */
1027
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1028
    {
1029
        $course_id = api_get_course_int_id();
1030
        if (!empty($courseInfo)) {
1031
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1032
        }
1033
1034
        // TODO: Implement a way of getting this to work when the current object is not set.
1035
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1036
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1037
        if (!empty($id) && ($id != $this->lp_id)) {
1038
            return false;
1039
        }
1040
1041
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1042
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1043
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1044
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1045
1046
        // Delete lp item id.
1047
        foreach ($this->items as $lpItemId => $dummy) {
1048
            $sql = "DELETE FROM $lp_item_view
1049
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1050
            Database::query($sql);
1051
        }
1052
1053
        // Proposed by Christophe (nickname: clefevre)
1054
        $sql = "DELETE FROM $lp_item
1055
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1056
        Database::query($sql);
1057
1058
        $sql = "DELETE FROM $lp_view
1059
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1060
        Database::query($sql);
1061
1062
        self::toggle_publish($this->lp_id, 'i');
1063
1064
        if ($this->type == 2 || $this->type == 3) {
1065
            // This is a scorm learning path, delete the files as well.
1066
            $sql = "SELECT path FROM $lp
1067
                    WHERE iid = ".$this->lp_id;
1068
            $res = Database::query($sql);
1069
            if (Database::num_rows($res) > 0) {
1070
                $row = Database::fetch_array($res);
1071
                $path = $row['path'];
1072
                $sql = "SELECT id FROM $lp
1073
                        WHERE
1074
                            c_id = $course_id AND
1075
                            path = '$path' AND
1076
                            iid != ".$this->lp_id;
1077
                $res = Database::query($sql);
1078
                if (Database::num_rows($res) > 0) {
1079
                    // Another learning path uses this directory, so don't delete it.
1080
                    if ($this->debug > 2) {
1081
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1082
                    }
1083
                } else {
1084
                    // No other LP uses that directory, delete it.
1085
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1086
                    // The absolute system path for this course.
1087
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1088
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1089
                        if ($this->debug > 2) {
1090
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1091
                        }
1092
                        // Proposed by Christophe (clefevre).
1093
                        if (strcmp(substr($path, -2), "/.") == 0) {
1094
                            $path = substr($path, 0, -1); // Remove "." at the end.
1095
                        }
1096
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1097
                        rmdirr($course_scorm_dir.$path);
1098
                    }
1099
                }
1100
            }
1101
        }
1102
1103
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1104
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1105
        // Delete tools
1106
        $sql = "DELETE FROM $tbl_tool
1107
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1108
        Database::query($sql);
1109
1110
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1111
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1112
            $sql = "DELETE FROM $table
1113
                    WHERE
1114
                        lp_id = {$this->lp_id} AND
1115
                        c_id = $course_id ";
1116
            Database::query($sql);
1117
        }
1118
1119
        $sql = "DELETE FROM $lp
1120
                WHERE iid = ".$this->lp_id;
1121
        Database::query($sql);
1122
        // Updates the display order of all lps.
1123
        $this->update_display_order();
1124
1125
        api_item_property_update(
1126
            api_get_course_info(),
1127
            TOOL_LEARNPATH,
1128
            $this->lp_id,
1129
            'delete',
1130
            api_get_user_id()
1131
        );
1132
1133
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1134
            api_get_course_id(),
1135
            4,
1136
            $id,
1137
            api_get_session_id()
1138
        );
1139
1140
        if ($link_info !== false) {
1141
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1142
        }
1143
1144
        if (api_get_setting('search_enabled') == 'true') {
1145
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1146
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1147
        }
1148
    }
1149
1150
    /**
1151
     * Removes all the children of one item - dangerous!
1152
     *
1153
     * @param int $id Element ID of which children have to be removed
1154
     *
1155
     * @return int Total number of children removed
1156
     */
1157
    public function delete_children_items($id)
1158
    {
1159
        $course_id = $this->course_info['real_id'];
1160
1161
        $num = 0;
1162
        $id = (int) $id;
1163
        if (empty($id) || empty($course_id)) {
1164
            return false;
1165
        }
1166
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1167
        $sql = "SELECT * FROM $lp_item
1168
                WHERE c_id = $course_id AND parent_item_id = $id";
1169
        $res = Database::query($sql);
1170
        while ($row = Database::fetch_array($res)) {
1171
            $num += $this->delete_children_items($row['iid']);
1172
            $sql = "DELETE FROM $lp_item
1173
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1174
            Database::query($sql);
1175
            $num++;
1176
        }
1177
1178
        return $num;
1179
    }
1180
1181
    /**
1182
     * Removes an item from the current learnpath.
1183
     *
1184
     * @param int $id Elem ID (0 if first)
1185
     *
1186
     * @return int Number of elements moved
1187
     *
1188
     * @todo implement resource removal
1189
     */
1190
    public function delete_item($id)
1191
    {
1192
        $course_id = api_get_course_int_id();
1193
        $id = (int) $id;
1194
        // TODO: Implement the resource removal.
1195
        if (empty($id) || empty($course_id)) {
1196
            return false;
1197
        }
1198
        // First select item to get previous, next, and display order.
1199
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1200
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1201
        $res_sel = Database::query($sql_sel);
1202
        if (Database::num_rows($res_sel) < 1) {
1203
            return false;
1204
        }
1205
        $row = Database::fetch_array($res_sel);
1206
        $previous = $row['previous_item_id'];
1207
        $next = $row['next_item_id'];
1208
        $display = $row['display_order'];
1209
        $parent = $row['parent_item_id'];
1210
        $lp = $row['lp_id'];
1211
        // Delete children items.
1212
        $this->delete_children_items($id);
1213
        // Now delete the item.
1214
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1215
        Database::query($sql_del);
1216
        // Now update surrounding items.
1217
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1218
                    WHERE iid = $previous";
1219
        Database::query($sql_upd);
1220
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1221
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1222
        Database::query($sql_upd);
1223
        // Now update all following items with new display order.
1224
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1225
                    WHERE
1226
                        c_id = $course_id AND
1227
                        lp_id = $lp AND
1228
                        parent_item_id = $parent AND
1229
                        display_order > $display";
1230
        Database::query($sql_all);
1231
1232
        //Removing prerequisites since the item will not longer exist
1233
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1234
                    WHERE c_id = $course_id AND prerequisite = $id";
1235
        Database::query($sql_all);
1236
1237
        $sql = "UPDATE $lp_item
1238
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1239
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1240
        Database::query($sql);
1241
1242
        // Remove from search engine if enabled.
1243
        if (api_get_setting('search_enabled') === 'true') {
1244
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1245
            $sql = 'SELECT * FROM %s
1246
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1247
                    LIMIT 1';
1248
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1249
            $res = Database::query($sql);
1250
            if (Database::num_rows($res) > 0) {
1251
                $row2 = Database::fetch_array($res);
1252
                $di = new ChamiloIndexer();
1253
                $di->remove_document($row2['search_did']);
1254
            }
1255
            $sql = 'DELETE FROM %s
1256
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1257
                    LIMIT 1';
1258
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1259
            Database::query($sql);
1260
        }
1261
    }
1262
1263
    /**
1264
     * Updates an item's content in place.
1265
     *
1266
     * @param int    $id               Element ID
1267
     * @param int    $parent           Parent item ID
1268
     * @param int    $previous         Previous item ID
1269
     * @param string $title            Item title
1270
     * @param string $description      Item description
1271
     * @param string $prerequisites    Prerequisites (optional)
1272
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1273
     * @param int    $max_time_allowed
1274
     * @param string $url
1275
     *
1276
     * @return bool True on success, false on error
1277
     */
1278
    public function edit_item(
1279
        $id,
1280
        $parent,
1281
        $previous,
1282
        $title,
1283
        $description,
1284
        $prerequisites = '0',
1285
        $audio = [],
1286
        $max_time_allowed = 0,
1287
        $url = ''
1288
    ) {
1289
        $course_id = api_get_course_int_id();
1290
        $_course = api_get_course_info();
1291
        $id = (int) $id;
1292
1293
        if (empty($max_time_allowed)) {
1294
            $max_time_allowed = 0;
1295
        }
1296
1297
        if (empty($id) || empty($_course)) {
1298
            return false;
1299
        }
1300
1301
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1302
        $sql = "SELECT * FROM $tbl_lp_item
1303
                WHERE iid = $id";
1304
        $res_select = Database::query($sql);
1305
        $row_select = Database::fetch_array($res_select);
1306
        $audio_update_sql = '';
1307
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1308
            // Create the audio folder if it does not exist yet.
1309
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1310
            if (!is_dir($filepath.'audio')) {
1311
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1312
                $audio_id = add_document(
1313
                    $_course,
1314
                    '/audio',
1315
                    'folder',
1316
                    0,
1317
                    'audio'
1318
                );
1319
                api_item_property_update(
1320
                    $_course,
1321
                    TOOL_DOCUMENT,
1322
                    $audio_id,
1323
                    'FolderCreated',
1324
                    api_get_user_id(),
1325
                    null,
1326
                    null,
1327
                    null,
1328
                    null,
1329
                    api_get_session_id()
1330
                );
1331
                api_item_property_update(
1332
                    $_course,
1333
                    TOOL_DOCUMENT,
1334
                    $audio_id,
1335
                    'invisible',
1336
                    api_get_user_id(),
1337
                    null,
1338
                    null,
1339
                    null,
1340
                    null,
1341
                    api_get_session_id()
1342
                );
1343
            }
1344
1345
            // Upload file in documents.
1346
            $pi = pathinfo($audio['name']);
1347
            if ($pi['extension'] === 'mp3') {
1348
                $c_det = api_get_course_info($this->cc);
1349
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1350
                $path = handle_uploaded_document(
1351
                    $c_det,
1352
                    $audio,
1353
                    $bp,
1354
                    '/audio',
1355
                    api_get_user_id(),
1356
                    0,
1357
                    null,
1358
                    0,
1359
                    'rename',
1360
                    false,
1361
                    0
1362
                );
1363
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1364
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1365
            }
1366
        }
1367
1368
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1369
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1370
1371
        // TODO: htmlspecialchars to be checked for encoding related problems.
1372
        if ($same_parent && $same_previous) {
1373
            // Only update title and description.
1374
            $sql = "UPDATE $tbl_lp_item
1375
                    SET title = '".Database::escape_string($title)."',
1376
                        prerequisite = '".$prerequisites."',
1377
                        description = '".Database::escape_string($description)."'
1378
                        ".$audio_update_sql.",
1379
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1380
                    WHERE iid = $id";
1381
            Database::query($sql);
1382
        } else {
1383
            $old_parent = $row_select['parent_item_id'];
1384
            $old_previous = $row_select['previous_item_id'];
1385
            $old_next = $row_select['next_item_id'];
1386
            $old_order = $row_select['display_order'];
1387
            $old_prerequisite = $row_select['prerequisite'];
1388
            $old_max_time_allowed = $row_select['max_time_allowed'];
1389
1390
            /* BEGIN -- virtually remove the current item id */
1391
            /* for the next and previous item it is like the current item doesn't exist anymore */
1392
            if ($old_previous != 0) {
1393
                // Next
1394
                $sql = "UPDATE $tbl_lp_item
1395
                        SET next_item_id = $old_next
1396
                        WHERE iid = $old_previous";
1397
                Database::query($sql);
1398
            }
1399
1400
            if (!empty($old_next)) {
1401
                // Previous
1402
                $sql = "UPDATE $tbl_lp_item
1403
                        SET previous_item_id = $old_previous
1404
                        WHERE iid = $old_next";
1405
                Database::query($sql);
1406
            }
1407
1408
            // display_order - 1 for every item with a display_order
1409
            // bigger then the display_order of the current item.
1410
            $sql = "UPDATE $tbl_lp_item
1411
                    SET display_order = display_order - 1
1412
                    WHERE
1413
                        c_id = $course_id AND
1414
                        display_order > $old_order AND
1415
                        lp_id = ".$this->lp_id." AND
1416
                        parent_item_id = $old_parent";
1417
            Database::query($sql);
1418
            /* END -- virtually remove the current item id */
1419
1420
            /* BEGIN -- update the current item id to his new location */
1421
            if ($previous == 0) {
1422
                // Select the data of the item that should come after the current item.
1423
                $sql = "SELECT id, display_order
1424
                        FROM $tbl_lp_item
1425
                        WHERE
1426
                            c_id = $course_id AND
1427
                            lp_id = ".$this->lp_id." AND
1428
                            parent_item_id = $parent AND
1429
                            previous_item_id = $previous";
1430
                $res_select_old = Database::query($sql);
1431
                $row_select_old = Database::fetch_array($res_select_old);
1432
1433
                // If the new parent didn't have children before.
1434
                if (Database::num_rows($res_select_old) == 0) {
1435
                    $new_next = 0;
1436
                    $new_order = 1;
1437
                } else {
1438
                    $new_next = $row_select_old['id'];
1439
                    $new_order = $row_select_old['display_order'];
1440
                }
1441
            } else {
1442
                // Select the data of the item that should come before the current item.
1443
                $sql = "SELECT next_item_id, display_order
1444
                        FROM $tbl_lp_item
1445
                        WHERE iid = $previous";
1446
                $res_select_old = Database::query($sql);
1447
                $row_select_old = Database::fetch_array($res_select_old);
1448
                $new_next = $row_select_old['next_item_id'];
1449
                $new_order = $row_select_old['display_order'] + 1;
1450
            }
1451
1452
            // TODO: htmlspecialchars to be checked for encoding related problems.
1453
            // Update the current item with the new data.
1454
            $sql = "UPDATE $tbl_lp_item
1455
                    SET
1456
                        title = '".Database::escape_string($title)."',
1457
                        description = '".Database::escape_string($description)."',
1458
                        parent_item_id = $parent,
1459
                        previous_item_id = $previous,
1460
                        next_item_id = $new_next,
1461
                        display_order = $new_order
1462
                        $audio_update_sql
1463
                    WHERE iid = $id";
1464
            Database::query($sql);
1465
1466
            if ($previous != 0) {
1467
                // Update the previous item's next_item_id.
1468
                $sql = "UPDATE $tbl_lp_item
1469
                        SET next_item_id = $id
1470
                        WHERE iid = $previous";
1471
                Database::query($sql);
1472
            }
1473
1474
            if (!empty($new_next)) {
1475
                // Update the next item's previous_item_id.
1476
                $sql = "UPDATE $tbl_lp_item
1477
                        SET previous_item_id = $id
1478
                        WHERE iid = $new_next";
1479
                Database::query($sql);
1480
            }
1481
1482
            if ($old_prerequisite != $prerequisites) {
1483
                $sql = "UPDATE $tbl_lp_item
1484
                        SET prerequisite = '$prerequisites'
1485
                        WHERE iid = $id";
1486
                Database::query($sql);
1487
            }
1488
1489
            if ($old_max_time_allowed != $max_time_allowed) {
1490
                // update max time allowed
1491
                $sql = "UPDATE $tbl_lp_item
1492
                        SET max_time_allowed = $max_time_allowed
1493
                        WHERE iid = $id";
1494
                Database::query($sql);
1495
            }
1496
1497
            // Update all the items with the same or a bigger display_order than the current item.
1498
            $sql = "UPDATE $tbl_lp_item
1499
                    SET display_order = display_order + 1
1500
                    WHERE
1501
                       c_id = $course_id AND
1502
                       lp_id = ".$this->get_id()." AND
1503
                       iid <> $id AND
1504
                       parent_item_id = $parent AND
1505
                       display_order >= $new_order";
1506
            Database::query($sql);
1507
        }
1508
1509
        if ($row_select['item_type'] == 'link') {
1510
            $link = new Link();
1511
            $linkId = $row_select['path'];
1512
            $link->updateLink($linkId, $url);
1513
        }
1514
    }
1515
1516
    /**
1517
     * Updates an item's prereq in place.
1518
     *
1519
     * @param int    $id              Element ID
1520
     * @param string $prerequisite_id Prerequisite Element ID
1521
     * @param int    $minScore        Prerequisite min score
1522
     * @param int    $maxScore        Prerequisite max score
1523
     *
1524
     * @return bool True on success, false on error
1525
     */
1526
    public function edit_item_prereq(
1527
        $id,
1528
        $prerequisite_id,
1529
        $minScore = 0,
1530
        $maxScore = 100
1531
    ) {
1532
        $id = (int) $id;
1533
1534
        if (empty($id)) {
1535
            return false;
1536
        }
1537
1538
        $prerequisite_id = (int) $prerequisite_id;
1539
1540
        if (empty($minScore) || $minScore < 0) {
1541
            $minScore = 0;
1542
        }
1543
1544
        if (empty($maxScore) || $maxScore < 0) {
1545
            $maxScore = 100;
1546
        }
1547
1548
        $minScore = (float) $minScore;
1549
        $maxScore = (float) $maxScore;
1550
1551
        if (empty($prerequisite_id)) {
1552
            $prerequisite_id = 'NULL';
1553
            $minScore = 0;
1554
            $maxScore = 100;
1555
        }
1556
1557
        $table = Database::get_course_table(TABLE_LP_ITEM);
1558
        $sql = " UPDATE $table
1559
                 SET
1560
                    prerequisite = $prerequisite_id ,
1561
                    prerequisite_min_score = $minScore ,
1562
                    prerequisite_max_score = $maxScore
1563
                 WHERE iid = $id";
1564
        Database::query($sql);
1565
1566
        return true;
1567
    }
1568
1569
    /**
1570
     * Get the specific prefix index terms of this learning path.
1571
     *
1572
     * @param string $prefix
1573
     *
1574
     * @return array Array of terms
1575
     */
1576
    public function get_common_index_terms_by_prefix($prefix)
1577
    {
1578
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1579
        $terms = get_specific_field_values_list_by_prefix(
1580
            $prefix,
1581
            $this->cc,
1582
            TOOL_LEARNPATH,
1583
            $this->lp_id
1584
        );
1585
        $prefix_terms = [];
1586
        if (!empty($terms)) {
1587
            foreach ($terms as $term) {
1588
                $prefix_terms[] = $term['value'];
1589
            }
1590
        }
1591
1592
        return $prefix_terms;
1593
    }
1594
1595
    /**
1596
     * Gets the number of items currently completed.
1597
     *
1598
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1599
     *
1600
     * @return int The number of items currently completed
1601
     */
1602
    public function get_complete_items_count($failedStatusException = false)
1603
    {
1604
        $i = 0;
1605
        $completedStatusList = [
1606
            'completed',
1607
            'passed',
1608
            'succeeded',
1609
            'browsed',
1610
        ];
1611
1612
        if (!$failedStatusException) {
1613
            $completedStatusList[] = 'failed';
1614
        }
1615
1616
        if ($this->debug) {
1617
            error_log('START - get_complete_items_count');
1618
            error_log('Counting steps with status in: '.print_r($completedStatusList, 1));
1619
        }
1620
1621
        foreach ($this->items as $id => $dummy) {
1622
            // Trying failed and browsed considered "progressed" as well.
1623
            if ($this->items[$id]->status_is($completedStatusList) &&
1624
                $this->items[$id]->get_type() !== 'dir'
1625
            ) {
1626
                $i++;
1627
            }
1628
        }
1629
1630
        if ($this->debug) {
1631
            error_log('END - Count: '.$i);
1632
        }
1633
1634
        return $i;
1635
    }
1636
1637
    /**
1638
     * Gets the current item ID.
1639
     *
1640
     * @return int The current learnpath item id
1641
     */
1642
    public function get_current_item_id()
1643
    {
1644
        $current = 0;
1645
        if (!empty($this->current)) {
1646
            $current = (int) $this->current;
1647
        }
1648
1649
        return $current;
1650
    }
1651
1652
    /**
1653
     * Force to get the first learnpath item id.
1654
     *
1655
     * @return int The current learnpath item id
1656
     */
1657
    public function get_first_item_id()
1658
    {
1659
        $current = 0;
1660
        if (is_array($this->ordered_items)) {
1661
            $current = $this->ordered_items[0];
1662
        }
1663
1664
        return $current;
1665
    }
1666
1667
    /**
1668
     * Gets the total number of items available for viewing in this SCORM.
1669
     *
1670
     * @return int The total number of items
1671
     */
1672
    public function get_total_items_count()
1673
    {
1674
        return count($this->items);
1675
    }
1676
1677
    /**
1678
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1679
     *
1680
     * @return int The total no-chapters number of items
1681
     */
1682
    public function getTotalItemsCountWithoutDirs()
1683
    {
1684
        $total = 0;
1685
        $typeListNotToCount = self::getChapterTypes();
1686
        foreach ($this->items as $temp2) {
1687
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1688
                $total++;
1689
            }
1690
        }
1691
1692
        return $total;
1693
    }
1694
1695
    /**
1696
     *  Sets the first element URL.
1697
     */
1698
    public function first()
1699
    {
1700
        if ($this->debug > 0) {
1701
            error_log('In learnpath::first()', 0);
1702
            error_log('$this->last_item_seen '.$this->last_item_seen);
1703
        }
1704
1705
        // Test if the last_item_seen exists and is not a dir.
1706
        if (count($this->ordered_items) == 0) {
1707
            $this->index = 0;
1708
        }
1709
1710
        if (!empty($this->last_item_seen) &&
1711
            !empty($this->items[$this->last_item_seen]) &&
1712
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1713
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1714
            //&& !$this->items[$this->last_item_seen]->is_done()
1715
        ) {
1716
            if ($this->debug > 2) {
1717
                error_log(
1718
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1719
                    $this->items[$this->last_item_seen]->get_type()
1720
                );
1721
            }
1722
            $index = -1;
1723
            foreach ($this->ordered_items as $myindex => $item_id) {
1724
                if ($item_id == $this->last_item_seen) {
1725
                    $index = $myindex;
1726
                    break;
1727
                }
1728
            }
1729
            if ($index == -1) {
1730
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1731
                if ($this->debug > 2) {
1732
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1733
                }
1734
1735
                return false;
1736
            } else {
1737
                $this->last = $this->last_item_seen;
1738
                $this->current = $this->last_item_seen;
1739
                $this->index = $index;
1740
            }
1741
        } else {
1742
            if ($this->debug > 2) {
1743
                error_log('In learnpath::first() - No last item seen', 0);
1744
            }
1745
            $index = 0;
1746
            // Loop through all ordered items and stop at the first item that is
1747
            // not a directory *and* that has not been completed yet.
1748
            while (!empty($this->ordered_items[$index]) &&
1749
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1750
                (
1751
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1752
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1753
                ) && $index < $this->max_ordered_items) {
1754
                $index++;
1755
            }
1756
1757
            $this->last = $this->current;
1758
            // current is
1759
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1760
            $this->index = $index;
1761
            if ($this->debug > 2) {
1762
                error_log('$index '.$index);
1763
                error_log('In learnpath::first() - No last item seen');
1764
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1765
            }
1766
        }
1767
        if ($this->debug > 2) {
1768
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1769
        }
1770
    }
1771
1772
    /**
1773
     * Gets the js library from the database.
1774
     *
1775
     * @return string The name of the javascript library to be used
1776
     */
1777
    public function get_js_lib()
1778
    {
1779
        $lib = '';
1780
        if (!empty($this->js_lib)) {
1781
            $lib = $this->js_lib;
1782
        }
1783
1784
        return $lib;
1785
    }
1786
1787
    /**
1788
     * Gets the learnpath database ID.
1789
     *
1790
     * @return int Learnpath ID in the lp table
1791
     */
1792
    public function get_id()
1793
    {
1794
        if (!empty($this->lp_id)) {
1795
            return (int) $this->lp_id;
1796
        }
1797
1798
        return 0;
1799
    }
1800
1801
    /**
1802
     * Gets the last element URL.
1803
     *
1804
     * @return string URL to load into the viewer
1805
     */
1806
    public function get_last()
1807
    {
1808
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1809
        if (count($this->ordered_items) > 0) {
1810
            $this->index = count($this->ordered_items) - 1;
1811
1812
            return $this->ordered_items[$this->index];
1813
        }
1814
1815
        return false;
1816
    }
1817
1818
    /**
1819
     * Get the last element in the first level.
1820
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1821
     *
1822
     * @return mixed
1823
     */
1824
    public function getLastInFirstLevel()
1825
    {
1826
        try {
1827
            $lastId = Database::getManager()
1828
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1829
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1830
                ->setMaxResults(1)
1831
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1832
                ->getSingleScalarResult();
1833
1834
            return $lastId;
1835
        } catch (Exception $exception) {
1836
            return 0;
1837
        }
1838
    }
1839
1840
    /**
1841
     * Get the learning path name by id.
1842
     *
1843
     * @param int $lpId
1844
     *
1845
     * @return mixed
1846
     */
1847
    public static function getLpNameById($lpId)
1848
    {
1849
        $em = Database::getManager();
1850
1851
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1852
            WHERE clp.iid = :iid')
1853
            ->setParameter('iid', $lpId)
1854
            ->getSingleScalarResult();
1855
    }
1856
1857
    /**
1858
     * Gets the navigation bar for the learnpath display screen.
1859
     *
1860
     * @param string $barId
1861
     *
1862
     * @return string The HTML string to use as a navigation bar
1863
     */
1864
    public function get_navigation_bar($barId = '')
1865
    {
1866
        if (empty($barId)) {
1867
            $barId = 'control-top';
1868
        }
1869
        $lpId = $this->lp_id;
1870
        $mycurrentitemid = $this->get_current_item_id();
1871
1872
        $reportingText = get_lang('Reporting');
1873
        $previousText = get_lang('ScormPrevious');
1874
        $nextText = get_lang('ScormNext');
1875
        $fullScreenText = get_lang('ScormExitFullScreen');
1876
1877
        $settings = api_get_configuration_value('lp_view_settings');
1878
        $display = isset($settings['display']) ? $settings['display'] : false;
1879
        $reportingIcon = '
1880
            <a class="icon-toolbar"
1881
                id="stats_link"
1882
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1883
                onclick="window.parent.API.save_asset(); return true;"
1884
                target="content_name" title="'.$reportingText.'">
1885
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1886
            </a>';
1887
1888
        if (!empty($display)) {
1889
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1890
            if ($showReporting === false) {
1891
                $reportingIcon = '';
1892
            }
1893
        }
1894
1895
        $hideArrows = false;
1896
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1897
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1898
        }
1899
1900
        $previousIcon = '';
1901
        $nextIcon = '';
1902
        if ($hideArrows === false) {
1903
            $previousIcon = '
1904
                <a class="icon-toolbar" id="scorm-previous" href="#"
1905
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1906
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1907
                </a>';
1908
1909
            $nextIcon = '
1910
                <a class="icon-toolbar" id="scorm-next" href="#"
1911
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1912
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1913
                </a>';
1914
        }
1915
1916
        if ($this->mode === 'fullscreen') {
1917
            $navbar = '
1918
                  <span id="'.$barId.'" class="buttons">
1919
                    '.$reportingIcon.'
1920
                    '.$previousIcon.'
1921
                    '.$nextIcon.'
1922
                    <a class="icon-toolbar" id="view-embedded"
1923
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1924
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1925
                    </a>
1926
                  </span>';
1927
        } else {
1928
            $navbar = '
1929
                 <span id="'.$barId.'" class="buttons text-right">
1930
                    '.$reportingIcon.'
1931
                    '.$previousIcon.'
1932
                    '.$nextIcon.'
1933
                </span>';
1934
        }
1935
1936
        return $navbar;
1937
    }
1938
1939
    /**
1940
     * Gets the next resource in queue (url).
1941
     *
1942
     * @return string URL to load into the viewer
1943
     */
1944
    public function get_next_index()
1945
    {
1946
        // TODO
1947
        $index = $this->index;
1948
        $index++;
1949
        while (
1950
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1951
            $index < $this->max_ordered_items
1952
        ) {
1953
            $index++;
1954
            if ($index == $this->max_ordered_items) {
1955
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1956
                    return $this->index;
1957
                }
1958
1959
                return $index;
1960
            }
1961
        }
1962
        if (empty($this->ordered_items[$index])) {
1963
            return $this->index;
1964
        }
1965
1966
        return $index;
1967
    }
1968
1969
    /**
1970
     * Gets item_id for the next element.
1971
     *
1972
     * @return int Next item (DB) ID
1973
     */
1974
    public function get_next_item_id()
1975
    {
1976
        $new_index = $this->get_next_index();
1977
        if (!empty($new_index)) {
1978
            if (isset($this->ordered_items[$new_index])) {
1979
                return $this->ordered_items[$new_index];
1980
            }
1981
        }
1982
1983
        return 0;
1984
    }
1985
1986
    /**
1987
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1988
     *
1989
     * Generally, the package provided is in the form of a zip file, so the function
1990
     * has been written to test a zip file. If not a zip, the function will return the
1991
     * default return value: ''
1992
     *
1993
     * @param string $file_path the path to the file
1994
     * @param string $file_name the original name of the file
1995
     *
1996
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1997
     */
1998
    public static function getPackageType($file_path, $file_name)
1999
    {
2000
        // Get name of the zip file without the extension.
2001
        $file_info = pathinfo($file_name);
2002
        $extension = $file_info['extension']; // Extension only.
2003
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2004
                'dll',
2005
                'exe',
2006
            ])) {
2007
            return 'oogie';
2008
        }
2009
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2010
                'dll',
2011
                'exe',
2012
            ])) {
2013
            return 'woogie';
2014
        }
2015
2016
        $zipFile = new PclZip($file_path);
2017
        // Check the zip content (real size and file extension).
2018
        $zipContentArray = $zipFile->listContent();
2019
        $package_type = '';
2020
        $manifest = '';
2021
        $aicc_match_crs = 0;
2022
        $aicc_match_au = 0;
2023
        $aicc_match_des = 0;
2024
        $aicc_match_cst = 0;
2025
        $countItems = 0;
2026
2027
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2028
        if (is_array($zipContentArray)) {
2029
            $countItems = count($zipContentArray);
2030
            if ($countItems > 0) {
2031
                foreach ($zipContentArray as $thisContent) {
2032
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2033
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2034
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2035
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2036
                        $package_type = 'scorm';
2037
                        break; // Exit the foreach loop.
2038
                    } elseif (
2039
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2040
                        in_array(
2041
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2042
                            ['crs', 'au', 'des', 'cst']
2043
                        )
2044
                    ) {
2045
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2046
                        switch ($ext) {
2047
                            case 'crs':
2048
                                $aicc_match_crs = 1;
2049
                                break;
2050
                            case 'au':
2051
                                $aicc_match_au = 1;
2052
                                break;
2053
                            case 'des':
2054
                                $aicc_match_des = 1;
2055
                                break;
2056
                            case 'cst':
2057
                                $aicc_match_cst = 1;
2058
                                break;
2059
                            default:
2060
                                break;
2061
                        }
2062
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2063
                    } else {
2064
                        $package_type = '';
2065
                    }
2066
                }
2067
            }
2068
        }
2069
2070
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2071
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2072
            $package_type = 'aicc';
2073
        }
2074
2075
        // Try with chamilo course builder
2076
        if (empty($package_type)) {
2077
            // Sometimes users will try to upload an empty zip, or a zip with
2078
            // only a folder. Catch that and make the calling function aware.
2079
            // If the single file was the imsmanifest.xml, then $package_type
2080
            // would be 'scorm' and we wouldn't be here.
2081
            if ($countItems < 2) {
2082
                return 'error-empty-package';
2083
            }
2084
            $package_type = 'chamilo';
2085
        }
2086
2087
        return $package_type;
2088
    }
2089
2090
    /**
2091
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2092
     *
2093
     * @return string URL to load into the viewer
2094
     */
2095
    public function get_previous_index()
2096
    {
2097
        $index = $this->index;
2098
        if (isset($this->ordered_items[$index - 1])) {
2099
            $index--;
2100
            while (isset($this->ordered_items[$index]) &&
2101
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2102
            ) {
2103
                $index--;
2104
                if ($index < 0) {
2105
                    return $this->index;
2106
                }
2107
            }
2108
        }
2109
2110
        return $index;
2111
    }
2112
2113
    /**
2114
     * Gets item_id for the next element.
2115
     *
2116
     * @return int Previous item (DB) ID
2117
     */
2118
    public function get_previous_item_id()
2119
    {
2120
        $index = $this->get_previous_index();
2121
2122
        return $this->ordered_items[$index];
2123
    }
2124
2125
    /**
2126
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2127
     *
2128
     * @param int    $lpItemId
2129
     * @param string $autostart
2130
     *
2131
     * @return string The mediaplayer HTML
2132
     */
2133
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2134
    {
2135
        $course_id = api_get_course_int_id();
2136
        $courseInfo = api_get_course_info();
2137
        $lpItemId = (int) $lpItemId;
2138
2139
        if (empty($courseInfo) || empty($lpItemId)) {
2140
            return '';
2141
        }
2142
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2143
2144
        if (empty($item)) {
2145
            return '';
2146
        }
2147
2148
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2149
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2150
        $itemViewId = (int) $item->db_item_view_id;
2151
2152
        // Getting all the information about the item.
2153
        $sql = "SELECT lp_view.status
2154
                FROM $tbl_lp_item as lpi
2155
                INNER JOIN $tbl_lp_item_view as lp_view
2156
                ON (lpi.iid = lp_view.lp_item_id)
2157
                WHERE
2158
                    lp_view.iid = $itemViewId AND
2159
                    lpi.iid = $lpItemId AND
2160
                    lp_view.c_id = $course_id";
2161
        $result = Database::query($sql);
2162
        $row = Database::fetch_assoc($result);
2163
        $output = '';
2164
        $audio = $item->audio;
2165
2166
        if (!empty($audio)) {
2167
            $list = $_SESSION['oLP']->get_toc();
2168
2169
            switch ($item->get_type()) {
2170
                case 'quiz':
2171
                    $type_quiz = false;
2172
                    foreach ($list as $toc) {
2173
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2174
                            $type_quiz = true;
2175
                        }
2176
                    }
2177
2178
                    if ($type_quiz) {
2179
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2180
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2181
                        } else {
2182
                            $autostart_audio = $autostart;
2183
                        }
2184
                    }
2185
                    break;
2186
                case TOOL_READOUT_TEXT:
2187
                    $autostart_audio = 'false';
2188
                    break;
2189
                default:
2190
                    $autostart_audio = 'true';
2191
            }
2192
2193
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2194
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2195
2196
            $player = Display::getMediaPlayer(
2197
                $file,
2198
                [
2199
                    'id' => 'lp_audio_media_player',
2200
                    'url' => $url,
2201
                    'autoplay' => $autostart_audio,
2202
                    'width' => '100%',
2203
                ]
2204
            );
2205
2206
            // The mp3 player.
2207
            $output = '<div id="container">';
2208
            $output .= $player;
2209
            $output .= '</div>';
2210
        }
2211
2212
        return $output;
2213
    }
2214
2215
    /**
2216
     * @param int   $studentId
2217
     * @param int   $prerequisite
2218
     * @param array $courseInfo
2219
     * @param int   $sessionId
2220
     *
2221
     * @return bool
2222
     */
2223
    public static function isBlockedByPrerequisite(
2224
        $studentId,
2225
        $prerequisite,
2226
        $courseInfo,
2227
        $sessionId
2228
    ) {
2229
        if (empty($courseInfo)) {
2230
            return false;
2231
        }
2232
2233
        $courseId = $courseInfo['real_id'];
2234
2235
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2236
        if ($allow) {
2237
            if (api_is_allowed_to_edit() ||
2238
                api_is_platform_admin(true) ||
2239
                api_is_drh() ||
2240
                api_is_coach($sessionId, $courseId, false)
2241
            ) {
2242
                return false;
2243
            }
2244
        }
2245
2246
        $isBlocked = false;
2247
        if (!empty($prerequisite)) {
2248
            $progress = self::getProgress(
2249
                $prerequisite,
2250
                $studentId,
2251
                $courseId,
2252
                $sessionId
2253
            );
2254
            if ($progress < 100) {
2255
                $isBlocked = true;
2256
            }
2257
2258
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2259
                // Block if it does not exceed minimum time
2260
                // Minimum time (in minutes) to pass the learning path
2261
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2262
2263
                if ($accumulateWorkTime > 0) {
2264
                    // Total time in course (sum of times in learning paths from course)
2265
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2266
2267
                    // Connect with the plugin_licences_course_session table
2268
                    // which indicates what percentage of the time applies
2269
                    // Minimum connection percentage
2270
                    $perc = 100;
2271
                    // Time from the course
2272
                    $tc = $accumulateWorkTimeTotal;
2273
2274
                    // Percentage of the learning paths
2275
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2276
                    // Minimum time for each learning path
2277
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2278
2279
                    // Spent time (in seconds) so far in the learning path
2280
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2281
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2282
2283
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2284
                        $isBlocked = true;
2285
                    }
2286
                }
2287
            }
2288
        }
2289
2290
        return $isBlocked;
2291
    }
2292
2293
    /**
2294
     * Checks if the learning path is visible for student after the progress
2295
     * of its prerequisite is completed, considering the time availability and
2296
     * the LP visibility.
2297
     *
2298
     * @param int   $lp_id
2299
     * @param int   $student_id
2300
     * @param array $courseInfo
2301
     * @param int   $sessionId
2302
     *
2303
     * @return bool
2304
     */
2305
    public static function is_lp_visible_for_student(
2306
        $lp_id,
2307
        $student_id,
2308
        $courseInfo = [],
2309
        $sessionId = 0
2310
    ) {
2311
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2312
        $lp_id = (int) $lp_id;
2313
        $sessionId = (int) $sessionId;
2314
2315
        if (empty($courseInfo)) {
2316
            return false;
2317
        }
2318
2319
        if (empty($sessionId)) {
2320
            $sessionId = api_get_session_id();
2321
        }
2322
2323
        $courseId = $courseInfo['real_id'];
2324
2325
        $itemInfo = api_get_item_property_info(
2326
            $courseId,
2327
            TOOL_LEARNPATH,
2328
            $lp_id,
2329
            $sessionId
2330
        );
2331
2332
        // If the item was deleted.
2333
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2334
            return false;
2335
        }
2336
2337
        // @todo remove this query and load the row info as a parameter
2338
        $table = Database::get_course_table(TABLE_LP_MAIN);
2339
        // Get current prerequisite
2340
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2341
                FROM $table
2342
                WHERE iid = $lp_id";
2343
        $rs = Database::query($sql);
2344
        $now = time();
2345
        if (Database::num_rows($rs) > 0) {
2346
            $row = Database::fetch_array($rs, 'ASSOC');
2347
            if (!empty($row['category_id'])) {
2348
                $category = self::getCategory($row['category_id']);
2349
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2350
                    return false;
2351
                }
2352
            }
2353
2354
            $prerequisite = $row['prerequisite'];
2355
            $is_visible = true;
2356
2357
            $isBlocked = self::isBlockedByPrerequisite(
2358
                $student_id,
2359
                $prerequisite,
2360
                $courseInfo,
2361
                $sessionId
2362
            );
2363
2364
            if ($isBlocked) {
2365
                $is_visible = false;
2366
            }
2367
2368
            // Also check the time availability of the LP
2369
            if ($is_visible) {
2370
                // Adding visibility restrictions
2371
                if (!empty($row['publicated_on'])) {
2372
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2373
                        $is_visible = false;
2374
                    }
2375
                }
2376
                // Blocking empty start times see BT#2800
2377
                global $_custom;
2378
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2379
                    $_custom['lps_hidden_when_no_start_date']
2380
                ) {
2381
                    if (empty($row['publicated_on'])) {
2382
                        $is_visible = false;
2383
                    }
2384
                }
2385
2386
                if (!empty($row['expired_on'])) {
2387
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2388
                        $is_visible = false;
2389
                    }
2390
                }
2391
            }
2392
2393
            if ($is_visible) {
2394
                $is_visible = self::isUserSubscribedToLp(
2395
                    $row,
2396
                    (int) $student_id,
2397
                    $courseInfo,
2398
                    (int) $sessionId
2399
                );
2400
            }
2401
2402
            return $is_visible;
2403
        }
2404
2405
        return false;
2406
    }
2407
2408
    public static function isUserSubscribedToLp(
2409
        array $lpInfo,
2410
        int $studentId,
2411
        array $courseInfo,
2412
        int $sessionId = 0
2413
    ): bool {
2414
        $subscriptionSettings = self::getSubscriptionSettings();
2415
2416
        // Check if the subscription users/group to a LP is ON
2417
        if (isset($lpInfo['subscribe_users']) && $lpInfo['subscribe_users'] == 1 &&
2418
            $subscriptionSettings['allow_add_users_to_lp'] === true
2419
        ) {
2420
            // Checking only the user visibility
2421
            $userVisibility = api_get_item_visibility(
2422
                $courseInfo,
2423
                'learnpath',
2424
                $lpInfo['id'],
2425
                $sessionId,
2426
                $studentId,
2427
                'LearnpathSubscription'
2428
            );
2429
2430
            if (1 == $userVisibility) {
2431
                return true;
2432
            }
2433
2434
            // Try group
2435
            $userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $courseInfo['real_id']);
2436
2437
            if (!empty($userGroups)) {
2438
                foreach ($userGroups as $groupInfo) {
2439
                    $userVisibility = api_get_item_visibility(
2440
                        $courseInfo,
2441
                        'learnpath',
2442
                        $lpInfo['id'],
2443
                        $sessionId,
2444
                        null,
2445
                        'LearnpathSubscription',
2446
                        $groupInfo['iid']
2447
                    );
2448
2449
                    if (1 == $userVisibility) {
2450
                        return true;
2451
                    }
2452
                }
2453
            }
2454
2455
            return false;
2456
        }
2457
2458
        return true;
2459
    }
2460
2461
    /**
2462
     * @param int $lpId
2463
     * @param int $userId
2464
     * @param int $courseId
2465
     * @param int $sessionId
2466
     *
2467
     * @return int
2468
     */
2469
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2470
    {
2471
        $lpId = (int) $lpId;
2472
        $userId = (int) $userId;
2473
        $courseId = (int) $courseId;
2474
        $sessionId = (int) $sessionId;
2475
2476
        $sessionCondition = api_get_session_condition($sessionId);
2477
        $table = Database::get_course_table(TABLE_LP_VIEW);
2478
        $sql = "SELECT progress FROM $table
2479
                WHERE
2480
                    c_id = $courseId AND
2481
                    lp_id = $lpId AND
2482
                    user_id = $userId $sessionCondition ";
2483
        $res = Database::query($sql);
2484
2485
        $progress = 0;
2486
        if (Database::num_rows($res) > 0) {
2487
            $row = Database::fetch_array($res);
2488
            $progress = (int) $row['progress'];
2489
        }
2490
2491
        return $progress;
2492
    }
2493
2494
    /**
2495
     * @param array $lpList
2496
     * @param int   $userId
2497
     * @param int   $courseId
2498
     * @param int   $sessionId
2499
     *
2500
     * @return array
2501
     */
2502
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2503
    {
2504
        $lpList = array_map('intval', $lpList);
2505
        if (empty($lpList)) {
2506
            return [];
2507
        }
2508
2509
        $lpList = implode("','", $lpList);
2510
2511
        $userId = (int) $userId;
2512
        $courseId = (int) $courseId;
2513
        $sessionId = (int) $sessionId;
2514
2515
        $sessionCondition = api_get_session_condition($sessionId);
2516
        $table = Database::get_course_table(TABLE_LP_VIEW);
2517
        $sql = "SELECT lp_id, progress FROM $table
2518
                WHERE
2519
                    c_id = $courseId AND
2520
                    lp_id IN ('".$lpList."') AND
2521
                    user_id = $userId $sessionCondition ";
2522
        $res = Database::query($sql);
2523
2524
        if (Database::num_rows($res) > 0) {
2525
            $list = [];
2526
            while ($row = Database::fetch_array($res)) {
2527
                $list[$row['lp_id']] = $row['progress'];
2528
            }
2529
2530
            return $list;
2531
        }
2532
2533
        return [];
2534
    }
2535
2536
    /**
2537
     * Displays a progress bar
2538
     * completed so far.
2539
     *
2540
     * @param int    $percentage Progress value to display
2541
     * @param string $text_add   Text to display near the progress value
2542
     *
2543
     * @return string HTML string containing the progress bar
2544
     */
2545
    public static function get_progress_bar($percentage = -1, $text_add = '')
2546
    {
2547
        $text = $percentage.$text_add;
2548
        $output = '<div class="progress">
2549
            <div id="progress_bar_value"
2550
                class="progress-bar progress-bar-success" role="progressbar"
2551
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2552
            '.$text.'
2553
            </div>
2554
        </div>';
2555
2556
        return $output;
2557
    }
2558
2559
    /**
2560
     * @param string $mode can be '%' or 'abs'
2561
     *                     otherwise this value will be used $this->progress_bar_mode
2562
     *
2563
     * @return string
2564
     */
2565
    public function getProgressBar($mode = null)
2566
    {
2567
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2568
2569
        return self::get_progress_bar($percentage, $text_add);
2570
    }
2571
2572
    /**
2573
     * Gets the progress bar info to display inside the progress bar.
2574
     * Also used by scorm_api.php.
2575
     *
2576
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2577
     *                     we display a number of completed elements per total elements
2578
     * @param int    $add  Additional steps to fake as completed
2579
     *
2580
     * @return array Percentage or number and symbol (% or /xx)
2581
     */
2582
    public function get_progress_bar_text($mode = '', $add = 0)
2583
    {
2584
        if (empty($mode)) {
2585
            $mode = $this->progress_bar_mode;
2586
        }
2587
        $text = '';
2588
        $percentage = 0;
2589
        // If the option to use the score as progress is set for this learning
2590
        // path, then the rules are completely different: we assume only one
2591
        // item exists and the progress of the LP depends on the score
2592
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2593
2594
        if ($scoreAsProgressSetting === true) {
2595
            $scoreAsProgress = $this->getUseScoreAsProgress();
2596
            if ($scoreAsProgress) {
2597
                // Get single item's score
2598
                $itemId = $this->get_current_item_id();
2599
                $item = $this->getItem($itemId);
2600
                $score = $item->get_score();
2601
                $maxScore = $item->get_max();
2602
                if ($mode = '%') {
2603
                    if (!empty($maxScore)) {
2604
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2605
                    }
2606
                    $percentage = number_format($percentage, 0);
2607
                    $text = '%';
2608
                } else {
2609
                    $percentage = $score;
2610
                    $text = '/'.$maxScore;
2611
                }
2612
2613
                return [$percentage, $text];
2614
            }
2615
        }
2616
        // otherwise just continue the normal processing of progress
2617
        $total_items = $this->getTotalItemsCountWithoutDirs();
2618
        $completeItems = $this->get_complete_items_count();
2619
        if ($add != 0) {
2620
            $completeItems += $add;
2621
        }
2622
        if ($completeItems > $total_items) {
2623
            $completeItems = $total_items;
2624
        }
2625
2626
        if ($mode == '%') {
2627
            if ($total_items > 0) {
2628
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2629
            }
2630
            $percentage = number_format($percentage, 0);
2631
            $text = '%';
2632
        } elseif ($mode === 'abs') {
2633
            $percentage = $completeItems;
2634
            $text = '/'.$total_items;
2635
        }
2636
2637
        return [
2638
            $percentage,
2639
            $text,
2640
        ];
2641
    }
2642
2643
    /**
2644
     * Gets the progress bar mode.
2645
     *
2646
     * @return string The progress bar mode attribute
2647
     */
2648
    public function get_progress_bar_mode()
2649
    {
2650
        if (!empty($this->progress_bar_mode)) {
2651
            return $this->progress_bar_mode;
2652
        }
2653
2654
        return '%';
2655
    }
2656
2657
    /**
2658
     * Gets the learnpath theme (remote or local).
2659
     *
2660
     * @return string Learnpath theme
2661
     */
2662
    public function get_theme()
2663
    {
2664
        if (!empty($this->theme)) {
2665
            return $this->theme;
2666
        }
2667
2668
        return '';
2669
    }
2670
2671
    /**
2672
     * Gets the learnpath session id.
2673
     *
2674
     * @return int
2675
     */
2676
    public function get_lp_session_id()
2677
    {
2678
        if (!empty($this->lp_session_id)) {
2679
            return (int) $this->lp_session_id;
2680
        }
2681
2682
        return 0;
2683
    }
2684
2685
    /**
2686
     * Gets the learnpath image.
2687
     *
2688
     * @return string Web URL of the LP image
2689
     */
2690
    public function get_preview_image()
2691
    {
2692
        if (!empty($this->preview_image)) {
2693
            return $this->preview_image;
2694
        }
2695
2696
        return '';
2697
    }
2698
2699
    /**
2700
     * @param string $size
2701
     * @param string $path_type
2702
     *
2703
     * @return bool|string
2704
     */
2705
    public function get_preview_image_path($size = null, $path_type = 'web')
2706
    {
2707
        $preview_image = $this->get_preview_image();
2708
        if (isset($preview_image) && !empty($preview_image)) {
2709
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2710
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2711
2712
            if (isset($size)) {
2713
                $info = pathinfo($preview_image);
2714
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2715
2716
                if (file_exists($image_sys_path.$image_custom_size)) {
2717
                    if ($path_type == 'web') {
2718
                        return $image_path.$image_custom_size;
2719
                    } else {
2720
                        return $image_sys_path.$image_custom_size;
2721
                    }
2722
                }
2723
            } else {
2724
                if ($path_type == 'web') {
2725
                    return $image_path.$preview_image;
2726
                } else {
2727
                    return $image_sys_path.$preview_image;
2728
                }
2729
            }
2730
        }
2731
2732
        return false;
2733
    }
2734
2735
    /**
2736
     * Gets the learnpath author.
2737
     *
2738
     * @return string LP's author
2739
     */
2740
    public function get_author()
2741
    {
2742
        if (!empty($this->author)) {
2743
            return $this->author;
2744
        }
2745
2746
        return '';
2747
    }
2748
2749
    /**
2750
     * Gets hide table of contents.
2751
     *
2752
     * @return int
2753
     */
2754
    public function getHideTableOfContents()
2755
    {
2756
        return (int) $this->hide_toc_frame;
2757
    }
2758
2759
    /**
2760
     * Generate a new prerequisites string for a given item. If this item was a sco and
2761
     * its prerequisites were strings (instead of IDs), then transform those strings into
2762
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2763
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2764
     * same rule as the scormExport() method.
2765
     *
2766
     * @param int $item_id Item ID
2767
     *
2768
     * @return string Prerequisites string ready for the export as SCORM
2769
     */
2770
    public function get_scorm_prereq_string($item_id)
2771
    {
2772
        if ($this->debug > 0) {
2773
            error_log('In learnpath::get_scorm_prereq_string()');
2774
        }
2775
        if (!is_object($this->items[$item_id])) {
2776
            return false;
2777
        }
2778
        /** @var learnpathItem $oItem */
2779
        $oItem = $this->items[$item_id];
2780
        $prereq = $oItem->get_prereq_string();
2781
2782
        if (empty($prereq)) {
2783
            return '';
2784
        }
2785
        if (preg_match('/^\d+$/', $prereq) &&
2786
            isset($this->items[$prereq]) &&
2787
            is_object($this->items[$prereq])
2788
        ) {
2789
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2790
            // then simply return it (with the ITEM_ prefix).
2791
            //return 'ITEM_' . $prereq;
2792
            return $this->items[$prereq]->ref;
2793
        } else {
2794
            if (isset($this->refs_list[$prereq])) {
2795
                // It's a simple string item from which the ID can be found in the refs list,
2796
                // so we can transform it directly to an ID for export.
2797
                return $this->items[$this->refs_list[$prereq]]->ref;
2798
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2799
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2800
            } else {
2801
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2802
                // and replace them, one by one, by the internal IDs (chamilo db)
2803
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2804
                // by a space as well.
2805
                $find = [
2806
                    '&',
2807
                    '|',
2808
                    '~',
2809
                    '=',
2810
                    '<>',
2811
                    '{',
2812
                    '}',
2813
                    '*',
2814
                    '(',
2815
                    ')',
2816
                ];
2817
                $replace = [
2818
                    ' ',
2819
                    ' ',
2820
                    ' ',
2821
                    ' ',
2822
                    ' ',
2823
                    ' ',
2824
                    ' ',
2825
                    ' ',
2826
                    ' ',
2827
                    ' ',
2828
                ];
2829
                $prereq_mod = str_replace($find, $replace, $prereq);
2830
                $ids = explode(' ', $prereq_mod);
2831
                foreach ($ids as $id) {
2832
                    $id = trim($id);
2833
                    if (isset($this->refs_list[$id])) {
2834
                        $prereq = preg_replace(
2835
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2836
                            'ITEM_'.$this->refs_list[$id],
2837
                            $prereq
2838
                        );
2839
                    }
2840
                }
2841
2842
                return $prereq;
2843
            }
2844
        }
2845
    }
2846
2847
    /**
2848
     * Returns the XML DOM document's node.
2849
     *
2850
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2851
     * @param string   $id       The identifier to look for
2852
     *
2853
     * @return mixed The reference to the element found with that identifier. False if not found
2854
     */
2855
    public function get_scorm_xml_node(&$children, $id)
2856
    {
2857
        for ($i = 0; $i < $children->length; $i++) {
2858
            $item_temp = $children->item($i);
2859
            if ($item_temp->nodeName == 'item') {
2860
                if ($item_temp->getAttribute('identifier') == $id) {
2861
                    return $item_temp;
2862
                }
2863
            }
2864
            $subchildren = $item_temp->childNodes;
2865
            if ($subchildren && $subchildren->length > 0) {
2866
                $val = $this->get_scorm_xml_node($subchildren, $id);
2867
                if (is_object($val)) {
2868
                    return $val;
2869
                }
2870
            }
2871
        }
2872
2873
        return false;
2874
    }
2875
2876
    /**
2877
     * Gets the status list for all LP's items.
2878
     *
2879
     * @return array Array of [index] => [item ID => current status]
2880
     */
2881
    public function get_items_status_list()
2882
    {
2883
        $list = [];
2884
        foreach ($this->ordered_items as $item_id) {
2885
            $list[] = [
2886
                $item_id => $this->items[$item_id]->get_status(),
2887
            ];
2888
        }
2889
2890
        return $list;
2891
    }
2892
2893
    /**
2894
     * Return the number of interactions for the given learnpath Item View ID.
2895
     * This method can be used as static.
2896
     *
2897
     * @param int $lp_iv_id  Item View ID
2898
     * @param int $course_id course id
2899
     *
2900
     * @return int
2901
     */
2902
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2903
    {
2904
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2905
        $lp_iv_id = (int) $lp_iv_id;
2906
        $course_id = (int) $course_id;
2907
2908
        $sql = "SELECT count(*) FROM $table
2909
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2910
        $res = Database::query($sql);
2911
        $num = 0;
2912
        if (Database::num_rows($res)) {
2913
            $row = Database::fetch_array($res);
2914
            $num = $row[0];
2915
        }
2916
2917
        return $num;
2918
    }
2919
2920
    /**
2921
     * Return the interactions as an array for the given lp_iv_id.
2922
     * This method can be used as static.
2923
     *
2924
     * @param int $lp_iv_id Learnpath Item View ID
2925
     *
2926
     * @return array
2927
     *
2928
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2929
     */
2930
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2931
    {
2932
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2933
        $list = [];
2934
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2935
        $lp_iv_id = (int) $lp_iv_id;
2936
2937
        if (empty($lp_iv_id) || empty($course_id)) {
2938
            return [];
2939
        }
2940
2941
        $sql = "SELECT * FROM $table
2942
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2943
                ORDER BY order_id ASC";
2944
        $res = Database::query($sql);
2945
        $num = Database::num_rows($res);
2946
        if ($num > 0) {
2947
            $list[] = [
2948
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2949
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2950
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2951
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2952
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2953
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2954
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2955
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2956
                'student_response_formatted' => '',
2957
            ];
2958
            while ($row = Database::fetch_array($res)) {
2959
                $studentResponseFormatted = urldecode($row['student_response']);
2960
                $content_student_response = explode('__|', $studentResponseFormatted);
2961
                if (count($content_student_response) > 0) {
2962
                    if (count($content_student_response) >= 3) {
2963
                        // Pop the element off the end of array.
2964
                        array_pop($content_student_response);
2965
                    }
2966
                    $studentResponseFormatted = implode(',', $content_student_response);
2967
                }
2968
2969
                $list[] = [
2970
                    'order_id' => $row['order_id'] + 1,
2971
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2972
                    'type' => $row['interaction_type'],
2973
                    'time' => $row['completion_time'],
2974
                    'correct_responses' => '', // Hide correct responses from students.
2975
                    'student_response' => $row['student_response'],
2976
                    'result' => $row['result'],
2977
                    'latency' => $row['latency'],
2978
                    'student_response_formatted' => $studentResponseFormatted,
2979
                ];
2980
            }
2981
        }
2982
2983
        return $list;
2984
    }
2985
2986
    /**
2987
     * Return the number of objectives for the given learnpath Item View ID.
2988
     * This method can be used as static.
2989
     *
2990
     * @param int $lp_iv_id  Item View ID
2991
     * @param int $course_id Course ID
2992
     *
2993
     * @return int Number of objectives
2994
     */
2995
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2996
    {
2997
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2998
        $course_id = (int) $course_id;
2999
        $lp_iv_id = (int) $lp_iv_id;
3000
        $sql = "SELECT count(*) FROM $table
3001
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3002
        //@todo seems that this always returns 0
3003
        $res = Database::query($sql);
3004
        $num = 0;
3005
        if (Database::num_rows($res)) {
3006
            $row = Database::fetch_array($res);
3007
            $num = $row[0];
3008
        }
3009
3010
        return $num;
3011
    }
3012
3013
    /**
3014
     * Return the objectives as an array for the given lp_iv_id.
3015
     * This method can be used as static.
3016
     *
3017
     * @param int $lpItemViewId Learnpath Item View ID
3018
     * @param int $course_id
3019
     *
3020
     * @return array
3021
     *
3022
     * @todo    Translate labels
3023
     */
3024
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3025
    {
3026
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3027
        $lpItemViewId = (int) $lpItemViewId;
3028
3029
        if (empty($course_id) || empty($lpItemViewId)) {
3030
            return [];
3031
        }
3032
3033
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3034
        $sql = "SELECT * FROM $table
3035
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3036
                ORDER BY order_id ASC";
3037
        $res = Database::query($sql);
3038
        $num = Database::num_rows($res);
3039
        $list = [];
3040
        if ($num > 0) {
3041
            $list[] = [
3042
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3043
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3044
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3045
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3046
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3047
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3048
            ];
3049
            while ($row = Database::fetch_array($res)) {
3050
                $list[] = [
3051
                    'order_id' => $row['order_id'] + 1,
3052
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3053
                    'score_raw' => $row['score_raw'],
3054
                    'score_max' => $row['score_max'],
3055
                    'score_min' => $row['score_min'],
3056
                    'status' => $row['status'],
3057
                ];
3058
            }
3059
        }
3060
3061
        return $list;
3062
    }
3063
3064
    /**
3065
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3066
     * used by get_html_toc() to be ready to display.
3067
     *
3068
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3069
     */
3070
    public function get_toc()
3071
    {
3072
        $toc = [];
3073
        foreach ($this->ordered_items as $item_id) {
3074
            // TODO: Change this link generation and use new function instead.
3075
            $toc[] = [
3076
                'id' => $item_id,
3077
                'title' => $this->items[$item_id]->get_title(),
3078
                'status' => $this->items[$item_id]->get_status(),
3079
                'level' => $this->items[$item_id]->get_level(),
3080
                'type' => $this->items[$item_id]->get_type(),
3081
                'description' => $this->items[$item_id]->get_description(),
3082
                'path' => $this->items[$item_id]->get_path(),
3083
                'parent' => $this->items[$item_id]->get_parent(),
3084
            ];
3085
        }
3086
3087
        return $toc;
3088
    }
3089
3090
    /**
3091
     * Returns the CSS class name associated with a given item status.
3092
     *
3093
     * @param $status string an item status
3094
     *
3095
     * @return string CSS class name
3096
     */
3097
    public static function getStatusCSSClassName($status)
3098
    {
3099
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3100
            return self::STATUS_CSS_CLASS_NAME[$status];
3101
        }
3102
3103
        return '';
3104
    }
3105
3106
    /**
3107
     * Generate the tree of contents for this learnpath as an associative array tree
3108
     * with keys id, title, status, type, description, path, parent_id, children
3109
     * (title and descriptions as secured)
3110
     * and clues for CSS class composition:
3111
     *  - booleans is_current, is_parent_of_current, is_chapter
3112
     *  - string status_css_class_name.
3113
     *
3114
     * @param $parentId int restrict returned list to children of this parent
3115
     *
3116
     * @return array TOC as a table
3117
     */
3118
    public function getTOCTree($parentId = 0)
3119
    {
3120
        $toc = [];
3121
        $currentItemId = $this->get_current_item_id();
3122
3123
        foreach ($this->ordered_items as $itemId) {
3124
            $item = $this->items[$itemId];
3125
            if ($item->get_parent() == $parentId) {
3126
                $title = $item->get_title();
3127
                if (empty($title)) {
3128
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3129
                }
3130
3131
                $itemData = [
3132
                    'id' => $itemId,
3133
                    'title' => Security::remove_XSS($title),
3134
                    'status' => $item->get_status(),
3135
                    'level' => $item->get_level(), // FIXME should not be needed
3136
                    'type' => $item->get_type(),
3137
                    'description' => Security::remove_XSS($item->get_description()),
3138
                    'path' => $item->get_path(),
3139
                    'parent_id' => $item->get_parent(),
3140
                    'children' => $this->getTOCTree($itemId),
3141
                    'is_current' => ($itemId == $currentItemId),
3142
                    'is_parent_of_current' => false,
3143
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3144
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3145
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3146
                ];
3147
3148
                if (!empty($itemData['children'])) {
3149
                    foreach ($itemData['children'] as $child) {
3150
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3151
                            $itemData['is_parent_of_current'] = true;
3152
                            break;
3153
                        }
3154
                    }
3155
                }
3156
3157
                $toc[] = $itemData;
3158
            }
3159
        }
3160
3161
        return $toc;
3162
    }
3163
3164
    /**
3165
     * Generate and return the table of contents for this learnpath. The JS
3166
     * table returned is used inside of scorm_api.php.
3167
     *
3168
     * @param string $varname
3169
     *
3170
     * @return string A JS array variable construction
3171
     */
3172
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3173
    {
3174
        $toc = $varname.' = new Array();';
3175
        foreach ($this->ordered_items as $item_id) {
3176
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3177
        }
3178
3179
        return $toc;
3180
    }
3181
3182
    /**
3183
     * Gets the learning path type.
3184
     *
3185
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3186
     *
3187
     * @return mixed Type ID or name, depending on the parameter
3188
     */
3189
    public function get_type($get_name = false)
3190
    {
3191
        $res = false;
3192
        if (!empty($this->type) && (!$get_name)) {
3193
            $res = $this->type;
3194
        }
3195
3196
        return $res;
3197
    }
3198
3199
    /**
3200
     * Gets the learning path type as static method.
3201
     *
3202
     * @param int $lp_id
3203
     *
3204
     * @return mixed Returns the lp_type: 1 = Chamilo lms / 2 = SCORM
3205
     */
3206
    public static function get_type_static($lp_id = 0)
3207
    {
3208
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3209
        $lp_id = (int) $lp_id;
3210
        $sql = "SELECT lp_type FROM $tbl_lp
3211
                WHERE iid = $lp_id";
3212
        $res = Database::query($sql);
3213
        if ($res === false) {
3214
            return null;
3215
        }
3216
        if (Database::num_rows($res) <= 0) {
3217
            return null;
3218
        }
3219
        $row = Database::fetch_array($res);
3220
3221
        return $row['lp_type'];
3222
    }
3223
3224
    /**
3225
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3226
     * This method can be used as abstract and is recursive.
3227
     *
3228
     * @param int $lp        Learnpath ID
3229
     * @param int $parent    Parent ID of the items to look for
3230
     * @param int $course_id
3231
     *
3232
     * @return array Ordered list of item IDs (empty array on error)
3233
     */
3234
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3235
    {
3236
        if (empty($course_id)) {
3237
            $course_id = api_get_course_int_id();
3238
        } else {
3239
            $course_id = (int) $course_id;
3240
        }
3241
        $list = [];
3242
3243
        if (empty($lp)) {
3244
            return $list;
3245
        }
3246
3247
        $lp = (int) $lp;
3248
        $parent = (int) $parent;
3249
3250
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3251
        $sql = "SELECT iid FROM $tbl_lp_item
3252
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3253
                ORDER BY display_order";
3254
3255
        $res = Database::query($sql);
3256
        while ($row = Database::fetch_array($res)) {
3257
            $sublist = self::get_flat_ordered_items_list(
3258
                $lp,
3259
                $row['iid'],
3260
                $course_id
3261
            );
3262
            $list[] = $row['iid'];
3263
            foreach ($sublist as $item) {
3264
                $list[] = $item;
3265
            }
3266
        }
3267
3268
        return $list;
3269
    }
3270
3271
    /**
3272
     * @return array
3273
     */
3274
    public static function getChapterTypes()
3275
    {
3276
        return [
3277
            'dir',
3278
        ];
3279
    }
3280
3281
    /**
3282
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3283
     *
3284
     * @param $tree
3285
     *
3286
     * @return array HTML TOC ready to display
3287
     */
3288
    public function getParentToc($tree)
3289
    {
3290
        if (empty($tree)) {
3291
            $tree = $this->get_toc();
3292
        }
3293
        $dirTypes = self::getChapterTypes();
3294
        $myCurrentId = $this->get_current_item_id();
3295
        $listParent = [];
3296
        $listChildren = [];
3297
        $listNotParent = [];
3298
        $list = [];
3299
        foreach ($tree as $subtree) {
3300
            if (in_array($subtree['type'], $dirTypes)) {
3301
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3302
                $subtree['children'] = $listChildren;
3303
                if (!empty($subtree['children'])) {
3304
                    foreach ($subtree['children'] as $subItem) {
3305
                        if ($subItem['id'] == $this->current) {
3306
                            $subtree['parent_current'] = 'in';
3307
                            $subtree['current'] = 'on';
3308
                        }
3309
                    }
3310
                }
3311
                $listParent[] = $subtree;
3312
            }
3313
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3314
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3315
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3316
                }
3317
3318
                $title = Security::remove_XSS($subtree['title']);
3319
                unset($subtree['title']);
3320
3321
                if (empty($title)) {
3322
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3323
                }
3324
                $classStyle = null;
3325
                if ($subtree['id'] == $this->current) {
3326
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3327
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3328
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3329
                }
3330
                $subtree['title'] = $title;
3331
                $subtree['class'] = $classStyle.' '.$cssStatus;
3332
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3333
                $subtree['current_id'] = $myCurrentId;
3334
                $listNotParent[] = $subtree;
3335
            }
3336
        }
3337
3338
        $list['are_parents'] = $listParent;
3339
        $list['not_parents'] = $listNotParent;
3340
3341
        return $list;
3342
    }
3343
3344
    /**
3345
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3346
     *
3347
     * @param array $tree
3348
     * @param int   $id
3349
     * @param bool  $parent
3350
     *
3351
     * @return array HTML TOC ready to display
3352
     */
3353
    public function getChildrenToc($tree, $id, $parent = true)
3354
    {
3355
        if (empty($tree)) {
3356
            $tree = $this->get_toc();
3357
        }
3358
3359
        $dirTypes = self::getChapterTypes();
3360
        $currentItemId = $this->get_current_item_id();
3361
        $list = [];
3362
3363
        foreach ($tree as $subtree) {
3364
            $subtree['tree'] = null;
3365
3366
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3367
                if ($subtree['id'] == $this->current) {
3368
                    $subtree['current'] = 'active';
3369
                } else {
3370
                    $subtree['current'] = null;
3371
                }
3372
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3373
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3374
                }
3375
3376
                $title = Security::remove_XSS($subtree['title']);
3377
                unset($subtree['title']);
3378
                if (empty($title)) {
3379
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3380
                }
3381
3382
                $classStyle = null;
3383
                if ($subtree['id'] == $this->current) {
3384
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3385
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3386
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3387
                }
3388
3389
                if (in_array($subtree['type'], $dirTypes)) {
3390
                    $subtree['title'] = stripslashes($title);
3391
                } else {
3392
                    $subtree['title'] = $title;
3393
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3394
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3395
                    $subtree['current_id'] = $currentItemId;
3396
                }
3397
                $list[] = $subtree;
3398
            }
3399
        }
3400
3401
        return $list;
3402
    }
3403
3404
    /**
3405
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3406
     *
3407
     * @param array $toc_list
3408
     *
3409
     * @return array HTML TOC ready to display
3410
     */
3411
    public function getListArrayToc($toc_list = [])
3412
    {
3413
        if (empty($toc_list)) {
3414
            $toc_list = $this->get_toc();
3415
        }
3416
        // Temporary variables.
3417
        $currentItemId = $this->get_current_item_id();
3418
        $list = [];
3419
        $arrayList = [];
3420
3421
        foreach ($toc_list as $item) {
3422
            $list['id'] = $item['id'];
3423
            $list['status'] = $item['status'];
3424
            $cssStatus = null;
3425
3426
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3427
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3428
            }
3429
3430
            $classStyle = ' ';
3431
            $dirTypes = self::getChapterTypes();
3432
3433
            if (in_array($item['type'], $dirTypes)) {
3434
                $classStyle = 'scorm_item_section ';
3435
            }
3436
            if ($item['id'] == $this->current) {
3437
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3438
            } elseif (!in_array($item['type'], $dirTypes)) {
3439
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3440
            }
3441
            $title = $item['title'];
3442
            if (empty($title)) {
3443
                $title = self::rl_get_resource_name(
3444
                    api_get_course_id(),
3445
                    $this->get_id(),
3446
                    $item['id']
3447
                );
3448
            }
3449
            $title = Security::remove_XSS($item['title']);
3450
3451
            if (empty($item['description'])) {
3452
                $list['description'] = $title;
3453
            } else {
3454
                $list['description'] = $item['description'];
3455
            }
3456
3457
            $list['class'] = $classStyle.' '.$cssStatus;
3458
            $list['level'] = $item['level'];
3459
            $list['type'] = $item['type'];
3460
3461
            if (in_array($item['type'], $dirTypes)) {
3462
                $list['css_level'] = 'level_'.$item['level'];
3463
            } else {
3464
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3465
            }
3466
3467
            if (in_array($item['type'], $dirTypes)) {
3468
                $list['title'] = stripslashes($title);
3469
            } else {
3470
                $list['title'] = stripslashes($title);
3471
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3472
                $list['current_id'] = $currentItemId;
3473
            }
3474
            $arrayList[] = $list;
3475
        }
3476
3477
        return $arrayList;
3478
    }
3479
3480
    /**
3481
     * Returns an HTML-formatted string ready to display with teacher buttons
3482
     * in LP view menu.
3483
     *
3484
     * @return string HTML TOC ready to display
3485
     */
3486
    public function get_teacher_toc_buttons()
3487
    {
3488
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3489
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3490
        $html = '';
3491
        if ($isAllow && $hideIcons == false) {
3492
            if ($this->get_lp_session_id() == api_get_session_id()) {
3493
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3494
                $html .= '<div class="btn-group">';
3495
                $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'>".
3496
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3497
                $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'>".
3498
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3499
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3500
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3501
                $html .= '</div>';
3502
                $html .= '</div>';
3503
            }
3504
        }
3505
3506
        return $html;
3507
    }
3508
3509
    /**
3510
     * Gets the learnpath maker name - generally the editor's name.
3511
     *
3512
     * @return string Learnpath maker name
3513
     */
3514
    public function get_maker()
3515
    {
3516
        if (!empty($this->maker)) {
3517
            return $this->maker;
3518
        }
3519
3520
        return '';
3521
    }
3522
3523
    /**
3524
     * Gets the learnpath name/title.
3525
     *
3526
     * @return string Learnpath name/title
3527
     */
3528
    public function get_name()
3529
    {
3530
        if (!empty($this->name)) {
3531
            return $this->name;
3532
        }
3533
3534
        return 'N/A';
3535
    }
3536
3537
    /**
3538
     * @return string
3539
     */
3540
    public function getNameNoTags()
3541
    {
3542
        return strip_tags($this->get_name());
3543
    }
3544
3545
    /**
3546
     * Gets a link to the resource from the present location, depending on item ID.
3547
     *
3548
     * @param string $type         Type of link expected
3549
     * @param int    $item_id      Learnpath item ID
3550
     * @param bool   $provided_toc
3551
     *
3552
     * @return string $provided_toc Link to the lp_item resource
3553
     */
3554
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3555
    {
3556
        $course_id = $this->get_course_int_id();
3557
        $item_id = (int) $item_id;
3558
3559
        if (empty($item_id)) {
3560
            $item_id = $this->get_current_item_id();
3561
3562
            if (empty($item_id)) {
3563
                //still empty, this means there was no item_id given and we are not in an object context or
3564
                //the object property is empty, return empty link
3565
                $this->first();
3566
3567
                return '';
3568
            }
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
3576
        $sql = "SELECT
3577
                    l.lp_type as ltype,
3578
                    l.path as lpath,
3579
                    li.item_type as litype,
3580
                    li.path as lipath,
3581
                    li.parameters as liparams
3582
        		FROM $lp_table l
3583
                INNER JOIN $lp_item_table li
3584
                ON (li.lp_id = l.iid)
3585
        		WHERE
3586
        		    li.iid = $item_id
3587
        		";
3588
        $res = Database::query($sql);
3589
        if (Database::num_rows($res) > 0) {
3590
            $row = Database::fetch_array($res);
3591
            $lp_type = $row['ltype'];
3592
            $lp_path = $row['lpath'];
3593
            $lp_item_type = $row['litype'];
3594
            $lp_item_path = $row['lipath'];
3595
            $lp_item_params = $row['liparams'];
3596
3597
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3598
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3599
            }
3600
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3601
            if ($type === 'http') {
3602
                //web path
3603
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3604
            } else {
3605
                $course_path = $sys_course_path; //system path
3606
            }
3607
3608
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3609
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3610
            if (in_array(
3611
                $lp_item_type,
3612
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication', 'xapi']
3613
            )
3614
            ) {
3615
                $lp_type = 1;
3616
            }
3617
3618
            // Now go through the specific cases to get the end of the path
3619
            // @todo Use constants instead of int values.
3620
            switch ($lp_type) {
3621
                case 1:
3622
                    $file = self::rl_get_resource_link_for_learnpath(
3623
                        $course_id,
3624
                        $this->get_id(),
3625
                        $item_id,
3626
                        $this->get_view_id(),
3627
                        $this->get_lp_session_id()
3628
                    );
3629
                    switch ($lp_item_type) {
3630
                        case 'document':
3631
                            // Shows a button to download the file instead of just downloading the file directly.
3632
                            $documentPathInfo = pathinfo($file);
3633
                            if (isset($documentPathInfo['extension'])) {
3634
                                $parsed = parse_url($documentPathInfo['extension']);
3635
                                if (isset($parsed['path'])) {
3636
                                    $extension = $parsed['path'];
3637
                                    $extensionsToDownload = [
3638
                                        'zip',
3639
                                        'ppt',
3640
                                        'pptx',
3641
                                        'ods',
3642
                                        'xlsx',
3643
                                        'xls',
3644
                                        'csv',
3645
                                        'doc',
3646
                                        'docx',
3647
                                        'dot',
3648
                                    ];
3649
3650
                                    if (in_array($extension, $extensionsToDownload)) {
3651
                                        $file = api_get_path(WEB_CODE_PATH).
3652
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3653
                                    }
3654
                                }
3655
                            }
3656
                            break;
3657
                        case 'dir':
3658
                            $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3659
                            break;
3660
                        case 'link':
3661
                            if (!empty($file)) {
3662
                                if (Link::is_youtube_link($file)) {
3663
                                    $src = Link::get_youtube_video_id($file);
3664
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3665
                                } elseif (Link::isVimeoLink($file)) {
3666
                                    $src = Link::getVimeoLinkId($file);
3667
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3668
                                } else {
3669
                                    // If the current site is HTTPS and the link is
3670
                                    // HTTP, browsers will refuse opening the link
3671
                                    $urlId = api_get_current_access_url_id();
3672
                                    $url = api_get_access_url($urlId, false);
3673
                                    $protocol = substr($url['url'], 0, 5);
3674
                                    if ($protocol === 'https') {
3675
                                        $linkProtocol = substr($file, 0, 5);
3676
                                        if ($linkProtocol === 'http:') {
3677
                                            //this is the special intervention case
3678
                                            $file = api_get_path(
3679
                                                    WEB_CODE_PATH
3680
                                                ).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3681
                                        }
3682
                                    }
3683
                                }
3684
                            }
3685
                            break;
3686
                        case 'quiz':
3687
                            // Check how much attempts of a exercise exits in lp
3688
                            $lp_item_id = $this->get_current_item_id();
3689
                            $lp_view_id = $this->get_view_id();
3690
3691
                            $prevent_reinit = null;
3692
                            if (isset($this->items[$this->current])) {
3693
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3694
                            }
3695
3696
                            if (empty($provided_toc)) {
3697
                                if ($this->debug > 0) {
3698
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3699
                                }
3700
                                $list = $this->get_toc();
3701
                            } else {
3702
                                if ($this->debug > 0) {
3703
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3704
                                }
3705
                                $list = $provided_toc;
3706
                            }
3707
3708
                            $type_quiz = false;
3709
                            foreach ($list as $toc) {
3710
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3711
                                    $type_quiz = true;
3712
                                }
3713
                            }
3714
3715
                            if ($type_quiz) {
3716
                                $lp_item_id = (int) $lp_item_id;
3717
                                $lp_view_id = (int) $lp_view_id;
3718
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3719
                                        WHERE
3720
                                            c_id = $course_id AND
3721
                                            lp_item_id='".$lp_item_id."' AND
3722
                                            lp_view_id ='".$lp_view_id."' AND
3723
                                            status='completed'";
3724
                                $result = Database::query($sql);
3725
                                $row_count = Database:: fetch_row($result);
3726
                                $count_item_view = (int) $row_count[0];
3727
                                $not_multiple_attempt = 0;
3728
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3729
                                    $not_multiple_attempt = 1;
3730
                                }
3731
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3732
                            }
3733
                            break;
3734
                    }
3735
3736
                    $tmp_array = explode('/', $file);
3737
                    $document_name = $tmp_array[count($tmp_array) - 1];
3738
                    if (strpos($document_name, '_DELETED_')) {
3739
                        $file = 'blank.php?error=document_deleted';
3740
                    }
3741
                    break;
3742
                case 2:
3743
                    if ($this->debug > 2) {
3744
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3745
                    }
3746
3747
                    if ($lp_item_type != 'dir') {
3748
                        // Quite complex here:
3749
                        // We want to make sure 'http://' (and similar) links can
3750
                        // be loaded as is (withouth the Chamilo path in front) but
3751
                        // some contents use this form: resource.htm?resource=http://blablabla
3752
                        // which means we have to find a protocol at the path's start, otherwise
3753
                        // it should not be considered as an external URL.
3754
                        // if ($this->prerequisites_match($item_id)) {
3755
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3756
                            if ($this->debug > 2) {
3757
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3758
                            }
3759
                            // Distant url, return as is.
3760
                            $file = $lp_item_path;
3761
                        } else {
3762
                            if ($this->debug > 2) {
3763
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3764
                            }
3765
                            // Prevent getting untranslatable urls.
3766
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3767
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3768
                            // Prepare the path.
3769
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3770
                            // TODO: Fix this for urls with protocol header.
3771
                            $file = str_replace('//', '/', $file);
3772
                            $file = str_replace(':/', '://', $file);
3773
                            if (substr($lp_path, -1) == '/') {
3774
                                $lp_path = substr($lp_path, 0, -1);
3775
                            }
3776
3777
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3778
                                // if file not found.
3779
                                $decoded = html_entity_decode($lp_item_path);
3780
                                [$decoded] = explode('?', $decoded);
3781
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3782
                                    $file = self::rl_get_resource_link_for_learnpath(
3783
                                        $course_id,
3784
                                        $this->get_id(),
3785
                                        $item_id,
3786
                                        $this->get_view_id()
3787
                                    );
3788
                                    if (empty($file)) {
3789
                                        $file = 'blank.php?error=document_not_found';
3790
                                    } else {
3791
                                        $tmp_array = explode('/', $file);
3792
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3793
                                        if (strpos($document_name, '_DELETED_')) {
3794
                                            $file = 'blank.php?error=document_deleted';
3795
                                        } else {
3796
                                            $file = 'blank.php?error=document_not_found';
3797
                                        }
3798
                                    }
3799
                                } else {
3800
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3801
                                }
3802
                            }
3803
                        }
3804
3805
                        // We want to use parameters if they were defined in the imsmanifest
3806
                        if (strpos($file, 'blank.php') === false) {
3807
                            $lp_item_params = ltrim($lp_item_params, '?');
3808
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3809
                        }
3810
                    } else {
3811
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3812
                    }
3813
                    break;
3814
                case 3:
3815
                    if ($this->debug > 2) {
3816
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3817
                    }
3818
                    // Formatting AICC HACP append URL.
3819
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3820
                    if (!empty($lp_item_params)) {
3821
                        $aicc_append .= $lp_item_params.'&';
3822
                    }
3823
                    if ($lp_item_type != 'dir') {
3824
                        // Quite complex here:
3825
                        // We want to make sure 'http://' (and similar) links can
3826
                        // be loaded as is (withouth the Chamilo path in front) but
3827
                        // some contents use this form: resource.htm?resource=http://blablabla
3828
                        // which means we have to find a protocol at the path's start, otherwise
3829
                        // it should not be considered as an external URL.
3830
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3831
                            if ($this->debug > 2) {
3832
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3833
                            }
3834
                            // Distant url, return as is.
3835
                            $file = $lp_item_path;
3836
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3837
                            /*
3838
                            if (stristr($file,'<servername>') !== false) {
3839
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3840
                            }
3841
                            */
3842
                            if (stripos($file, '<servername>') !== false) {
3843
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3844
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3845
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3846
                            }
3847
3848
                            $file .= $aicc_append;
3849
                        } else {
3850
                            if ($this->debug > 2) {
3851
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3852
                            }
3853
                            // Prevent getting untranslatable urls.
3854
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3855
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3856
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3857
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3858
                            // TODO: Fix this for urls with protocol header.
3859
                            $file = str_replace('//', '/', $file);
3860
                            $file = str_replace(':/', '://', $file);
3861
                            $file .= $aicc_append;
3862
                        }
3863
                    } else {
3864
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3865
                    }
3866
                    break;
3867
                case 4:
3868
                    break;
3869
                default:
3870
                    break;
3871
            }
3872
            // Replace &amp; by & because &amp; will break URL with params
3873
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3874
        }
3875
        if ($this->debug > 2) {
3876
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3877
        }
3878
3879
        return $file;
3880
    }
3881
3882
    /**
3883
     * Gets the latest usable view or generate a new one.
3884
     *
3885
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3886
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3887
     *
3888
     * @return int DB lp_view id
3889
     */
3890
    public function get_view($attempt_num = 0, $userId = null)
3891
    {
3892
        $search = '';
3893
        // Use $attempt_num to enable multi-views management (disabled so far).
3894
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3895
            $search = 'AND view_count = '.$attempt_num;
3896
        }
3897
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3898
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3899
3900
        $course_id = api_get_course_int_id();
3901
        $sessionId = api_get_session_id();
3902
3903
        // Check user ID.
3904
        if (empty($userId)) {
3905
            if (empty($this->get_user_id())) {
3906
                $this->error = 'User ID is empty in learnpath::get_view()';
3907
3908
                return null;
3909
            } else {
3910
                $userId = $this->get_user_id();
3911
            }
3912
        }
3913
3914
        $sql = "SELECT iid, view_count FROM $lp_view_table
3915
        		WHERE
3916
        		    c_id = $course_id AND
3917
        		    lp_id = ".$this->get_id()." AND
3918
        		    user_id = ".$userId." AND
3919
        		    session_id = $sessionId
3920
        		    $search
3921
                ORDER BY view_count DESC";
3922
        $res = Database::query($sql);
3923
        if (Database::num_rows($res) > 0) {
3924
            $row = Database::fetch_array($res);
3925
            $this->lp_view_id = $row['iid'];
3926
        } elseif (!api_is_invitee()) {
3927
            // There is no database record, create one.
3928
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3929
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3930
            Database::query($sql);
3931
            $id = Database::insert_id();
3932
            $this->lp_view_id = $id;
3933
3934
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3935
            Database::query($sql);
3936
        }
3937
3938
        return $this->lp_view_id;
3939
    }
3940
3941
    /**
3942
     * Gets the current view id.
3943
     *
3944
     * @return int View ID (from lp_view)
3945
     */
3946
    public function get_view_id()
3947
    {
3948
        if (!empty($this->lp_view_id)) {
3949
            return (int) $this->lp_view_id;
3950
        }
3951
3952
        return 0;
3953
    }
3954
3955
    /**
3956
     * Gets the update queue.
3957
     *
3958
     * @return array Array containing IDs of items to be updated by JavaScript
3959
     */
3960
    public function get_update_queue()
3961
    {
3962
        return $this->update_queue;
3963
    }
3964
3965
    /**
3966
     * Gets the user ID.
3967
     *
3968
     * @return int User ID
3969
     */
3970
    public function get_user_id()
3971
    {
3972
        if (!empty($this->user_id)) {
3973
            return (int) $this->user_id;
3974
        }
3975
3976
        return false;
3977
    }
3978
3979
    /**
3980
     * Checks if any of the items has an audio element attached.
3981
     *
3982
     * @return bool True or false
3983
     */
3984
    public function has_audio()
3985
    {
3986
        $has = false;
3987
        foreach ($this->items as $i => $item) {
3988
            if (!empty($this->items[$i]->audio)) {
3989
                $has = true;
3990
                break;
3991
            }
3992
        }
3993
3994
        return $has;
3995
    }
3996
3997
    /**
3998
     * Moves an item up and down at its level.
3999
     *
4000
     * @param int    $id        Item to move up and down
4001
     * @param string $direction Direction 'up' or 'down'
4002
     *
4003
     * @return bool|int
4004
     */
4005
    public function move_item($id, $direction)
4006
    {
4007
        $course_id = api_get_course_int_id();
4008
        if (empty($id) || empty($direction)) {
4009
            return false;
4010
        }
4011
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4012
        $sql_sel = "SELECT *
4013
                    FROM $tbl_lp_item
4014
                    WHERE
4015
                        iid = $id
4016
                    ";
4017
        $res_sel = Database::query($sql_sel);
4018
        // Check if elem exists.
4019
        if (Database::num_rows($res_sel) < 1) {
4020
            return false;
4021
        }
4022
        // Gather data.
4023
        $row = Database::fetch_array($res_sel);
4024
        $previous = $row['previous_item_id'];
4025
        $next = $row['next_item_id'];
4026
        $display = $row['display_order'];
4027
        $parent = $row['parent_item_id'];
4028
        $lp = $row['lp_id'];
4029
        // Update the item (switch with previous/next one).
4030
        switch ($direction) {
4031
            case 'up':
4032
                if ($display > 1) {
4033
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4034
                                 WHERE iid = $previous";
4035
                    $res_sel2 = Database::query($sql_sel2);
4036
                    if (Database::num_rows($res_sel2) < 1) {
4037
                        $previous_previous = 0;
4038
                    }
4039
                    // Gather data.
4040
                    $row2 = Database::fetch_array($res_sel2);
4041
                    $previous_previous = $row2['previous_item_id'];
4042
                    // Update previous_previous item (switch "next" with current).
4043
                    if ($previous_previous != 0) {
4044
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4045
                                        next_item_id = $id
4046
                                    WHERE iid = $previous_previous";
4047
                        Database::query($sql_upd2);
4048
                    }
4049
                    // Update previous item (switch with current).
4050
                    if ($previous != 0) {
4051
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4052
                                    next_item_id = $next,
4053
                                    previous_item_id = $id,
4054
                                    display_order = display_order +1
4055
                                    WHERE iid = $previous";
4056
                        Database::query($sql_upd2);
4057
                    }
4058
4059
                    // Update current item (switch with previous).
4060
                    if ($id != 0) {
4061
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4062
                                        next_item_id = $previous,
4063
                                        previous_item_id = $previous_previous,
4064
                                        display_order = display_order-1
4065
                                    WHERE c_id = ".$course_id." AND id = $id";
4066
                        Database::query($sql_upd2);
4067
                    }
4068
                    // Update next item (new previous item).
4069
                    if (!empty($next)) {
4070
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4071
                                     WHERE iid = $next";
4072
                        Database::query($sql_upd2);
4073
                    }
4074
                    $display = $display - 1;
4075
                }
4076
                break;
4077
            case 'down':
4078
                if ($next != 0) {
4079
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4080
                                 WHERE iid = $next";
4081
                    $res_sel2 = Database::query($sql_sel2);
4082
                    if (Database::num_rows($res_sel2) < 1) {
4083
                        $next_next = 0;
4084
                    }
4085
                    // Gather data.
4086
                    $row2 = Database::fetch_array($res_sel2);
4087
                    $next_next = $row2['next_item_id'];
4088
                    // Update previous item (switch with current).
4089
                    if ($previous != 0) {
4090
                        $sql_upd2 = "UPDATE $tbl_lp_item
4091
                                     SET next_item_id = $next
4092
                                     WHERE iid = $previous";
4093
                        Database::query($sql_upd2);
4094
                    }
4095
                    // Update current item (switch with previous).
4096
                    if ($id != 0) {
4097
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4098
                                     previous_item_id = $next,
4099
                                     next_item_id = $next_next,
4100
                                     display_order = display_order + 1
4101
                                     WHERE iid = $id";
4102
                        Database::query($sql_upd2);
4103
                    }
4104
4105
                    // Update next item (new previous item).
4106
                    if ($next != 0) {
4107
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4108
                                     previous_item_id = $previous,
4109
                                     next_item_id = $id,
4110
                                     display_order = display_order-1
4111
                                     WHERE iid = $next";
4112
                        Database::query($sql_upd2);
4113
                    }
4114
4115
                    // Update next_next item (switch "previous" with current).
4116
                    if ($next_next != 0) {
4117
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4118
                                     previous_item_id = $id
4119
                                     WHERE iid = $next_next";
4120
                        Database::query($sql_upd2);
4121
                    }
4122
                    $display = $display + 1;
4123
                }
4124
                break;
4125
            default:
4126
                return false;
4127
        }
4128
4129
        return $display;
4130
    }
4131
4132
    /**
4133
     * Move a LP up (display_order).
4134
     *
4135
     * @param int $lp_id      Learnpath ID
4136
     * @param int $categoryId Category ID
4137
     *
4138
     * @return bool
4139
     */
4140
    public static function move_up($lp_id, $categoryId = 0)
4141
    {
4142
        $courseId = api_get_course_int_id();
4143
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4144
4145
        $categoryCondition = '';
4146
        if (!empty($categoryId)) {
4147
            $categoryId = (int) $categoryId;
4148
            $categoryCondition = " AND category_id = $categoryId";
4149
        }
4150
        $sql = "SELECT * FROM $lp_table
4151
                WHERE c_id = $courseId
4152
                $categoryCondition
4153
                ORDER BY display_order";
4154
        $res = Database::query($sql);
4155
        if ($res === false) {
4156
            return false;
4157
        }
4158
4159
        $lps = [];
4160
        $lp_order = [];
4161
        $num = Database::num_rows($res);
4162
        // First check the order is correct, globally (might be wrong because
4163
        // of versions < 1.8.4)
4164
        if ($num > 0) {
4165
            $i = 1;
4166
            while ($row = Database::fetch_array($res)) {
4167
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4168
                    $sql = "UPDATE $lp_table SET display_order = $i
4169
                            WHERE iid = ".$row['iid'];
4170
                    Database::query($sql);
4171
                }
4172
                $row['display_order'] = $i;
4173
                $lps[$row['iid']] = $row;
4174
                $lp_order[$i] = $row['iid'];
4175
                $i++;
4176
            }
4177
        }
4178
        if ($num > 1) { // If there's only one element, no need to sort.
4179
            $order = $lps[$lp_id]['display_order'];
4180
            if ($order > 1) { // If it's the first element, no need to move up.
4181
                $sql = "UPDATE $lp_table SET display_order = $order
4182
                        WHERE iid = ".$lp_order[$order - 1];
4183
                Database::query($sql);
4184
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4185
                        WHERE iid = $lp_id";
4186
                Database::query($sql);
4187
            }
4188
        }
4189
4190
        return true;
4191
    }
4192
4193
    /**
4194
     * Move a learnpath down (display_order).
4195
     *
4196
     * @param int $lp_id      Learnpath ID
4197
     * @param int $categoryId Category ID
4198
     *
4199
     * @return bool
4200
     */
4201
    public static function move_down($lp_id, $categoryId = 0)
4202
    {
4203
        $courseId = api_get_course_int_id();
4204
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4205
4206
        $categoryCondition = '';
4207
        if (!empty($categoryId)) {
4208
            $categoryId = (int) $categoryId;
4209
            $categoryCondition = " AND category_id = $categoryId";
4210
        }
4211
4212
        $sql = "SELECT * FROM $lp_table
4213
                WHERE c_id = $courseId
4214
                $categoryCondition
4215
                ORDER BY display_order";
4216
        $res = Database::query($sql);
4217
        if ($res === false) {
4218
            return false;
4219
        }
4220
        $lps = [];
4221
        $lp_order = [];
4222
        $num = Database::num_rows($res);
4223
        $max = 0;
4224
        // First check the order is correct, globally (might be wrong because
4225
        // of versions < 1.8.4).
4226
        if ($num > 0) {
4227
            $i = 1;
4228
            while ($row = Database::fetch_array($res)) {
4229
                $max = $i;
4230
                if ($row['display_order'] != $i) {
4231
                    // If we find a gap in the order, we need to fix it.
4232
                    $sql = "UPDATE $lp_table SET display_order = $i
4233
                              WHERE iid = ".$row['iid'];
4234
                    Database::query($sql);
4235
                }
4236
                $row['display_order'] = $i;
4237
                $lps[$row['iid']] = $row;
4238
                $lp_order[$i] = $row['iid'];
4239
                $i++;
4240
            }
4241
        }
4242
        if ($num > 1) { // If there's only one element, no need to sort.
4243
            $order = $lps[$lp_id]['display_order'];
4244
            if ($order < $max) { // If it's the first element, no need to move up.
4245
                $sql = "UPDATE $lp_table SET display_order = $order
4246
                        WHERE iid = ".$lp_order[$order + 1];
4247
                Database::query($sql);
4248
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4249
                        WHERE iid = $lp_id";
4250
                Database::query($sql);
4251
            }
4252
        }
4253
4254
        return true;
4255
    }
4256
4257
    /**
4258
     * Updates learnpath attributes to point to the next element
4259
     * The last part is similar to set_current_item but processing the other way around.
4260
     */
4261
    public function next()
4262
    {
4263
        if ($this->debug > 0) {
4264
            error_log('In learnpath::next()', 0);
4265
        }
4266
        $this->last = $this->get_current_item_id();
4267
        $this->items[$this->last]->save(
4268
            false,
4269
            $this->prerequisites_match($this->last)
4270
        );
4271
        $this->autocomplete_parents($this->last);
4272
        $new_index = $this->get_next_index();
4273
        if ($this->debug > 2) {
4274
            error_log('New index: '.$new_index, 0);
4275
        }
4276
        $this->index = $new_index;
4277
        if ($this->debug > 2) {
4278
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4279
        }
4280
        $this->current = $this->ordered_items[$new_index];
4281
        if ($this->debug > 2) {
4282
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4283
        }
4284
    }
4285
4286
    /**
4287
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4288
     * class, this might be redefined to allow several behaviours depending on the document type.
4289
     *
4290
     * @param int $id Resource ID
4291
     */
4292
    public function open($id)
4293
    {
4294
        // TODO:
4295
        // set the current resource attribute to this resource
4296
        // switch on element type (redefine in child class?)
4297
        // set status for this item to "opened"
4298
        // start timer
4299
        // initialise score
4300
        $this->index = 0; //or = the last item seen (see $this->last)
4301
    }
4302
4303
    /**
4304
     * Check that all prerequisites are fulfilled. Returns true and an
4305
     * empty string on success, returns false
4306
     * and the prerequisite string on error.
4307
     * This function is based on the rules for aicc_script language as
4308
     * described in the SCORM 1.2 CAM documentation page 108.
4309
     *
4310
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4311
     *
4312
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4313
     *              string otherwise
4314
     */
4315
    public function prerequisites_match($itemId = null)
4316
    {
4317
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4318
        if ($allow) {
4319
            if (api_is_allowed_to_edit() ||
4320
                api_is_platform_admin(true) ||
4321
                api_is_drh() ||
4322
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4323
            ) {
4324
                return true;
4325
            }
4326
        }
4327
4328
        $debug = $this->debug;
4329
        if ($debug > 0) {
4330
            error_log('In learnpath::prerequisites_match()');
4331
        }
4332
4333
        if (empty($itemId)) {
4334
            $itemId = $this->current;
4335
        }
4336
4337
        $currentItem = $this->getItem($itemId);
4338
        if ($debug > 0) {
4339
            error_log("Checking item id $itemId");
4340
        }
4341
4342
        if ($currentItem) {
4343
            if ($this->type == 2) {
4344
                // Getting prereq from scorm
4345
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4346
            } else {
4347
                $prereq_string = $currentItem->get_prereq_string();
4348
            }
4349
4350
            if (empty($prereq_string)) {
4351
                if ($debug > 0) {
4352
                    error_log('Found prereq_string is empty return true');
4353
                }
4354
4355
                return true;
4356
            }
4357
4358
            // Clean spaces.
4359
            $prereq_string = str_replace(' ', '', $prereq_string);
4360
            if ($debug > 0) {
4361
                error_log('Found prereq_string: '.$prereq_string, 0);
4362
            }
4363
4364
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4365
            $result = $currentItem->parse_prereq(
4366
                $prereq_string,
4367
                $this->items,
4368
                $this->refs_list,
4369
                $this->get_user_id()
4370
            );
4371
4372
            if ($result === false) {
4373
                $this->set_error_msg($currentItem->prereq_alert);
4374
            }
4375
        } else {
4376
            $result = true;
4377
            if ($debug > 1) {
4378
                error_log('$this->items['.$itemId.'] was not an object');
4379
            }
4380
        }
4381
4382
        if ($debug > 1) {
4383
            error_log('Result: '.$result);
4384
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4385
        }
4386
4387
        return $result;
4388
    }
4389
4390
    /**
4391
     * Updates learnpath attributes to point to the previous element
4392
     * The last part is similar to set_current_item but processing the other way around.
4393
     */
4394
    public function previous()
4395
    {
4396
        $this->last = $this->get_current_item_id();
4397
        $this->items[$this->last]->save(
4398
            false,
4399
            $this->prerequisites_match($this->last)
4400
        );
4401
        $this->autocomplete_parents($this->last);
4402
        $new_index = $this->get_previous_index();
4403
        $this->index = $new_index;
4404
        $this->current = $this->ordered_items[$new_index];
4405
    }
4406
4407
    /**
4408
     * Publishes a learnpath. This basically means show or hide the learnpath
4409
     * to normal users.
4410
     * Can be used as abstract.
4411
     *
4412
     * @param int $lp_id          Learnpath ID
4413
     * @param int $set_visibility New visibility
4414
     *
4415
     * @return bool
4416
     */
4417
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4418
    {
4419
        if (empty($lp_id)) {
4420
            return false;
4421
        }
4422
4423
        $action = 'visible';
4424
        if ($set_visibility != 1) {
4425
            $action = 'invisible';
4426
            self::toggle_publish($lp_id, 'i');
4427
        }
4428
4429
        return api_item_property_update(
4430
            api_get_course_info(),
4431
            TOOL_LEARNPATH,
4432
            $lp_id,
4433
            $action,
4434
            api_get_user_id()
4435
        );
4436
    }
4437
4438
    /**
4439
     * Publishes a learnpath category.
4440
     * This basically means show or hide the learnpath category to normal users.
4441
     *
4442
     * @param int $id
4443
     * @param int $visibility
4444
     *
4445
     * @return bool
4446
     */
4447
    public static function toggleCategoryVisibility($id, $visibility = 1)
4448
    {
4449
        $action = 'visible';
4450
        if ($visibility != 1) {
4451
            self::toggleCategoryPublish($id, 0);
4452
            $action = 'invisible';
4453
        }
4454
4455
        return api_item_property_update(
4456
            api_get_course_info(),
4457
            TOOL_LEARNPATH_CATEGORY,
4458
            $id,
4459
            $action,
4460
            api_get_user_id()
4461
        );
4462
    }
4463
4464
    /**
4465
     * Publishes a learnpath. This basically means show or hide the learnpath
4466
     * on the course homepage
4467
     * Can be used as abstract.
4468
     *
4469
     * @param int    $lp_id          Learnpath id
4470
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4471
     *
4472
     * @return bool
4473
     */
4474
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4475
    {
4476
        if (empty($lp_id)) {
4477
            return false;
4478
        }
4479
        $course_id = api_get_course_int_id();
4480
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4481
        $lp_id = (int) $lp_id;
4482
        $sql = "SELECT * FROM $tbl_lp
4483
                WHERE iid = $lp_id";
4484
        $result = Database::query($sql);
4485
        if (Database::num_rows($result)) {
4486
            $row = Database::fetch_array($result);
4487
            $name = Database::escape_string($row['name']);
4488
            if ($set_visibility === 'i') {
4489
                $v = 0;
4490
            }
4491
            if ($set_visibility === 'v') {
4492
                $v = 1;
4493
            }
4494
4495
            $session_id = api_get_session_id();
4496
            $session_condition = api_get_session_condition($session_id);
4497
4498
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4499
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4500
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4501
4502
            $extraLpCondition = '';
4503
            $extraLink = '';
4504
            if (!empty($session_id)) {
4505
                $extraLink = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session=0';
4506
                $extraLpCondition = " OR (link = '$link' AND session_id = $session_id )  ";
4507
            }
4508
4509
            $sql = "SELECT * FROM $tbl_tool
4510
                    WHERE
4511
                        c_id = $course_id AND
4512
                        (link = '$link' OR link = '$oldLink' $extraLpCondition ) AND
4513
                        image = 'scormbuilder.gif' AND
4514
                        (
4515
                            link LIKE '$link%' OR
4516
                            link LIKE '$oldLink%'
4517
                            $extraLpCondition
4518
                        )
4519
                        $session_condition
4520
                    ";
4521
4522
            $result = Database::query($sql);
4523
            $num = Database::num_rows($result);
4524
            $resultTool = Database::fetch_array($result, 'ASSOC');
4525
4526
            if ($set_visibility === 'i') {
4527
                if ($num > 0) {
4528
                    $sql = "DELETE FROM $tbl_tool
4529
                            WHERE
4530
                                c_id = $course_id AND
4531
                                (link = '$link' OR link = '$oldLink') AND
4532
                                image='scormbuilder.gif'
4533
                                $session_condition";
4534
                    Database::query($sql);
4535
                }
4536
4537
                // Disables the base course link inside a session.
4538
                if (!empty($session_id) && 0 === (int) $row['session_id']) {
4539
                    $sql = "SELECT iid FROM $tbl_tool
4540
                            WHERE
4541
                                c_id = $course_id AND
4542
                                (link = '$extraLink') AND
4543
                                image = 'scormbuilder.gif' AND
4544
                                session_id = $session_id
4545
                    ";
4546
                    $resultBaseLp = Database::query($sql);
4547
                    if (Database::num_rows($resultBaseLp)) {
4548
                        $resultBaseLpRow = Database::fetch_array($resultBaseLp);
4549
                        $id = $resultBaseLpRow['iid'];
4550
                        /*$sql = "UPDATE $tbl_tool
4551
                                SET visibility = 0
4552
                                WHERE iid = $id ";
4553
                        Database::query($sql);*/
4554
                        $sql = "DELETE FROM $tbl_tool
4555
                                WHERE iid = $id";
4556
                        Database::query($sql);
4557
                    } else {
4558
                        /*$params = [
4559
                            'category' => 'authoring',
4560
                            'c_id' => $course_id,
4561
                            'name' => $name,
4562
                            'link' => $link,
4563
                            'image' => 'scormbuilder.gif',
4564
                            'visibility' => '0',
4565
                            'admin' => '0',
4566
                            'address' => 'pastillegris.gif',
4567
                            'added_tool' => '0',
4568
                            'session_id' => $session_id,
4569
                        ];
4570
                        $insertId = Database::insert($tbl_tool, $params);
4571
                        if ($insertId) {
4572
                            $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4573
                            Database::query($sql);
4574
                        }*/
4575
                    }
4576
                }
4577
            }
4578
4579
            if ($set_visibility === 'v') {
4580
                if ($num == 0) {
4581
                    $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id)
4582
                            VALUES ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4583
                    Database::query($sql);
4584
                    $insertId = Database::insert_id();
4585
                    if ($insertId) {
4586
                        $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4587
                        Database::query($sql);
4588
                    }
4589
                }
4590
                if ($num > 0) {
4591
                    $id = $resultTool['iid'];
4592
                    $sql = "UPDATE $tbl_tool SET
4593
                        c_id = $course_id,
4594
                        name = '$name',
4595
                        link = '$link',
4596
                        image = 'scormbuilder.gif',
4597
                        visibility = '$v',
4598
                        admin = '0',
4599
                        address = 'pastillegris.gif',
4600
                        added_tool = 0,
4601
                        session_id = $session_id
4602
                    WHERE
4603
                        c_id = ".$course_id." AND
4604
                        iid = $id
4605
                    ";
4606
                    Database::query($sql);
4607
                }
4608
            }
4609
        }
4610
4611
        return false;
4612
    }
4613
4614
    /**
4615
     * Publishes a learnpath.
4616
     * Show or hide the learnpath category on the course homepage.
4617
     *
4618
     * @param int $id
4619
     * @param int $setVisibility
4620
     *
4621
     * @return bool
4622
     */
4623
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4624
    {
4625
        $courseId = api_get_course_int_id();
4626
        $sessionId = api_get_session_id();
4627
        $sessionCondition = api_get_session_condition(
4628
            $sessionId,
4629
            true,
4630
            false,
4631
            't.sessionId'
4632
        );
4633
4634
        $em = Database::getManager();
4635
        $category = self::getCategory($id);
4636
4637
        if (!$category) {
4638
            return false;
4639
        }
4640
4641
        if (empty($courseId)) {
4642
            return false;
4643
        }
4644
4645
        $link = self::getCategoryLinkForTool($id);
4646
4647
        /** @var CTool $tool */
4648
        $tool = $em->createQuery("
4649
                SELECT t FROM ChamiloCourseBundle:CTool t
4650
                WHERE
4651
                    t.cId = :course AND
4652
                    t.link = :link1 AND
4653
                    t.image = 'lp_category.gif' AND
4654
                    t.link LIKE :link2
4655
                    $sessionCondition
4656
            ")
4657
            ->setParameters([
4658
                'course' => $courseId,
4659
                'link1' => $link,
4660
                'link2' => "$link%",
4661
            ])
4662
            ->getOneOrNullResult();
4663
4664
        if ($setVisibility == 0 && $tool) {
4665
            $em->remove($tool);
4666
            $em->flush();
4667
4668
            return true;
4669
        }
4670
4671
        if ($setVisibility == 1 && !$tool) {
4672
            $tool = new CTool();
4673
            $tool
4674
                ->setCategory('authoring')
4675
                ->setCId($courseId)
4676
                ->setName(strip_tags($category->getName()))
4677
                ->setLink($link)
4678
                ->setImage('lp_category.gif')
4679
                ->setVisibility(1)
4680
                ->setAdmin(0)
4681
                ->setAddress('pastillegris.gif')
4682
                ->setAddedTool(0)
4683
                ->setSessionId($sessionId)
4684
                ->setTarget('_self');
4685
4686
            $em->persist($tool);
4687
            $em->flush();
4688
4689
            $tool->setId($tool->getIid());
4690
4691
            $em->persist($tool);
4692
            $em->flush();
4693
4694
            return true;
4695
        }
4696
4697
        if ($setVisibility == 1 && $tool) {
4698
            $tool
4699
                ->setName(strip_tags($category->getName()))
4700
                ->setVisibility(1);
4701
4702
            $em->persist($tool);
4703
            $em->flush();
4704
4705
            return true;
4706
        }
4707
4708
        return false;
4709
    }
4710
4711
    /**
4712
     * Check if the learnpath category is visible for a user.
4713
     *
4714
     * @param int
4715
     * @param int
4716
     *
4717
     * @return bool
4718
     */
4719
    public static function categoryIsVisibleForStudent(
4720
        CLpCategory $category,
4721
        User $user,
4722
        $courseId = 0,
4723
        $sessionId = 0
4724
    ) {
4725
        if (empty($category)) {
4726
            return false;
4727
        }
4728
4729
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4730
4731
        if ($isAllowedToEdit) {
4732
            return true;
4733
        }
4734
4735
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4736
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4737
4738
        $courseInfo = api_get_course_info_by_id($courseId);
4739
4740
        $categoryVisibility = api_get_item_visibility(
4741
            $courseInfo,
4742
            TOOL_LEARNPATH_CATEGORY,
4743
            $category->getId(),
4744
            $sessionId
4745
        );
4746
4747
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4748
            return false;
4749
        }
4750
4751
        $subscriptionSettings = self::getSubscriptionSettings();
4752
4753
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4754
            return true;
4755
        }
4756
4757
        $noUserSubscribed = false;
4758
        $noGroupSubscribed = true;
4759
        $users = $category->getUsers();
4760
        if (empty($users) || !$users->count()) {
4761
            $noUserSubscribed = true;
4762
        } elseif ($category->hasUserAdded($user)) {
4763
            return true;
4764
        }
4765
4766
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4767
        $em = Database::getManager();
4768
4769
        /** @var ItemPropertyRepository $itemRepo */
4770
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4771
4772
        /** @var CourseRepository $courseRepo */
4773
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4774
        $session = null;
4775
        if (!empty($sessionId)) {
4776
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4777
        }
4778
4779
        $course = $courseRepo->find($courseId);
4780
4781
        if ($courseId != 0) {
4782
            // Subscribed groups to a LP
4783
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4784
                TOOL_LEARNPATH_CATEGORY,
4785
                $category->getId(),
4786
                $course,
4787
                $session
4788
            );
4789
        }
4790
4791
        if (!empty($subscribedGroupsInLp)) {
4792
            $noGroupSubscribed = false;
4793
            if (!empty($groups)) {
4794
                $groups = array_column($groups, 'iid');
4795
                /** @var CItemProperty $item */
4796
                foreach ($subscribedGroupsInLp as $item) {
4797
                    if ($item->getGroup() &&
4798
                        in_array($item->getGroup()->getId(), $groups)
4799
                    ) {
4800
                        return true;
4801
                    }
4802
                }
4803
            }
4804
        }
4805
        $response = $noGroupSubscribed && $noUserSubscribed;
4806
4807
        return $response;
4808
    }
4809
4810
    /**
4811
     * Check if a learnpath category is published as course tool.
4812
     *
4813
     * @param int $courseId
4814
     *
4815
     * @return bool
4816
     */
4817
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4818
    {
4819
        $link = self::getCategoryLinkForTool($category->getId());
4820
        $em = Database::getManager();
4821
4822
        $tools = $em
4823
            ->createQuery("
4824
                SELECT t FROM ChamiloCourseBundle:CTool t
4825
                WHERE t.cId = :course AND
4826
                    t.name = :name AND
4827
                    t.image = 'lp_category.gif' AND
4828
                    t.link LIKE :link
4829
            ")
4830
            ->setParameters([
4831
                'course' => $courseId,
4832
                'name' => strip_tags($category->getName()),
4833
                'link' => "$link%",
4834
            ])
4835
            ->getResult();
4836
4837
        /** @var CTool $tool */
4838
        $tool = current($tools);
4839
4840
        return $tool ? $tool->getVisibility() : false;
4841
    }
4842
4843
    /**
4844
     * Restart the whole learnpath. Return the URL of the first element.
4845
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4846
     * To use a similar method  statically, use the create_new_attempt() method.
4847
     *
4848
     * @return bool
4849
     */
4850
    public function restart()
4851
    {
4852
        if ($this->debug > 0) {
4853
            error_log('In learnpath::restart()', 0);
4854
        }
4855
        // TODO
4856
        // Call autosave method to save the current progress.
4857
        //$this->index = 0;
4858
        if (api_is_invitee()) {
4859
            return false;
4860
        }
4861
        $session_id = api_get_session_id();
4862
        $course_id = api_get_course_int_id();
4863
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4864
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4865
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4866
        if ($this->debug > 2) {
4867
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4868
        }
4869
        Database::query($sql);
4870
        $view_id = Database::insert_id();
4871
4872
        if ($view_id) {
4873
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4874
            Database::query($sql);
4875
            $this->lp_view_id = $view_id;
4876
            $this->attempt = $this->attempt + 1;
4877
        } else {
4878
            $this->error = 'Could not insert into item_view table...';
4879
4880
            return false;
4881
        }
4882
        $this->autocomplete_parents($this->current);
4883
        foreach ($this->items as $index => $dummy) {
4884
            $this->items[$index]->restart();
4885
            $this->items[$index]->set_lp_view($this->lp_view_id);
4886
        }
4887
        $this->first();
4888
4889
        return true;
4890
    }
4891
4892
    /**
4893
     * Saves the current item.
4894
     *
4895
     * @return bool
4896
     */
4897
    public function save_current()
4898
    {
4899
        $debug = $this->debug;
4900
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4901
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4902
        if ($debug) {
4903
            error_log('save_current() saving item '.$this->current, 0);
4904
            error_log(''.print_r($this->items, true), 0);
4905
        }
4906
        if (isset($this->items[$this->current]) &&
4907
            is_object($this->items[$this->current])
4908
        ) {
4909
            if ($debug) {
4910
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4911
            }
4912
4913
            $res = $this->items[$this->current]->save(
4914
                false,
4915
                $this->prerequisites_match($this->current)
4916
            );
4917
            $this->autocomplete_parents($this->current);
4918
            $status = $this->items[$this->current]->get_status();
4919
            $this->update_queue[$this->current] = $status;
4920
4921
            if ($debug) {
4922
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4923
            }
4924
4925
            return $res;
4926
        }
4927
4928
        return false;
4929
    }
4930
4931
    /**
4932
     * Saves the given item.
4933
     *
4934
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4935
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4936
     *
4937
     * @return bool
4938
     */
4939
    public function save_item($item_id = null, $from_outside = true)
4940
    {
4941
        $debug = $this->debug;
4942
        if ($debug) {
4943
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4944
        }
4945
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4946
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4947
        if (empty($item_id)) {
4948
            $item_id = (int) $_REQUEST['id'];
4949
        }
4950
4951
        if (empty($item_id)) {
4952
            $item_id = $this->get_current_item_id();
4953
        }
4954
        if (isset($this->items[$item_id]) &&
4955
            is_object($this->items[$item_id])
4956
        ) {
4957
            // Saving the item.
4958
            $res = $this->items[$item_id]->save(
4959
                $from_outside,
4960
                $this->prerequisites_match($item_id)
4961
            );
4962
4963
            if ($debug) {
4964
                error_log('update_queue before:');
4965
                error_log(print_r($this->update_queue, 1));
4966
            }
4967
            $this->autocomplete_parents($item_id);
4968
4969
            $status = $this->items[$item_id]->get_status();
4970
            $this->update_queue[$item_id] = $status;
4971
4972
            if ($debug) {
4973
                error_log('get_status(): '.$status);
4974
                error_log('update_queue after:');
4975
                error_log(print_r($this->update_queue, 1));
4976
            }
4977
4978
            return $res;
4979
        }
4980
4981
        return false;
4982
    }
4983
4984
    /**
4985
     * Saves the last item seen's ID only in case.
4986
     */
4987
    public function save_last($score = null)
4988
    {
4989
        $course_id = api_get_course_int_id();
4990
        $debug = $this->debug;
4991
        if ($debug) {
4992
            error_log('In learnpath::save_last()', 0);
4993
        }
4994
        $session_condition = api_get_session_condition(
4995
            api_get_session_id(),
4996
            true,
4997
            false
4998
        );
4999
        $table = Database::get_course_table(TABLE_LP_VIEW);
5000
5001
        $userId = $this->get_user_id();
5002
        if (empty($userId)) {
5003
            $userId = api_get_user_id();
5004
            if ($debug) {
5005
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
5006
            }
5007
        }
5008
        if (isset($this->current) && !api_is_invitee()) {
5009
            if ($debug) {
5010
                error_log('Saving current item ('.$this->current.') for later review', 0);
5011
            }
5012
            $sql = "UPDATE $table SET
5013
                        last_item = ".$this->get_current_item_id()."
5014
                    WHERE
5015
                        c_id = $course_id AND
5016
                        lp_id = ".$this->get_id()." AND
5017
                        user_id = ".$userId." ".$session_condition;
5018
            if ($debug) {
5019
                error_log('Saving last item seen : '.$sql, 0);
5020
            }
5021
            Database::query($sql);
5022
        }
5023
5024
        if (!api_is_invitee()) {
5025
            // Save progress.
5026
            [$progress] = $this->get_progress_bar_text('%');
5027
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
5028
            $scoreAsProgress = $this->getUseScoreAsProgress();
5029
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
5030
                if ($debug) {
5031
                    error_log("Return false: Dont save score: $score");
5032
                    error_log("progress: $progress");
5033
                }
5034
5035
                return false;
5036
            }
5037
5038
            if ($scoreAsProgress && $scoreAsProgressSetting) {
5039
                $storedProgress = self::getProgress(
5040
                    $this->get_id(),
5041
                    $userId,
5042
                    $course_id,
5043
                    $this->get_lp_session_id()
5044
                );
5045
5046
                // Check if the stored progress is higher than the new value
5047
                if ($storedProgress >= $progress) {
5048
                    if ($debug) {
5049
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
5050
                    }
5051
5052
                    return false;
5053
                }
5054
            }
5055
5056
            if ($progress >= 0 && $progress <= 100) {
5057
                // Check database.
5058
                $progress = (int) $progress;
5059
                $sql = "UPDATE $table SET
5060
                            progress = $progress
5061
                        WHERE
5062
                            c_id = $course_id AND
5063
                            lp_id = ".$this->get_id()." AND
5064
                            user_id = ".$userId." ".$session_condition;
5065
                // Ignore errors as some tables might not have the progress field just yet.
5066
                Database::query($sql);
5067
                if ($debug) {
5068
                    error_log($sql);
5069
                }
5070
                $this->progress_db = $progress;
5071
5072
                if (100 == $progress) {
5073
                    HookLearningPathEnd::create()
5074
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
5075
                        ->hookLearningPathEnd();
5076
                }
5077
            }
5078
        }
5079
    }
5080
5081
    /**
5082
     * Sets the current item ID (checks if valid and authorized first).
5083
     *
5084
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5085
     */
5086
    public function set_current_item($item_id = null)
5087
    {
5088
        $debug = $this->debug;
5089
        if ($debug) {
5090
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5091
        }
5092
        if (empty($item_id)) {
5093
            if ($debug) {
5094
                error_log('No new current item given, ignore...', 0);
5095
            }
5096
            // Do nothing.
5097
        } else {
5098
            if ($debug) {
5099
                error_log('New current item given is '.$item_id.'...', 0);
5100
            }
5101
            if (is_numeric($item_id)) {
5102
                $item_id = (int) $item_id;
5103
                // TODO: Check in database here.
5104
                $this->last = $this->current;
5105
                $this->current = $item_id;
5106
                // TODO: Update $this->index as well.
5107
                foreach ($this->ordered_items as $index => $item) {
5108
                    if ($item == $this->current) {
5109
                        $this->index = $index;
5110
                        break;
5111
                    }
5112
                }
5113
                if ($debug) {
5114
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5115
                }
5116
            } else {
5117
                if ($debug) {
5118
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5119
                }
5120
            }
5121
        }
5122
    }
5123
5124
    /**
5125
     * Sets the encoding.
5126
     *
5127
     * @param string $enc New encoding
5128
     *
5129
     * @return bool
5130
     *
5131
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5132
     */
5133
    public function set_encoding($enc = 'UTF-8')
5134
    {
5135
        $enc = api_refine_encoding_id($enc);
5136
        if (empty($enc)) {
5137
            $enc = api_get_system_encoding();
5138
        }
5139
        if (api_is_encoding_supported($enc)) {
5140
            $lp = $this->get_id();
5141
            if ($lp != 0) {
5142
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5143
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5144
                        WHERE iid = ".$lp;
5145
                $res = Database::query($sql);
5146
5147
                return $res;
5148
            }
5149
        }
5150
5151
        return false;
5152
    }
5153
5154
    /**
5155
     * Sets the JS lib setting in the database directly.
5156
     * This is the JavaScript library file this lp needs to load on startup.
5157
     *
5158
     * @param string $lib Proximity setting
5159
     *
5160
     * @return bool True on update success. False otherwise.
5161
     */
5162
    public function set_jslib($lib = '')
5163
    {
5164
        $lp = $this->get_id();
5165
5166
        if ($lp != 0) {
5167
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5168
            $lib = Database::escape_string($lib);
5169
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5170
                    WHERE iid = $lp";
5171
            $res = Database::query($sql);
5172
5173
            return $res;
5174
        }
5175
5176
        return false;
5177
    }
5178
5179
    /**
5180
     * Sets the name of the LP maker (publisher) (and save).
5181
     *
5182
     * @param string $name Optional string giving the new content_maker of this learnpath
5183
     *
5184
     * @return bool True
5185
     */
5186
    public function set_maker($name = '')
5187
    {
5188
        if (empty($name)) {
5189
            return false;
5190
        }
5191
        $this->maker = $name;
5192
        $table = Database::get_course_table(TABLE_LP_MAIN);
5193
        $lp_id = $this->get_id();
5194
        $sql = "UPDATE $table SET
5195
                content_maker = '".Database::escape_string($this->maker)."'
5196
                WHERE iid = $lp_id";
5197
        Database::query($sql);
5198
5199
        return true;
5200
    }
5201
5202
    /**
5203
     * Sets the name of the current learnpath (and save).
5204
     *
5205
     * @param string $name Optional string giving the new name of this learnpath
5206
     *
5207
     * @return bool True/False
5208
     */
5209
    public function set_name($name = null)
5210
    {
5211
        if (empty($name)) {
5212
            return false;
5213
        }
5214
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5215
        $name = Database::escape_string($name);
5216
5217
        $this->name = $name;
5218
5219
        $lp_id = $this->get_id();
5220
        $course_id = $this->course_info['real_id'];
5221
        $sql = "UPDATE $lp_table SET
5222
                name = '$name'
5223
                WHERE iid = $lp_id";
5224
        $result = Database::query($sql);
5225
        // If the lp is visible on the homepage, change his name there.
5226
        if (Database::affected_rows($result)) {
5227
            $session_id = api_get_session_id();
5228
            $session_condition = api_get_session_condition($session_id);
5229
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5230
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5231
            $sql = "UPDATE $tbl_tool SET name = '$name'
5232
            	    WHERE
5233
            	        c_id = $course_id AND
5234
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5235
            Database::query($sql);
5236
5237
            return true;
5238
        }
5239
5240
        return false;
5241
    }
5242
5243
    /**
5244
     * Set index specified prefix terms for all items in this path.
5245
     *
5246
     * @param string $terms_string Comma-separated list of terms
5247
     * @param string $prefix       Xapian term prefix
5248
     *
5249
     * @return bool False on error, true otherwise
5250
     */
5251
    public function set_terms_by_prefix($terms_string, $prefix)
5252
    {
5253
        $course_id = api_get_course_int_id();
5254
        if (api_get_setting('search_enabled') !== 'true') {
5255
            return false;
5256
        }
5257
5258
        if (!extension_loaded('xapian')) {
5259
            return false;
5260
        }
5261
5262
        $terms_string = trim($terms_string);
5263
        $terms = explode(',', $terms_string);
5264
        array_walk($terms, 'trim_value');
5265
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5266
5267
        // Don't do anything if no change, verify only at DB, not the search engine.
5268
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5269
            return false;
5270
        }
5271
5272
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5273
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5274
5275
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5276
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5277
        $lp_id = (int) $_POST['lp_id'];
5278
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5279
        $result = Database::query($sql);
5280
        $di = new ChamiloIndexer();
5281
5282
        while ($lp_item = Database::fetch_array($result)) {
5283
            // Get search_did.
5284
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5285
            $sql = 'SELECT * FROM %s
5286
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5287
                    LIMIT 1';
5288
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5289
5290
            //echo $sql; echo '<br>';
5291
            $res = Database::query($sql);
5292
            if (Database::num_rows($res) > 0) {
5293
                $se_ref = Database::fetch_array($res);
5294
                // Compare terms.
5295
                $doc = $di->get_document($se_ref['search_did']);
5296
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5297
                $xterms = [];
5298
                foreach ($xapian_terms as $xapian_term) {
5299
                    $xterms[] = substr($xapian_term['name'], 1);
5300
                }
5301
5302
                $dterms = $terms;
5303
                $missing_terms = array_diff($dterms, $xterms);
5304
                $deprecated_terms = array_diff($xterms, $dterms);
5305
5306
                // Save it to search engine.
5307
                foreach ($missing_terms as $term) {
5308
                    $doc->add_term($prefix.$term, 1);
5309
                }
5310
                foreach ($deprecated_terms as $term) {
5311
                    $doc->remove_term($prefix.$term);
5312
                }
5313
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5314
                $di->getDb()->flush();
5315
            }
5316
        }
5317
5318
        return true;
5319
    }
5320
5321
    /**
5322
     * Sets the theme of the LP (local/remote) (and save).
5323
     *
5324
     * @param string $name Optional string giving the new theme of this learnpath
5325
     *
5326
     * @return bool Returns true if theme name is not empty
5327
     */
5328
    public function set_theme($name = '')
5329
    {
5330
        $this->theme = $name;
5331
        $table = Database::get_course_table(TABLE_LP_MAIN);
5332
        $lp_id = $this->get_id();
5333
        $sql = "UPDATE $table
5334
                SET theme = '".Database::escape_string($this->theme)."'
5335
                WHERE iid = $lp_id";
5336
        Database::query($sql);
5337
5338
        return true;
5339
    }
5340
5341
    /**
5342
     * Sets the image of an LP (and save).
5343
     *
5344
     * @param string $name Optional string giving the new image of this learnpath
5345
     *
5346
     * @return bool Returns true if theme name is not empty
5347
     */
5348
    public function set_preview_image($name = '')
5349
    {
5350
        $this->preview_image = $name;
5351
        $table = Database::get_course_table(TABLE_LP_MAIN);
5352
        $lp_id = $this->get_id();
5353
        $sql = "UPDATE $table SET
5354
                preview_image = '".Database::escape_string($this->preview_image)."'
5355
                WHERE iid = $lp_id";
5356
        Database::query($sql);
5357
5358
        return true;
5359
    }
5360
5361
    /**
5362
     * Sets the author of a LP (and save).
5363
     *
5364
     * @param string $name Optional string giving the new author of this learnpath
5365
     *
5366
     * @return bool Returns true if author's name is not empty
5367
     */
5368
    public function set_author($name = '')
5369
    {
5370
        $this->author = $name;
5371
        $table = Database::get_course_table(TABLE_LP_MAIN);
5372
        $lp_id = $this->get_id();
5373
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5374
                WHERE iid = $lp_id";
5375
        Database::query($sql);
5376
5377
        return true;
5378
    }
5379
5380
    /**
5381
     * Sets the hide_toc_frame parameter of a LP (and save).
5382
     *
5383
     * @param int $hide 1 if frame is hidden 0 then else
5384
     *
5385
     * @return bool Returns true if author's name is not empty
5386
     */
5387
    public function set_hide_toc_frame($hide)
5388
    {
5389
        if (intval($hide) == $hide) {
5390
            $this->hide_toc_frame = $hide;
5391
            $table = Database::get_course_table(TABLE_LP_MAIN);
5392
            $lp_id = $this->get_id();
5393
            $sql = "UPDATE $table SET
5394
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5395
                    WHERE iid = $lp_id";
5396
            Database::query($sql);
5397
5398
            return true;
5399
        }
5400
5401
        return false;
5402
    }
5403
5404
    /**
5405
     * Sets the prerequisite of a LP (and save).
5406
     *
5407
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5408
     *
5409
     * @return bool returns true if prerequisite is not empty
5410
     */
5411
    public function set_prerequisite($prerequisite)
5412
    {
5413
        $this->prerequisite = (int) $prerequisite;
5414
        $table = Database::get_course_table(TABLE_LP_MAIN);
5415
        $lp_id = $this->get_id();
5416
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5417
                WHERE iid = $lp_id";
5418
        Database::query($sql);
5419
5420
        return true;
5421
    }
5422
5423
    /**
5424
     * Sets the location/proximity of the LP (local/remote) (and save).
5425
     *
5426
     * @param string $name Optional string giving the new location of this learnpath
5427
     *
5428
     * @return bool True on success / False on error
5429
     */
5430
    public function set_proximity($name = '')
5431
    {
5432
        if (empty($name)) {
5433
            return false;
5434
        }
5435
5436
        $this->proximity = $name;
5437
        $table = Database::get_course_table(TABLE_LP_MAIN);
5438
        $lp_id = $this->get_id();
5439
        $sql = "UPDATE $table SET
5440
                    content_local = '".Database::escape_string($name)."'
5441
                WHERE iid = $lp_id";
5442
        Database::query($sql);
5443
5444
        return true;
5445
    }
5446
5447
    /**
5448
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5449
     *
5450
     * @param int $id DB ID of the item
5451
     */
5452
    public function set_previous_item($id)
5453
    {
5454
        if ($this->debug > 0) {
5455
            error_log('In learnpath::set_previous_item()', 0);
5456
        }
5457
        $this->last = $id;
5458
    }
5459
5460
    /**
5461
     * Sets use_max_score.
5462
     *
5463
     * @param int $use_max_score Optional string giving the new location of this learnpath
5464
     *
5465
     * @return bool True on success / False on error
5466
     */
5467
    public function set_use_max_score($use_max_score = 1)
5468
    {
5469
        $use_max_score = (int) $use_max_score;
5470
        $this->use_max_score = $use_max_score;
5471
        $table = Database::get_course_table(TABLE_LP_MAIN);
5472
        $lp_id = $this->get_id();
5473
        $sql = "UPDATE $table SET
5474
                    use_max_score = '".$this->use_max_score."'
5475
                WHERE iid = $lp_id";
5476
        Database::query($sql);
5477
5478
        return true;
5479
    }
5480
5481
    /**
5482
     * Sets and saves the expired_on date.
5483
     *
5484
     * @param string $expired_on Optional string giving the new author of this learnpath
5485
     *
5486
     * @throws \Doctrine\ORM\OptimisticLockException
5487
     *
5488
     * @return bool Returns true if author's name is not empty
5489
     */
5490
    public function set_expired_on($expired_on)
5491
    {
5492
        $em = Database::getManager();
5493
        /** @var CLp $lp */
5494
        $lp = $em
5495
            ->getRepository('ChamiloCourseBundle:CLp')
5496
            ->findOneBy(
5497
                [
5498
                    'iid' => $this->get_id(),
5499
                ]
5500
            );
5501
5502
        if (!$lp) {
5503
            return false;
5504
        }
5505
5506
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5507
5508
        $lp->setExpiredOn($this->expired_on);
5509
        $em->persist($lp);
5510
        $em->flush();
5511
5512
        return true;
5513
    }
5514
5515
    /**
5516
     * Sets and saves the publicated_on date.
5517
     *
5518
     * @param string $publicated_on Optional string giving the new author of this learnpath
5519
     *
5520
     * @throws \Doctrine\ORM\OptimisticLockException
5521
     *
5522
     * @return bool Returns true if author's name is not empty
5523
     */
5524
    public function set_publicated_on($publicated_on)
5525
    {
5526
        $em = Database::getManager();
5527
        /** @var CLp $lp */
5528
        $lp = $em
5529
            ->getRepository('ChamiloCourseBundle:CLp')
5530
            ->findOneBy(
5531
                [
5532
                    'iid' => $this->get_id(),
5533
                ]
5534
            );
5535
5536
        if (!$lp) {
5537
            return false;
5538
        }
5539
5540
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5541
        $lp->setPublicatedOn($this->publicated_on);
5542
        $em->persist($lp);
5543
        $em->flush();
5544
5545
        return true;
5546
    }
5547
5548
    /**
5549
     * Sets and saves the expired_on date.
5550
     *
5551
     * @return bool Returns true if author's name is not empty
5552
     */
5553
    public function set_modified_on()
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
        Database::query($sql);
5561
5562
        return true;
5563
    }
5564
5565
    /**
5566
     * Sets the object's error message.
5567
     *
5568
     * @param string $error Error message. If empty, reinits the error string
5569
     */
5570
    public function set_error_msg($error = '')
5571
    {
5572
        if ($this->debug > 0) {
5573
            error_log('In learnpath::set_error_msg()', 0);
5574
        }
5575
        if (empty($error)) {
5576
            $this->error = '';
5577
        } else {
5578
            $this->error .= $error;
5579
        }
5580
    }
5581
5582
    /**
5583
     * Launches the current item if not 'sco'
5584
     * (starts timer and make sure there is a record ready in the DB).
5585
     *
5586
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5587
     *
5588
     * @return bool
5589
     */
5590
    public function start_current_item($allow_new_attempt = false)
5591
    {
5592
        $debug = $this->debug;
5593
        if ($debug) {
5594
            error_log('In learnpath::start_current_item()');
5595
            error_log('current: '.$this->current);
5596
        }
5597
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5598
            $type = $this->get_type();
5599
            $item_type = $this->items[$this->current]->get_type();
5600
            if (($type == 2 && $item_type != 'sco') ||
5601
                ($type == 3 && $item_type != 'au') ||
5602
                (
5603
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5604
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5605
                )
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
5618
                $saveStatus = learnpathItem::isLpItemAutoComplete($this->current);
5619
                if ($saveStatus) {
5620
                    $this->items[$this->current]->save(false, $prereq_check);
5621
                }
5622
            }
5623
            // If sco, then it is supposed to have been updated by some other call.
5624
            if ($item_type === 'sco') {
5625
                $this->items[$this->current]->restart();
5626
            }
5627
        }
5628
        if ($debug) {
5629
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5630
            error_log('api_get_session_id: '.api_get_session_id());
5631
            error_log('End of learnpath::start_current_item()');
5632
        }
5633
5634
        return true;
5635
    }
5636
5637
    /**
5638
     * Stops the processing and counters for the old item (as held in $this->last).
5639
     *
5640
     * @return bool True/False
5641
     */
5642
    public function stop_previous_item()
5643
    {
5644
        $debug = $this->debug;
5645
        if ($debug) {
5646
            error_log('In learnpath::stop_previous_item()');
5647
        }
5648
5649
        if ($this->last != 0 && $this->last != $this->current &&
5650
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5651
        ) {
5652
            if ($debug) {
5653
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5654
            }
5655
            switch ($this->get_type()) {
5656
                case '3':
5657
                    if ($this->items[$this->last]->get_type() != 'au') {
5658
                        if ($debug) {
5659
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5660
                        }
5661
                        $this->items[$this->last]->close();
5662
                    } else {
5663
                        if ($debug) {
5664
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5665
                        }
5666
                    }
5667
                    break;
5668
                case '2':
5669
                    if ($this->items[$this->last]->get_type() != 'sco') {
5670
                        if ($debug) {
5671
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5672
                        }
5673
                        $this->items[$this->last]->close();
5674
                    } else {
5675
                        if ($debug) {
5676
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5677
                        }
5678
                    }
5679
                    break;
5680
                case '1':
5681
                default:
5682
                    if ($debug) {
5683
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5684
                    }
5685
                    $this->items[$this->last]->close();
5686
                    break;
5687
            }
5688
        } else {
5689
            if ($debug) {
5690
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5691
            }
5692
5693
            return false;
5694
        }
5695
5696
        return true;
5697
    }
5698
5699
    /**
5700
     * Updates the default view mode from fullscreen to embedded and inversely.
5701
     *
5702
     * @return string The current default view mode ('fullscreen' or 'embedded')
5703
     */
5704
    public function update_default_view_mode()
5705
    {
5706
        $table = Database::get_course_table(TABLE_LP_MAIN);
5707
        $sql = "SELECT * FROM $table
5708
                WHERE iid = ".$this->get_id();
5709
        $res = Database::query($sql);
5710
        if (Database::num_rows($res) > 0) {
5711
            $row = Database::fetch_array($res);
5712
            $default_view_mode = $row['default_view_mod'];
5713
            $view_mode = $default_view_mode;
5714
            switch ($default_view_mode) {
5715
                case 'fullscreen': // default with popup
5716
                    $view_mode = 'embedded';
5717
                    break;
5718
                case 'embedded': // default view with left menu
5719
                    $view_mode = 'embedframe';
5720
                    break;
5721
                case 'embedframe': //folded menu
5722
                    $view_mode = 'impress';
5723
                    break;
5724
                case 'impress':
5725
                    $view_mode = 'fullscreen';
5726
                    break;
5727
            }
5728
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5729
                    WHERE iid = ".$this->get_id();
5730
            Database::query($sql);
5731
            $this->mode = $view_mode;
5732
5733
            return $view_mode;
5734
        }
5735
5736
        return -1;
5737
    }
5738
5739
    /**
5740
     * Updates the default behaviour about auto-commiting SCORM updates.
5741
     *
5742
     * @return bool True if auto-commit has been set to 'on', false otherwise
5743
     */
5744
    public function update_default_scorm_commit()
5745
    {
5746
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5747
        $sql = "SELECT * FROM $lp_table
5748
                WHERE iid = ".$this->get_id();
5749
        $res = Database::query($sql);
5750
        if (Database::num_rows($res) > 0) {
5751
            $row = Database::fetch_array($res);
5752
            $force = $row['force_commit'];
5753
            if ($force == 1) {
5754
                $force = 0;
5755
                $force_return = false;
5756
            } elseif ($force == 0) {
5757
                $force = 1;
5758
                $force_return = true;
5759
            }
5760
            $sql = "UPDATE $lp_table SET force_commit = $force
5761
                    WHERE iid = ".$this->get_id();
5762
            Database::query($sql);
5763
            $this->force_commit = $force_return;
5764
5765
            return $force_return;
5766
        }
5767
5768
        return -1;
5769
    }
5770
5771
    /**
5772
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5773
     *
5774
     * @return bool True on success, false on failure
5775
     */
5776
    public function update_display_order()
5777
    {
5778
        $course_id = api_get_course_int_id();
5779
        $table = Database::get_course_table(TABLE_LP_MAIN);
5780
        $sql = "SELECT * FROM $table
5781
                WHERE c_id = $course_id
5782
                ORDER BY display_order";
5783
        $res = Database::query($sql);
5784
        if ($res === false) {
5785
            return false;
5786
        }
5787
5788
        $num = Database::num_rows($res);
5789
        // First check the order is correct, globally (might be wrong because
5790
        // of versions < 1.8.4).
5791
        if ($num > 0) {
5792
            $i = 1;
5793
            while ($row = Database::fetch_array($res)) {
5794
                if ($row['display_order'] != $i) {
5795
                    // If we find a gap in the order, we need to fix it.
5796
                    $sql = "UPDATE $table SET display_order = $i
5797
                            WHERE iid = ".$row['iid'];
5798
                    Database::query($sql);
5799
                }
5800
                $i++;
5801
            }
5802
        }
5803
5804
        return true;
5805
    }
5806
5807
    /**
5808
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5809
     *
5810
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5811
     */
5812
    public function update_reinit()
5813
    {
5814
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5815
        $sql = "SELECT * FROM $lp_table
5816
                WHERE iid = ".$this->get_id();
5817
        $res = Database::query($sql);
5818
        if (Database::num_rows($res) > 0) {
5819
            $row = Database::fetch_array($res);
5820
            $force = $row['prevent_reinit'];
5821
            if ($force == 1) {
5822
                $force = 0;
5823
            } elseif ($force == 0) {
5824
                $force = 1;
5825
            }
5826
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5827
                    WHERE iid = ".$this->get_id();
5828
            Database::query($sql);
5829
            $this->prevent_reinit = $force;
5830
5831
            return $force;
5832
        }
5833
5834
        return -1;
5835
    }
5836
5837
    /**
5838
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5839
     *
5840
     * @return string 'single', 'multi' or 'seriousgame'
5841
     *
5842
     * @author ndiechburg <[email protected]>
5843
     */
5844
    public function get_attempt_mode()
5845
    {
5846
        //Set default value for seriousgame_mode
5847
        if (!isset($this->seriousgame_mode)) {
5848
            $this->seriousgame_mode = 0;
5849
        }
5850
        // Set default value for prevent_reinit
5851
        if (!isset($this->prevent_reinit)) {
5852
            $this->prevent_reinit = 1;
5853
        }
5854
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5855
            return 'seriousgame';
5856
        }
5857
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5858
            return 'single';
5859
        }
5860
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5861
            return 'multiple';
5862
        }
5863
5864
        return 'single';
5865
    }
5866
5867
    /**
5868
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5869
     *
5870
     * @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...
5871
     *
5872
     * @return bool
5873
     *
5874
     * @author ndiechburg <[email protected]>
5875
     */
5876
    public function set_attempt_mode($mode)
5877
    {
5878
        switch ($mode) {
5879
            case 'seriousgame':
5880
                $sg_mode = 1;
5881
                $prevent_reinit = 1;
5882
                break;
5883
            case 'single':
5884
                $sg_mode = 0;
5885
                $prevent_reinit = 1;
5886
                break;
5887
            case 'multiple':
5888
                $sg_mode = 0;
5889
                $prevent_reinit = 0;
5890
                break;
5891
            default:
5892
                $sg_mode = 0;
5893
                $prevent_reinit = 0;
5894
                break;
5895
        }
5896
        $this->prevent_reinit = $prevent_reinit;
5897
        $this->seriousgame_mode = $sg_mode;
5898
        $table = Database::get_course_table(TABLE_LP_MAIN);
5899
        $sql = "UPDATE $table SET
5900
                prevent_reinit = $prevent_reinit ,
5901
                seriousgame_mode = $sg_mode
5902
                WHERE iid = ".$this->get_id();
5903
        $res = Database::query($sql);
5904
        if ($res) {
5905
            return true;
5906
        } else {
5907
            return false;
5908
        }
5909
    }
5910
5911
    /**
5912
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5913
     *
5914
     * @author ndiechburg <[email protected]>
5915
     */
5916
    public function switch_attempt_mode()
5917
    {
5918
        $mode = $this->get_attempt_mode();
5919
        switch ($mode) {
5920
            case 'single':
5921
                $next_mode = 'multiple';
5922
                break;
5923
            case 'multiple':
5924
                $next_mode = 'seriousgame';
5925
                break;
5926
            case 'seriousgame':
5927
            default:
5928
                $next_mode = 'single';
5929
                break;
5930
        }
5931
        $this->set_attempt_mode($next_mode);
5932
    }
5933
5934
    /**
5935
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5936
     * but possibility to do again a completed item.
5937
     *
5938
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5939
     *
5940
     * @author ndiechburg <[email protected]>
5941
     */
5942
    public function set_seriousgame_mode()
5943
    {
5944
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5945
        $sql = "SELECT * FROM $lp_table
5946
                WHERE iid = ".$this->get_id();
5947
        $res = Database::query($sql);
5948
        if (Database::num_rows($res) > 0) {
5949
            $row = Database::fetch_array($res);
5950
            $force = $row['seriousgame_mode'];
5951
            if ($force == 1) {
5952
                $force = 0;
5953
            } elseif ($force == 0) {
5954
                $force = 1;
5955
            }
5956
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5957
			        WHERE iid = ".$this->get_id();
5958
            Database::query($sql);
5959
            $this->seriousgame_mode = $force;
5960
5961
            return $force;
5962
        }
5963
5964
        return -1;
5965
    }
5966
5967
    /**
5968
     * Updates the "scorm_debug" value that shows or hide the debug window.
5969
     *
5970
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5971
     */
5972
    public function update_scorm_debug()
5973
    {
5974
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5975
        $sql = "SELECT * FROM $lp_table
5976
                WHERE iid = ".$this->get_id();
5977
        $res = Database::query($sql);
5978
        if (Database::num_rows($res) > 0) {
5979
            $row = Database::fetch_array($res);
5980
            $force = $row['debug'];
5981
            if ($force == 1) {
5982
                $force = 0;
5983
            } elseif ($force == 0) {
5984
                $force = 1;
5985
            }
5986
            $sql = "UPDATE $lp_table SET debug = $force
5987
                    WHERE iid = ".$this->get_id();
5988
            Database::query($sql);
5989
            $this->scorm_debug = $force;
5990
5991
            return $force;
5992
        }
5993
5994
        return -1;
5995
    }
5996
5997
    /**
5998
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5999
     *
6000
     * @author Kevin Van Den Haute
6001
     *
6002
     * @param  array
6003
     */
6004
    public function tree_array($array)
6005
    {
6006
        $array = $this->sort_tree_array($array);
6007
        $this->create_tree_array($array);
6008
    }
6009
6010
    /**
6011
     * Creates an array with the elements of the learning path tree in it.
6012
     *
6013
     * @author Kevin Van Den Haute
6014
     *
6015
     * @param array $array
6016
     * @param int   $parent
6017
     * @param int   $depth
6018
     * @param array $tmp
6019
     */
6020
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6021
    {
6022
        if (is_array($array)) {
6023
            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...
6024
                if ($array[$i]['parent_item_id'] == $parent) {
6025
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6026
                        $tmp[] = $array[$i]['parent_item_id'];
6027
                        $depth++;
6028
                    }
6029
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6030
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6031
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6032
6033
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6034
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6035
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6036
                    $this->arrMenu[] = [
6037
                        'id' => $array[$i]['id'],
6038
                        'ref' => $ref,
6039
                        'item_type' => $array[$i]['item_type'],
6040
                        'title' => $array[$i]['title'],
6041
                        'title_raw' => $array[$i]['title_raw'],
6042
                        'path' => $path,
6043
                        'description' => $array[$i]['description'],
6044
                        'parent_item_id' => $array[$i]['parent_item_id'],
6045
                        'previous_item_id' => $array[$i]['previous_item_id'],
6046
                        'next_item_id' => $array[$i]['next_item_id'],
6047
                        'min_score' => $array[$i]['min_score'],
6048
                        'max_score' => $array[$i]['max_score'],
6049
                        'mastery_score' => $array[$i]['mastery_score'],
6050
                        'display_order' => $array[$i]['display_order'],
6051
                        'prerequisite' => $preq,
6052
                        'depth' => $depth,
6053
                        'audio' => $audio,
6054
                        'prerequisite_min_score' => $prerequisiteMinScore,
6055
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6056
                    ];
6057
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6058
                }
6059
            }
6060
        }
6061
    }
6062
6063
    /**
6064
     * Sorts a multi dimensional array by parent id and display order.
6065
     *
6066
     * @author Kevin Van Den Haute
6067
     *
6068
     * @param array $array (array with al the learning path items in it)
6069
     *
6070
     * @return array
6071
     */
6072
    public function sort_tree_array($array)
6073
    {
6074
        foreach ($array as $key => $row) {
6075
            $parent[$key] = $row['parent_item_id'];
6076
            $position[$key] = $row['display_order'];
6077
        }
6078
6079
        if (count($array) > 0) {
6080
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6081
        }
6082
6083
        return $array;
6084
    }
6085
6086
    /**
6087
     * Function that creates a html list of learning path items so that we can add audio files to them.
6088
     *
6089
     * @author Kevin Van Den Haute
6090
     *
6091
     * @return string
6092
     */
6093
    public function overview()
6094
    {
6095
        $return = '';
6096
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6097
6098
        // we need to start a form when we want to update all the mp3 files
6099
        if ($update_audio == 'true') {
6100
            $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">';
6101
        }
6102
        $return .= '<div id="message"></div>';
6103
        if (count($this->items) == 0) {
6104
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6105
        } else {
6106
            $return_audio = '<table class="table table-hover table-striped data_table">';
6107
            $return_audio .= '<tr>';
6108
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6109
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6110
            $return_audio .= '</tr>';
6111
6112
            if ($update_audio != 'true') {
6113
                $return .= '<div class="col-md-12">';
6114
                $return .= self::return_new_tree($update_audio);
6115
                $return .= '</div>';
6116
                $return .= Display::div(
6117
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6118
                    ['style' => 'float:left; margin-top:15px;width:100%']
6119
                );
6120
            } else {
6121
                $return_audio .= self::return_new_tree($update_audio);
6122
                $return .= $return_audio.'</table>';
6123
            }
6124
6125
            // We need to close the form when we are updating the mp3 files.
6126
            if ($update_audio == 'true') {
6127
                $return .= '<div class="footer-audio">';
6128
                $return .= Display::button(
6129
                    'save_audio',
6130
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6131
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6132
                );
6133
                $return .= '</div>';
6134
            }
6135
        }
6136
6137
        // We need to close the form when we are updating the mp3 files.
6138
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6139
            $return .= '</form>';
6140
        }
6141
6142
        return $return;
6143
    }
6144
6145
    /**
6146
     * @param string $update_audio
6147
     *
6148
     * @return array
6149
     */
6150
    public function processBuildMenuElements($update_audio = 'false')
6151
    {
6152
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6153
        $arrLP = $this->getItemsForForm();
6154
6155
        $this->tree_array($arrLP);
6156
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6157
        unset($this->arrMenu);
6158
        $default_data = null;
6159
        $default_content = null;
6160
        $elements = [];
6161
        $return_audio = null;
6162
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6163
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6164
        $countItems = count($arrLP);
6165
6166
        $upIcon = Display::return_icon(
6167
            'up.png',
6168
            get_lang('Up'),
6169
            [],
6170
            ICON_SIZE_TINY
6171
        );
6172
6173
        $disableUpIcon = Display::return_icon(
6174
            'up_na.png',
6175
            get_lang('Up'),
6176
            [],
6177
            ICON_SIZE_TINY
6178
        );
6179
6180
        $downIcon = Display::return_icon(
6181
            'down.png',
6182
            get_lang('Down'),
6183
            [],
6184
            ICON_SIZE_TINY
6185
        );
6186
6187
        $disableDownIcon = Display::return_icon(
6188
            'down_na.png',
6189
            get_lang('Down'),
6190
            [],
6191
            ICON_SIZE_TINY
6192
        );
6193
6194
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6195
6196
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6197
        $plugin = null;
6198
        if ($pluginCalendar) {
6199
            $plugin = LearningCalendarPlugin::create();
6200
        }
6201
6202
        for ($i = 0; $i < $countItems; $i++) {
6203
            $parent_id = $arrLP[$i]['parent_item_id'];
6204
            $title = $arrLP[$i]['title'];
6205
            $title_cut = $arrLP[$i]['title_raw'];
6206
            if ($show === false) {
6207
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6208
            }
6209
            // Link for the documents
6210
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6211
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6212
                $title_cut = Display::url(
6213
                    $title_cut,
6214
                    $url,
6215
                    [
6216
                        'class' => 'ajax moved',
6217
                        'data-title' => $title,
6218
                        'title' => $title,
6219
                    ]
6220
                );
6221
            }
6222
6223
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6224
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6225
                Session::write('pathItem', $arrLP[$i]['path']);
6226
            }
6227
6228
            $oddClass = 'row_even';
6229
            if (($i % 2) == 0) {
6230
                $oddClass = 'row_odd';
6231
            }
6232
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6233
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6234
6235
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6236
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6237
            } else {
6238
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6239
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6240
                } else {
6241
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6242
                        $icon = Display::return_icon('certificate.png');
6243
                    } elseif (TOOL_XAPI === $arrLP[$i]['item_type']) {
6244
                        $icon = Display::return_icon('import_scorm.png');
6245
                    } else {
6246
                        $icon = Display::return_icon('folder_document.gif');
6247
                    }
6248
                }
6249
            }
6250
6251
            // The audio column.
6252
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6253
            $audio = '';
6254
            if (!$update_audio || $update_audio != 'true') {
6255
                if (empty($arrLP[$i]['audio'])) {
6256
                    $audio .= '';
6257
                }
6258
            } else {
6259
                $types = self::getChapterTypes();
6260
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6261
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6262
                    if (!empty($arrLP[$i]['audio'])) {
6263
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6264
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6265
                    }
6266
                }
6267
            }
6268
6269
            $return_audio .= Display::span($icon.' '.$title).
6270
                Display::tag(
6271
                    'td',
6272
                    $audio,
6273
                    ['style' => '']
6274
                );
6275
            $return_audio .= '</td>';
6276
            $move_icon = '';
6277
            $move_item_icon = '';
6278
            $edit_icon = '';
6279
            $delete_icon = '';
6280
            $audio_icon = '';
6281
            $prerequisities_icon = '';
6282
            $forumIcon = '';
6283
            $previewIcon = '';
6284
            $pluginCalendarIcon = '';
6285
            $orderIcons = '';
6286
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6287
6288
            if ($is_allowed_to_edit) {
6289
                if (!$update_audio || $update_audio != 'true') {
6290
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6291
                        $move_icon .= '<a class="moved" href="#">';
6292
                        $move_icon .= Display::return_icon(
6293
                            'move_everywhere.png',
6294
                            get_lang('Move'),
6295
                            [],
6296
                            ICON_SIZE_TINY
6297
                        );
6298
                        $move_icon .= '</a>';
6299
                    }
6300
                }
6301
6302
                // No edit for this item types
6303
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6304
                    if ($arrLP[$i]['item_type'] != 'dir') {
6305
                        $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">';
6306
                        $edit_icon .= Display::return_icon(
6307
                            'edit.png',
6308
                            get_lang('LearnpathEditModule'),
6309
                            [],
6310
                            ICON_SIZE_TINY
6311
                        );
6312
                        $edit_icon .= '</a>';
6313
6314
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6315
                            $forumThread = null;
6316
                            if (isset($this->items[$arrLP[$i]['id']])) {
6317
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6318
                                    $this->course_int_id,
6319
                                    $this->lp_session_id
6320
                                );
6321
                            }
6322
                            if ($forumThread) {
6323
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6324
                                        'action' => 'dissociate_forum',
6325
                                        'id' => $arrLP[$i]['id'],
6326
                                        'lp_id' => $this->lp_id,
6327
                                    ]);
6328
                                $forumIcon = Display::url(
6329
                                    Display::return_icon(
6330
                                        'forum.png',
6331
                                        get_lang('DissociateForumToLPItem'),
6332
                                        [],
6333
                                        ICON_SIZE_TINY
6334
                                    ),
6335
                                    $forumIconUrl,
6336
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6337
                                );
6338
                            } else {
6339
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6340
                                        'action' => 'create_forum',
6341
                                        'id' => $arrLP[$i]['id'],
6342
                                        'lp_id' => $this->lp_id,
6343
                                    ]);
6344
                                $forumIcon = Display::url(
6345
                                    Display::return_icon(
6346
                                        'forum.png',
6347
                                        get_lang('AssociateForumToLPItem'),
6348
                                        [],
6349
                                        ICON_SIZE_TINY
6350
                                    ),
6351
                                    $forumIconUrl,
6352
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6353
                                );
6354
                            }
6355
                        }
6356
                    } else {
6357
                        $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">';
6358
                        $edit_icon .= Display::return_icon(
6359
                            'edit.png',
6360
                            get_lang('LearnpathEditModule'),
6361
                            [],
6362
                            ICON_SIZE_TINY
6363
                        );
6364
                        $edit_icon .= '</a>';
6365
                    }
6366
                } else {
6367
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6368
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6369
                        $edit_icon .= Display::return_icon(
6370
                            'edit.png',
6371
                            get_lang('Edit'),
6372
                            [],
6373
                            ICON_SIZE_TINY
6374
                        );
6375
                        $edit_icon .= '</a>';
6376
                    }
6377
                }
6378
6379
                if ($pluginCalendar) {
6380
                    $pluginLink = $pluginUrl.
6381
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6382
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6383
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6384
                    if ($itemInfo && $itemInfo['value'] == 1) {
6385
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6386
                    }
6387
                    $pluginCalendarIcon = Display::url(
6388
                        $iconCalendar,
6389
                        $pluginLink,
6390
                        ['class' => 'btn btn-default']
6391
                    );
6392
                }
6393
6394
                if ($arrLP[$i]['item_type'] != 'final_item') {
6395
                    $orderIcons = Display::url(
6396
                        $upIcon,
6397
                        'javascript:void(0)',
6398
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6399
                    );
6400
                    $orderIcons .= Display::url(
6401
                        $downIcon,
6402
                        'javascript:void(0)',
6403
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6404
                    );
6405
                }
6406
6407
                $delete_icon .= ' <a
6408
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6409
                    onclick="return confirmation(\''.addslashes($title).'\');"
6410
                    class="btn btn-default">';
6411
                $delete_icon .= Display::return_icon(
6412
                    'delete.png',
6413
                    get_lang('LearnpathDeleteModule'),
6414
                    [],
6415
                    ICON_SIZE_TINY
6416
                );
6417
                $delete_icon .= '</a>';
6418
6419
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6420
                $previewImage = Display::return_icon(
6421
                    'preview_view.png',
6422
                    get_lang('Preview'),
6423
                    [],
6424
                    ICON_SIZE_TINY
6425
                );
6426
6427
                switch ($arrLP[$i]['item_type']) {
6428
                    case TOOL_DOCUMENT:
6429
                    case TOOL_LP_FINAL_ITEM:
6430
                    case TOOL_READOUT_TEXT:
6431
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6432
                        $previewIcon = Display::url(
6433
                            $previewImage,
6434
                            $urlPreviewLink,
6435
                            [
6436
                                'target' => '_blank',
6437
                                'class' => 'btn btn-default',
6438
                                'data-title' => $arrLP[$i]['title'],
6439
                                'title' => $arrLP[$i]['title'],
6440
                            ]
6441
                        );
6442
                        break;
6443
                    case TOOL_THREAD:
6444
                    case TOOL_FORUM:
6445
                    case TOOL_QUIZ:
6446
                    case TOOL_STUDENTPUBLICATION:
6447
                    case TOOL_LP_FINAL_ITEM:
6448
                    case TOOL_LINK:
6449
                        $class = 'btn btn-default';
6450
                        $target = '_blank';
6451
                        $link = self::rl_get_resource_link_for_learnpath(
6452
                            $this->course_int_id,
6453
                            $this->lp_id,
6454
                            $arrLP[$i]['id'],
6455
                            0
6456
                        );
6457
                        $previewIcon = Display::url(
6458
                            $previewImage,
6459
                            $link,
6460
                            [
6461
                                'class' => $class,
6462
                                'data-title' => $arrLP[$i]['title'],
6463
                                'title' => $arrLP[$i]['title'],
6464
                                'target' => $target,
6465
                            ]
6466
                        );
6467
                        break;
6468
                    default:
6469
                        $previewIcon = Display::url(
6470
                            $previewImage,
6471
                            $url.'&action=view_item',
6472
                            ['class' => 'btn btn-default', 'target' => '_blank']
6473
                        );
6474
                        break;
6475
                }
6476
6477
                if ($arrLP[$i]['item_type'] != 'dir') {
6478
                    $prerequisities_icon = Display::url(
6479
                        Display::return_icon(
6480
                            'accept.png',
6481
                            get_lang('LearnpathPrerequisites'),
6482
                            [],
6483
                            ICON_SIZE_TINY
6484
                        ),
6485
                        $url.'&action=edit_item_prereq',
6486
                        ['class' => 'btn btn-default']
6487
                    );
6488
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6489
                        $move_item_icon = Display::url(
6490
                            Display::return_icon(
6491
                                'move.png',
6492
                                get_lang('Move'),
6493
                                [],
6494
                                ICON_SIZE_TINY
6495
                            ),
6496
                            $url.'&action=move_item',
6497
                            ['class' => 'btn btn-default']
6498
                        );
6499
                    }
6500
                    $audio_icon = Display::url(
6501
                        Display::return_icon(
6502
                            'audio.png',
6503
                            get_lang('UplUpload'),
6504
                            [],
6505
                            ICON_SIZE_TINY
6506
                        ),
6507
                        $url.'&action=add_audio',
6508
                        ['class' => 'btn btn-default']
6509
                    );
6510
                }
6511
            }
6512
            if ($update_audio != 'true') {
6513
                $row = $move_icon.' '.$icon.
6514
                    Display::span($title_cut).
6515
                    Display::tag(
6516
                        'div',
6517
                        "<div class=\"btn-group btn-group-xs\">
6518
                                    $previewIcon
6519
                                    $audio
6520
                                    $edit_icon
6521
                                    $pluginCalendarIcon
6522
                                    $forumIcon
6523
                                    $prerequisities_icon
6524
                                    $move_item_icon
6525
                                    $audio_icon
6526
                                    $orderIcons
6527
                                    $delete_icon
6528
                                </div>",
6529
                        ['class' => 'btn-toolbar button_actions']
6530
                    );
6531
            } else {
6532
                $row =
6533
                    Display::span($title.$icon).
6534
                    Display::span($audio, ['class' => 'button_actions']);
6535
            }
6536
6537
            $default_data[$arrLP[$i]['id']] = $row;
6538
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6539
6540
            if (empty($parent_id)) {
6541
                $elements[$arrLP[$i]['id']]['data'] = $row;
6542
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6543
            } else {
6544
                $parent_arrays = [];
6545
                if ($arrLP[$i]['depth'] > 1) {
6546
                    // Getting list of parents
6547
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6548
                        foreach ($arrLP as $item) {
6549
                            if ($item['id'] == $parent_id) {
6550
                                if ($item['parent_item_id'] == 0) {
6551
                                    $parent_id = $item['id'];
6552
                                    break;
6553
                                } else {
6554
                                    $parent_id = $item['parent_item_id'];
6555
                                    if (empty($parent_arrays)) {
6556
                                        $parent_arrays[] = intval($item['id']);
6557
                                    }
6558
                                    $parent_arrays[] = $parent_id;
6559
                                    break;
6560
                                }
6561
                            }
6562
                        }
6563
                    }
6564
                }
6565
6566
                if (!empty($parent_arrays)) {
6567
                    $parent_arrays = array_reverse($parent_arrays);
6568
                    $val = '$elements';
6569
                    $x = 0;
6570
                    foreach ($parent_arrays as $item) {
6571
                        if ($x != count($parent_arrays) - 1) {
6572
                            $val .= '["'.$item.'"]["children"]';
6573
                        } else {
6574
                            $val .= '["'.$item.'"]["children"]';
6575
                        }
6576
                        $x++;
6577
                    }
6578
                    $val .= "";
6579
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6580
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6581
                } else {
6582
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6583
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6584
                }
6585
            }
6586
        }
6587
6588
        return [
6589
            'elements' => $elements,
6590
            'default_data' => $default_data,
6591
            'default_content' => $default_content,
6592
            'return_audio' => $return_audio,
6593
        ];
6594
    }
6595
6596
    /**
6597
     * @param string $updateAudio true/false strings
6598
     *
6599
     * @return string
6600
     */
6601
    public function returnLpItemList($updateAudio)
6602
    {
6603
        $result = $this->processBuildMenuElements($updateAudio);
6604
6605
        $html = self::print_recursive(
6606
            $result['elements'],
6607
            $result['default_data'],
6608
            $result['default_content']
6609
        );
6610
6611
        if (!empty($html)) {
6612
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6613
        }
6614
6615
        return $html;
6616
    }
6617
6618
    /**
6619
     * @param string $update_audio
6620
     * @param bool   $drop_element_here
6621
     *
6622
     * @return string
6623
     */
6624
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6625
    {
6626
        $result = $this->processBuildMenuElements($update_audio);
6627
6628
        $list = '<ul id="lp_item_list">';
6629
        $tree = $this->print_recursive(
6630
            $result['elements'],
6631
            $result['default_data'],
6632
            $result['default_content']
6633
        );
6634
6635
        if (!empty($tree)) {
6636
            $list .= $tree;
6637
        } else {
6638
            if ($drop_element_here) {
6639
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6640
            }
6641
        }
6642
        $list .= '</ul>';
6643
6644
        $return = Display::panelCollapse(
6645
            $this->name,
6646
            $list,
6647
            'scorm-list',
6648
            null,
6649
            'scorm-list-accordion',
6650
            'scorm-list-collapse'
6651
        );
6652
6653
        if ($update_audio === 'true') {
6654
            $return = $result['return_audio'];
6655
        }
6656
6657
        return $return;
6658
    }
6659
6660
    /**
6661
     * @param array $elements
6662
     * @param array $default_data
6663
     * @param array $default_content
6664
     *
6665
     * @return string
6666
     */
6667
    public function print_recursive($elements, $default_data, $default_content)
6668
    {
6669
        $return = '';
6670
        foreach ($elements as $key => $item) {
6671
            if (isset($item['load_data']) || empty($item['data'])) {
6672
                $item['data'] = $default_data[$item['load_data']];
6673
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6674
            }
6675
            $sub_list = '';
6676
            if (isset($item['type']) && $item['type'] === 'dir') {
6677
                // empty value
6678
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6679
            }
6680
            if (empty($item['children'])) {
6681
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6682
                $active = null;
6683
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6684
                    $active = 'active';
6685
                }
6686
                $return .= Display::tag(
6687
                    'li',
6688
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6689
                    ['id' => $key, 'class' => 'record li_container']
6690
                );
6691
            } else {
6692
                // Sections
6693
                $data = '';
6694
                if (isset($item['children'])) {
6695
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6696
                }
6697
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6698
                $return .= Display::tag(
6699
                    'li',
6700
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6701
                    ['id' => $key, 'class' => 'record li_container']
6702
                );
6703
            }
6704
        }
6705
6706
        return $return;
6707
    }
6708
6709
    /**
6710
     * This function builds the action menu.
6711
     *
6712
     * @param bool   $returnString           Optional
6713
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6714
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6715
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6716
     * @param string $action
6717
     * @param array  $extraField
6718
     *
6719
     * @return string
6720
     */
6721
    public function build_action_menu(
6722
        $returnString = false,
6723
        $showRequirementButtons = true,
6724
        $isConfigPage = false,
6725
        $allowExpand = true,
6726
        $action = '',
6727
        $extraField = []
6728
    ) {
6729
        $actionsRight = '';
6730
        $lpId = $this->lp_id;
6731
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6732
            $back = Display::url(
6733
                Display::return_icon(
6734
                    'back.png',
6735
                    get_lang('ReturnToLearningPaths'),
6736
                    '',
6737
                    ICON_SIZE_MEDIUM
6738
                ),
6739
                'lp_controller.php?'.api_get_cidreq()
6740
            );
6741
        } else {
6742
            $back = Display::url(
6743
                Display::return_icon(
6744
                    'back.png',
6745
                    get_lang('Back'),
6746
                    '',
6747
                    ICON_SIZE_MEDIUM
6748
                ),
6749
                $extraField['backTo']
6750
            );
6751
        }
6752
6753
        $actionsLeft = $back;
6754
        $actionsLeft .= Display::url(
6755
            Display::return_icon(
6756
                'preview_view.png',
6757
                get_lang('Preview'),
6758
                '',
6759
                ICON_SIZE_MEDIUM
6760
            ),
6761
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6762
                'action' => 'view',
6763
                'lp_id' => $lpId,
6764
                'isStudentView' => 'true',
6765
            ])
6766
        );
6767
6768
        $actionsLeft .= Display::url(
6769
            Display::return_icon(
6770
                'upload_audio.png',
6771
                get_lang('UpdateAllAudioFragments'),
6772
                '',
6773
                ICON_SIZE_MEDIUM
6774
            ),
6775
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6776
                'action' => 'admin_view',
6777
                'lp_id' => $lpId,
6778
                'updateaudio' => 'true',
6779
            ])
6780
        );
6781
6782
        $subscriptionSettings = self::getSubscriptionSettings();
6783
        $request = api_request_uri();
6784
        if (strpos($request, 'edit') === false) {
6785
            $actionsLeft .= Display::url(
6786
                Display::return_icon(
6787
                    'settings.png',
6788
                    get_lang('CourseSettings'),
6789
                    '',
6790
                    ICON_SIZE_MEDIUM
6791
                ),
6792
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6793
                    'action' => 'edit',
6794
                    'lp_id' => $lpId,
6795
                ])
6796
            );
6797
        }
6798
6799
        if ((strpos($request, 'build') === false &&
6800
            strpos($request, 'add_item') === false) ||
6801
            in_array($action, ['add_audio'])
6802
        ) {
6803
            $actionsLeft .= Display::url(
6804
                Display::return_icon(
6805
                    'edit.png',
6806
                    get_lang('Edit'),
6807
                    '',
6808
                    ICON_SIZE_MEDIUM
6809
                ),
6810
                'lp_controller.php?'.http_build_query([
6811
                    'action' => 'build',
6812
                    'lp_id' => $lpId,
6813
                ]).'&'.api_get_cidreq()
6814
            );
6815
        }
6816
6817
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6818
            if ($this->subscribeUsers == 1 &&
6819
                $subscriptionSettings['allow_add_users_to_lp']) {
6820
                $actionsLeft .= Display::url(
6821
                    Display::return_icon(
6822
                        'user.png',
6823
                        get_lang('SubscribeUsersToLp'),
6824
                        '',
6825
                        ICON_SIZE_MEDIUM
6826
                    ),
6827
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6828
                );
6829
            }
6830
        }
6831
6832
        if ($allowExpand) {
6833
            $actionsLeft .= Display::url(
6834
                Display::return_icon(
6835
                    'expand.png',
6836
                    get_lang('Expand'),
6837
                    ['id' => 'expand'],
6838
                    ICON_SIZE_MEDIUM
6839
                ).
6840
                Display::return_icon(
6841
                    'contract.png',
6842
                    get_lang('Collapse'),
6843
                    ['id' => 'contract', 'class' => 'hide'],
6844
                    ICON_SIZE_MEDIUM
6845
                ),
6846
                '#',
6847
                ['role' => 'button', 'id' => 'hide_bar_template']
6848
            );
6849
        }
6850
6851
        if ($showRequirementButtons) {
6852
            $buttons = [
6853
                [
6854
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6855
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6856
                        'action' => 'set_previous_step_as_prerequisite',
6857
                        'lp_id' => $lpId,
6858
                    ]),
6859
                ],
6860
                [
6861
                    'title' => get_lang('ClearAllPrerequisites'),
6862
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6863
                        'action' => 'clear_prerequisites',
6864
                        'lp_id' => $lpId,
6865
                    ]),
6866
                ],
6867
            ];
6868
            $actionsRight = Display::groupButtonWithDropDown(
6869
                get_lang('PrerequisitesOptions'),
6870
                $buttons,
6871
                true
6872
            );
6873
        }
6874
6875
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6876
            $actionsLeft .= Display::url(
6877
                Display::return_icon(
6878
                    'add-groups.png',
6879
                    get_lang('Author'),
6880
                    '',
6881
                    ICON_SIZE_MEDIUM
6882
                ),
6883
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6884
                    'action' => 'author_view',
6885
                    'lp_id' => $lpId,
6886
                ])
6887
            );
6888
        }
6889
6890
        $toolbar = Display::toolbarAction(
6891
            'actions-lp-controller',
6892
            [$actionsLeft, $actionsRight]
6893
        );
6894
6895
        if ($returnString) {
6896
            return $toolbar;
6897
        }
6898
6899
        echo $toolbar;
6900
    }
6901
6902
    /**
6903
     * Creates the default learning path folder.
6904
     *
6905
     * @param array $course
6906
     * @param int   $creatorId
6907
     *
6908
     * @return bool
6909
     */
6910
    public static function generate_learning_path_folder($course, $creatorId = 0)
6911
    {
6912
        // Creating learning_path folder
6913
        $dir = '/learning_path';
6914
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6915
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6916
6917
        $folder = false;
6918
        if (!is_dir($filepath.'/'.$dir)) {
6919
            $folderData = create_unexisting_directory(
6920
                $course,
6921
                $creatorId,
6922
                0,
6923
                null,
6924
                0,
6925
                $filepath,
6926
                $dir,
6927
                get_lang('LearningPaths'),
6928
                0
6929
            );
6930
            if (!empty($folderData)) {
6931
                $folder = true;
6932
            }
6933
        } else {
6934
            $folder = true;
6935
        }
6936
6937
        return $folder;
6938
    }
6939
6940
    /**
6941
     * @param array  $course
6942
     * @param string $lp_name
6943
     * @param int    $creatorId
6944
     *
6945
     * @return array
6946
     */
6947
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6948
    {
6949
        $filepath = '';
6950
        $dir = '/learning_path/';
6951
6952
        if (empty($lp_name)) {
6953
            $lp_name = $this->name;
6954
        }
6955
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6956
        $folder = self::generate_learning_path_folder($course, $creatorId);
6957
6958
        // Limits title size
6959
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6960
        $dir = $dir.$title;
6961
6962
        // Creating LP folder
6963
        $documentId = null;
6964
        if ($folder) {
6965
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6966
            if (!is_dir($filepath.'/'.$dir)) {
6967
                $folderData = create_unexisting_directory(
6968
                    $course,
6969
                    $creatorId,
6970
                    0,
6971
                    0,
6972
                    0,
6973
                    $filepath,
6974
                    $dir,
6975
                    $lp_name
6976
                );
6977
                if (!empty($folderData)) {
6978
                    $folder = true;
6979
                }
6980
6981
                $documentId = $folderData['id'];
6982
            } else {
6983
                $folder = true;
6984
            }
6985
            $dir = $dir.'/';
6986
            if ($folder) {
6987
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6988
            }
6989
        }
6990
6991
        if (empty($documentId)) {
6992
            $dir = api_remove_trailing_slash($dir);
6993
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6994
        }
6995
6996
        $array = [
6997
            'dir' => $dir,
6998
            'filepath' => $filepath,
6999
            'folder' => $folder,
7000
            'id' => $documentId,
7001
        ];
7002
7003
        return $array;
7004
    }
7005
7006
    /**
7007
     * Create a new document //still needs some finetuning.
7008
     *
7009
     * @param array  $courseInfo
7010
     * @param string $content
7011
     * @param string $title
7012
     * @param string $extension
7013
     * @param int    $parentId
7014
     * @param int    $creatorId  creator id
7015
     *
7016
     * @return int
7017
     */
7018
    public function create_document(
7019
        $courseInfo,
7020
        $content = '',
7021
        $title = '',
7022
        $extension = 'html',
7023
        $parentId = 0,
7024
        $creatorId = 0
7025
    ) {
7026
        if (!empty($courseInfo)) {
7027
            $course_id = $courseInfo['real_id'];
7028
        } else {
7029
            $course_id = api_get_course_int_id();
7030
        }
7031
7032
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7033
        $sessionId = api_get_session_id();
7034
7035
        // Generates folder
7036
        $result = $this->generate_lp_folder($courseInfo);
7037
        $dir = $result['dir'];
7038
7039
        if (empty($parentId) || $parentId == '/') {
7040
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7041
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7042
7043
            if ($parentId === '/') {
7044
                $dir = '/';
7045
            }
7046
7047
            // Please, do not modify this dirname formatting.
7048
            if (strstr($dir, '..')) {
7049
                $dir = '/';
7050
            }
7051
7052
            if (!empty($dir[0]) && $dir[0] == '.') {
7053
                $dir = substr($dir, 1);
7054
            }
7055
            if (!empty($dir[0]) && $dir[0] != '/') {
7056
                $dir = '/'.$dir;
7057
            }
7058
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7059
                $dir .= '/';
7060
            }
7061
        } else {
7062
            $parentInfo = DocumentManager::get_document_data_by_id(
7063
                $parentId,
7064
                $courseInfo['code']
7065
            );
7066
            if (!empty($parentInfo)) {
7067
                $dir = $parentInfo['path'].'/';
7068
            }
7069
        }
7070
7071
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7072
        if (!is_dir($filepath)) {
7073
            $dir = '/';
7074
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7075
        }
7076
7077
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7078
        // is already escaped twice when it gets here.
7079
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7080
        if (!empty($title)) {
7081
            $title = api_replace_dangerous_char(stripslashes($title));
7082
        } else {
7083
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7084
        }
7085
7086
        $title = disable_dangerous_file($title);
7087
        $filename = $title;
7088
        $content = !empty($content) ? $content : $_POST['content_lp'];
7089
        $tmp_filename = $filename;
7090
7091
        $i = 0;
7092
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7093
            $tmp_filename = $filename.'_'.++$i;
7094
        }
7095
7096
        $filename = $tmp_filename.'.'.$extension;
7097
        if ($extension == 'html') {
7098
            $content = stripslashes($content);
7099
            $content = str_replace(
7100
                api_get_path(WEB_COURSE_PATH),
7101
                api_get_path(REL_PATH).'courses/',
7102
                $content
7103
            );
7104
7105
            // Change the path of mp3 to absolute.
7106
            // The first regexp deals with :// urls.
7107
            $content = preg_replace(
7108
                "|(flashvars=\"file=)([^:/]+)/|",
7109
                "$1".api_get_path(
7110
                    REL_COURSE_PATH
7111
                ).$courseInfo['path'].'/document/',
7112
                $content
7113
            );
7114
            // The second regexp deals with audio/ urls.
7115
            $content = preg_replace(
7116
                "|(flashvars=\"file=)([^/]+)/|",
7117
                "$1".api_get_path(
7118
                    REL_COURSE_PATH
7119
                ).$courseInfo['path'].'/document/$2/',
7120
                $content
7121
            );
7122
            // For flv player: To prevent edition problem with firefox,
7123
            // we have to use a strange tip (don't blame me please).
7124
            $content = str_replace(
7125
                '</body>',
7126
                '<style type="text/css">body{}</style></body>',
7127
                $content
7128
            );
7129
        }
7130
7131
        if (!file_exists($filepath.$filename)) {
7132
            if ($fp = @fopen($filepath.$filename, 'w')) {
7133
                fputs($fp, $content);
7134
                fclose($fp);
7135
7136
                $file_size = filesize($filepath.$filename);
7137
                $save_file_path = $dir.$filename;
7138
7139
                $document_id = add_document(
7140
                    $courseInfo,
7141
                    $save_file_path,
7142
                    'file',
7143
                    $file_size,
7144
                    $tmp_filename,
7145
                    '',
7146
                    0, //readonly
7147
                    true,
7148
                    null,
7149
                    $sessionId,
7150
                    $creatorId
7151
                );
7152
7153
                if ($document_id) {
7154
                    api_item_property_update(
7155
                        $courseInfo,
7156
                        TOOL_DOCUMENT,
7157
                        $document_id,
7158
                        'DocumentAdded',
7159
                        $creatorId,
7160
                        null,
7161
                        null,
7162
                        null,
7163
                        null,
7164
                        $sessionId
7165
                    );
7166
7167
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7168
                    $new_title = $originalTitle;
7169
7170
                    if ($new_comment || $new_title) {
7171
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7172
                        $ct = '';
7173
                        if ($new_comment) {
7174
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7175
                        }
7176
                        if ($new_title) {
7177
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7178
                        }
7179
7180
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7181
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7182
                        Database::query($sql);
7183
                    }
7184
                }
7185
7186
                return $document_id;
7187
            }
7188
        }
7189
    }
7190
7191
    /**
7192
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7193
     *
7194
     * @param array $_course array
7195
     */
7196
    public function edit_document($_course)
7197
    {
7198
        $course_id = api_get_course_int_id();
7199
        $urlAppend = api_get_configuration_value('url_append');
7200
        // Please, do not modify this dirname formatting.
7201
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7202
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7203
7204
        if (strstr($dir, '..')) {
7205
            $dir = '/';
7206
        }
7207
7208
        if (isset($dir[0]) && $dir[0] == '.') {
7209
            $dir = substr($dir, 1);
7210
        }
7211
7212
        if (isset($dir[0]) && $dir[0] != '/') {
7213
            $dir = '/'.$dir;
7214
        }
7215
7216
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7217
            $dir .= '/';
7218
        }
7219
7220
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7221
        if (!is_dir($filepath)) {
7222
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7223
        }
7224
7225
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7226
7227
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7228
            $document_id = (int) $_POST['path'];
7229
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7230
            if (empty($documentInfo)) {
7231
                // Try with iid
7232
                $table = Database::get_course_table(TABLE_DOCUMENT);
7233
                $sql = "SELECT id, path FROM $table
7234
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7235
                $res_doc = Database::query($sql);
7236
                $row = Database::fetch_array($res_doc);
7237
                if ($row) {
7238
                    $document_id = $row['id'];
7239
                    $documentPath = $row['path'];
7240
                }
7241
            } else {
7242
                $documentPath = $documentInfo['path'];
7243
            }
7244
7245
            $content = stripslashes($_POST['content_lp']);
7246
            $file = $filepath.$documentPath;
7247
7248
            if (!file_exists($file)) {
7249
                return false;
7250
            }
7251
7252
            if ($fp = @fopen($file, 'w')) {
7253
                $content = str_replace(
7254
                    api_get_path(WEB_COURSE_PATH),
7255
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7256
                    $content
7257
                );
7258
                // Change the path of mp3 to absolute.
7259
                // The first regexp deals with :// urls.
7260
                $content = preg_replace(
7261
                    "|(flashvars=\"file=)([^:/]+)/|",
7262
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7263
                    $content
7264
                );
7265
                // The second regexp deals with audio/ urls.
7266
                $content = preg_replace(
7267
                    "|(flashvars=\"file=)([^:/]+)/|",
7268
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7269
                    $content
7270
                );
7271
                fputs($fp, $content);
7272
                fclose($fp);
7273
7274
                $sql = "UPDATE $table_doc SET
7275
                            title='".Database::escape_string($_POST['title'])."'
7276
                        WHERE c_id = $course_id AND id = ".$document_id;
7277
                Database::query($sql);
7278
            }
7279
        }
7280
    }
7281
7282
    /**
7283
     * Displays the selected item, with a panel for manipulating the item.
7284
     *
7285
     * @param int    $item_id
7286
     * @param string $msg
7287
     * @param bool   $show_actions
7288
     *
7289
     * @return string
7290
     */
7291
    public function display_item($item_id, $msg = null, $show_actions = true)
7292
    {
7293
        $course_id = api_get_course_int_id();
7294
        $return = '';
7295
        if (is_numeric($item_id)) {
7296
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7297
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7298
                    WHERE lp.iid = ".intval($item_id);
7299
            $result = Database::query($sql);
7300
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7301
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7302
7303
                // Prevents wrong parent selection for document, see Bug#1251.
7304
                if ($row['item_type'] != 'dir') {
7305
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7306
                }
7307
7308
                if ($show_actions) {
7309
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7310
                }
7311
                $return .= '<div style="padding:10px;">';
7312
7313
                if ($msg != '') {
7314
                    $return .= $msg;
7315
                }
7316
7317
                $return .= '<h3>'.$row['title'].'</h3>';
7318
7319
                switch ($row['item_type']) {
7320
                    case TOOL_THREAD:
7321
                        $link = $this->rl_get_resource_link_for_learnpath(
7322
                            $course_id,
7323
                            $row['lp_id'],
7324
                            $item_id,
7325
                            0
7326
                        );
7327
                        $return .= Display::url(
7328
                            get_lang('GoToThread'),
7329
                            $link,
7330
                            ['class' => 'btn btn-primary']
7331
                        );
7332
                        break;
7333
                    case TOOL_FORUM:
7334
                        $return .= Display::url(
7335
                            get_lang('GoToForum'),
7336
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7337
                            ['class' => 'btn btn-primary']
7338
                        );
7339
                        break;
7340
                    case TOOL_QUIZ:
7341
                        if (!empty($row['path'])) {
7342
                            $exercise = new Exercise();
7343
                            $exercise->read($row['path']);
7344
                            $return .= $exercise->description.'<br />';
7345
                            $return .= Display::url(
7346
                                get_lang('GoToExercise'),
7347
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7348
                                ['class' => 'btn btn-primary']
7349
                            );
7350
                        }
7351
                        break;
7352
                    case TOOL_LP_FINAL_ITEM:
7353
                        $return .= $this->getSavedFinalItem();
7354
                        break;
7355
                    case TOOL_DOCUMENT:
7356
                    case TOOL_READOUT_TEXT:
7357
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7358
                        $sql_doc = "SELECT path FROM $tbl_doc
7359
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7360
                        $result = Database::query($sql_doc);
7361
                        $path_file = Database::result($result, 0, 0);
7362
                        $path_parts = pathinfo($path_file);
7363
                        // TODO: Correct the following naive comparisons.
7364
                        if (in_array($path_parts['extension'], [
7365
                            'html',
7366
                            'txt',
7367
                            'png',
7368
                            'jpg',
7369
                            'JPG',
7370
                            'jpeg',
7371
                            'JPEG',
7372
                            'gif',
7373
                            'swf',
7374
                            'pdf',
7375
                            'htm',
7376
                        ])) {
7377
                            $return .= $this->display_document($row['path'], true, true);
7378
                        }
7379
                        break;
7380
                    case TOOL_HOTPOTATOES:
7381
                        $return .= $this->display_document($row['path'], false, true);
7382
                        break;
7383
                }
7384
                $return .= '</div>';
7385
            }
7386
        }
7387
7388
        return $return;
7389
    }
7390
7391
    /**
7392
     * Shows the needed forms for editing a specific item.
7393
     *
7394
     * @param int $item_id
7395
     *
7396
     * @throws Exception
7397
     * @throws HTML_QuickForm_Error
7398
     *
7399
     * @return string
7400
     */
7401
    public function display_edit_item($item_id, $excludeExtraFields = [])
7402
    {
7403
        $course_id = api_get_course_int_id();
7404
        $return = '';
7405
        $item_id = (int) $item_id;
7406
7407
        if (empty($item_id)) {
7408
            return '';
7409
        }
7410
7411
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7412
        $sql = "SELECT * FROM $tbl_lp_item
7413
                WHERE iid = ".$item_id;
7414
        $res = Database::query($sql);
7415
        $row = Database::fetch_array($res);
7416
        switch ($row['item_type']) {
7417
            case 'dir':
7418
            case 'asset':
7419
            case 'sco':
7420
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7421
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7422
                $return .= $this->display_item_form(
7423
                        $row['item_type'],
7424
                        get_lang('EditCurrentChapter').' :',
7425
                        'edit',
7426
                        $item_id,
7427
                        $row
7428
                    );
7429
            } else {
7430
                $return .= $this->display_item_form(
7431
                        $row['item_type'],
7432
                        get_lang('EditCurrentChapter').' :',
7433
                        'edit_item',
7434
                        $item_id,
7435
                        $row
7436
                    );
7437
            }
7438
                break;
7439
            case TOOL_DOCUMENT:
7440
            case TOOL_READOUT_TEXT:
7441
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7442
                $sql = "SELECT lp.*, doc.path as dir
7443
                        FROM $tbl_lp_item as lp
7444
                        LEFT JOIN $tbl_doc as doc
7445
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7446
                        WHERE
7447
                            doc.c_id = $course_id AND
7448
                            lp.iid = ".$item_id;
7449
                $res_step = Database::query($sql);
7450
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7451
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7452
7453
                if ($row['item_type'] === TOOL_DOCUMENT) {
7454
                    $return .= $this->display_document_form(
7455
                        'edit',
7456
                        $item_id,
7457
                        $row_step,
7458
                        null,
7459
                        $excludeExtraFields
7460
                    );
7461
                }
7462
7463
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7464
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7465
                }
7466
                break;
7467
            case TOOL_LINK:
7468
                $linkId = (int) $row['path'];
7469
                if (!empty($linkId)) {
7470
                    $table = Database::get_course_table(TABLE_LINK);
7471
                    $sql = 'SELECT url FROM '.$table.'
7472
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7473
                    $res_link = Database::query($sql);
7474
                    $row_link = Database::fetch_array($res_link);
7475
                    if (empty($row_link)) {
7476
                        // Try with id
7477
                        $sql = 'SELECT url FROM '.$table.'
7478
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7479
                        $res_link = Database::query($sql);
7480
                        $row_link = Database::fetch_array($res_link);
7481
                    }
7482
7483
                    if (is_array($row_link)) {
7484
                        $row['url'] = $row_link['url'];
7485
                    }
7486
                }
7487
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7488
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7489
                break;
7490
            case TOOL_LP_FINAL_ITEM:
7491
                Session::write('finalItem', true);
7492
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7493
                $sql = "SELECT lp.*, doc.path as dir
7494
                        FROM $tbl_lp_item as lp
7495
                        LEFT JOIN $tbl_doc as doc
7496
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7497
                        WHERE
7498
                            doc.c_id = $course_id AND
7499
                            lp.iid = ".$item_id;
7500
                $res_step = Database::query($sql);
7501
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7502
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7503
                $return .= $this->display_document_form(
7504
                    'edit',
7505
                    $item_id,
7506
                    $row_step,
7507
                    null,
7508
                    $excludeExtraFields
7509
                );
7510
                break;
7511
            case TOOL_QUIZ:
7512
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7513
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7514
                break;
7515
            case TOOL_HOTPOTATOES:
7516
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7517
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7518
                break;
7519
            case TOOL_STUDENTPUBLICATION:
7520
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7521
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7522
                break;
7523
            case TOOL_FORUM:
7524
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7525
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7526
                break;
7527
            case TOOL_THREAD:
7528
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7529
                $return .= $this->display_thread_form('edit', $item_id, $row);
7530
                break;
7531
        }
7532
7533
        return $return;
7534
    }
7535
7536
    /**
7537
     * Function that displays a list with al the resources that
7538
     * could be added to the learning path.
7539
     *
7540
     * @throws Exception
7541
     * @throws HTML_QuickForm_Error
7542
     *
7543
     * @return bool
7544
     */
7545
    public function display_resources()
7546
    {
7547
        $course_code = api_get_course_id();
7548
7549
        // Get all the docs.
7550
        $documents = $this->get_documents(true);
7551
7552
        // Get all the exercises.
7553
        $exercises = $this->get_exercises();
7554
7555
        // Get all the links.
7556
        $links = $this->get_links();
7557
7558
        // Get all the student publications.
7559
        $works = $this->get_student_publications();
7560
7561
        // Get all the forums.
7562
        $forums = $this->get_forums(null, $course_code);
7563
7564
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7565
7566
        // Get the final item form (see BT#11048) .
7567
        $finish = $this->getFinalItemForm();
7568
7569
        $headers = [
7570
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7571
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7572
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7573
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7574
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7575
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7576
        ];
7577
7578
        $items = [
7579
            $documents,
7580
            $exercises,
7581
            $links,
7582
            $works,
7583
            $forums,
7584
            $dir,
7585
        ];
7586
7587
        $xApiPlugin = XApiPlugin::create();
7588
7589
        if ($xApiPlugin->isEnabled()) {
7590
            $headers[] = Display::return_icon(
7591
                'import_scorm.png',
7592
                get_lang($xApiPlugin->get_lang('ToolTinCan')),
7593
                [],
7594
                ICON_SIZE_BIG
7595
            );
7596
            $items[] = $xApiPlugin->getLpResourceBlock($this->lp_id);
7597
        }
7598
7599
        $headers[] = Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG);
7600
        $items[] = $finish;
7601
7602
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7603
7604
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7605
7606
        echo Display::tabs(
7607
            $headers,
7608
            $items,
7609
            'resource_tab',
7610
            [],
7611
            [],
7612
            $selected
7613
        );
7614
7615
        return true;
7616
    }
7617
7618
    /**
7619
     * Returns the extension of a document.
7620
     *
7621
     * @param string $filename
7622
     *
7623
     * @return string Extension (part after the last dot)
7624
     */
7625
    public function get_extension($filename)
7626
    {
7627
        $explode = explode('.', $filename);
7628
7629
        return $explode[count($explode) - 1];
7630
    }
7631
7632
    /**
7633
     * Displays a document by id.
7634
     *
7635
     * @param int  $id
7636
     * @param bool $show_title
7637
     * @param bool $iframe
7638
     * @param bool $edit_link
7639
     *
7640
     * @return string
7641
     */
7642
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7643
    {
7644
        $_course = api_get_course_info();
7645
        $course_id = api_get_course_int_id();
7646
        $id = (int) $id;
7647
        $return = '';
7648
        $table = Database::get_course_table(TABLE_DOCUMENT);
7649
        $sql_doc = "SELECT * FROM $table
7650
                    WHERE c_id = $course_id AND iid = $id";
7651
        $res_doc = Database::query($sql_doc);
7652
        $row_doc = Database::fetch_array($res_doc);
7653
7654
        // TODO: Add a path filter.
7655
        if ($iframe) {
7656
            $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>';
7657
        } else {
7658
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7659
        }
7660
7661
        return $return;
7662
    }
7663
7664
    /**
7665
     * Return HTML form to add/edit a quiz.
7666
     *
7667
     * @param string $action     Action (add/edit)
7668
     * @param int    $id         Item ID if already exists
7669
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7670
     *
7671
     * @throws Exception
7672
     *
7673
     * @return string HTML form
7674
     */
7675
    public function display_quiz_form(
7676
        $action = 'add',
7677
        $id = 0,
7678
        $extra_info = '',
7679
        $excludeExtraFields = []
7680
    ) {
7681
        $course_id = api_get_course_int_id();
7682
        $id = (int) $id;
7683
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7684
7685
        if ($id != 0 && is_array($extra_info)) {
7686
            $item_title = $extra_info['title'];
7687
            $item_description = $extra_info['description'];
7688
        } elseif (is_numeric($extra_info)) {
7689
            $sql = "SELECT title, description
7690
                    FROM $tbl_quiz
7691
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7692
7693
            $result = Database::query($sql);
7694
            $row = Database::fetch_array($result);
7695
            $item_title = $row['title'];
7696
            $item_description = $row['description'];
7697
        } else {
7698
            $item_title = '';
7699
            $item_description = '';
7700
        }
7701
        $item_title = Security::remove_XSS($item_title);
7702
        $item_description = Security::remove_XSS($item_description);
7703
7704
        $parent = 0;
7705
        if ($id != 0 && is_array($extra_info)) {
7706
            $parent = $extra_info['parent_item_id'];
7707
        }
7708
7709
        $arrLP = $this->getItemsForForm();
7710
        $this->tree_array($arrLP);
7711
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7712
        unset($this->arrMenu);
7713
7714
        $form = new FormValidator(
7715
            'quiz_form',
7716
            'POST',
7717
            $this->getCurrentBuildingModeURL()
7718
        );
7719
        $defaults = [];
7720
7721
        if ($action === 'add') {
7722
            $legend = get_lang('CreateTheExercise');
7723
        } elseif ($action === 'move') {
7724
            $legend = get_lang('MoveTheCurrentExercise');
7725
        } else {
7726
            $legend = get_lang('EditCurrentExecice');
7727
        }
7728
7729
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7730
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7731
        }
7732
7733
        $form->addHeader($legend);
7734
7735
        if ($action != 'move') {
7736
            $this->setItemTitle($form);
7737
            $defaults['title'] = $item_title;
7738
        }
7739
7740
        // Select for Parent item, root or chapter
7741
        $selectParent = $form->addSelect(
7742
            'parent',
7743
            get_lang('Parent'),
7744
            [],
7745
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7746
        );
7747
        $selectParent->addOption($this->name, 0);
7748
7749
        $arrHide = [
7750
            $id,
7751
        ];
7752
        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...
7753
            if ($action != 'add') {
7754
                if (
7755
                    ($arrLP[$i]['item_type'] == 'dir') &&
7756
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7757
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7758
                ) {
7759
                    $selectParent->addOption(
7760
                        $arrLP[$i]['title'],
7761
                        $arrLP[$i]['id'],
7762
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7763
                    );
7764
7765
                    if ($parent == $arrLP[$i]['id']) {
7766
                        $selectParent->setSelected($arrLP[$i]['id']);
7767
                    }
7768
                } else {
7769
                    $arrHide[] = $arrLP[$i]['id'];
7770
                }
7771
            } else {
7772
                if ($arrLP[$i]['item_type'] == 'dir') {
7773
                    $selectParent->addOption(
7774
                        $arrLP[$i]['title'],
7775
                        $arrLP[$i]['id'],
7776
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7777
                    );
7778
7779
                    if ($parent == $arrLP[$i]['id']) {
7780
                        $selectParent->setSelected($arrLP[$i]['id']);
7781
                    }
7782
                }
7783
            }
7784
        }
7785
7786
        if (is_array($arrLP)) {
7787
            reset($arrLP);
7788
        }
7789
7790
        $selectPrevious = $form->addSelect(
7791
            'previous',
7792
            get_lang('Position'),
7793
            [],
7794
            ['id' => 'previous']
7795
        );
7796
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7797
7798
        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...
7799
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7800
                $arrLP[$i]['id'] != $id
7801
            ) {
7802
                $selectPrevious->addOption(
7803
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7804
                    $arrLP[$i]['id']
7805
                );
7806
7807
                if (is_array($extra_info)) {
7808
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7809
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7810
                    }
7811
                } elseif ($action == 'add') {
7812
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7813
                }
7814
            }
7815
        }
7816
7817
        if ($action != 'move') {
7818
            $arrHide = [];
7819
            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...
7820
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7821
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7822
                }
7823
            }
7824
        }
7825
7826
        if ('edit' === $action) {
7827
            $extraField = new ExtraField('lp_item');
7828
            $extraField->addElements($form, $id, $excludeExtraFields);
7829
        }
7830
7831
        if ($action === 'add') {
7832
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7833
        } else {
7834
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7835
        }
7836
7837
        if ($action === 'move') {
7838
            $form->addHidden('title', $item_title);
7839
            $form->addHidden('description', $item_description);
7840
        }
7841
7842
        if (is_numeric($extra_info)) {
7843
            $form->addHidden('path', $extra_info);
7844
        } elseif (is_array($extra_info)) {
7845
            $form->addHidden('path', $extra_info['path']);
7846
        }
7847
7848
        $form->addHidden('type', TOOL_QUIZ);
7849
        $form->addHidden('post_time', time());
7850
        $this->setAuthorLpItem($form);
7851
        $form->setDefaults($defaults);
7852
7853
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7854
    }
7855
7856
    /**
7857
     * Addition of Hotpotatoes tests.
7858
     *
7859
     * @param string $action
7860
     * @param int    $id         Internal ID of the item
7861
     * @param string $extra_info
7862
     *
7863
     * @return string HTML structure to display the hotpotatoes addition formular
7864
     */
7865
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7866
    {
7867
        $course_id = api_get_course_int_id();
7868
        $uploadPath = DIR_HOTPOTATOES;
7869
7870
        if ($id != 0 && is_array($extra_info)) {
7871
            $item_title = stripslashes($extra_info['title']);
7872
            $item_description = stripslashes($extra_info['description']);
7873
        } elseif (is_numeric($extra_info)) {
7874
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7875
7876
            $sql = "SELECT * FROM $TBL_DOCUMENT
7877
                    WHERE
7878
                        c_id = $course_id AND
7879
                        path LIKE '".$uploadPath."/%/%htm%' AND
7880
                        iid = ".(int) $extra_info."
7881
                    ORDER BY iid ASC";
7882
7883
            $res_hot = Database::query($sql);
7884
            $row = Database::fetch_array($res_hot);
7885
7886
            $item_title = $row['title'];
7887
            $item_description = $row['description'];
7888
7889
            if (!empty($row['comment'])) {
7890
                $item_title = $row['comment'];
7891
            }
7892
        } else {
7893
            $item_title = '';
7894
            $item_description = '';
7895
        }
7896
7897
        $parent = 0;
7898
        if ($id != 0 && is_array($extra_info)) {
7899
            $parent = $extra_info['parent_item_id'];
7900
        }
7901
7902
        $arrLP = $this->getItemsForForm();
7903
        $legend = '<legend>';
7904
        if ($action == 'add') {
7905
            $legend .= get_lang('CreateTheExercise');
7906
        } elseif ($action == 'move') {
7907
            $legend .= get_lang('MoveTheCurrentExercise');
7908
        } else {
7909
            $legend .= get_lang('EditCurrentExecice');
7910
        }
7911
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7912
            $legend .= Display:: return_message(
7913
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7914
            );
7915
        }
7916
        $legend .= '</legend>';
7917
7918
        $return = '<form method="POST">';
7919
        $return .= $legend;
7920
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7921
        $return .= '<tr>';
7922
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7923
        $return .= '<td class="input">';
7924
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7925
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7926
        $arrHide = [$id];
7927
7928
        if (count($arrLP) > 0) {
7929
            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...
7930
                if ($action != 'add') {
7931
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7932
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7933
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7934
                    ) {
7935
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7936
                    } else {
7937
                        $arrHide[] = $arrLP[$i]['id'];
7938
                    }
7939
                } else {
7940
                    if ($arrLP[$i]['item_type'] == 'dir') {
7941
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7942
                    }
7943
                }
7944
            }
7945
            reset($arrLP);
7946
        }
7947
7948
        $return .= '</select>';
7949
        $return .= '</td>';
7950
        $return .= '</tr>';
7951
        $return .= '<tr>';
7952
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7953
        $return .= '<td class="input">';
7954
        $return .= '<select id="previous" name="previous" size="1">';
7955
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7956
7957
        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...
7958
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7959
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7960
                    $selected = 'selected="selected" ';
7961
                } elseif ($action == 'add') {
7962
                    $selected = 'selected="selected" ';
7963
                } else {
7964
                    $selected = '';
7965
                }
7966
7967
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7968
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7969
            }
7970
        }
7971
7972
        $return .= '</select>';
7973
        $return .= '</td>';
7974
        $return .= '</tr>';
7975
7976
        if ($action != 'move') {
7977
            $return .= '<tr>';
7978
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7979
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7980
            $return .= '</tr>';
7981
            $id_prerequisite = 0;
7982
            if (is_array($arrLP) && count($arrLP) > 0) {
7983
                foreach ($arrLP as $key => $value) {
7984
                    if ($value['id'] == $id) {
7985
                        $id_prerequisite = $value['prerequisite'];
7986
                        break;
7987
                    }
7988
                }
7989
7990
                $arrHide = [];
7991
                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...
7992
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7993
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7994
                    }
7995
                }
7996
            }
7997
        }
7998
7999
        $return .= '<tr>';
8000
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8001
            get_lang('SaveHotpotatoes').'</button></td>';
8002
        $return .= '</tr>';
8003
        $return .= '</table>';
8004
8005
        if ($action == 'move') {
8006
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8007
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8008
        }
8009
8010
        if (is_numeric($extra_info)) {
8011
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8012
        } elseif (is_array($extra_info)) {
8013
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8014
        }
8015
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8016
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8017
        $return .= '</form>';
8018
8019
        return $return;
8020
    }
8021
8022
    /**
8023
     * Return the form to display the forum edit/add option.
8024
     *
8025
     * @param string $action
8026
     * @param int    $id         ID of the lp_item if already exists
8027
     * @param string $extra_info
8028
     *
8029
     * @throws Exception
8030
     *
8031
     * @return string HTML form
8032
     */
8033
    public function display_forum_form(
8034
        $action = 'add',
8035
        $id = 0,
8036
        $extra_info = '',
8037
        $excludeExtraFields = []
8038
    ) {
8039
        $course_id = api_get_course_int_id();
8040
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8041
8042
        $item_title = '';
8043
        $item_description = '';
8044
8045
        if ($id != 0 && is_array($extra_info)) {
8046
            $item_title = stripslashes($extra_info['title']);
8047
        } elseif (is_numeric($extra_info)) {
8048
            $sql = "SELECT forum_title as title, forum_comment as comment
8049
                    FROM $tbl_forum
8050
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
8051
8052
            $result = Database::query($sql);
8053
            $row = Database::fetch_array($result);
8054
8055
            $item_title = $row['title'];
8056
            $item_description = $row['comment'];
8057
        }
8058
        $parent = 0;
8059
        if ($id != 0 && is_array($extra_info)) {
8060
            $parent = $extra_info['parent_item_id'];
8061
        }
8062
        $arrLP = $this->getItemsForForm();
8063
        $this->tree_array($arrLP);
8064
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8065
        unset($this->arrMenu);
8066
8067
        if ($action == 'add') {
8068
            $legend = get_lang('CreateTheForum');
8069
        } elseif ($action == 'move') {
8070
            $legend = get_lang('MoveTheCurrentForum');
8071
        } else {
8072
            $legend = get_lang('EditCurrentForum');
8073
        }
8074
8075
        $form = new FormValidator(
8076
            'forum_form',
8077
            'POST',
8078
            $this->getCurrentBuildingModeURL()
8079
        );
8080
        $defaults = [];
8081
8082
        $form->addHeader($legend);
8083
8084
        if ($action != 'move') {
8085
            $this->setItemTitle($form);
8086
            $defaults['title'] = $item_title;
8087
        }
8088
8089
        $selectParent = $form->addSelect(
8090
            'parent',
8091
            get_lang('Parent'),
8092
            [],
8093
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8094
        );
8095
        $selectParent->addOption($this->name, 0);
8096
        $arrHide = [
8097
            $id,
8098
        ];
8099
        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...
8100
            if ($action != 'add') {
8101
                if ($arrLP[$i]['item_type'] == 'dir' &&
8102
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8103
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8104
                ) {
8105
                    $selectParent->addOption(
8106
                        $arrLP[$i]['title'],
8107
                        $arrLP[$i]['id'],
8108
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8109
                    );
8110
8111
                    if ($parent == $arrLP[$i]['id']) {
8112
                        $selectParent->setSelected($arrLP[$i]['id']);
8113
                    }
8114
                } else {
8115
                    $arrHide[] = $arrLP[$i]['id'];
8116
                }
8117
            } else {
8118
                if ($arrLP[$i]['item_type'] == 'dir') {
8119
                    $selectParent->addOption(
8120
                        $arrLP[$i]['title'],
8121
                        $arrLP[$i]['id'],
8122
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8123
                    );
8124
8125
                    if ($parent == $arrLP[$i]['id']) {
8126
                        $selectParent->setSelected($arrLP[$i]['id']);
8127
                    }
8128
                }
8129
            }
8130
        }
8131
8132
        if (is_array($arrLP)) {
8133
            reset($arrLP);
8134
        }
8135
8136
        $selectPrevious = $form->addSelect(
8137
            'previous',
8138
            get_lang('Position'),
8139
            [],
8140
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8141
        );
8142
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8143
8144
        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...
8145
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8146
                $arrLP[$i]['id'] != $id
8147
            ) {
8148
                $selectPrevious->addOption(
8149
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8150
                    $arrLP[$i]['id']
8151
                );
8152
8153
                if (isset($extra_info['previous_item_id']) &&
8154
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8155
                ) {
8156
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8157
                } elseif ($action == 'add') {
8158
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8159
                }
8160
            }
8161
        }
8162
8163
        if ($action != 'move') {
8164
            $id_prerequisite = 0;
8165
            if (is_array($arrLP)) {
8166
                foreach ($arrLP as $key => $value) {
8167
                    if ($value['id'] == $id) {
8168
                        $id_prerequisite = $value['prerequisite'];
8169
                        break;
8170
                    }
8171
                }
8172
            }
8173
8174
            $arrHide = [];
8175
            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...
8176
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8177
                    if (isset($extra_info['previous_item_id']) &&
8178
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8179
                    ) {
8180
                        $s_selected_position = $arrLP[$i]['id'];
8181
                    } elseif ($action == 'add') {
8182
                        $s_selected_position = 0;
8183
                    }
8184
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8185
                }
8186
            }
8187
        }
8188
8189
        if ('edit' === $action) {
8190
            $extraField = new ExtraField('lp_item');
8191
            $extraField->addElements($form, $id, $excludeExtraFields);
8192
        }
8193
8194
        if ($action == 'add') {
8195
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8196
        } else {
8197
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8198
        }
8199
8200
        if ($action == 'move') {
8201
            $form->addHidden('title', $item_title);
8202
            $form->addHidden('description', $item_description);
8203
        }
8204
8205
        if (is_numeric($extra_info)) {
8206
            $form->addHidden('path', $extra_info);
8207
        } elseif (is_array($extra_info)) {
8208
            $form->addHidden('path', $extra_info['path']);
8209
        }
8210
        $form->addHidden('type', TOOL_FORUM);
8211
        $form->addHidden('post_time', time());
8212
        $this->setAuthorLpItem($form);
8213
        $form->setDefaults($defaults);
8214
8215
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8216
    }
8217
8218
    /**
8219
     * Return HTML form to add/edit forum threads.
8220
     *
8221
     * @param string $action
8222
     * @param int    $id         Item ID if already exists in learning path
8223
     * @param string $extra_info
8224
     *
8225
     * @throws Exception
8226
     *
8227
     * @return string HTML form
8228
     */
8229
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8230
    {
8231
        $course_id = api_get_course_int_id();
8232
        if (empty($course_id)) {
8233
            return null;
8234
        }
8235
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8236
8237
        $item_title = '';
8238
        $item_description = '';
8239
        if ($id != 0 && is_array($extra_info)) {
8240
            $item_title = stripslashes($extra_info['title']);
8241
        } elseif (is_numeric($extra_info)) {
8242
            $sql = "SELECT thread_title as title FROM $tbl_forum
8243
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8244
8245
            $result = Database::query($sql);
8246
            $row = Database::fetch_array($result);
8247
8248
            $item_title = $row['title'];
8249
            $item_description = '';
8250
        }
8251
8252
        $parent = 0;
8253
        if ($id != 0 && is_array($extra_info)) {
8254
            $parent = $extra_info['parent_item_id'];
8255
        }
8256
8257
        $arrLP = $this->getItemsForForm();
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
            $this->setItemTitle($form);
8353
            $defaults['title'] = $item_title;
8354
8355
            $id_prerequisite = 0;
8356
            if ($arrLP != null) {
8357
                foreach ($arrLP as $key => $value) {
8358
                    if ($value['id'] == $id) {
8359
                        $id_prerequisite = $value['prerequisite'];
8360
                        break;
8361
                    }
8362
                }
8363
            }
8364
8365
            $arrHide = [];
8366
            $s_selected_position = 0;
8367
            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...
8368
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8369
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8370
                        $s_selected_position = $arrLP[$i]['id'];
8371
                    } elseif ($action == 'add') {
8372
                        $s_selected_position = 0;
8373
                    }
8374
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8375
                }
8376
            }
8377
8378
            $selectPrerequisites = $form->addSelect(
8379
                'prerequisites',
8380
                get_lang('LearnpathPrerequisites'),
8381
                [],
8382
                ['id' => 'prerequisites']
8383
            );
8384
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8385
8386
            foreach ($arrHide as $key => $value) {
8387
                $selectPrerequisites->addOption($value['value'], $key);
8388
8389
                if ($key == $s_selected_position && $action == 'add') {
8390
                    $selectPrerequisites->setSelected($key);
8391
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8392
                    $selectPrerequisites->setSelected($key);
8393
                }
8394
            }
8395
        }
8396
8397
        if ('edit' === $action) {
8398
            $extraField = new ExtraField('lp_item');
8399
            $extraField->addElements($form, $id);
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
        $this->setAuthorLpItem($form);
8418
        $form->setDefaults($defaults);
8419
8420
        return $form->returnForm();
8421
    }
8422
8423
    /**
8424
     * Return the HTML form to display an item (generally a dir item).
8425
     *
8426
     * @param string $item_type
8427
     * @param string $title
8428
     * @param string $action
8429
     * @param int    $id
8430
     * @param string $extra_info
8431
     *
8432
     * @throws Exception
8433
     * @throws HTML_QuickForm_Error
8434
     *
8435
     * @return string HTML form
8436
     */
8437
    public function display_item_form(
8438
        $item_type,
8439
        $title = '',
8440
        $action = 'add_item',
8441
        $id = 0,
8442
        $extra_info = 'new'
8443
    ) {
8444
        $_course = api_get_course_info();
8445
8446
        global $charset;
8447
8448
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8449
        $item_title = '';
8450
        $item_description = '';
8451
        $item_path_fck = '';
8452
8453
        $parent = 0;
8454
        $previousId = null;
8455
        if ($id != 0 && is_array($extra_info)) {
8456
            $item_title = $extra_info['title'];
8457
            $item_description = $extra_info['description'];
8458
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8459
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8460
            $parent = $extra_info['parent_item_id'];
8461
            $previousId = $extra_info['previous_item_id'];
8462
        }
8463
8464
        if ($extra_info instanceof learnpathItem) {
8465
            $item_title = $extra_info->get_title();
8466
            $item_description = $extra_info->get_description();
8467
            $path = $extra_info->get_path();
8468
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8469
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8470
            $parent = $extra_info->get_parent();
8471
            $previousId = $extra_info->previous;
8472
        }
8473
8474
        $id = (int) $id;
8475
        $sql = "SELECT * FROM $tbl_lp_item
8476
                WHERE
8477
                    lp_id = ".$this->lp_id." AND
8478
                    iid != $id";
8479
8480
        if ($item_type === 'dir') {
8481
            $sql .= " AND parent_item_id = 0";
8482
        }
8483
8484
        $result = Database::query($sql);
8485
        $arrLP = [];
8486
        while ($row = Database::fetch_array($result)) {
8487
            $arrLP[] = [
8488
                'id' => $row['iid'],
8489
                'item_type' => $row['item_type'],
8490
                'title' => $this->cleanItemTitle($row['title']),
8491
                'title_raw' => $row['title'],
8492
                'path' => $row['path'],
8493
                'description' => $row['description'],
8494
                'parent_item_id' => $row['parent_item_id'],
8495
                'previous_item_id' => $row['previous_item_id'],
8496
                'next_item_id' => $row['next_item_id'],
8497
                'max_score' => $row['max_score'],
8498
                'min_score' => $row['min_score'],
8499
                'mastery_score' => $row['mastery_score'],
8500
                'prerequisite' => $row['prerequisite'],
8501
                'display_order' => $row['display_order'],
8502
            ];
8503
        }
8504
8505
        $this->tree_array($arrLP);
8506
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8507
        unset($this->arrMenu);
8508
8509
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8510
8511
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8512
        $defaults['title'] = api_html_entity_decode(
8513
            $item_title,
8514
            ENT_QUOTES,
8515
            $charset
8516
        );
8517
        $defaults['description'] = $item_description;
8518
8519
        $form->addHeader($title);
8520
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8521
        $arrHide[0]['padding'] = 20;
8522
        $charset = api_get_system_encoding();
8523
        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...
8524
            if ($action != 'add') {
8525
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8526
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8527
                ) {
8528
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8529
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8530
                    if ($parent == $arrLP[$i]['id']) {
8531
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8532
                    }
8533
                }
8534
            } else {
8535
                if ($arrLP[$i]['item_type'] === 'dir') {
8536
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8537
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8538
                    if ($parent == $arrLP[$i]['id']) {
8539
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8540
                    }
8541
                }
8542
            }
8543
        }
8544
8545
        if ($action !== 'move') {
8546
            $this->setItemTitle($form);
8547
        } else {
8548
            $form->addElement('hidden', 'title');
8549
        }
8550
8551
        $parentSelect = $form->addElement(
8552
            'select',
8553
            'parent',
8554
            get_lang('Parent'),
8555
            '',
8556
            [
8557
                'id' => 'idParent',
8558
                'onchange' => 'javascript: load_cbo(this.value);',
8559
            ]
8560
        );
8561
8562
        foreach ($arrHide as $key => $value) {
8563
            $parentSelect->addOption(
8564
                $value['value'],
8565
                $key,
8566
                'style="padding-left:'.$value['padding'].'px;"'
8567
            );
8568
            $lastPosition = $key;
8569
        }
8570
8571
        if (!empty($s_selected_parent)) {
8572
            $parentSelect->setSelected($s_selected_parent);
8573
        }
8574
8575
        if (is_array($arrLP)) {
8576
            reset($arrLP);
8577
        }
8578
8579
        $arrHide = [];
8580
        // POSITION
8581
        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...
8582
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8583
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8584
                //this is the same!
8585
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8586
                    $s_selected_position = $arrLP[$i]['id'];
8587
                } elseif ($action === 'add') {
8588
                    $s_selected_position = $arrLP[$i]['id'];
8589
                }
8590
8591
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8592
            }
8593
        }
8594
8595
        $position = $form->addElement(
8596
            'select',
8597
            'previous',
8598
            get_lang('Position'),
8599
            '',
8600
            ['id' => 'previous']
8601
        );
8602
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8603
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8604
8605
        $lastPosition = null;
8606
        foreach ($arrHide as $key => $value) {
8607
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8608
            $lastPosition = $key;
8609
        }
8610
8611
        if (!empty($s_selected_position)) {
8612
            $position->setSelected($s_selected_position);
8613
        }
8614
8615
        // When new chapter add at the end
8616
        if ($action === 'add_item') {
8617
            $position->setSelected($lastPosition);
8618
        }
8619
8620
        if (is_array($arrLP)) {
8621
            reset($arrLP);
8622
        }
8623
8624
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8625
8626
        //fix in order to use the tab
8627
        if ($item_type === 'dir') {
8628
            $form->addElement('hidden', 'type', 'dir');
8629
        }
8630
8631
        $extension = null;
8632
        if (!empty($item_path)) {
8633
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8634
        }
8635
8636
        //assets can't be modified
8637
        //$item_type == 'asset' ||
8638
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8639
            if ($item_type === 'sco') {
8640
                $form->addElement(
8641
                    'html',
8642
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8643
                );
8644
            }
8645
            $renderer = $form->defaultRenderer();
8646
            $renderer->setElementTemplate(
8647
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8648
                'content_lp'
8649
            );
8650
8651
            $relative_prefix = '';
8652
            $editor_config = [
8653
                'ToolbarSet' => 'LearningPathDocuments',
8654
                'Width' => '100%',
8655
                'Height' => '500',
8656
                'FullPage' => true,
8657
                'CreateDocumentDir' => $relative_prefix,
8658
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8659
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8660
            ];
8661
8662
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8663
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8664
            $defaults['content_lp'] = file_get_contents($content_path);
8665
        }
8666
8667
        if (!empty($id)) {
8668
            $form->addHidden('id', $id);
8669
        }
8670
8671
        $form->addElement('hidden', 'type', $item_type);
8672
        $form->addElement('hidden', 'post_time', time());
8673
        $form->setDefaults($defaults);
8674
8675
        return $form->returnForm();
8676
    }
8677
8678
    /**
8679
     * @return string
8680
     */
8681
    public function getCurrentBuildingModeURL()
8682
    {
8683
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8684
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8685
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8686
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8687
8688
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8689
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8690
8691
        return $currentUrl;
8692
    }
8693
8694
    /**
8695
     * Returns the form to update or create a document.
8696
     *
8697
     * @param string        $action     (add/edit)
8698
     * @param int           $id         ID of the lp_item (if already exists)
8699
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
8700
     * @param learnpathItem $item
8701
     *
8702
     * @return string HTML form
8703
     */
8704
    public function display_document_form(
8705
        $action = 'add',
8706
        $id = 0,
8707
        $extra_info = 'new',
8708
        $item = null,
8709
        $excludeExtraFields = []
8710
    ) {
8711
        $course_id = api_get_course_int_id();
8712
        $_course = api_get_course_info();
8713
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8714
8715
        $no_display_edit_textarea = false;
8716
        //If action==edit document
8717
        //We don't display the document form if it's not an editable document (html or txt file)
8718
        if ($action === 'edit') {
8719
            if (is_array($extra_info)) {
8720
                $path_parts = pathinfo($extra_info['dir']);
8721
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
8722
                    $no_display_edit_textarea = true;
8723
                }
8724
            }
8725
        }
8726
        $no_display_add = false;
8727
        // If action==add an existing document
8728
        // We don't display the document form if it's not an editable document (html or txt file).
8729
        if ($action === 'add') {
8730
            if (is_numeric($extra_info)) {
8731
                $extra_info = (int) $extra_info;
8732
                $sql_doc = "SELECT path FROM $tbl_doc
8733
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8734
                $result = Database::query($sql_doc);
8735
                $path_file = Database::result($result, 0, 0);
8736
                $path_parts = pathinfo($path_file);
8737
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8738
                    $no_display_add = true;
8739
                }
8740
            }
8741
        }
8742
8743
        $item_title = '';
8744
        $item_description = '';
8745
        if ($id != 0 && is_array($extra_info)) {
8746
            $item_title = stripslashes($extra_info['title']);
8747
            $item_description = stripslashes($extra_info['description']);
8748
            if (empty($item_title)) {
8749
                $path_parts = pathinfo($extra_info['path']);
8750
                $item_title = stripslashes($path_parts['filename']);
8751
            }
8752
        } elseif (is_numeric($extra_info)) {
8753
            $sql = "SELECT path, title FROM $tbl_doc
8754
                    WHERE
8755
                        c_id = ".$course_id." AND
8756
                        iid = ".intval($extra_info);
8757
            $result = Database::query($sql);
8758
            $row = Database::fetch_array($result);
8759
            $item_title = $row['title'];
8760
            $item_title = str_replace('_', ' ', $item_title);
8761
            if (empty($item_title)) {
8762
                $path_parts = pathinfo($row['path']);
8763
                $item_title = stripslashes($path_parts['filename']);
8764
            }
8765
        }
8766
8767
        $return = '<legend>';
8768
        $parent = 0;
8769
        if ($id != 0 && is_array($extra_info)) {
8770
            $parent = $extra_info['parent_item_id'];
8771
        }
8772
8773
        $selectedPosition = 0;
8774
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
8775
            $selectedPosition = $extra_info['previous_item_id'];
8776
        }
8777
8778
        if ($item instanceof learnpathItem) {
8779
            $item_title = stripslashes($item->get_title());
8780
            $item_description = stripslashes($item->get_description());
8781
            $selectedPosition = $item->previous;
8782
            $parent = $item->parent;
8783
        }
8784
8785
        $arrLP = $this->getItemsForForm();
8786
        $this->tree_array($arrLP);
8787
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8788
        unset($this->arrMenu);
8789
8790
        if ($action === 'add') {
8791
            $return .= get_lang('CreateTheDocument');
8792
        } elseif ($action === 'move') {
8793
            $return .= get_lang('MoveTheCurrentDocument');
8794
        } else {
8795
            $return .= get_lang('EditTheCurrentDocument');
8796
        }
8797
        $return .= '</legend>';
8798
8799
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8800
            $return .= Display::return_message(
8801
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8802
                false
8803
            );
8804
        }
8805
        $form = new FormValidator(
8806
            'form',
8807
            'POST',
8808
            $this->getCurrentBuildingModeURL(),
8809
            '',
8810
            ['enctype' => 'multipart/form-data']
8811
        );
8812
        $defaults['title'] = Security::remove_XSS($item_title);
8813
        if (empty($item_title)) {
8814
            $defaults['title'] = Security::remove_XSS($item_title);
8815
        }
8816
        $defaults['description'] = $item_description;
8817
        $form->addElement('html', $return);
8818
8819
        if ($action !== 'move') {
8820
            $data = $this->generate_lp_folder($_course);
8821
            if ($action !== 'edit') {
8822
                $folders = DocumentManager::get_all_document_folders(
8823
                    $_course,
8824
                    0,
8825
                    true
8826
                );
8827
                DocumentManager::build_directory_selector(
8828
                    $folders,
8829
                    '',
8830
                    [],
8831
                    true,
8832
                    $form,
8833
                    'directory_parent_id'
8834
                );
8835
            }
8836
8837
            if (isset($data['id'])) {
8838
                $defaults['directory_parent_id'] = $data['id'];
8839
            }
8840
            $this->setItemTitle($form);
8841
        }
8842
8843
        $arrHide[0]['value'] = $this->name;
8844
        $arrHide[0]['padding'] = 20;
8845
8846
        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...
8847
            if ($action !== 'add') {
8848
                if ($arrLP[$i]['item_type'] === 'dir' &&
8849
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8850
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8851
                ) {
8852
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8853
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8854
                }
8855
            } else {
8856
                if ($arrLP[$i]['item_type'] == 'dir') {
8857
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8858
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8859
                }
8860
            }
8861
        }
8862
8863
        $parentSelect = $form->addSelect(
8864
            'parent',
8865
            get_lang('Parent'),
8866
            [],
8867
            [
8868
                'id' => 'idParent',
8869
                'onchange' => 'javascript: load_cbo(this.value);',
8870
            ]
8871
        );
8872
8873
        $my_count = 0;
8874
        foreach ($arrHide as $key => $value) {
8875
            if ($my_count != 0) {
8876
                // The LP name is also the first section and is not in the same charset like the other sections.
8877
                $value['value'] = Security::remove_XSS($value['value']);
8878
                $parentSelect->addOption(
8879
                    $value['value'],
8880
                    $key,
8881
                    'style="padding-left:'.$value['padding'].'px;"'
8882
                );
8883
            } else {
8884
                $value['value'] = Security::remove_XSS($value['value']);
8885
                $parentSelect->addOption(
8886
                    $value['value'],
8887
                    $key,
8888
                    'style="padding-left:'.$value['padding'].'px;"'
8889
                );
8890
            }
8891
            $my_count++;
8892
        }
8893
8894
        if (!empty($id)) {
8895
            $parentSelect->setSelected($parent);
8896
        } else {
8897
            $parent_item_id = Session::read('parent_item_id', 0);
8898
            $parentSelect->setSelected($parent_item_id);
8899
        }
8900
8901
        if (is_array($arrLP)) {
8902
            reset($arrLP);
8903
        }
8904
8905
        $arrHide = [];
8906
        // POSITION
8907
        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...
8908
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8909
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8910
            ) {
8911
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8912
            }
8913
        }
8914
8915
        $position = $form->addSelect(
8916
            'previous',
8917
            get_lang('Position'),
8918
            [],
8919
            ['id' => 'previous']
8920
        );
8921
8922
        $position->addOption(get_lang('FirstPosition'), 0);
8923
        foreach ($arrHide as $key => $value) {
8924
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8925
            $position->addOption(
8926
                $value['value'],
8927
                $key,
8928
                'style="padding-left:'.$padding.'px;"'
8929
            );
8930
        }
8931
        $position->setSelected($selectedPosition);
8932
8933
        if (is_array($arrLP)) {
8934
            reset($arrLP);
8935
        }
8936
8937
        if ('edit' === $action) {
8938
            $extraField = new ExtraField('lp_item');
8939
            $extraField->addElements($form, $id, $excludeExtraFields);
8940
        }
8941
8942
        if ($action !== 'move') {
8943
            $arrHide = [];
8944
            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...
8945
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8946
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8947
                ) {
8948
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8949
                }
8950
            }
8951
8952
            if (!$no_display_add) {
8953
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8954
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8955
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8956
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8957
                ) {
8958
                    if (isset($_POST['content'])) {
8959
                        $content = stripslashes($_POST['content']);
8960
                    } elseif (is_array($extra_info)) {
8961
                        //If it's an html document or a text file
8962
                        if (!$no_display_edit_textarea) {
8963
                            $content = $this->display_document(
8964
                                $extra_info['path'],
8965
                                false,
8966
                                false
8967
                            );
8968
                        }
8969
                    } elseif (is_numeric($extra_info)) {
8970
                        $content = $this->display_document(
8971
                            $extra_info,
8972
                            false,
8973
                            false
8974
                        );
8975
                    } else {
8976
                        $content = '';
8977
                    }
8978
8979
                    if (!$no_display_edit_textarea) {
8980
                        // We need to calculate here some specific settings for the online editor.
8981
                        // The calculated settings work for documents in the Documents tool
8982
                        // (on the root or in subfolders).
8983
                        // For documents in native scorm packages it is unclear whether the
8984
                        // online editor should be activated or not.
8985
8986
                        // A new document, it is in the root of the repository.
8987
                        if (is_array($extra_info) && $extra_info != 'new') {
8988
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8989
                            $relative_path = explode('/', $extra_info['dir']);
8990
                            $cnt = count($relative_path) - 2;
8991
                            if ($cnt < 0) {
8992
                                $cnt = 0;
8993
                            }
8994
                            $relative_prefix = str_repeat('../', $cnt);
8995
                            $relative_path = array_slice($relative_path, 1, $cnt);
8996
                            $relative_path = implode('/', $relative_path);
8997
                            if (strlen($relative_path) > 0) {
8998
                                $relative_path = $relative_path.'/';
8999
                            }
9000
                        } else {
9001
                            $result = $this->generate_lp_folder($_course);
9002
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9003
                            $relative_prefix = '../../';
9004
                        }
9005
9006
                        $editor_config = [
9007
                            'ToolbarSet' => 'LearningPathDocuments',
9008
                            'Width' => '100%',
9009
                            'Height' => '500',
9010
                            'FullPage' => true,
9011
                            'CreateDocumentDir' => $relative_prefix,
9012
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9013
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9014
                        ];
9015
9016
                        if ($_GET['action'] === 'add_item') {
9017
                            $class = 'add';
9018
                            $text = get_lang('LPCreateDocument');
9019
                        } else {
9020
                            if ($_GET['action'] === 'edit_item') {
9021
                                $class = 'save';
9022
                                $text = get_lang('SaveDocument');
9023
                            }
9024
                        }
9025
9026
                        $form->addButtonSave($text, 'submit_button');
9027
                        $renderer = $form->defaultRenderer();
9028
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9029
                        $form->addElement('html', '<div class="editor-lp">');
9030
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9031
                        $form->addElement('html', '</div>');
9032
                        $defaults['content_lp'] = $content;
9033
                    }
9034
                } elseif (is_numeric($extra_info)) {
9035
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9036
9037
                    $return = $this->display_document($extra_info, true, true, true);
9038
                    $form->addElement('html', $return);
9039
                }
9040
            }
9041
        }
9042
        if (isset($extra_info['item_type']) &&
9043
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9044
        ) {
9045
            $parentSelect->freeze();
9046
            $position->freeze();
9047
        }
9048
9049
        if ($action === 'move') {
9050
            $form->addElement('hidden', 'title', $item_title);
9051
            $form->addElement('hidden', 'description', $item_description);
9052
        }
9053
        if (is_numeric($extra_info)) {
9054
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9055
            $form->addElement('hidden', 'path', $extra_info);
9056
        } elseif (is_array($extra_info)) {
9057
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9058
            $form->addElement('hidden', 'path', $extra_info['path']);
9059
        }
9060
9061
        if ($item instanceof learnpathItem) {
9062
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9063
            $form->addElement('hidden', 'path', $item->path);
9064
        }
9065
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9066
        $form->addElement('hidden', 'post_time', time());
9067
        $this->setAuthorLpItem($form);
9068
        $form->setDefaults($defaults);
9069
9070
        return $form->returnForm();
9071
    }
9072
9073
    /**
9074
     * Returns the form to update or create a read-out text.
9075
     *
9076
     * @param string $action     "add" or "edit"
9077
     * @param int    $id         ID of the lp_item (if already exists)
9078
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9079
     *
9080
     * @throws Exception
9081
     * @throws HTML_QuickForm_Error
9082
     *
9083
     * @return string HTML form
9084
     */
9085
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9086
    {
9087
        $course_id = api_get_course_int_id();
9088
        $_course = api_get_course_info();
9089
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9090
9091
        $no_display_edit_textarea = false;
9092
        //If action==edit document
9093
        //We don't display the document form if it's not an editable document (html or txt file)
9094
        if ($action == 'edit') {
9095
            if (is_array($extra_info)) {
9096
                $path_parts = pathinfo($extra_info['dir']);
9097
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9098
                    $no_display_edit_textarea = true;
9099
                }
9100
            }
9101
        }
9102
        $no_display_add = false;
9103
9104
        $item_title = '';
9105
        $item_description = '';
9106
        if ($id != 0 && is_array($extra_info)) {
9107
            $item_title = stripslashes($extra_info['title']);
9108
            $item_description = stripslashes($extra_info['description']);
9109
            $item_terms = stripslashes($extra_info['terms']);
9110
            if (empty($item_title)) {
9111
                $path_parts = pathinfo($extra_info['path']);
9112
                $item_title = stripslashes($path_parts['filename']);
9113
            }
9114
        } elseif (is_numeric($extra_info)) {
9115
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9116
            $result = Database::query($sql);
9117
            $row = Database::fetch_array($result);
9118
            $item_title = $row['title'];
9119
            $item_title = str_replace('_', ' ', $item_title);
9120
            if (empty($item_title)) {
9121
                $path_parts = pathinfo($row['path']);
9122
                $item_title = stripslashes($path_parts['filename']);
9123
            }
9124
        }
9125
9126
        $parent = 0;
9127
        if ($id != 0 && is_array($extra_info)) {
9128
            $parent = $extra_info['parent_item_id'];
9129
        }
9130
9131
        $arrLP = $this->getItemsForForm();
9132
        $this->tree_array($arrLP);
9133
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9134
        unset($this->arrMenu);
9135
9136
        if ($action === 'add') {
9137
            $formHeader = get_lang('CreateTheDocument');
9138
        } else {
9139
            $formHeader = get_lang('EditTheCurrentDocument');
9140
        }
9141
9142
        if ('edit' === $action) {
9143
            $urlAudioIcon = Display::url(
9144
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9145
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9146
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9147
            );
9148
        } else {
9149
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9150
        }
9151
9152
        $form = new FormValidator(
9153
            'frm_add_reading',
9154
            'POST',
9155
            $this->getCurrentBuildingModeURL(),
9156
            '',
9157
            ['enctype' => 'multipart/form-data']
9158
        );
9159
        $form->addHeader($formHeader);
9160
        $form->addHtml(
9161
            Display::return_message(
9162
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9163
                'normal',
9164
                false
9165
            )
9166
        );
9167
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9168
        $defaults['description'] = $item_description;
9169
9170
        $data = $this->generate_lp_folder($_course);
9171
9172
        if ($action != 'edit') {
9173
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9174
            DocumentManager::build_directory_selector(
9175
                $folders,
9176
                '',
9177
                [],
9178
                true,
9179
                $form,
9180
                'directory_parent_id'
9181
            );
9182
        }
9183
9184
        if (isset($data['id'])) {
9185
            $defaults['directory_parent_id'] = $data['id'];
9186
        }
9187
        $this->setItemTitle($form);
9188
9189
        $arrHide[0]['value'] = $this->name;
9190
        $arrHide[0]['padding'] = 20;
9191
9192
        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...
9193
            if ($action != 'add') {
9194
                if ($arrLP[$i]['item_type'] == 'dir' &&
9195
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9196
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9197
                ) {
9198
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9199
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9200
                }
9201
            } else {
9202
                if ($arrLP[$i]['item_type'] == 'dir') {
9203
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9204
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9205
                }
9206
            }
9207
        }
9208
9209
        $parent_select = $form->addSelect(
9210
            'parent',
9211
            get_lang('Parent'),
9212
            [],
9213
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9214
        );
9215
9216
        $my_count = 0;
9217
        foreach ($arrHide as $key => $value) {
9218
            if ($my_count != 0) {
9219
                // The LP name is also the first section and is not in the same charset like the other sections.
9220
                $value['value'] = Security::remove_XSS($value['value']);
9221
                $parent_select->addOption(
9222
                    $value['value'],
9223
                    $key,
9224
                    'style="padding-left:'.$value['padding'].'px;"'
9225
                );
9226
            } else {
9227
                $value['value'] = Security::remove_XSS($value['value']);
9228
                $parent_select->addOption(
9229
                    $value['value'],
9230
                    $key,
9231
                    'style="padding-left:'.$value['padding'].'px;"'
9232
                );
9233
            }
9234
            $my_count++;
9235
        }
9236
9237
        if (!empty($id)) {
9238
            $parent_select->setSelected($parent);
9239
        } else {
9240
            $parent_item_id = Session::read('parent_item_id', 0);
9241
            $parent_select->setSelected($parent_item_id);
9242
        }
9243
9244
        if (is_array($arrLP)) {
9245
            reset($arrLP);
9246
        }
9247
9248
        $arrHide = [];
9249
        $s_selected_position = null;
9250
9251
        // POSITION
9252
        $lastPosition = null;
9253
9254
        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...
9255
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9256
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9257
            ) {
9258
                if ((isset($extra_info['previous_item_id']) &&
9259
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9260
                ) {
9261
                    $s_selected_position = $arrLP[$i]['id'];
9262
                }
9263
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9264
            }
9265
            $lastPosition = $arrLP[$i]['id'];
9266
        }
9267
9268
        if (empty($s_selected_position)) {
9269
            $s_selected_position = $lastPosition;
9270
        }
9271
9272
        $position = $form->addSelect(
9273
            'previous',
9274
            get_lang('Position'),
9275
            []
9276
        );
9277
        $position->addOption(get_lang('FirstPosition'), 0);
9278
9279
        foreach ($arrHide as $key => $value) {
9280
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9281
            $position->addOption(
9282
                $value['value'],
9283
                $key,
9284
                'style="padding-left:'.$padding.'px;"'
9285
            );
9286
        }
9287
        $position->setSelected($s_selected_position);
9288
9289
        if (is_array($arrLP)) {
9290
            reset($arrLP);
9291
        }
9292
9293
        if ('edit' === $action) {
9294
            $extraField = new ExtraField('lp_item');
9295
            $extraField->addElements($form, $id);
9296
        }
9297
9298
        $arrHide = [];
9299
9300
        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...
9301
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9302
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9303
            ) {
9304
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9305
            }
9306
        }
9307
9308
        if (!$no_display_add) {
9309
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9310
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9311
9312
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9313
                if (!$no_display_edit_textarea) {
9314
                    $content = '';
9315
9316
                    if (isset($_POST['content'])) {
9317
                        $content = stripslashes($_POST['content']);
9318
                    } elseif (is_array($extra_info)) {
9319
                        $content = $this->display_document($extra_info['path'], false, false);
9320
                    } elseif (is_numeric($extra_info)) {
9321
                        $content = $this->display_document($extra_info, false, false);
9322
                    }
9323
9324
                    // A new document, it is in the root of the repository.
9325
                    if (is_array($extra_info) && $extra_info != 'new') {
9326
                    } else {
9327
                        $this->generate_lp_folder($_course);
9328
                    }
9329
9330
                    if ($_GET['action'] == 'add_item') {
9331
                        $text = get_lang('LPCreateDocument');
9332
                    } else {
9333
                        $text = get_lang('SaveDocument');
9334
                    }
9335
9336
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9337
                    $form
9338
                        ->defaultRenderer()
9339
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9340
                    $form->addButtonSave($text, 'submit_button');
9341
                    $defaults['content_lp'] = $content;
9342
                }
9343
            } elseif (is_numeric($extra_info)) {
9344
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9345
9346
                $return = $this->display_document($extra_info, true, true, true);
9347
                $form->addElement('html', $return);
9348
            }
9349
        }
9350
9351
        if (is_numeric($extra_info)) {
9352
            $form->addElement('hidden', 'path', $extra_info);
9353
        } elseif (is_array($extra_info)) {
9354
            $form->addElement('hidden', 'path', $extra_info['path']);
9355
        }
9356
9357
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9358
        $form->addElement('hidden', 'post_time', time());
9359
        $this->setAuthorLpItem($form);
9360
        $form->setDefaults($defaults);
9361
9362
        return $form->returnForm();
9363
    }
9364
9365
    /**
9366
     * @param array  $courseInfo
9367
     * @param string $content
9368
     * @param string $title
9369
     * @param int    $parentId
9370
     *
9371
     * @throws \Doctrine\ORM\ORMException
9372
     * @throws \Doctrine\ORM\OptimisticLockException
9373
     * @throws \Doctrine\ORM\TransactionRequiredException
9374
     *
9375
     * @return int
9376
     */
9377
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9378
    {
9379
        $creatorId = api_get_user_id();
9380
        $sessionId = api_get_session_id();
9381
9382
        // Generates folder
9383
        $result = $this->generate_lp_folder($courseInfo);
9384
        $dir = $result['dir'];
9385
9386
        if (empty($parentId) || $parentId == '/') {
9387
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9388
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9389
9390
            if ($parentId === '/') {
9391
                $dir = '/';
9392
            }
9393
9394
            // Please, do not modify this dirname formatting.
9395
            if (strstr($dir, '..')) {
9396
                $dir = '/';
9397
            }
9398
9399
            if (!empty($dir[0]) && $dir[0] == '.') {
9400
                $dir = substr($dir, 1);
9401
            }
9402
            if (!empty($dir[0]) && $dir[0] != '/') {
9403
                $dir = '/'.$dir;
9404
            }
9405
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9406
                $dir .= '/';
9407
            }
9408
        } else {
9409
            $parentInfo = DocumentManager::get_document_data_by_id(
9410
                $parentId,
9411
                $courseInfo['code']
9412
            );
9413
            if (!empty($parentInfo)) {
9414
                $dir = $parentInfo['path'].'/';
9415
            }
9416
        }
9417
9418
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9419
9420
        if (!is_dir($filepath)) {
9421
            $dir = '/';
9422
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9423
        }
9424
9425
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9426
9427
        if (!empty($title)) {
9428
            $title = api_replace_dangerous_char(stripslashes($title));
9429
        } else {
9430
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9431
        }
9432
9433
        $title = disable_dangerous_file($title);
9434
        $filename = $title;
9435
        $content = !empty($content) ? $content : $_POST['content_lp'];
9436
        $tmpFileName = $filename;
9437
9438
        $i = 0;
9439
        while (file_exists($filepath.$tmpFileName.'.html')) {
9440
            $tmpFileName = $filename.'_'.++$i;
9441
        }
9442
9443
        $filename = $tmpFileName.'.html';
9444
        $content = stripslashes($content);
9445
9446
        if (file_exists($filepath.$filename)) {
9447
            return 0;
9448
        }
9449
9450
        $putContent = file_put_contents($filepath.$filename, $content);
9451
9452
        if ($putContent === false) {
9453
            return 0;
9454
        }
9455
9456
        $fileSize = filesize($filepath.$filename);
9457
        $saveFilePath = $dir.$filename;
9458
9459
        $documentId = add_document(
9460
            $courseInfo,
9461
            $saveFilePath,
9462
            'file',
9463
            $fileSize,
9464
            $tmpFileName,
9465
            '',
9466
            0, //readonly
9467
            true,
9468
            null,
9469
            $sessionId,
9470
            $creatorId
9471
        );
9472
9473
        if (!$documentId) {
9474
            return 0;
9475
        }
9476
9477
        api_item_property_update(
9478
            $courseInfo,
9479
            TOOL_DOCUMENT,
9480
            $documentId,
9481
            'DocumentAdded',
9482
            $creatorId,
9483
            null,
9484
            null,
9485
            null,
9486
            null,
9487
            $sessionId
9488
        );
9489
9490
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9491
        $newTitle = $originalTitle;
9492
9493
        if ($newComment || $newTitle) {
9494
            $em = Database::getManager();
9495
9496
            /** @var CDocument $doc */
9497
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9498
9499
            if ($newComment) {
9500
                $doc->setComment($newComment);
9501
            }
9502
9503
            if ($newTitle) {
9504
                $doc->setTitle($newTitle);
9505
            }
9506
9507
            $em->persist($doc);
9508
            $em->flush();
9509
        }
9510
9511
        return $documentId;
9512
    }
9513
9514
    /**
9515
     * Return HTML form to add/edit a link item.
9516
     *
9517
     * @param string $action     (add/edit)
9518
     * @param int    $id         Item ID if exists
9519
     * @param mixed  $extra_info
9520
     *
9521
     * @throws Exception
9522
     * @throws HTML_QuickForm_Error
9523
     *
9524
     * @return string HTML form
9525
     */
9526
    public function display_link_form(
9527
        $action = 'add',
9528
        $id = 0,
9529
        $extra_info = '',
9530
        $item = null,
9531
        $excludeExtraFields = []
9532
    ) {
9533
        $course_id = api_get_course_int_id();
9534
        $tbl_link = Database::get_course_table(TABLE_LINK);
9535
9536
        $item_title = '';
9537
        $item_description = '';
9538
        $item_url = '';
9539
9540
        $previousId = 0;
9541
        if ($id != 0 && is_array($extra_info)) {
9542
            $item_title = stripslashes($extra_info['title']);
9543
            $item_description = stripslashes($extra_info['description']);
9544
            $item_url = stripslashes($extra_info['url']);
9545
            $previousId = $extra_info['previous_item_id'];
9546
        } elseif (is_numeric($extra_info)) {
9547
            $extra_info = (int) $extra_info;
9548
            $sql = "SELECT title, description, url
9549
                    FROM $tbl_link
9550
                    WHERE c_id = $course_id AND iid = $extra_info";
9551
            $result = Database::query($sql);
9552
            $row = Database::fetch_array($result);
9553
            $item_title = $row['title'];
9554
            $item_description = $row['description'];
9555
            $item_url = $row['url'];
9556
        }
9557
9558
        if ($item instanceof learnpathItem) {
9559
            $previousId = $extra_info->previous;
9560
        }
9561
9562
        $form = new FormValidator(
9563
            'edit_link',
9564
            'POST',
9565
            $this->getCurrentBuildingModeURL()
9566
        );
9567
        $defaults = [];
9568
        $parent = 0;
9569
        if ($id != 0 && is_array($extra_info)) {
9570
            $parent = $extra_info['parent_item_id'];
9571
        }
9572
9573
        $arrLP = $this->getItemsForForm();
9574
9575
        $this->tree_array($arrLP);
9576
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9577
        unset($this->arrMenu);
9578
9579
        if ($action === 'add') {
9580
            $legend = get_lang('CreateTheLink');
9581
        } elseif ($action === 'move') {
9582
            $legend = get_lang('MoveCurrentLink');
9583
        } else {
9584
            $legend = get_lang('EditCurrentLink');
9585
        }
9586
9587
        $form->addHeader($legend);
9588
9589
        if ($action !== 'move') {
9590
            $this->setItemTitle($form);
9591
            $defaults['title'] = $item_title;
9592
        }
9593
9594
        $selectParent = $form->addSelect(
9595
            'parent',
9596
            get_lang('Parent'),
9597
            [],
9598
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9599
        );
9600
        $selectParent->addOption($this->name, 0);
9601
        $arrHide = [
9602
            $id,
9603
        ];
9604
9605
        $parent_item_id = Session::read('parent_item_id', 0);
9606
9607
        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...
9608
            if ($action != 'add') {
9609
                if (
9610
                    ($arrLP[$i]['item_type'] == 'dir') &&
9611
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9612
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9613
                ) {
9614
                    $selectParent->addOption(
9615
                        $arrLP[$i]['title'],
9616
                        $arrLP[$i]['id'],
9617
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9618
                    );
9619
9620
                    if ($parent == $arrLP[$i]['id']) {
9621
                        $selectParent->setSelected($arrLP[$i]['id']);
9622
                    }
9623
                } else {
9624
                    $arrHide[] = $arrLP[$i]['id'];
9625
                }
9626
            } else {
9627
                if ($arrLP[$i]['item_type'] == 'dir') {
9628
                    $selectParent->addOption(
9629
                        $arrLP[$i]['title'],
9630
                        $arrLP[$i]['id'],
9631
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9632
                    );
9633
9634
                    if ($parent_item_id == $arrLP[$i]['id']) {
9635
                        $selectParent->setSelected($arrLP[$i]['id']);
9636
                    }
9637
                }
9638
            }
9639
        }
9640
9641
        if (is_array($arrLP)) {
9642
            reset($arrLP);
9643
        }
9644
9645
        $selectPrevious = $form->addSelect(
9646
            'previous',
9647
            get_lang('Position'),
9648
            [],
9649
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9650
        );
9651
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9652
9653
        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...
9654
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9655
                $selectPrevious->addOption(
9656
                    $arrLP[$i]['title'],
9657
                    $arrLP[$i]['id']
9658
                );
9659
9660
                if ($previousId == $arrLP[$i]['id']) {
9661
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9662
                } elseif ($action === 'add') {
9663
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9664
                }
9665
            }
9666
        }
9667
9668
        if ($action !== 'move') {
9669
            $urlAttributes = ['class' => 'learnpath_item_form'];
9670
9671
            if (is_numeric($extra_info)) {
9672
                $urlAttributes['disabled'] = 'disabled';
9673
            }
9674
9675
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9676
            $defaults['url'] = $item_url;
9677
            $arrHide = [];
9678
            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...
9679
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9680
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9681
                }
9682
            }
9683
        }
9684
9685
        if ('edit' === $action) {
9686
            $extraField = new ExtraField('lp_item');
9687
            $extraField->addElements($form, $id, $excludeExtraFields);
9688
        }
9689
9690
        if ($action === 'add') {
9691
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9692
        } else {
9693
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9694
        }
9695
9696
        if ($action === 'move') {
9697
            $form->addHidden('title', $item_title);
9698
            $form->addHidden('description', $item_description);
9699
        }
9700
9701
        if (is_numeric($extra_info)) {
9702
            $form->addHidden('path', $extra_info);
9703
        } elseif (is_array($extra_info)) {
9704
            $form->addHidden('path', $extra_info['path']);
9705
        }
9706
        $form->addHidden('type', TOOL_LINK);
9707
        $form->addHidden('post_time', time());
9708
        $this->setAuthorLpItem($form);
9709
        $form->setDefaults($defaults);
9710
9711
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9712
    }
9713
9714
    /**
9715
     * Return HTML form to add/edit a student publication (work).
9716
     *
9717
     * @param string $action
9718
     * @param int    $id         Item ID if already exists
9719
     * @param string $extra_info
9720
     *
9721
     * @throws Exception
9722
     *
9723
     * @return string HTML form
9724
     */
9725
    public function display_student_publication_form(
9726
        $action = 'add',
9727
        $id = 0,
9728
        $extra_info = '',
9729
        $item = null,
9730
        $excludeExtraFields = []
9731
    ) {
9732
        $course_id = api_get_course_int_id();
9733
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9734
9735
        $item_title = get_lang('Student_publication');
9736
        $previousId = 0;
9737
        if ($id != 0 && is_array($extra_info)) {
9738
            $item_title = stripslashes($extra_info['title']);
9739
            $item_description = stripslashes($extra_info['description']);
9740
            $previousId = $extra_info['previous_item_id'];
9741
        } elseif (is_numeric($extra_info)) {
9742
            $extra_info = (int) $extra_info;
9743
            $sql = "SELECT title, description
9744
                    FROM $tbl_publication
9745
                    WHERE c_id = $course_id AND id = ".$extra_info;
9746
9747
            $result = Database::query($sql);
9748
            $row = Database::fetch_array($result);
9749
            if ($row) {
9750
                $item_title = $row['title'];
9751
            }
9752
        }
9753
9754
        if ($item instanceof learnpathItem) {
9755
            $item_title = $item->get_title();
9756
            $item_description = $item->get_description();
9757
            $previousId = $item->previous;
9758
        }
9759
9760
        $parent = 0;
9761
        if ($id != 0 && is_array($extra_info)) {
9762
            $parent = $extra_info['parent_item_id'];
9763
        }
9764
9765
        $arrLP = $this->getItemsForForm();
9766
9767
        $this->tree_array($arrLP);
9768
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9769
        unset($this->arrMenu);
9770
9771
        $form = new FormValidator('frm_student_publication', 'post', '#');
9772
9773
        if ($action === 'add') {
9774
            $form->addHeader(get_lang('Student_publication'));
9775
        } elseif ($action === 'move') {
9776
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9777
        } else {
9778
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9779
        }
9780
9781
        if ($action !== 'move') {
9782
            $this->setItemTitle($form);
9783
        }
9784
9785
        $parentSelect = $form->addSelect(
9786
            'parent',
9787
            get_lang('Parent'),
9788
            ['0' => $this->name],
9789
            [
9790
                'onchange' => 'javascript: load_cbo(this.value);',
9791
                'class' => 'learnpath_item_form',
9792
                'id' => 'idParent',
9793
            ]
9794
        );
9795
9796
        $arrHide = [$id];
9797
        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...
9798
            if ($action != 'add') {
9799
                if (
9800
                    ($arrLP[$i]['item_type'] == 'dir') &&
9801
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9802
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9803
                ) {
9804
                    $parentSelect->addOption(
9805
                        $arrLP[$i]['title'],
9806
                        $arrLP[$i]['id'],
9807
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9808
                    );
9809
9810
                    if ($parent == $arrLP[$i]['id']) {
9811
                        $parentSelect->setSelected($arrLP[$i]['id']);
9812
                    }
9813
                } else {
9814
                    $arrHide[] = $arrLP[$i]['id'];
9815
                }
9816
            } else {
9817
                if ($arrLP[$i]['item_type'] === 'dir') {
9818
                    $parentSelect->addOption(
9819
                        $arrLP[$i]['title'],
9820
                        $arrLP[$i]['id'],
9821
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9822
                    );
9823
9824
                    if ($parent == $arrLP[$i]['id']) {
9825
                        $parentSelect->setSelected($arrLP[$i]['id']);
9826
                    }
9827
                }
9828
            }
9829
        }
9830
9831
        if (is_array($arrLP)) {
9832
            reset($arrLP);
9833
        }
9834
9835
        $previousSelect = $form->addSelect(
9836
            'previous',
9837
            get_lang('Position'),
9838
            ['0' => get_lang('FirstPosition')],
9839
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9840
        );
9841
9842
        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...
9843
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9844
                $previousSelect->addOption(
9845
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9846
                    $arrLP[$i]['id']
9847
                );
9848
9849
                if ($previousId == $arrLP[$i]['id']) {
9850
                    $previousSelect->setSelected($arrLP[$i]['id']);
9851
                } elseif ($action === 'add') {
9852
                    $previousSelect->setSelected($arrLP[$i]['id']);
9853
                }
9854
            }
9855
        }
9856
9857
        if ('edit' === $action) {
9858
            $extraField = new ExtraField('lp_item');
9859
            $extraField->addElements($form, $id, $excludeExtraFields);
9860
        }
9861
9862
        if ($action === 'add') {
9863
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9864
        } else {
9865
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9866
        }
9867
9868
        if ($action === 'move') {
9869
            $form->addHidden('title', $item_title);
9870
            $form->addHidden('description', $item_description);
9871
        }
9872
9873
        if (is_numeric($extra_info)) {
9874
            $form->addHidden('path', $extra_info);
9875
        } elseif (is_array($extra_info)) {
9876
            $form->addHidden('path', $extra_info['path']);
9877
        }
9878
9879
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9880
        $form->addHidden('post_time', time());
9881
        $this->setAuthorLpItem($form);
9882
        $form->setDefaults(['title' => $item_title]);
9883
9884
        $return = '<div class="sectioncomment">';
9885
        $return .= $form->returnForm();
9886
        $return .= '</div>';
9887
9888
        return $return;
9889
    }
9890
9891
    /**
9892
     * Displays the menu for manipulating a step.
9893
     *
9894
     * @param int    $item_id
9895
     * @param string $item_type
9896
     *
9897
     * @return string
9898
     */
9899
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9900
    {
9901
        $_course = api_get_course_info();
9902
        $course_code = api_get_course_id();
9903
        $item_id = (int) $item_id;
9904
9905
        $return = '<div class="actions">';
9906
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9907
        $sql = "SELECT * FROM $tbl_lp_item
9908
                WHERE iid = ".$item_id;
9909
        $result = Database::query($sql);
9910
        $row = Database::fetch_assoc($result);
9911
9912
        $audio_player = null;
9913
        // We display an audio player if needed.
9914
        if (!empty($row['audio'])) {
9915
            $audio = learnpathItem::fixAudio($row['audio']);
9916
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
9917
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9918
                            <audio src="'.$webAudioPath.'" controls>
9919
                            </div><br />';
9920
        }
9921
9922
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9923
9924
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9925
            $return .= Display::url(
9926
                Display::return_icon(
9927
                    'edit.png',
9928
                    get_lang('Edit'),
9929
                    [],
9930
                    ICON_SIZE_SMALL
9931
                ),
9932
                $url.'&action=edit_item&path_item='.$row['path']
9933
            );
9934
9935
            $return .= Display::url(
9936
                Display::return_icon(
9937
                    'move.png',
9938
                    get_lang('Move'),
9939
                    [],
9940
                    ICON_SIZE_SMALL
9941
                ),
9942
                $url.'&action=move_item'
9943
            );
9944
        }
9945
9946
        // Commented for now as prerequisites cannot be added to chapters.
9947
        if ($item_type != 'dir') {
9948
            $return .= Display::url(
9949
                Display::return_icon(
9950
                    'accept.png',
9951
                    get_lang('LearnpathPrerequisites'),
9952
                    [],
9953
                    ICON_SIZE_SMALL
9954
                ),
9955
                $url.'&action=edit_item_prereq'
9956
            );
9957
        }
9958
        $return .= Display::url(
9959
            Display::return_icon(
9960
                'delete.png',
9961
                get_lang('Delete'),
9962
                [],
9963
                ICON_SIZE_SMALL
9964
            ),
9965
            $url.'&action=delete_item'
9966
        );
9967
9968
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9969
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9970
            if (empty($documentData)) {
9971
                // Try with iid
9972
                $table = Database::get_course_table(TABLE_DOCUMENT);
9973
                $sql = "SELECT path FROM $table
9974
                        WHERE
9975
                              c_id = ".api_get_course_int_id()." AND
9976
                              iid = ".$row['path']." AND
9977
                              path NOT LIKE '%_DELETED_%'";
9978
                $result = Database::query($sql);
9979
                $documentData = Database::fetch_array($result);
9980
                if ($documentData) {
9981
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9982
                }
9983
            }
9984
            if (isset($documentData['absolute_path_from_document'])) {
9985
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9986
            }
9987
        }
9988
9989
        $return .= '</div>';
9990
9991
        if (!empty($audio_player)) {
9992
            $return .= $audio_player;
9993
        }
9994
9995
        return $return;
9996
    }
9997
9998
    /**
9999
     * Creates the javascript needed for filling up the checkboxes without page reload.
10000
     *
10001
     * @return string
10002
     */
10003
    public function get_js_dropdown_array()
10004
    {
10005
        $course_id = api_get_course_int_id();
10006
        $return = 'var child_name = new Array();'."\n";
10007
        $return .= 'var child_value = new Array();'."\n\n";
10008
        $return .= 'child_name[0] = new Array();'."\n";
10009
        $return .= 'child_value[0] = new Array();'."\n\n";
10010
10011
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10012
        $i = 0;
10013
        $list = $this->getItemsForForm(true);
10014
10015
        foreach ($list as $row_zero) {
10016
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10017
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10018
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10019
                }
10020
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10021
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10022
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10023
            }
10024
        }
10025
10026
        $return .= "\n";
10027
        $sql = "SELECT * FROM $tbl_lp_item
10028
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10029
        $res = Database::query($sql);
10030
        while ($row = Database::fetch_array($res)) {
10031
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10032
                           WHERE
10033
                                c_id = ".$course_id." AND
10034
                                parent_item_id = ".$row['iid']."
10035
                           ORDER BY display_order ASC";
10036
            $res_parent = Database::query($sql_parent);
10037
            $i = 0;
10038
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10039
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10040
10041
            while ($row_parent = Database::fetch_array($res_parent)) {
10042
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
10043
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10044
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10045
            }
10046
            $return .= "\n";
10047
        }
10048
10049
        $return .= "
10050
            function load_cbo(id) {
10051
                if (!id) {
10052
                    return false;
10053
                }
10054
10055
                var cbo = document.getElementById('previous');
10056
                for (var i = cbo.length - 1; i > 0; i--) {
10057
                    cbo.options[i] = null;
10058
                }
10059
10060
                var k=0;
10061
                for(var i = 1; i <= child_name[id].length; i++){
10062
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10063
                    option.style.paddingLeft = '40px';
10064
                    cbo.options[i] = option;
10065
                    k = i;
10066
                }
10067
10068
                cbo.options[k].selected = true;
10069
                $('#previous').selectpicker('refresh');
10070
            }";
10071
10072
        return $return;
10073
    }
10074
10075
    /**
10076
     * Display the form to allow moving an item.
10077
     *
10078
     * @param learnpathItem $item Item ID
10079
     *
10080
     * @throws Exception
10081
     * @throws HTML_QuickForm_Error
10082
     *
10083
     * @return string HTML form
10084
     */
10085
    public function display_move_item($item)
10086
    {
10087
        $return = '';
10088
        if ($item) {
10089
            $item_id = $item->getIid();
10090
            $type = $item->get_type();
10091
            $path = (int) $item->get_path();
10092
10093
            switch ($type) {
10094
                case 'dir':
10095
                case 'asset':
10096
                    $return .= $this->display_manipulate($item_id, $type);
10097
                    $return .= $this->display_item_form(
10098
                        $type,
10099
                        get_lang('MoveCurrentChapter'),
10100
                        'move',
10101
                        $item_id,
10102
                        $item
10103
                    );
10104
                    break;
10105
                case TOOL_DOCUMENT:
10106
                    $return .= $this->display_manipulate($item_id, $type);
10107
                    $return .= $this->display_document_form('move', $item_id, null, $item);
10108
                    break;
10109
                case TOOL_LINK:
10110
                    $return .= $this->display_manipulate($item_id, $type);
10111
                    $return .= $this->display_link_form('move', $item_id, $path, $item);
10112
                    break;
10113
                case TOOL_HOTPOTATOES:
10114
                    $return .= $this->display_manipulate($item_id, $type);
10115
                    $return .= $this->display_link_form('move', $item_id, $item);
10116
                    break;
10117
                case TOOL_QUIZ:
10118
                    $return .= $this->display_manipulate($item_id, $type);
10119
                    $return .= $this->display_quiz_form('move', $item_id, $item);
10120
                    break;
10121
                case TOOL_STUDENTPUBLICATION:
10122
                    $return .= $this->display_manipulate($item_id, $type);
10123
                    $return .= $this->display_student_publication_form('move', $item_id, $path, $item);
10124
                    break;
10125
                case TOOL_FORUM:
10126
                    $return .= $this->display_manipulate($item_id, $type);
10127
                    $return .= $this->display_forum_form('move', $item_id, $path);
10128
                    break;
10129
                case TOOL_THREAD:
10130
                    $return .= $this->display_manipulate($item_id, $type);
10131
                    $return .= $this->display_forum_form('move', $item_id, $path);
10132
                    break;
10133
            }
10134
        }
10135
10136
        return $return;
10137
    }
10138
10139
    /**
10140
     * Return HTML form to allow prerequisites selection.
10141
     *
10142
     * @todo use FormValidator
10143
     *
10144
     * @param int Item ID
10145
     *
10146
     * @return string HTML form
10147
     */
10148
    public function display_item_prerequisites_form($item_id = 0)
10149
    {
10150
        $course_id = api_get_course_int_id();
10151
        $item_id = (int) $item_id;
10152
10153
        if (empty($item_id)) {
10154
            return '';
10155
        }
10156
10157
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10158
10159
        /* Current prerequisite */
10160
        $sql = "SELECT * FROM $tbl_lp_item
10161
                WHERE iid = $item_id";
10162
        $result = Database::query($sql);
10163
        $row = Database::fetch_array($result);
10164
        $prerequisiteId = $row['prerequisite'];
10165
10166
        $return = '<legend>';
10167
        $return .= get_lang('AddEditPrerequisites');
10168
        $return .= '</legend>';
10169
        $return .= '<form method="POST">';
10170
        $return .= '<div class="table-responsive">';
10171
        $return .= '<table class="table table-hover">';
10172
        $return .= '<thead>';
10173
        $return .= '<tr>';
10174
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10175
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10176
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10177
        $return .= '</tr>';
10178
        $return .= '</thead>';
10179
10180
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10181
        $return .= '<tbody>';
10182
        $return .= '<tr>';
10183
        $return .= '<td colspan="3">';
10184
        $return .= '<div class="radio learnpath"><label for="idNone">';
10185
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10186
        $return .= get_lang('None').'</label>';
10187
        $return .= '</div>';
10188
        $return .= '</tr>';
10189
10190
        $sql = "SELECT * FROM $tbl_lp_item
10191
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10192
        $result = Database::query($sql);
10193
10194
        $selectedMinScore = [];
10195
        $selectedMaxScore = [];
10196
        $masteryScore = [];
10197
        while ($row = Database::fetch_array($result)) {
10198
            if ($row['iid'] == $item_id) {
10199
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10200
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10201
            }
10202
            $masteryScore[$row['iid']] = $row['mastery_score'];
10203
        }
10204
10205
        $arrLP = $this->getItemsForForm();
10206
        $this->tree_array($arrLP);
10207
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10208
        unset($this->arrMenu);
10209
10210
        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...
10211
            $item = $arrLP[$i];
10212
10213
            if ($item['id'] == $item_id) {
10214
                break;
10215
            }
10216
10217
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10218
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10219
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10220
10221
            $return .= '<tr>';
10222
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10223
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10224
            $return .= '<label for="id'.$item['id'].'">';
10225
10226
            $checked = '';
10227
            if (null !== $prerequisiteId) {
10228
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10229
            }
10230
10231
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10232
10233
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10234
10235
            $icon_name = str_replace(' ', '', $item['item_type']);
10236
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10237
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10238
            } else {
10239
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10240
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10241
                } else {
10242
                    $return .= Display::return_icon('folder_document.png');
10243
                }
10244
            }
10245
10246
            $return .= $item['title'].'</label>';
10247
            $return .= '</div>';
10248
            $return .= '</td>';
10249
10250
            if ($item['item_type'] == TOOL_QUIZ) {
10251
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10252
                $lpItemObj = new LpItem($course_id, $item['id']);
10253
                $exercise = new Exercise($course_id);
10254
                $exercise->read($lpItemObj->path);
10255
                $lpItemObj->max_score = $exercise->get_max_score();
10256
                $lpItemObj->update();
10257
                $item['max_score'] = $lpItemObj->max_score;
10258
10259
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10260
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10261
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10262
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10263
                }
10264
10265
                $return .= '<td>';
10266
                $return .= '<input
10267
                    class="form-control"
10268
                    size="4" maxlength="3"
10269
                    name="min_'.$item['id'].'"
10270
                    type="number"
10271
                    min="0"
10272
                    step="any"
10273
                    max="'.$item['max_score'].'"
10274
                    value="'.$selectedMinScoreValue.'"
10275
                />';
10276
                $return .= '</td>';
10277
                $return .= '<td>';
10278
                $return .= '<input
10279
                    class="form-control"
10280
                    size="4"
10281
                    maxlength="3"
10282
                    name="max_'.$item['id'].'"
10283
                    type="number"
10284
                    min="0"
10285
                    step="any"
10286
                    max="'.$item['max_score'].'"
10287
                    value="'.$selectedMaxScoreValue.'"
10288
                />';
10289
                $return .= '</td>';
10290
            }
10291
10292
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10293
                $return .= '<td>';
10294
                $return .= '<input
10295
                    size="4"
10296
                    maxlength="3"
10297
                    name="min_'.$item['id'].'"
10298
                    type="number"
10299
                    min="0"
10300
                    step="any"
10301
                    max="'.$item['max_score'].'"
10302
                    value="'.$selectedMinScoreValue.'"
10303
                />';
10304
                $return .= '</td>';
10305
                $return .= '<td>';
10306
                $return .= '<input
10307
                    size="4"
10308
                    maxlength="3"
10309
                    name="max_'.$item['id'].'"
10310
                    type="number"
10311
                    min="0"
10312
                    step="any"
10313
                    max="'.$item['max_score'].'"
10314
                    value="'.$selectedMaxScoreValue.'"
10315
                />';
10316
                $return .= '</td>';
10317
            }
10318
            $return .= '</tr>';
10319
        }
10320
        $return .= '<tr>';
10321
        $return .= '</tr>';
10322
        $return .= '</tbody>';
10323
        $return .= '</table>';
10324
        $return .= '</div>';
10325
        $return .= '<div class="form-group">';
10326
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10327
            get_lang('ModifyPrerequisites').'</button>';
10328
        $return .= '</form>';
10329
10330
        return $return;
10331
    }
10332
10333
    /**
10334
     * Return HTML list to allow prerequisites selection for lp.
10335
     *
10336
     * @return string HTML form
10337
     */
10338
    public function display_lp_prerequisites_list()
10339
    {
10340
        $course_id = api_get_course_int_id();
10341
        $lp_id = $this->lp_id;
10342
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10343
10344
        // get current prerequisite
10345
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10346
        $result = Database::query($sql);
10347
        $row = Database::fetch_array($result);
10348
        $prerequisiteId = $row['prerequisite'];
10349
        $session_id = api_get_session_id();
10350
        $session_condition = api_get_session_condition($session_id, true, true);
10351
        $sql = "SELECT * FROM $tbl_lp
10352
                WHERE c_id = $course_id $session_condition
10353
                ORDER BY display_order ";
10354
        $rs = Database::query($sql);
10355
        $return = '';
10356
        $return .= '<select name="prerequisites" class="form-control">';
10357
        $return .= '<option value="0">'.get_lang('None').'</option>';
10358
        if (Database::num_rows($rs) > 0) {
10359
            while ($row = Database::fetch_array($rs)) {
10360
                if ($row['id'] == $lp_id) {
10361
                    continue;
10362
                }
10363
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10364
            }
10365
        }
10366
        $return .= '</select>';
10367
10368
        return $return;
10369
    }
10370
10371
    /**
10372
     * Creates a list with all the documents in it.
10373
     *
10374
     * @param bool $showInvisibleFiles
10375
     *
10376
     * @throws Exception
10377
     * @throws HTML_QuickForm_Error
10378
     *
10379
     * @return string
10380
     */
10381
    public function get_documents($showInvisibleFiles = false)
10382
    {
10383
        $course_info = api_get_course_info();
10384
        $sessionId = api_get_session_id();
10385
        $documentTree = DocumentManager::get_document_preview(
10386
            $course_info,
10387
            $this->lp_id,
10388
            null,
10389
            $sessionId,
10390
            true,
10391
            null,
10392
            null,
10393
            $showInvisibleFiles,
10394
            true
10395
        );
10396
10397
        $headers = [
10398
            get_lang('Files'),
10399
            get_lang('CreateTheDocument'),
10400
            get_lang('CreateReadOutText'),
10401
            get_lang('Upload'),
10402
        ];
10403
10404
        $form = new FormValidator(
10405
            'form_upload',
10406
            'POST',
10407
            $this->getCurrentBuildingModeURL(),
10408
            '',
10409
            ['enctype' => 'multipart/form-data']
10410
        );
10411
10412
        $folders = DocumentManager::get_all_document_folders(
10413
            api_get_course_info(),
10414
            0,
10415
            true
10416
        );
10417
10418
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10419
10420
        DocumentManager::build_directory_selector(
10421
            $folders,
10422
            $lpPathInfo['id'],
10423
            [],
10424
            true,
10425
            $form,
10426
            'directory_parent_id'
10427
        );
10428
10429
        $group = [
10430
            $form->createElement(
10431
                'radio',
10432
                'if_exists',
10433
                get_lang('UplWhatIfFileExists'),
10434
                get_lang('UplDoNothing'),
10435
                'nothing'
10436
            ),
10437
            $form->createElement(
10438
                'radio',
10439
                'if_exists',
10440
                null,
10441
                get_lang('UplOverwriteLong'),
10442
                'overwrite'
10443
            ),
10444
            $form->createElement(
10445
                'radio',
10446
                'if_exists',
10447
                null,
10448
                get_lang('UplRenameLong'),
10449
                'rename'
10450
            ),
10451
        ];
10452
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10453
10454
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10455
        $defaultFileExistsOption = 'rename';
10456
        if (!empty($fileExistsOption)) {
10457
            $defaultFileExistsOption = $fileExistsOption;
10458
        }
10459
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10460
10461
        // Check box options
10462
        $form->addElement(
10463
            'checkbox',
10464
            'unzip',
10465
            get_lang('Options'),
10466
            get_lang('Uncompress')
10467
        );
10468
10469
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10470
        $form->addMultipleUpload($url);
10471
        $new = $this->display_document_form('add', 0);
10472
        $frmReadOutText = $this->displayFrmReadOutText('add');
10473
        $tabs = Display::tabs(
10474
            $headers,
10475
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10476
            'subtab'
10477
        );
10478
10479
        return $tabs;
10480
    }
10481
10482
    /**
10483
     * Creates a list with all the exercises (quiz) in it.
10484
     *
10485
     * @return string
10486
     */
10487
    public function get_exercises()
10488
    {
10489
        $course_id = api_get_course_int_id();
10490
        $session_id = api_get_session_id();
10491
        $userInfo = api_get_user_info();
10492
10493
        // New for hotpotatoes.
10494
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10495
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10496
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10497
        $condition_session = api_get_session_condition($session_id, true, true);
10498
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10499
10500
        $activeCondition = ' active <> -1 ';
10501
        if ($setting) {
10502
            $activeCondition = ' active = 1 ';
10503
        }
10504
10505
        $categoryCondition = '';
10506
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10507
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10508
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10509
        }
10510
10511
        $keywordCondition = '';
10512
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10513
10514
        if (!empty($keyword)) {
10515
            $keyword = Database::escape_string($keyword);
10516
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10517
        }
10518
10519
        $sql_quiz = "SELECT * FROM $tbl_quiz
10520
                     WHERE
10521
                            c_id = $course_id AND
10522
                            $activeCondition
10523
                            $condition_session
10524
                            $categoryCondition
10525
                            $keywordCondition
10526
                     ORDER BY title ASC";
10527
10528
        $sql_hot = "SELECT * FROM $tbl_doc
10529
                    WHERE
10530
                        c_id = $course_id AND
10531
                        path LIKE '".$uploadPath."/%/%htm%'
10532
                        $condition_session
10533
                     ORDER BY id ASC";
10534
10535
        $res_quiz = Database::query($sql_quiz);
10536
        $res_hot = Database::query($sql_hot);
10537
10538
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10539
10540
        // Create a search-box
10541
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10542
        $form->addHidden('action', 'add_item');
10543
        $form->addHidden('type', 'step');
10544
        $form->addHidden('lp_id', $this->lp_id);
10545
        $form->addHidden('lp_build_selected', '2');
10546
10547
        $form->addCourseHiddenParams();
10548
        $form->addText(
10549
            'keyword',
10550
            get_lang('Search'),
10551
            false,
10552
            [
10553
                'aria-label' => get_lang('Search'),
10554
            ]
10555
        );
10556
10557
        if (api_get_configuration_value('allow_exercise_categories')) {
10558
            $manager = new ExerciseCategoryManager();
10559
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10560
            if (!empty($options)) {
10561
                $form->addSelect(
10562
                    'category_id',
10563
                    get_lang('Category'),
10564
                    $options,
10565
                    ['placeholder' => get_lang('SelectAnOption')]
10566
                );
10567
            }
10568
        }
10569
10570
        $form->addButtonSearch(get_lang('Search'));
10571
        $return = $form->returnForm();
10572
10573
        $return .= '<ul class="lp_resource">';
10574
10575
        $return .= '<li class="lp_resource_element">';
10576
        $return .= Display::return_icon('new_exercice.png');
10577
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10578
            get_lang('NewExercise').'</a>';
10579
        $return .= '</li>';
10580
10581
        $previewIcon = Display::return_icon(
10582
            'preview_view.png',
10583
            get_lang('Preview')
10584
        );
10585
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10586
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10587
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10588
10589
        // Display hotpotatoes
10590
        while ($row_hot = Database::fetch_array($res_hot)) {
10591
            $link = Display::url(
10592
                $previewIcon,
10593
                $exerciseUrl.'&file='.$row_hot['path'],
10594
                ['target' => '_blank']
10595
            );
10596
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10597
            $return .= '<a class="moved" href="#">';
10598
            $return .= Display::return_icon(
10599
                'move_everywhere.png',
10600
                get_lang('Move'),
10601
                [],
10602
                ICON_SIZE_TINY
10603
            );
10604
            $return .= '</a> ';
10605
            $return .= Display::return_icon('hotpotatoes_s.png');
10606
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10607
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10608
            $return .= '</li>';
10609
        }
10610
10611
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10612
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10613
            $title = strip_tags(
10614
                api_html_entity_decode($row_quiz['title'])
10615
            );
10616
10617
            $visibility = api_get_item_visibility(
10618
                ['real_id' => $course_id],
10619
                TOOL_QUIZ,
10620
                $row_quiz['iid'],
10621
                $session_id
10622
            );
10623
10624
            $link = Display::url(
10625
                $previewIcon,
10626
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10627
                ['target' => '_blank']
10628
            );
10629
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10630
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10631
            $return .= $quizIcon;
10632
            $sessionStar = api_get_session_image(
10633
                $row_quiz['session_id'],
10634
                $userInfo['status']
10635
            );
10636
            $return .= Display::url(
10637
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10638
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10639
                [
10640
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10641
                ]
10642
            );
10643
            $return .= '</li>';
10644
        }
10645
10646
        $return .= '</ul>';
10647
10648
        return $return;
10649
    }
10650
10651
    /**
10652
     * Creates a list with all the links in it.
10653
     *
10654
     * @return string
10655
     */
10656
    public function get_links()
10657
    {
10658
        $selfUrl = api_get_self();
10659
        $courseIdReq = api_get_cidreq();
10660
        $course = api_get_course_info();
10661
        $userInfo = api_get_user_info();
10662
10663
        $course_id = $course['real_id'];
10664
        $tbl_link = Database::get_course_table(TABLE_LINK);
10665
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10666
        $moveEverywhereIcon = Display::return_icon(
10667
            'move_everywhere.png',
10668
            get_lang('Move'),
10669
            [],
10670
            ICON_SIZE_TINY
10671
        );
10672
10673
        $session_id = api_get_session_id();
10674
        $condition_session = api_get_session_condition(
10675
            $session_id,
10676
            true,
10677
            true,
10678
            'link.session_id'
10679
        );
10680
10681
        $sql = "SELECT
10682
                    link.id as link_id,
10683
                    link.title as link_title,
10684
                    link.session_id as link_session_id,
10685
                    link.category_id as category_id,
10686
                    link_category.category_title as category_title
10687
                FROM $tbl_link as link
10688
                LEFT JOIN $linkCategoryTable as link_category
10689
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10690
                WHERE link.c_id = $course_id $condition_session
10691
                ORDER BY link_category.category_title ASC, link.title ASC";
10692
        $result = Database::query($sql);
10693
        $categorizedLinks = [];
10694
        $categories = [];
10695
10696
        while ($link = Database::fetch_array($result)) {
10697
            if (!$link['category_id']) {
10698
                $link['category_title'] = get_lang('Uncategorized');
10699
            }
10700
            $categories[$link['category_id']] = $link['category_title'];
10701
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10702
        }
10703
10704
        $linksHtmlCode =
10705
            '<script>
10706
            function toggle_tool(tool, id) {
10707
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10708
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10709
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10710
                } else {
10711
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10712
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10713
                }
10714
            }
10715
        </script>
10716
10717
        <ul class="lp_resource">
10718
            <li class="lp_resource_element">
10719
                '.Display::return_icon('linksnew.gif').'
10720
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10721
                get_lang('LinkAdd').'
10722
                </a>
10723
            </li>';
10724
10725
        foreach ($categorizedLinks as $categoryId => $links) {
10726
            $linkNodes = null;
10727
            foreach ($links as $key => $linkInfo) {
10728
                $title = $linkInfo['link_title'];
10729
                $linkSessionId = $linkInfo['link_session_id'];
10730
10731
                $link = Display::url(
10732
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10733
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10734
                    ['target' => '_blank']
10735
                );
10736
10737
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10738
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10739
                    $linkNodes .=
10740
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10741
                        <a class="moved" href="#">'.
10742
                            $moveEverywhereIcon.
10743
                        '</a>
10744
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10745
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10746
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10747
                        Security::remove_XSS($title).$sessionStar.$link.
10748
                        '</a>
10749
                    </li>';
10750
                }
10751
            }
10752
            $linksHtmlCode .=
10753
                '<li>
10754
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10755
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10756
                    align="absbottom" />
10757
                </a>
10758
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10759
            </li>
10760
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10761
        }
10762
        $linksHtmlCode .= '</ul>';
10763
10764
        return $linksHtmlCode;
10765
    }
10766
10767
    /**
10768
     * Creates a list with all the student publications in it.
10769
     *
10770
     * @return string
10771
     */
10772
    public function get_student_publications()
10773
    {
10774
        $return = '<ul class="lp_resource">';
10775
        $return .= '<li class="lp_resource_element">';
10776
        $return .= Display::return_icon('works_new.gif');
10777
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10778
            get_lang('AddAssignmentPage').'</a>';
10779
        $return .= '</li>';
10780
10781
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10782
        $works = getWorkListTeacher(0, 100, null, null, null);
10783
        if (!empty($works)) {
10784
            foreach ($works as $work) {
10785
                $link = Display::url(
10786
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10787
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10788
                    ['target' => '_blank']
10789
                );
10790
10791
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10792
                $return .= '<a class="moved" href="#">';
10793
                $return .= Display::return_icon(
10794
                    'move_everywhere.png',
10795
                    get_lang('Move'),
10796
                    [],
10797
                    ICON_SIZE_TINY
10798
                );
10799
                $return .= '</a> ';
10800
10801
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10802
                $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.'">'.
10803
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10804
                </a>';
10805
10806
                $return .= '</li>';
10807
            }
10808
        }
10809
10810
        $return .= '</ul>';
10811
10812
        return $return;
10813
    }
10814
10815
    /**
10816
     * Creates a list with all the forums in it.
10817
     *
10818
     * @return string
10819
     */
10820
    public function get_forums()
10821
    {
10822
        require_once '../forum/forumfunction.inc.php';
10823
10824
        $forumCategories = get_forum_categories();
10825
        $forumsInNoCategory = get_forums_in_category(0);
10826
        if (!empty($forumsInNoCategory)) {
10827
            $forumCategories = array_merge(
10828
                $forumCategories,
10829
                [
10830
                    [
10831
                        'cat_id' => 0,
10832
                        'session_id' => 0,
10833
                        'visibility' => 1,
10834
                        'cat_comment' => null,
10835
                    ],
10836
                ]
10837
            );
10838
        }
10839
10840
        $forumList = get_forums();
10841
        $a_forums = [];
10842
        foreach ($forumCategories as $forumCategory) {
10843
            // The forums in this category.
10844
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10845
            if (!empty($forumsInCategory)) {
10846
                foreach ($forumList as $forum) {
10847
                    if (isset($forum['forum_category']) &&
10848
                        $forum['forum_category'] == $forumCategory['cat_id']
10849
                    ) {
10850
                        $a_forums[] = $forum;
10851
                    }
10852
                }
10853
            }
10854
        }
10855
10856
        $return = '<ul class="lp_resource">';
10857
10858
        // First add link
10859
        $return .= '<li class="lp_resource_element">';
10860
        $return .= Display::return_icon('new_forum.png');
10861
        $return .= Display::url(
10862
            get_lang('CreateANewForum'),
10863
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10864
                'action' => 'add',
10865
                'content' => 'forum',
10866
                'lp_id' => $this->lp_id,
10867
            ]),
10868
            ['title' => get_lang('CreateANewForum')]
10869
        );
10870
        $return .= '</li>';
10871
10872
        $return .= '<script>
10873
            function toggle_forum(forum_id) {
10874
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10875
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10876
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10877
                } else {
10878
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10879
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10880
                }
10881
            }
10882
        </script>';
10883
10884
        foreach ($a_forums as $forum) {
10885
            if (!empty($forum['forum_id'])) {
10886
                $link = Display::url(
10887
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10888
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10889
                    ['target' => '_blank']
10890
                );
10891
10892
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10893
                $return .= '<a class="moved" href="#">';
10894
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10895
                $return .= ' </a>';
10896
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10897
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10898
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10899
                            </a>
10900
                            <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">'.
10901
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10902
10903
                $return .= '</li>';
10904
10905
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10906
                $a_threads = get_threads($forum['forum_id']);
10907
                if (is_array($a_threads)) {
10908
                    foreach ($a_threads as $thread) {
10909
                        $link = Display::url(
10910
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10911
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10912
                            ['target' => '_blank']
10913
                        );
10914
10915
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10916
                        $return .= '&nbsp;<a class="moved" href="#">';
10917
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10918
                        $return .= ' </a>';
10919
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10920
                        $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.'">'.
10921
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10922
                        $return .= '</li>';
10923
                    }
10924
                }
10925
                $return .= '</div>';
10926
            }
10927
        }
10928
        $return .= '</ul>';
10929
10930
        return $return;
10931
    }
10932
10933
    /**
10934
     * // TODO: The output encoding should be equal to the system encoding.
10935
     *
10936
     * Exports the learning path as a SCORM package. This is the main function that
10937
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10938
     * whole thing and returns the zip.
10939
     *
10940
     * This method needs to be called in PHP5, as it will fail with non-adequate
10941
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10942
     * you need to call it on a learnpath object.
10943
     *
10944
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10945
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10946
     * path has been modified, it should use the generic method here below.
10947
     *
10948
     * @return string Returns the zip package string, or null if error
10949
     */
10950
    public function scormExport()
10951
    {
10952
        api_set_more_memory_and_time_limits();
10953
10954
        $_course = api_get_course_info();
10955
        $course_id = $_course['real_id'];
10956
        // Create the zip handler (this will remain available throughout the method).
10957
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10958
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10959
        $temp_dir_short = uniqid('scorm_export', true);
10960
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10961
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10962
        $zip_folder = new PclZip($temp_zip_file);
10963
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10964
        $root_path = $main_path = api_get_path(SYS_PATH);
10965
        $files_cleanup = [];
10966
10967
        // Place to temporarily stash the zip file.
10968
        // create the temp dir if it doesn't exist
10969
        // or do a cleanup before creating the zip file.
10970
        if (!is_dir($temp_zip_dir)) {
10971
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10972
        } else {
10973
            // Cleanup: Check the temp dir for old files and delete them.
10974
            $handle = opendir($temp_zip_dir);
10975
            while (false !== ($file = readdir($handle))) {
10976
                if ($file != '.' && $file != '..') {
10977
                    unlink("$temp_zip_dir/$file");
10978
                }
10979
            }
10980
            closedir($handle);
10981
        }
10982
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10983
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10984
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10985
        ) {
10986
            // Remove the possible . at the end of the path.
10987
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10988
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10989
            mkdir(
10990
                $dest_path_to_scorm_folder,
10991
                api_get_permissions_for_new_directories(),
10992
                true
10993
            );
10994
            copyr(
10995
                $current_course_path.'/scorm/'.$this->path,
10996
                $dest_path_to_scorm_folder,
10997
                ['imsmanifest'],
10998
                $zip_files
10999
            );
11000
        }
11001
11002
        // Build a dummy imsmanifest structure.
11003
        // Do not add to the zip yet (we still need it).
11004
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11005
        // Aggregation Model official document, section "2.3 Content Packaging".
11006
        // We are going to build a UTF-8 encoded manifest.
11007
        // Later we will recode it to the desired (and supported) encoding.
11008
        $xmldoc = new DOMDocument('1.0');
11009
        $root = $xmldoc->createElement('manifest');
11010
        $root->setAttribute('identifier', 'SingleCourseManifest');
11011
        $root->setAttribute('version', '1.1');
11012
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11013
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11014
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11015
        $root->setAttribute(
11016
            'xsi:schemaLocation',
11017
            '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'
11018
        );
11019
        // Build mandatory sub-root container elements.
11020
        $metadata = $xmldoc->createElement('metadata');
11021
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11022
        $metadata->appendChild($md_schema);
11023
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11024
        $metadata->appendChild($md_schemaversion);
11025
        $root->appendChild($metadata);
11026
11027
        $organizations = $xmldoc->createElement('organizations');
11028
        $resources = $xmldoc->createElement('resources');
11029
11030
        // Build the only organization we will use in building our learnpaths.
11031
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11032
        $organization = $xmldoc->createElement('organization');
11033
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11034
        // To set the title of the SCORM entity (=organization), we take the name given
11035
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11036
        // learning path charset) as it is the encoding that defines how it is stored
11037
        // in the database. Then we convert it to HTML entities again as the "&" character
11038
        // alone is not authorized in XML (must be &amp;).
11039
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11040
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11041
        $organization->appendChild($org_title);
11042
        $folder_name = 'document';
11043
11044
        // Removes the learning_path/scorm_folder path when exporting see #4841
11045
        $path_to_remove = '';
11046
        $path_to_replace = '';
11047
        $result = $this->generate_lp_folder($_course);
11048
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11049
            $path_to_remove = 'document'.$result['dir'];
11050
            $path_to_replace = $folder_name.'/';
11051
        }
11052
11053
        // Fixes chamilo scorm exports
11054
        if ($this->ref === 'chamilo_scorm_export') {
11055
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11056
        }
11057
11058
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11059
        $link_updates = [];
11060
        $links_to_create = [];
11061
        foreach ($this->ordered_items as $index => $itemId) {
11062
            /** @var learnpathItem $item */
11063
            $item = $this->items[$itemId];
11064
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11065
                // Get included documents from this item.
11066
                if ($item->type === 'sco') {
11067
                    $inc_docs = $item->get_resources_from_source(
11068
                        null,
11069
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11070
                    );
11071
                } else {
11072
                    $inc_docs = $item->get_resources_from_source();
11073
                }
11074
11075
                // Give a child element <item> to the <organization> element.
11076
                $my_item_id = $item->get_id();
11077
                $my_item = $xmldoc->createElement('item');
11078
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11079
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11080
                $my_item->setAttribute('isvisible', 'true');
11081
                // Give a child element <title> to the <item> element.
11082
                $my_title = $xmldoc->createElement(
11083
                    'title',
11084
                    htmlspecialchars(
11085
                        api_utf8_encode($item->get_title()),
11086
                        ENT_QUOTES,
11087
                        'UTF-8'
11088
                    )
11089
                );
11090
                $my_item->appendChild($my_title);
11091
                // Give a child element <adlcp:prerequisites> to the <item> element.
11092
                $my_prereqs = $xmldoc->createElement(
11093
                    'adlcp:prerequisites',
11094
                    $this->get_scorm_prereq_string($my_item_id)
11095
                );
11096
                $my_prereqs->setAttribute('type', 'aicc_script');
11097
                $my_item->appendChild($my_prereqs);
11098
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11099
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11100
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11101
                //$xmldoc->createElement('adlcp:timelimitaction','');
11102
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11103
                //$xmldoc->createElement('adlcp:datafromlms','');
11104
                // Give a child element <adlcp:masteryscore> to the <item> element.
11105
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11106
                $my_item->appendChild($my_masteryscore);
11107
11108
                // Attach this item to the organization element or hits parent if there is one.
11109
                if (!empty($item->parent) && $item->parent != 0) {
11110
                    $children = $organization->childNodes;
11111
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11112
                    if (is_object($possible_parent)) {
11113
                        $possible_parent->appendChild($my_item);
11114
                    } else {
11115
                        if ($this->debug > 0) {
11116
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11117
                        }
11118
                    }
11119
                } else {
11120
                    if ($this->debug > 0) {
11121
                        error_log('No parent');
11122
                    }
11123
                    $organization->appendChild($my_item);
11124
                }
11125
11126
                // Get the path of the file(s) from the course directory root.
11127
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11128
                $my_xml_file_path = $my_file_path;
11129
                if (!empty($path_to_remove)) {
11130
                    // From docs
11131
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11132
11133
                    // From quiz
11134
                    if ($this->ref === 'chamilo_scorm_export') {
11135
                        $path_to_remove = 'scorm/'.$this->path.'/';
11136
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11137
                    }
11138
                }
11139
11140
                $my_sub_dir = dirname($my_file_path);
11141
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11142
                $my_xml_sub_dir = $my_sub_dir;
11143
                // Give a <resource> child to the <resources> element
11144
                $my_resource = $xmldoc->createElement('resource');
11145
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11146
                $my_resource->setAttribute('type', 'webcontent');
11147
                $my_resource->setAttribute('href', $my_xml_file_path);
11148
                // adlcp:scormtype can be either 'sco' or 'asset'.
11149
                if ($item->type === 'sco') {
11150
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11151
                } else {
11152
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11153
                }
11154
                // xml:base is the base directory to find the files declared in this resource.
11155
                $my_resource->setAttribute('xml:base', '');
11156
                // Give a <file> child to the <resource> element.
11157
                $my_file = $xmldoc->createElement('file');
11158
                $my_file->setAttribute('href', $my_xml_file_path);
11159
                $my_resource->appendChild($my_file);
11160
11161
                // Dependency to other files - not yet supported.
11162
                $i = 1;
11163
                if ($inc_docs) {
11164
                    foreach ($inc_docs as $doc_info) {
11165
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11166
                            continue;
11167
                        }
11168
                        $my_dep = $xmldoc->createElement('resource');
11169
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11170
                        $my_dep->setAttribute('identifier', $res_id);
11171
                        $my_dep->setAttribute('type', 'webcontent');
11172
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11173
                        $my_dep_file = $xmldoc->createElement('file');
11174
                        // Check type of URL.
11175
                        if ($doc_info[1] == 'remote') {
11176
                            // Remote file. Save url as is.
11177
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11178
                            $my_dep->setAttribute('xml:base', '');
11179
                        } elseif ($doc_info[1] === 'local') {
11180
                            switch ($doc_info[2]) {
11181
                                case 'url':
11182
                                    // Local URL - save path as url for now, don't zip file.
11183
                                    $abs_path = api_get_path(SYS_PATH).
11184
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11185
                                    $current_dir = dirname($abs_path);
11186
                                    $current_dir = str_replace('\\', '/', $current_dir);
11187
                                    $file_path = realpath($abs_path);
11188
                                    $file_path = str_replace('\\', '/', $file_path);
11189
                                    $my_dep_file->setAttribute('href', $file_path);
11190
                                    $my_dep->setAttribute('xml:base', '');
11191
                                    if (strstr($file_path, $main_path) !== false) {
11192
                                        // The calculated real path is really inside Chamilo's root path.
11193
                                        // Reduce file path to what's under the DocumentRoot.
11194
                                        $replace = $file_path;
11195
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11196
                                        $destinationFile = $file_path;
11197
11198
                                        if (strstr($file_path, 'upload/users') !== false) {
11199
                                            $pos = strpos($file_path, 'my_files/');
11200
                                            if ($pos !== false) {
11201
                                                $onlyDirectory = str_replace(
11202
                                                    'upload/users/',
11203
                                                    '',
11204
                                                    substr($file_path, $pos, strlen($file_path))
11205
                                                );
11206
                                            }
11207
                                            $replace = $onlyDirectory;
11208
                                            $destinationFile = $replace;
11209
                                        }
11210
                                        $zip_files_abs[] = $file_path;
11211
                                        $link_updates[$my_file_path][] = [
11212
                                            'orig' => $doc_info[0],
11213
                                            'dest' => $destinationFile,
11214
                                            'replace' => $replace,
11215
                                        ];
11216
                                        $my_dep_file->setAttribute('href', $file_path);
11217
                                        $my_dep->setAttribute('xml:base', '');
11218
                                    } elseif (empty($file_path)) {
11219
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11220
                                        $file_path = str_replace('//', '/', $file_path);
11221
                                        if (file_exists($file_path)) {
11222
                                            // We get the relative path.
11223
                                            $file_path = substr($file_path, strlen($current_dir));
11224
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11225
                                            $link_updates[$my_file_path][] = [
11226
                                                'orig' => $doc_info[0],
11227
                                                'dest' => $file_path,
11228
                                            ];
11229
                                            $my_dep_file->setAttribute('href', $file_path);
11230
                                            $my_dep->setAttribute('xml:base', '');
11231
                                        }
11232
                                    }
11233
                                    break;
11234
                                case 'abs':
11235
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11236
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11237
                                    $my_dep->setAttribute('xml:base', '');
11238
11239
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11240
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11241
                                    $abs_img_path_without_subdir = $doc_info[0];
11242
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11243
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11244
                                    if ($pos === 0) {
11245
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11246
                                    }
11247
11248
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11249
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11250
11251
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11252
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11253
                                    // Check if the current document is in that path.
11254
                                    if (strstr($file_path, $cur_path) !== false) {
11255
                                        $destinationFile = substr($file_path, strlen($cur_path));
11256
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11257
11258
                                        $fileToTest = $cur_path.$my_file_path;
11259
                                        if (!empty($path_to_remove)) {
11260
                                            $fileToTest = str_replace(
11261
                                                $path_to_remove.'/',
11262
                                                $path_to_replace,
11263
                                                $cur_path.$my_file_path
11264
                                            );
11265
                                        }
11266
11267
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11268
11269
                                        // Put the current document in the zip (this array is the array
11270
                                        // that will manage documents already in the course folder - relative).
11271
                                        $zip_files[] = $filePathNoCoursePart;
11272
                                        // Update the links to the current document in the
11273
                                        // containing document (make them relative).
11274
                                        $link_updates[$my_file_path][] = [
11275
                                            'orig' => $doc_info[0],
11276
                                            'dest' => $destinationFile,
11277
                                            'replace' => $relative_path,
11278
                                        ];
11279
11280
                                        $my_dep_file->setAttribute('href', $file_path);
11281
                                        $my_dep->setAttribute('xml:base', '');
11282
                                    } elseif (strstr($file_path, $main_path) !== false) {
11283
                                        // The calculated real path is really inside Chamilo's root path.
11284
                                        // Reduce file path to what's under the DocumentRoot.
11285
                                        $file_path = substr($file_path, strlen($root_path));
11286
                                        $zip_files_abs[] = $file_path;
11287
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11288
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11289
                                        $my_dep->setAttribute('xml:base', '');
11290
                                    } elseif (empty($file_path)) {
11291
                                        // Probably this is an image inside "/main" directory
11292
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11293
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11294
11295
                                        if (file_exists($file_path)) {
11296
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11297
                                                // We get the relative path.
11298
                                                $pos = strpos($file_path, 'main/default_course_document/');
11299
                                                if ($pos !== false) {
11300
                                                    $onlyDirectory = str_replace(
11301
                                                        'main/default_course_document/',
11302
                                                        '',
11303
                                                        substr($file_path, $pos, strlen($file_path))
11304
                                                    );
11305
                                                }
11306
11307
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11308
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11309
                                                $zip_files_abs[] = $fileAbs;
11310
                                                $link_updates[$my_file_path][] = [
11311
                                                    'orig' => $doc_info[0],
11312
                                                    'dest' => $destinationFile,
11313
                                                ];
11314
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11315
                                                $my_dep->setAttribute('xml:base', '');
11316
                                            }
11317
                                        }
11318
                                    }
11319
                                    break;
11320
                                case 'rel':
11321
                                    // Path relative to the current document.
11322
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11323
                                    if (substr($doc_info[0], 0, 2) === '..') {
11324
                                        // Relative path going up.
11325
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11326
                                        $current_dir = str_replace('\\', '/', $current_dir);
11327
                                        $file_path = realpath($current_dir.$doc_info[0]);
11328
                                        $file_path = str_replace('\\', '/', $file_path);
11329
                                        if (strstr($file_path, $main_path) !== false) {
11330
                                            // The calculated real path is really inside Chamilo's root path.
11331
                                            // Reduce file path to what's under the DocumentRoot.
11332
                                            $file_path = substr($file_path, strlen($root_path));
11333
                                            $zip_files_abs[] = $file_path;
11334
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11335
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11336
                                            $my_dep->setAttribute('xml:base', '');
11337
                                        }
11338
                                    } else {
11339
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11340
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11341
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11342
                                    }
11343
                                    break;
11344
                                default:
11345
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11346
                                    $my_dep->setAttribute('xml:base', '');
11347
                                    break;
11348
                            }
11349
                        }
11350
                        $my_dep->appendChild($my_dep_file);
11351
                        $resources->appendChild($my_dep);
11352
                        $dependency = $xmldoc->createElement('dependency');
11353
                        $dependency->setAttribute('identifierref', $res_id);
11354
                        $my_resource->appendChild($dependency);
11355
                        $i++;
11356
                    }
11357
                }
11358
                $resources->appendChild($my_resource);
11359
                $zip_files[] = $my_file_path;
11360
            } else {
11361
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11362
                switch ($item->type) {
11363
                    case TOOL_LINK:
11364
                        $my_item = $xmldoc->createElement('item');
11365
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11366
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11367
                        $my_item->setAttribute('isvisible', 'true');
11368
                        // Give a child element <title> to the <item> element.
11369
                        $my_title = $xmldoc->createElement(
11370
                            'title',
11371
                            htmlspecialchars(
11372
                                api_utf8_encode($item->get_title()),
11373
                                ENT_QUOTES,
11374
                                'UTF-8'
11375
                            )
11376
                        );
11377
                        $my_item->appendChild($my_title);
11378
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11379
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11380
                        $my_prereqs->setAttribute('type', 'aicc_script');
11381
                        $my_item->appendChild($my_prereqs);
11382
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11383
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11384
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11385
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11386
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11387
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11388
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11389
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11390
                        $my_item->appendChild($my_masteryscore);
11391
11392
                        // Attach this item to the organization element or its parent if there is one.
11393
                        if (!empty($item->parent) && $item->parent != 0) {
11394
                            $children = $organization->childNodes;
11395
                            for ($i = 0; $i < $children->length; $i++) {
11396
                                $item_temp = $children->item($i);
11397
                                if ($item_temp->nodeName == 'item') {
11398
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11399
                                        $item_temp->appendChild($my_item);
11400
                                    }
11401
                                }
11402
                            }
11403
                        } else {
11404
                            $organization->appendChild($my_item);
11405
                        }
11406
11407
                        $my_file_path = 'link_'.$item->get_id().'.html';
11408
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11409
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11410
                        $rs = Database::query($sql);
11411
                        if ($link = Database::fetch_array($rs)) {
11412
                            $url = $link['url'];
11413
                            $title = stripslashes($link['title']);
11414
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11415
                            $my_xml_file_path = $my_file_path;
11416
                            $my_sub_dir = dirname($my_file_path);
11417
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11418
                            $my_xml_sub_dir = $my_sub_dir;
11419
                            // Give a <resource> child to the <resources> element.
11420
                            $my_resource = $xmldoc->createElement('resource');
11421
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11422
                            $my_resource->setAttribute('type', 'webcontent');
11423
                            $my_resource->setAttribute('href', $my_xml_file_path);
11424
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11425
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11426
                            // xml:base is the base directory to find the files declared in this resource.
11427
                            $my_resource->setAttribute('xml:base', '');
11428
                            // give a <file> child to the <resource> element.
11429
                            $my_file = $xmldoc->createElement('file');
11430
                            $my_file->setAttribute('href', $my_xml_file_path);
11431
                            $my_resource->appendChild($my_file);
11432
                            $resources->appendChild($my_resource);
11433
                        }
11434
                        break;
11435
                    case TOOL_QUIZ:
11436
                        $exe_id = $item->path;
11437
                        // Should be using ref when everything will be cleaned up in this regard.
11438
                        $exe = new Exercise();
11439
                        $exe->read($exe_id);
11440
                        $my_item = $xmldoc->createElement('item');
11441
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11442
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11443
                        $my_item->setAttribute('isvisible', 'true');
11444
                        // Give a child element <title> to the <item> element.
11445
                        $my_title = $xmldoc->createElement(
11446
                            'title',
11447
                            htmlspecialchars(
11448
                                api_utf8_encode($item->get_title()),
11449
                                ENT_QUOTES,
11450
                                'UTF-8'
11451
                            )
11452
                        );
11453
                        $my_item->appendChild($my_title);
11454
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11455
                        $my_item->appendChild($my_max_score);
11456
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11457
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11458
                        $my_prereqs->setAttribute('type', 'aicc_script');
11459
                        $my_item->appendChild($my_prereqs);
11460
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11461
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11462
                        $my_item->appendChild($my_masteryscore);
11463
11464
                        // Attach this item to the organization element or hits parent if there is one.
11465
                        if (!empty($item->parent) && $item->parent != 0) {
11466
                            $children = $organization->childNodes;
11467
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11468
                            if ($possible_parent) {
11469
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11470
                                    $possible_parent->appendChild($my_item);
11471
                                }
11472
                            }
11473
                        } else {
11474
                            $organization->appendChild($my_item);
11475
                        }
11476
11477
                        // Get the path of the file(s) from the course directory root
11478
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11479
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11480
                        // Write the contents of the exported exercise into a (big) html file
11481
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11482
                        $scormExercise = new ScormExercise($exe, true);
11483
                        $contents = $scormExercise->export();
11484
11485
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11486
                        $res = file_put_contents($tmp_file_path, $contents);
11487
                        if ($res === false) {
11488
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11489
                        }
11490
                        $files_cleanup[] = $tmp_file_path;
11491
                        $my_xml_file_path = $my_file_path;
11492
                        $my_sub_dir = dirname($my_file_path);
11493
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11494
                        $my_xml_sub_dir = $my_sub_dir;
11495
                        // Give a <resource> child to the <resources> element.
11496
                        $my_resource = $xmldoc->createElement('resource');
11497
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11498
                        $my_resource->setAttribute('type', 'webcontent');
11499
                        $my_resource->setAttribute('href', $my_xml_file_path);
11500
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11501
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11502
                        // xml:base is the base directory to find the files declared in this resource.
11503
                        $my_resource->setAttribute('xml:base', '');
11504
                        // Give a <file> child to the <resource> element.
11505
                        $my_file = $xmldoc->createElement('file');
11506
                        $my_file->setAttribute('href', $my_xml_file_path);
11507
                        $my_resource->appendChild($my_file);
11508
11509
                        // Get included docs.
11510
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11511
11512
                        // Dependency to other files - not yet supported.
11513
                        $i = 1;
11514
                        foreach ($inc_docs as $doc_info) {
11515
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11516
                                continue;
11517
                            }
11518
                            $my_dep = $xmldoc->createElement('resource');
11519
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11520
                            $my_dep->setAttribute('identifier', $res_id);
11521
                            $my_dep->setAttribute('type', 'webcontent');
11522
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11523
                            $my_dep_file = $xmldoc->createElement('file');
11524
                            // Check type of URL.
11525
                            if ($doc_info[1] == 'remote') {
11526
                                // Remote file. Save url as is.
11527
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11528
                                $my_dep->setAttribute('xml:base', '');
11529
                            } elseif ($doc_info[1] == 'local') {
11530
                                switch ($doc_info[2]) {
11531
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11532
                                        // Save file but as local file (retrieve from URL).
11533
                                        $abs_path = api_get_path(SYS_PATH).
11534
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11535
                                        $current_dir = dirname($abs_path);
11536
                                        $current_dir = str_replace('\\', '/', $current_dir);
11537
                                        $file_path = realpath($abs_path);
11538
                                        $file_path = str_replace('\\', '/', $file_path);
11539
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11540
                                        $my_dep->setAttribute('xml:base', '');
11541
                                        if (strstr($file_path, $main_path) !== false) {
11542
                                            // The calculated real path is really inside the chamilo root path.
11543
                                            // Reduce file path to what's under the DocumentRoot.
11544
                                            $file_path = substr($file_path, strlen($root_path));
11545
                                            $zip_files_abs[] = $file_path;
11546
                                            $link_updates[$my_file_path][] = [
11547
                                                'orig' => $doc_info[0],
11548
                                                'dest' => 'document/'.$file_path,
11549
                                            ];
11550
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11551
                                            $my_dep->setAttribute('xml:base', '');
11552
                                        } elseif (empty($file_path)) {
11553
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11554
                                            $file_path = str_replace('//', '/', $file_path);
11555
                                            if (file_exists($file_path)) {
11556
                                                $file_path = substr($file_path, strlen($current_dir));
11557
                                                // We get the relative path.
11558
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11559
                                                $link_updates[$my_file_path][] = [
11560
                                                    'orig' => $doc_info[0],
11561
                                                    'dest' => 'document/'.$file_path,
11562
                                                ];
11563
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11564
                                                $my_dep->setAttribute('xml:base', '');
11565
                                            }
11566
                                        }
11567
                                        break;
11568
                                    case 'abs':
11569
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11570
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11571
                                        $current_dir = str_replace('\\', '/', $current_dir);
11572
                                        $file_path = realpath($doc_info[0]);
11573
                                        $file_path = str_replace('\\', '/', $file_path);
11574
                                        $my_dep_file->setAttribute('href', $file_path);
11575
                                        $my_dep->setAttribute('xml:base', '');
11576
11577
                                        if (strstr($file_path, $main_path) !== false) {
11578
                                            // The calculated real path is really inside the chamilo root path.
11579
                                            // Reduce file path to what's under the DocumentRoot.
11580
                                            $file_path = substr($file_path, strlen($root_path));
11581
                                            $zip_files_abs[] = $file_path;
11582
                                            $link_updates[$my_file_path][] = [
11583
                                                'orig' => $doc_info[0],
11584
                                                'dest' => $file_path,
11585
                                            ];
11586
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11587
                                            $my_dep->setAttribute('xml:base', '');
11588
                                        } elseif (empty($file_path)) {
11589
                                            $docSysPartPath = str_replace(
11590
                                                api_get_path(REL_COURSE_PATH),
11591
                                                '',
11592
                                                $doc_info[0]
11593
                                            );
11594
11595
                                            $docSysPartPathNoCourseCode = str_replace(
11596
                                                $_course['directory'].'/',
11597
                                                '',
11598
                                                $docSysPartPath
11599
                                            );
11600
11601
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11602
                                            if (file_exists($docSysPath)) {
11603
                                                $file_path = $docSysPartPathNoCourseCode;
11604
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11605
                                                $link_updates[$my_file_path][] = [
11606
                                                    'orig' => $doc_info[0],
11607
                                                    'dest' => $file_path,
11608
                                                ];
11609
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11610
                                                $my_dep->setAttribute('xml:base', '');
11611
                                            }
11612
                                        }
11613
                                        break;
11614
                                    case 'rel':
11615
                                        // Path relative to the current document. Save xml:base as current document's
11616
                                        // directory and save file in zip as subdir.file_path
11617
                                        if (substr($doc_info[0], 0, 2) === '..') {
11618
                                            // Relative path going up.
11619
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11620
                                            $current_dir = str_replace('\\', '/', $current_dir);
11621
                                            $file_path = realpath($current_dir.$doc_info[0]);
11622
                                            $file_path = str_replace('\\', '/', $file_path);
11623
                                            if (strstr($file_path, $main_path) !== false) {
11624
                                                // The calculated real path is really inside Chamilo's root path.
11625
                                                // Reduce file path to what's under the DocumentRoot.
11626
11627
                                                $file_path = substr($file_path, strlen($root_path));
11628
                                                $file_path_dest = $file_path;
11629
11630
                                                // File path is courses/CHAMILO/document/....
11631
                                                $info_file_path = explode('/', $file_path);
11632
                                                if ($info_file_path[0] == 'courses') {
11633
                                                    // Add character "/" in file path.
11634
                                                    $file_path_dest = 'document/'.$file_path;
11635
                                                }
11636
                                                $zip_files_abs[] = $file_path;
11637
11638
                                                $link_updates[$my_file_path][] = [
11639
                                                    'orig' => $doc_info[0],
11640
                                                    'dest' => $file_path_dest,
11641
                                                ];
11642
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11643
                                                $my_dep->setAttribute('xml:base', '');
11644
                                            }
11645
                                        } else {
11646
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11647
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11648
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11649
                                        }
11650
                                        break;
11651
                                    default:
11652
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11653
                                        $my_dep->setAttribute('xml:base', '');
11654
                                        break;
11655
                                }
11656
                            }
11657
                            $my_dep->appendChild($my_dep_file);
11658
                            $resources->appendChild($my_dep);
11659
                            $dependency = $xmldoc->createElement('dependency');
11660
                            $dependency->setAttribute('identifierref', $res_id);
11661
                            $my_resource->appendChild($dependency);
11662
                            $i++;
11663
                        }
11664
                        $resources->appendChild($my_resource);
11665
                        $zip_files[] = $my_file_path;
11666
                        break;
11667
                    default:
11668
                        // Get the path of the file(s) from the course directory root
11669
                        $my_file_path = 'non_exportable.html';
11670
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11671
                        $my_xml_file_path = $my_file_path;
11672
                        $my_sub_dir = dirname($my_file_path);
11673
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11674
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11675
                        $my_xml_sub_dir = $my_sub_dir;
11676
                        // Give a <resource> child to the <resources> element.
11677
                        $my_resource = $xmldoc->createElement('resource');
11678
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11679
                        $my_resource->setAttribute('type', 'webcontent');
11680
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11681
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11682
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11683
                        // xml:base is the base directory to find the files declared in this resource.
11684
                        $my_resource->setAttribute('xml:base', '');
11685
                        // Give a <file> child to the <resource> element.
11686
                        $my_file = $xmldoc->createElement('file');
11687
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11688
                        $my_resource->appendChild($my_file);
11689
                        $resources->appendChild($my_resource);
11690
                        break;
11691
                }
11692
            }
11693
        }
11694
        $organizations->appendChild($organization);
11695
        $root->appendChild($organizations);
11696
        $root->appendChild($resources);
11697
        $xmldoc->appendChild($root);
11698
11699
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11700
11701
        // then add the file to the zip, then destroy the file (this is done automatically).
11702
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11703
        foreach ($zip_files as $file_path) {
11704
            if (empty($file_path)) {
11705
                continue;
11706
            }
11707
11708
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11709
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11710
11711
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11712
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11713
            }
11714
11715
            $this->create_path($dest_file);
11716
            @copy($filePath, $dest_file);
11717
11718
            // Check if the file needs a link update.
11719
            if (in_array($file_path, array_keys($link_updates))) {
11720
                $string = file_get_contents($dest_file);
11721
                unlink($dest_file);
11722
                foreach ($link_updates[$file_path] as $old_new) {
11723
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11724
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11725
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11726
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11727
                    if (substr($old_new['dest'], -3) === 'flv' &&
11728
                        substr($old_new['dest'], 0, 5) === 'main/'
11729
                    ) {
11730
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11731
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11732
                        substr($old_new['dest'], 0, 6) === 'video/'
11733
                    ) {
11734
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11735
                    }
11736
11737
                    // Fix to avoid problems with default_course_document
11738
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11739
                        $newDestination = $old_new['dest'];
11740
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11741
                            $newDestination = $old_new['replace'];
11742
                        }
11743
                    } else {
11744
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11745
                    }
11746
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11747
11748
                    // Add files inside the HTMLs
11749
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11750
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11751
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11752
                        copy(
11753
                            $sys_course_path.$new_path,
11754
                            $destinationFile
11755
                        );
11756
                    }
11757
                }
11758
                file_put_contents($dest_file, $string);
11759
            }
11760
11761
            if (file_exists($filePath) && $copyAll) {
11762
                $extension = $this->get_extension($filePath);
11763
                if (in_array($extension, ['html', 'html'])) {
11764
                    $containerOrigin = dirname($filePath);
11765
                    $containerDestination = dirname($dest_file);
11766
11767
                    $finder = new Finder();
11768
                    $finder->files()->in($containerOrigin)
11769
                        ->notName('*_DELETED_*')
11770
                        ->exclude('share_folder')
11771
                        ->exclude('chat_files')
11772
                        ->exclude('certificates')
11773
                    ;
11774
11775
                    if (is_dir($containerOrigin) &&
11776
                        is_dir($containerDestination)
11777
                    ) {
11778
                        $fs = new Filesystem();
11779
                        $fs->mirror(
11780
                            $containerOrigin,
11781
                            $containerDestination,
11782
                            $finder
11783
                        );
11784
                    }
11785
                }
11786
            }
11787
        }
11788
11789
        foreach ($zip_files_abs as $file_path) {
11790
            if (empty($file_path)) {
11791
                continue;
11792
            }
11793
11794
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11795
                continue;
11796
            }
11797
11798
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11799
            if (strstr($file_path, 'upload/users') !== false) {
11800
                $pos = strpos($file_path, 'my_files/');
11801
                if ($pos !== false) {
11802
                    $onlyDirectory = str_replace(
11803
                        'upload/users/',
11804
                        '',
11805
                        substr($file_path, $pos, strlen($file_path))
11806
                    );
11807
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11808
                }
11809
            }
11810
11811
            if (strstr($file_path, 'default_course_document/') !== false) {
11812
                $replace = str_replace('/main', '', $file_path);
11813
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11814
            }
11815
11816
            if (empty($dest_file)) {
11817
                continue;
11818
            }
11819
11820
            $this->create_path($dest_file);
11821
            copy($main_path.$file_path, $dest_file);
11822
            // Check if the file needs a link update.
11823
            if (in_array($file_path, array_keys($link_updates))) {
11824
                $string = file_get_contents($dest_file);
11825
                unlink($dest_file);
11826
                foreach ($link_updates[$file_path] as $old_new) {
11827
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11828
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11829
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11830
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11831
                    if (substr($old_new['dest'], -3) == 'flv' &&
11832
                        substr($old_new['dest'], 0, 5) == 'main/'
11833
                    ) {
11834
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11835
                    }
11836
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11837
                }
11838
                file_put_contents($dest_file, $string);
11839
            }
11840
        }
11841
11842
        if (is_array($links_to_create)) {
11843
            foreach ($links_to_create as $file => $link) {
11844
                $content = '<!DOCTYPE html><head>
11845
                            <meta charset="'.api_get_language_isocode().'" />
11846
                            <title>'.$link['title'].'</title>
11847
                            </head>
11848
                            <body dir="'.api_get_text_direction().'">
11849
                            <div style="text-align:center">
11850
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11851
                            </body>
11852
                            </html>';
11853
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11854
            }
11855
        }
11856
11857
        // Add non exportable message explanation.
11858
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11859
        $file_content = '<!DOCTYPE html><head>
11860
                        <meta charset="'.api_get_language_isocode().'" />
11861
                        <title>'.$lang_not_exportable.'</title>
11862
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11863
                        </head>
11864
                        <body dir="'.api_get_text_direction().'">';
11865
        $file_content .=
11866
            <<<EOD
11867
                    <style>
11868
            .error-message {
11869
                font-family: arial, verdana, helvetica, sans-serif;
11870
                border-width: 1px;
11871
                border-style: solid;
11872
                left: 50%;
11873
                margin: 10px auto;
11874
                min-height: 30px;
11875
                padding: 5px;
11876
                right: 50%;
11877
                width: 500px;
11878
                background-color: #FFD1D1;
11879
                border-color: #FF0000;
11880
                color: #000;
11881
            }
11882
        </style>
11883
    <body>
11884
        <div class="error-message">
11885
            $lang_not_exportable
11886
        </div>
11887
    </body>
11888
</html>
11889
EOD;
11890
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11891
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11892
        }
11893
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11894
11895
        // Add the extra files that go along with a SCORM package.
11896
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11897
11898
        $fs = new Filesystem();
11899
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11900
11901
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11902
        $manifest = @$xmldoc->saveXML();
11903
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11904
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11905
        $zip_folder->add(
11906
            $archivePath.'/'.$temp_dir_short,
11907
            PCLZIP_OPT_REMOVE_PATH,
11908
            $archivePath.'/'.$temp_dir_short.'/'
11909
        );
11910
11911
        // Clean possible temporary files.
11912
        foreach ($files_cleanup as $file) {
11913
            $res = unlink($file);
11914
            if ($res === false) {
11915
                error_log(
11916
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11917
                    0
11918
                );
11919
            }
11920
        }
11921
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11922
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11923
    }
11924
11925
    /**
11926
     * @param int $lp_id
11927
     *
11928
     * @return bool
11929
     */
11930
    public function scorm_export_to_pdf($lp_id)
11931
    {
11932
        $lp_id = (int) $lp_id;
11933
        $files_to_export = [];
11934
11935
        $sessionId = api_get_session_id();
11936
        $course_data = api_get_course_info($this->cc);
11937
11938
        if (!empty($course_data)) {
11939
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11940
            $list = self::get_flat_ordered_items_list($lp_id);
11941
            if (!empty($list)) {
11942
                foreach ($list as $item_id) {
11943
                    $item = $this->items[$item_id];
11944
                    switch ($item->type) {
11945
                        case 'document':
11946
                            // Getting documents from a LP with chamilo documents
11947
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11948
                            // Try loading document from the base course.
11949
                            if (empty($file_data) && !empty($sessionId)) {
11950
                                $file_data = DocumentManager::get_document_data_by_id(
11951
                                    $item->path,
11952
                                    $this->cc,
11953
                                    false,
11954
                                    0
11955
                                );
11956
                            }
11957
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11958
                            if (file_exists($file_path)) {
11959
                                $files_to_export[] = [
11960
                                    'title' => $item->get_title(),
11961
                                    'path' => $file_path,
11962
                                ];
11963
                            }
11964
                            break;
11965
                        case 'asset': //commes from a scorm package generated by chamilo
11966
                        case 'sco':
11967
                            $file_path = $scorm_path.'/'.$item->path;
11968
                            if (file_exists($file_path)) {
11969
                                $files_to_export[] = [
11970
                                    'title' => $item->get_title(),
11971
                                    'path' => $file_path,
11972
                                ];
11973
                            }
11974
                            break;
11975
                        case 'dir':
11976
                            $files_to_export[] = [
11977
                                'title' => $item->get_title(),
11978
                                'path' => null,
11979
                            ];
11980
                            break;
11981
                    }
11982
                }
11983
            }
11984
11985
            $pdf = new PDF();
11986
            $result = $pdf->html_to_pdf(
11987
                $files_to_export,
11988
                $this->name,
11989
                $this->cc,
11990
                true,
11991
                true,
11992
                true,
11993
                $this->get_name()
11994
            );
11995
11996
            return $result;
11997
        }
11998
11999
        return false;
12000
    }
12001
12002
    /**
12003
     * Temp function to be moved in main_api or the best place around for this.
12004
     * Creates a file path if it doesn't exist.
12005
     *
12006
     * @param string $path
12007
     */
12008
    public function create_path($path)
12009
    {
12010
        $path_bits = explode('/', dirname($path));
12011
12012
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12013
        $path_built = IS_WINDOWS_OS ? '' : '/';
12014
        foreach ($path_bits as $bit) {
12015
            if (!empty($bit)) {
12016
                $new_path = $path_built.$bit;
12017
                if (is_dir($new_path)) {
12018
                    $path_built = $new_path.'/';
12019
                } else {
12020
                    mkdir($new_path, api_get_permissions_for_new_directories());
12021
                    $path_built = $new_path.'/';
12022
                }
12023
            }
12024
        }
12025
    }
12026
12027
    /**
12028
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12029
     *
12030
     * @return bool The results of the unlink function, or false if there was no image to start with
12031
     */
12032
    public function delete_lp_image()
12033
    {
12034
        $img = $this->get_preview_image();
12035
        if ($img != '') {
12036
            $del_file = $this->get_preview_image_path(null, 'sys');
12037
            if (isset($del_file) && file_exists($del_file)) {
12038
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12039
                if (file_exists($del_file_2)) {
12040
                    unlink($del_file_2);
12041
                }
12042
                $this->set_preview_image('');
12043
12044
                return @unlink($del_file);
12045
            }
12046
        }
12047
12048
        return false;
12049
    }
12050
12051
    /**
12052
     * Uploads an author image to the upload/learning_path/images directory.
12053
     *
12054
     * @param array    The image array, coming from the $_FILES superglobal
12055
     *
12056
     * @return bool True on success, false on error
12057
     */
12058
    public function upload_image($image_array)
12059
    {
12060
        if (!empty($image_array['name'])) {
12061
            $upload_ok = process_uploaded_file($image_array);
12062
            $has_attachment = true;
12063
        }
12064
12065
        if ($upload_ok && $has_attachment) {
12066
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12067
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12068
            $updir = $sys_course_path.$courseDir;
12069
            // Try to add an extension to the file if it hasn't one.
12070
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12071
12072
            if (filter_extension($new_file_name)) {
12073
                $file_extension = explode('.', $image_array['name']);
12074
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
12075
                $filename = uniqid('');
12076
                $new_file_name = $filename.'.'.$file_extension;
12077
                $new_path = $updir.'/'.$new_file_name;
12078
12079
                // Resize the image.
12080
                $temp = new Image($image_array['tmp_name']);
12081
                $temp->resize(104);
12082
                $result = $temp->send_image($new_path);
12083
12084
                // Storing the image filename.
12085
                if ($result) {
12086
                    $this->set_preview_image($new_file_name);
12087
12088
                    //Resize to 64px to use on course homepage
12089
                    $temp->resize(64);
12090
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12091
12092
                    return true;
12093
                }
12094
            }
12095
        }
12096
12097
        return false;
12098
    }
12099
12100
    /**
12101
     * @param int    $lp_id
12102
     * @param string $status
12103
     */
12104
    public function set_autolaunch($lp_id, $status)
12105
    {
12106
        $course_id = api_get_course_int_id();
12107
        $lp_id = (int) $lp_id;
12108
        $status = (int) $status;
12109
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12110
12111
        // Setting everything to autolaunch = 0
12112
        $attributes['autolaunch'] = 0;
12113
        $where = [
12114
            'session_id = ? AND c_id = ? ' => [
12115
                api_get_session_id(),
12116
                $course_id,
12117
            ],
12118
        ];
12119
        Database::update($lp_table, $attributes, $where);
12120
        if ($status == 1) {
12121
            //Setting my lp_id to autolaunch = 1
12122
            $attributes['autolaunch'] = 1;
12123
            $where = [
12124
                'iid = ? AND session_id = ? AND c_id = ?' => [
12125
                    $lp_id,
12126
                    api_get_session_id(),
12127
                    $course_id,
12128
                ],
12129
            ];
12130
            Database::update($lp_table, $attributes, $where);
12131
        }
12132
    }
12133
12134
    /**
12135
     * Gets previous_item_id for the next element of the lp_item table.
12136
     *
12137
     * @author Isaac flores paz
12138
     *
12139
     * @return int Previous item ID
12140
     */
12141
    public function select_previous_item_id()
12142
    {
12143
        $course_id = api_get_course_int_id();
12144
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12145
12146
        // Get the max order of the items
12147
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12148
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12149
        $rs_max_order = Database::query($sql);
12150
        $row_max_order = Database::fetch_object($rs_max_order);
12151
        $max_order = $row_max_order->display_order;
12152
        // Get the previous item ID
12153
        $sql = "SELECT iid as previous FROM $table_lp_item
12154
                WHERE
12155
                    c_id = $course_id AND
12156
                    lp_id = ".$this->lp_id." AND
12157
                    display_order = '$max_order' ";
12158
        $rs_max = Database::query($sql);
12159
        $row_max = Database::fetch_object($rs_max);
12160
12161
        // Return the previous item ID
12162
        return $row_max->previous;
12163
    }
12164
12165
    /**
12166
     * Copies an LP.
12167
     */
12168
    public function copy()
12169
    {
12170
        // Course builder
12171
        $cb = new CourseBuilder();
12172
12173
        //Setting tools that will be copied
12174
        $cb->set_tools_to_build(['learnpaths']);
12175
12176
        //Setting elements that will be copied
12177
        $cb->set_tools_specific_id_list(['learnpaths' => [$this->lp_id]]);
12178
12179
        $course = $cb->build();
12180
12181
        //Course restorer
12182
        $course_restorer = new CourseRestorer($course);
12183
        $course_restorer->set_add_text_in_items(true);
12184
        $course_restorer->set_tool_copy_settings(
12185
            ['learnpaths' => ['reset_dates' => true]]
12186
        );
12187
        $course_restorer->restore(
12188
            api_get_course_id(),
12189
            api_get_session_id(),
12190
            false,
12191
            false
12192
        );
12193
    }
12194
12195
    /**
12196
     * Verify document size.
12197
     *
12198
     * @param string $s
12199
     *
12200
     * @return bool
12201
     */
12202
    public static function verify_document_size($s)
12203
    {
12204
        $post_max = ini_get('post_max_size');
12205
        if (substr($post_max, -1, 1) == 'M') {
12206
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12207
        } elseif (substr($post_max, -1, 1) == 'G') {
12208
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12209
        }
12210
        $upl_max = ini_get('upload_max_filesize');
12211
        if (substr($upl_max, -1, 1) == 'M') {
12212
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12213
        } elseif (substr($upl_max, -1, 1) == 'G') {
12214
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12215
        }
12216
        $documents_total_space = DocumentManager::documents_total_space();
12217
        $course_max_space = DocumentManager::get_course_quota();
12218
        $total_size = filesize($s) + $documents_total_space;
12219
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12220
            return true;
12221
        }
12222
12223
        return false;
12224
    }
12225
12226
    /**
12227
     * Clear LP prerequisites.
12228
     */
12229
    public function clear_prerequisites()
12230
    {
12231
        $course_id = $this->get_course_int_id();
12232
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12233
        $lp_id = $this->get_id();
12234
        // Cleaning prerequisites
12235
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12236
                WHERE c_id = $course_id AND lp_id = $lp_id";
12237
        Database::query($sql);
12238
12239
        // Cleaning mastery score for exercises
12240
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12241
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12242
        Database::query($sql);
12243
    }
12244
12245
    public function set_previous_step_as_prerequisite_for_all_items()
12246
    {
12247
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12248
        $course_id = $this->get_course_int_id();
12249
        $lp_id = $this->get_id();
12250
12251
        if (!empty($this->items)) {
12252
            $previous_item_id = null;
12253
            $previous_item_max = 0;
12254
            $previous_item_type = null;
12255
            $last_item_not_dir = null;
12256
            $last_item_not_dir_type = null;
12257
            $last_item_not_dir_max = null;
12258
12259
            foreach ($this->ordered_items as $itemId) {
12260
                $item = $this->getItem($itemId);
12261
                // if there was a previous item... (otherwise jump to set it)
12262
                if (!empty($previous_item_id)) {
12263
                    $current_item_id = $item->get_id(); //save current id
12264
                    if ($item->get_type() != 'dir') {
12265
                        // Current item is not a folder, so it qualifies to get a prerequisites
12266
                        if ($last_item_not_dir_type == 'quiz') {
12267
                            // if previous is quiz, mark its max score as default score to be achieved
12268
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12269
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12270
                            Database::query($sql);
12271
                        }
12272
                        // now simply update the prerequisite to set it to the last non-chapter item
12273
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12274
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12275
                        Database::query($sql);
12276
                        // record item as 'non-chapter' reference
12277
                        $last_item_not_dir = $item->get_id();
12278
                        $last_item_not_dir_type = $item->get_type();
12279
                        $last_item_not_dir_max = $item->get_max();
12280
                    }
12281
                } else {
12282
                    if ($item->get_type() != 'dir') {
12283
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12284
                        $last_item_not_dir = $item->get_id();
12285
                        $last_item_not_dir_type = $item->get_type();
12286
                        $last_item_not_dir_max = $item->get_max();
12287
                    }
12288
                }
12289
                // Saving the item as "previous item" for the next loop
12290
                $previous_item_id = $item->get_id();
12291
                $previous_item_max = $item->get_max();
12292
                $previous_item_type = $item->get_type();
12293
            }
12294
        }
12295
    }
12296
12297
    /**
12298
     * @param array $params
12299
     *
12300
     * @throws \Doctrine\ORM\OptimisticLockException
12301
     *
12302
     * @return int
12303
     */
12304
    public static function createCategory($params)
12305
    {
12306
        $em = Database::getManager();
12307
        $item = new CLpCategory();
12308
        $item->setName($params['name']);
12309
        $item->setCId($params['c_id']);
12310
        $em->persist($item);
12311
        $em->flush();
12312
12313
        $id = $item->getId();
12314
12315
        $sessionId = api_get_session_id();
12316
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12317
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12318
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12319
            Database::query($sql);
12320
        }
12321
12322
        api_item_property_update(
12323
            api_get_course_info(),
12324
            TOOL_LEARNPATH_CATEGORY,
12325
            $id,
12326
            'visible',
12327
            api_get_user_id()
12328
        );
12329
12330
        return $item->getId();
12331
    }
12332
12333
    /**
12334
     * @param array $params
12335
     */
12336
    public static function updateCategory($params)
12337
    {
12338
        $em = Database::getManager();
12339
        $item = self::getCategory($params['id']);
12340
12341
        if ($item) {
12342
            $item->setName($params['name']);
12343
            $em->persist($item);
12344
            $em->flush();
12345
        }
12346
    }
12347
12348
    /**
12349
     * @param int $id
12350
     */
12351
    public static function moveUpCategory($id)
12352
    {
12353
        $item = self::getCategory($id);
12354
        if ($item) {
12355
            $em = Database::getManager();
12356
            $position = $item->getPosition() - 1;
12357
            $item->setPosition($position);
12358
            $em->persist($item);
12359
            $em->flush();
12360
        }
12361
    }
12362
12363
    /**
12364
     * @param int $id
12365
     *
12366
     * @throws \Doctrine\ORM\ORMException
12367
     * @throws \Doctrine\ORM\OptimisticLockException
12368
     * @throws \Doctrine\ORM\TransactionRequiredException
12369
     */
12370
    public static function moveDownCategory($id)
12371
    {
12372
        $item = self::getCategory($id);
12373
        if ($item) {
12374
            $em = Database::getManager();
12375
            $position = $item->getPosition() + 1;
12376
            $item->setPosition($position);
12377
            $em->persist($item);
12378
            $em->flush();
12379
        }
12380
    }
12381
12382
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12383
    {
12384
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12385
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12386
        $courseId = (int) $courseId;
12387
        $sessionId = (int) $sessionId;
12388
12389
        $sql = "SELECT lp.id, lp.name
12390
                FROM $TABLE_LP lp
12391
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12392
                ON lp.id = ip.ref
12393
                WHERE lp.c_id = $courseId ";
12394
12395
        if (!empty($sessionId)) {
12396
            $sql .= "AND ip.session_id = $sessionId ";
12397
        }
12398
12399
        if ($onlyActiveLp) {
12400
            $sql .= "AND ip.tool = 'learnpath' ";
12401
            $sql .= "AND ip.visibility = 1 ";
12402
        }
12403
12404
        $sql .= "GROUP BY lp.id";
12405
12406
        $result = Database::query($sql);
12407
12408
        return Database::store_result($result, 'ASSOC');
12409
    }
12410
12411
    /**
12412
     * @param int $courseId
12413
     *
12414
     * @throws \Doctrine\ORM\Query\QueryException
12415
     *
12416
     * @return int|mixed
12417
     */
12418
    public static function getCountCategories($courseId)
12419
    {
12420
        if (empty($courseId)) {
12421
            return 0;
12422
        }
12423
        $em = Database::getManager();
12424
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12425
        $query->setParameter('id', $courseId);
12426
12427
        return $query->getSingleScalarResult();
12428
    }
12429
12430
    /**
12431
     * @param int $courseId
12432
     *
12433
     * @return array|CLpCategory[]
12434
     */
12435
    public static function getCategories($courseId, $withNoneCategory = false)
12436
    {
12437
        $em = Database::getManager();
12438
12439
        // Using doctrine extensions
12440
        /** @var SortableRepository $repo */
12441
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12442
12443
        $categories = $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12444
12445
        if ($withNoneCategory) {
12446
            $categoryTest = new CLpCategory();
12447
            $categoryTest->setId(0)
12448
                ->setName(get_lang('WithOutCategory'))
12449
                ->setPosition(0);
12450
12451
            array_unshift($categories, $categoryTest);
12452
        }
12453
12454
        return $categories;
12455
    }
12456
12457
    public static function getCategorySessionId($id)
12458
    {
12459
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12460
            return 0;
12461
        }
12462
12463
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12464
        $id = (int) $id;
12465
12466
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12467
        $result = Database::query($sql);
12468
        $result = Database::fetch_array($result, 'ASSOC');
12469
12470
        if ($result) {
12471
            return (int) $result['session_id'];
12472
        }
12473
12474
        return 0;
12475
    }
12476
12477
    /**
12478
     * @param int $id
12479
     *
12480
     * @return CLpCategory
12481
     */
12482
    public static function getCategory($id)
12483
    {
12484
        $id = (int) $id;
12485
        $em = Database::getManager();
12486
12487
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12488
    }
12489
12490
    /**
12491
     * @param int $courseId
12492
     *
12493
     * @return CLpCategory[]
12494
     */
12495
    public static function getCategoryByCourse($courseId)
12496
    {
12497
        $em = Database::getManager();
12498
12499
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12500
    }
12501
12502
    /**
12503
     * @param int $id
12504
     *
12505
     * @return bool
12506
     */
12507
    public static function deleteCategory($id)
12508
    {
12509
        $em = Database::getManager();
12510
        $id = (int) $id;
12511
        $item = self::getCategory($id);
12512
        if ($item) {
12513
            $courseId = $item->getCId();
12514
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12515
            $query->setParameter('id', $courseId);
12516
            $query->setParameter('catId', $item->getId());
12517
            $lps = $query->getResult();
12518
12519
            // Setting category = 0.
12520
            if ($lps) {
12521
                foreach ($lps as $lpItem) {
12522
                    $lpItem->setCategoryId(0);
12523
                }
12524
            }
12525
12526
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
12527
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
12528
                $sql = "DELETE FROM $table
12529
                        WHERE
12530
                            lp_category_id = $id AND
12531
                            c_id = $courseId ";
12532
                Database::query($sql);
12533
            }
12534
12535
            // Removing category.
12536
            $em->remove($item);
12537
            $em->flush();
12538
12539
            $courseInfo = api_get_course_info_by_id($courseId);
12540
            $sessionId = api_get_session_id();
12541
12542
            // Delete link tool
12543
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12544
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12545
            // Delete tools
12546
            $sql = "DELETE FROM $tbl_tool
12547
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12548
            Database::query($sql);
12549
12550
            return true;
12551
        }
12552
12553
        return false;
12554
    }
12555
12556
    /**
12557
     * @param int  $courseId
12558
     * @param bool $addSelectOption
12559
     *
12560
     * @return mixed
12561
     */
12562
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12563
    {
12564
        $items = self::getCategoryByCourse($courseId);
12565
        $cats = [];
12566
        if ($addSelectOption) {
12567
            $cats = [get_lang('SelectACategory')];
12568
        }
12569
12570
        $sessionId = api_get_session_id();
12571
        $avoidCategoryInSession = false;
12572
        if (empty($sessionId)) {
12573
            $avoidCategoryInSession = true;
12574
        }
12575
        /*$checkSession = false;
12576
        if (api_get_configuration_value('allow_session_lp_category')) {
12577
            $checkSession = true;
12578
        }*/
12579
12580
        if (!empty($items)) {
12581
            foreach ($items as $cat) {
12582
                $categoryId = $cat->getId();
12583
                if ($avoidCategoryInSession) {
12584
                    $inSession = self::getCategorySessionId($categoryId);
12585
                    if (!empty($inSession)) {
12586
                        continue;
12587
                    }
12588
                }
12589
                $cats[$categoryId] = $cat->getName();
12590
            }
12591
        }
12592
12593
        return $cats;
12594
    }
12595
12596
    /**
12597
     * @param string $courseCode
12598
     * @param int    $lpId
12599
     * @param int    $user_id
12600
     *
12601
     * @return learnpath
12602
     */
12603
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12604
    {
12605
        $debug = 0;
12606
        $learnPath = null;
12607
        $lpObject = Session::read('lpobject');
12608
        if ($lpObject !== null) {
12609
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12610
            if ($debug) {
12611
                error_log('getLpFromSession: unserialize');
12612
                error_log('------getLpFromSession------');
12613
                error_log('------unserialize------');
12614
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12615
                error_log("api_get_sessionid: ".api_get_session_id());
12616
            }
12617
        }
12618
12619
        if (!is_object($learnPath)) {
12620
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12621
            if ($debug) {
12622
                error_log('------getLpFromSession------');
12623
                error_log('getLpFromSession: create new learnpath');
12624
                error_log("create new LP with $courseCode - $lpId - $user_id");
12625
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12626
                error_log("api_get_sessionid: ".api_get_session_id());
12627
            }
12628
        }
12629
12630
        return $learnPath;
12631
    }
12632
12633
    /**
12634
     * @param int $itemId
12635
     *
12636
     * @return learnpathItem|false
12637
     */
12638
    public function getItem($itemId)
12639
    {
12640
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12641
            return $this->items[$itemId];
12642
        }
12643
12644
        return false;
12645
    }
12646
12647
    /**
12648
     * @return int
12649
     */
12650
    public function getCurrentAttempt()
12651
    {
12652
        $attempt = $this->getItem($this->get_current_item_id());
12653
        if ($attempt) {
12654
            $attemptId = $attempt->get_attempt_id();
12655
12656
            return $attemptId;
12657
        }
12658
12659
        return 0;
12660
    }
12661
12662
    /**
12663
     * @return int
12664
     */
12665
    public function getCategoryId()
12666
    {
12667
        return (int) $this->categoryId;
12668
    }
12669
12670
    /**
12671
     * @param int $categoryId
12672
     *
12673
     * @return bool
12674
     */
12675
    public function setCategoryId($categoryId)
12676
    {
12677
        $this->categoryId = (int) $categoryId;
12678
        $table = Database::get_course_table(TABLE_LP_MAIN);
12679
        $lp_id = $this->get_id();
12680
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12681
                WHERE iid = $lp_id";
12682
        Database::query($sql);
12683
12684
        return true;
12685
    }
12686
12687
    /**
12688
     * Get whether this is a learning path with the possibility to subscribe
12689
     * users or not.
12690
     *
12691
     * @return int
12692
     */
12693
    public function getSubscribeUsers()
12694
    {
12695
        return $this->subscribeUsers;
12696
    }
12697
12698
    /**
12699
     * Set whether this is a learning path with the possibility to subscribe
12700
     * users or not.
12701
     *
12702
     * @param int $value (0 = false, 1 = true)
12703
     *
12704
     * @return bool
12705
     */
12706
    public function setSubscribeUsers($value)
12707
    {
12708
        $this->subscribeUsers = (int) $value;
12709
        $table = Database::get_course_table(TABLE_LP_MAIN);
12710
        $lp_id = $this->get_id();
12711
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12712
                WHERE iid = $lp_id";
12713
        Database::query($sql);
12714
12715
        return true;
12716
    }
12717
12718
    /**
12719
     * Calculate the count of stars for a user in this LP
12720
     * This calculation is based on the following rules:
12721
     * - the student gets one star when he gets to 50% of the learning path
12722
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12723
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12724
     * - the student gets the final star when the score for the *last* test is >= 80%.
12725
     *
12726
     * @param int $sessionId Optional. The session ID
12727
     *
12728
     * @return int The count of stars
12729
     */
12730
    public function getCalculateStars($sessionId = 0)
12731
    {
12732
        $stars = 0;
12733
        $progress = self::getProgress(
12734
            $this->lp_id,
12735
            $this->user_id,
12736
            $this->course_int_id,
12737
            $sessionId
12738
        );
12739
12740
        if ($progress >= 50) {
12741
            $stars++;
12742
        }
12743
12744
        // Calculate stars chapters evaluation
12745
        $exercisesItems = $this->getExercisesItems();
12746
12747
        if (!empty($exercisesItems)) {
12748
            $totalResult = 0;
12749
12750
            foreach ($exercisesItems as $exerciseItem) {
12751
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12752
                    $this->user_id,
12753
                    $exerciseItem->path,
12754
                    $this->course_int_id,
12755
                    $sessionId,
12756
                    $this->lp_id,
12757
                    $exerciseItem->db_id
12758
                );
12759
12760
                $exerciseResultInfo = end($exerciseResultInfo);
12761
12762
                if (!$exerciseResultInfo) {
12763
                    continue;
12764
                }
12765
12766
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12767
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12768
                } else {
12769
                    $exerciseResult = 0;
12770
                }
12771
                $totalResult += $exerciseResult;
12772
            }
12773
12774
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12775
12776
            if ($totalExerciseAverage >= 50) {
12777
                $stars++;
12778
            }
12779
12780
            if ($totalExerciseAverage >= 80) {
12781
                $stars++;
12782
            }
12783
        }
12784
12785
        // Calculate star for final evaluation
12786
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12787
12788
        if (!empty($finalEvaluationItem)) {
12789
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12790
                $this->user_id,
12791
                $finalEvaluationItem->path,
12792
                $this->course_int_id,
12793
                $sessionId,
12794
                $this->lp_id,
12795
                $finalEvaluationItem->db_id
12796
            );
12797
12798
            $evaluationResultInfo = end($evaluationResultInfo);
12799
12800
            if ($evaluationResultInfo) {
12801
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12802
12803
                if ($evaluationResult >= 80) {
12804
                    $stars++;
12805
                }
12806
            }
12807
        }
12808
12809
        return $stars;
12810
    }
12811
12812
    /**
12813
     * Get the items of exercise type.
12814
     *
12815
     * @return array The items. Otherwise return false
12816
     */
12817
    public function getExercisesItems()
12818
    {
12819
        $exercises = [];
12820
        foreach ($this->items as $item) {
12821
            if ($item->type != 'quiz') {
12822
                continue;
12823
            }
12824
            $exercises[] = $item;
12825
        }
12826
12827
        array_pop($exercises);
12828
12829
        return $exercises;
12830
    }
12831
12832
    /**
12833
     * Get the item of exercise type (evaluation type).
12834
     *
12835
     * @return array The final evaluation. Otherwise return false
12836
     */
12837
    public function getFinalEvaluationItem()
12838
    {
12839
        $exercises = [];
12840
        foreach ($this->items as $item) {
12841
            if ($item->type != 'quiz') {
12842
                continue;
12843
            }
12844
12845
            $exercises[] = $item;
12846
        }
12847
12848
        return array_pop($exercises);
12849
    }
12850
12851
    /**
12852
     * Calculate the total points achieved for the current user in this learning path.
12853
     *
12854
     * @param int $sessionId Optional. The session Id
12855
     *
12856
     * @return int
12857
     */
12858
    public function getCalculateScore($sessionId = 0)
12859
    {
12860
        // Calculate stars chapters evaluation
12861
        $exercisesItems = $this->getExercisesItems();
12862
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12863
        $totalExercisesResult = 0;
12864
        $totalEvaluationResult = 0;
12865
12866
        if ($exercisesItems !== false) {
12867
            foreach ($exercisesItems as $exerciseItem) {
12868
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12869
                    $this->user_id,
12870
                    $exerciseItem->path,
12871
                    $this->course_int_id,
12872
                    $sessionId,
12873
                    $this->lp_id,
12874
                    $exerciseItem->db_id
12875
                );
12876
12877
                $exerciseResultInfo = end($exerciseResultInfo);
12878
12879
                if (!$exerciseResultInfo) {
12880
                    continue;
12881
                }
12882
12883
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12884
            }
12885
        }
12886
12887
        if (!empty($finalEvaluationItem)) {
12888
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12889
                $this->user_id,
12890
                $finalEvaluationItem->path,
12891
                $this->course_int_id,
12892
                $sessionId,
12893
                $this->lp_id,
12894
                $finalEvaluationItem->db_id
12895
            );
12896
12897
            $evaluationResultInfo = end($evaluationResultInfo);
12898
12899
            if ($evaluationResultInfo) {
12900
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12901
            }
12902
        }
12903
12904
        return $totalExercisesResult + $totalEvaluationResult;
12905
    }
12906
12907
    /**
12908
     * Check if URL is not allowed to be show in a iframe.
12909
     *
12910
     * @param string $src
12911
     *
12912
     * @return string
12913
     */
12914
    public function fixBlockedLinks($src)
12915
    {
12916
        $urlInfo = parse_url($src);
12917
12918
        $platformProtocol = 'https';
12919
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12920
            $platformProtocol = 'http';
12921
        }
12922
12923
        $protocolFixApplied = false;
12924
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12925
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12926
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12927
12928
        if ($platformProtocol != $scheme) {
12929
            Session::write('x_frame_source', $src);
12930
            $src = 'blank.php?error=x_frames_options';
12931
            $protocolFixApplied = true;
12932
        }
12933
12934
        if ($protocolFixApplied == false) {
12935
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12936
                // Check X-Frame-Options
12937
                $ch = curl_init();
12938
                $options = [
12939
                    CURLOPT_URL => $src,
12940
                    CURLOPT_RETURNTRANSFER => true,
12941
                    CURLOPT_HEADER => true,
12942
                    CURLOPT_FOLLOWLOCATION => true,
12943
                    CURLOPT_ENCODING => "",
12944
                    CURLOPT_AUTOREFERER => true,
12945
                    CURLOPT_CONNECTTIMEOUT => 120,
12946
                    CURLOPT_TIMEOUT => 120,
12947
                    CURLOPT_MAXREDIRS => 10,
12948
                ];
12949
12950
                $proxySettings = api_get_configuration_value('proxy_settings');
12951
                if (!empty($proxySettings) &&
12952
                    isset($proxySettings['curl_setopt_array'])
12953
                ) {
12954
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12955
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12956
                }
12957
12958
                curl_setopt_array($ch, $options);
12959
                $response = curl_exec($ch);
12960
                $httpCode = curl_getinfo($ch);
12961
                $headers = substr($response, 0, $httpCode['header_size']);
12962
12963
                $error = false;
12964
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12965
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12966
                ) {
12967
                    $error = true;
12968
                }
12969
12970
                if ($error) {
12971
                    Session::write('x_frame_source', $src);
12972
                    $src = 'blank.php?error=x_frames_options';
12973
                }
12974
            }
12975
        }
12976
12977
        return $src;
12978
    }
12979
12980
    /**
12981
     * Check if this LP has a created forum in the basis course.
12982
     *
12983
     * @return bool
12984
     */
12985
    public function lpHasForum()
12986
    {
12987
        $forumTable = Database::get_course_table(TABLE_FORUM);
12988
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12989
12990
        $fakeFrom = "
12991
            $forumTable f
12992
            INNER JOIN $itemProperty ip
12993
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12994
        ";
12995
12996
        $resultData = Database::select(
12997
            'COUNT(f.iid) AS qty',
12998
            $fakeFrom,
12999
            [
13000
                'where' => [
13001
                    'ip.visibility != ? AND ' => 2,
13002
                    'ip.tool = ? AND ' => TOOL_FORUM,
13003
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13004
                    'f.lp_id = ?' => intval($this->lp_id),
13005
                ],
13006
            ],
13007
            'first'
13008
        );
13009
13010
        return $resultData['qty'] > 0;
13011
    }
13012
13013
    /**
13014
     * Get the forum for this learning path.
13015
     *
13016
     * @param int $sessionId
13017
     *
13018
     * @return bool
13019
     */
13020
    public function getForum($sessionId = 0)
13021
    {
13022
        $forumTable = Database::get_course_table(TABLE_FORUM);
13023
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13024
13025
        $fakeFrom = "$forumTable f
13026
            INNER JOIN $itemProperty ip ";
13027
13028
        if ($this->lp_session_id == 0) {
13029
            $fakeFrom .= "
13030
                ON (
13031
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13032
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13033
                    )
13034
                )
13035
            ";
13036
        } else {
13037
            $fakeFrom .= "
13038
                ON (
13039
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13040
                )
13041
            ";
13042
        }
13043
13044
        $resultData = Database::select(
13045
            'f.*',
13046
            $fakeFrom,
13047
            [
13048
                'where' => [
13049
                    'ip.visibility != ? AND ' => 2,
13050
                    'ip.tool = ? AND ' => TOOL_FORUM,
13051
                    'f.session_id = ? AND ' => $sessionId,
13052
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13053
                    'f.lp_id = ?' => intval($this->lp_id),
13054
                ],
13055
            ],
13056
            'first'
13057
        );
13058
13059
        if (empty($resultData)) {
13060
            return false;
13061
        }
13062
13063
        return $resultData;
13064
    }
13065
13066
    /**
13067
     * Create a forum for this learning path.
13068
     *
13069
     * @param int $forumCategoryId
13070
     *
13071
     * @return int The forum ID if was created. Otherwise return false
13072
     */
13073
    public function createForum($forumCategoryId)
13074
    {
13075
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13076
13077
        $forumId = store_forum(
13078
            [
13079
                'lp_id' => $this->lp_id,
13080
                'forum_title' => $this->name,
13081
                'forum_comment' => null,
13082
                'forum_category' => (int) $forumCategoryId,
13083
                'students_can_edit_group' => ['students_can_edit' => 0],
13084
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13085
                'default_view_type_group' => ['default_view_type' => 'flat'],
13086
                'group_forum' => 0,
13087
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13088
            ],
13089
            [],
13090
            true
13091
        );
13092
13093
        return $forumId;
13094
    }
13095
13096
    /**
13097
     * Get the LP Final Item form.
13098
     *
13099
     * @throws Exception
13100
     * @throws HTML_QuickForm_Error
13101
     *
13102
     * @return string
13103
     */
13104
    public function getFinalItemForm()
13105
    {
13106
        $finalItem = $this->getFinalItem();
13107
        $title = '';
13108
13109
        if ($finalItem) {
13110
            $title = $finalItem->get_title();
13111
            $buttonText = get_lang('Save');
13112
            $content = $this->getSavedFinalItem();
13113
        } else {
13114
            $buttonText = get_lang('LPCreateDocument');
13115
            $content = $this->getFinalItemTemplate();
13116
        }
13117
13118
        $courseInfo = api_get_course_info();
13119
        $result = $this->generate_lp_folder($courseInfo);
13120
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13121
        $relative_prefix = '../../';
13122
13123
        $editorConfig = [
13124
            'ToolbarSet' => 'LearningPathDocuments',
13125
            'Width' => '100%',
13126
            'Height' => '500',
13127
            'FullPage' => true,
13128
            'CreateDocumentDir' => $relative_prefix,
13129
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13130
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13131
        ];
13132
13133
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13134
            'type' => 'document',
13135
            'lp_id' => $this->lp_id,
13136
        ]);
13137
13138
        $form = new FormValidator('final_item', 'POST', $url);
13139
        $form->addText('title', get_lang('Title'));
13140
        $form->addButtonSave($buttonText);
13141
        $form->addHtml(
13142
            Display::return_message(
13143
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13144
                'normal',
13145
                false
13146
            )
13147
        );
13148
13149
        $renderer = $form->defaultRenderer();
13150
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13151
13152
        $form->addHtmlEditor(
13153
            'content_lp_certificate',
13154
            null,
13155
            true,
13156
            false,
13157
            $editorConfig,
13158
            true
13159
        );
13160
        $form->addHidden('action', 'add_final_item');
13161
        $form->addHidden('path', Session::read('pathItem'));
13162
        $form->addHidden('previous', $this->get_last());
13163
        $form->setDefaults(
13164
            ['title' => $title, 'content_lp_certificate' => $content]
13165
        );
13166
13167
        if ($form->validate()) {
13168
            $values = $form->exportValues();
13169
            $lastItemId = $this->getLastInFirstLevel();
13170
13171
            if (!$finalItem) {
13172
                $documentId = $this->create_document(
13173
                    $this->course_info,
13174
                    $values['content_lp_certificate'],
13175
                    $values['title']
13176
                );
13177
                $this->add_item(
13178
                    0,
13179
                    $lastItemId,
13180
                    'final_item',
13181
                    $documentId,
13182
                    $values['title'],
13183
                    ''
13184
                );
13185
13186
                Display::addFlash(
13187
                    Display::return_message(get_lang('Added'))
13188
                );
13189
            } else {
13190
                $this->edit_document($this->course_info);
13191
            }
13192
        }
13193
13194
        return $form->returnForm();
13195
    }
13196
13197
    /**
13198
     * Check if the current lp item is first, both, last or none from lp list.
13199
     *
13200
     * @param int $currentItemId
13201
     *
13202
     * @return string
13203
     */
13204
    public function isFirstOrLastItem($currentItemId)
13205
    {
13206
        $lpItemId = [];
13207
        $typeListNotToVerify = self::getChapterTypes();
13208
13209
        // Using get_toc() function instead $this->items because returns the correct order of the items
13210
        foreach ($this->get_toc() as $item) {
13211
            if (!in_array($item['type'], $typeListNotToVerify)) {
13212
                $lpItemId[] = $item['id'];
13213
            }
13214
        }
13215
13216
        $lastLpItemIndex = count($lpItemId) - 1;
13217
        $position = array_search($currentItemId, $lpItemId);
13218
13219
        switch ($position) {
13220
            case 0:
13221
                if (!$lastLpItemIndex) {
13222
                    $answer = 'both';
13223
                    break;
13224
                }
13225
13226
                $answer = 'first';
13227
                break;
13228
            case $lastLpItemIndex:
13229
                $answer = 'last';
13230
                break;
13231
            default:
13232
                $answer = 'none';
13233
        }
13234
13235
        return $answer;
13236
    }
13237
13238
    /**
13239
     * Get whether this is a learning path with the accumulated SCORM time or not.
13240
     *
13241
     * @return int
13242
     */
13243
    public function getAccumulateScormTime()
13244
    {
13245
        return $this->accumulateScormTime;
13246
    }
13247
13248
    /**
13249
     * Set whether this is a learning path with the accumulated SCORM time or not.
13250
     *
13251
     * @param int $value (0 = false, 1 = true)
13252
     *
13253
     * @return bool Always returns true
13254
     */
13255
    public function setAccumulateScormTime($value)
13256
    {
13257
        $this->accumulateScormTime = (int) $value;
13258
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13259
        $lp_id = $this->get_id();
13260
        $sql = "UPDATE $lp_table
13261
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13262
                WHERE iid = $lp_id";
13263
        Database::query($sql);
13264
13265
        return true;
13266
    }
13267
13268
    /**
13269
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13270
     * the new learning path tool.
13271
     *
13272
     * The function is a big switch on tool type.
13273
     * In each case, we query the corresponding table for information and build the link
13274
     * with that information.
13275
     *
13276
     * @author Yannick Warnier <[email protected]> - rebranding based on
13277
     * previous work (display_addedresource_link_in_learnpath())
13278
     *
13279
     * @param int $course_id      Course code
13280
     * @param int $learningPathId The learning path ID (in lp table)
13281
     * @param int $id_in_path     the unique index in the items table
13282
     * @param int $lpViewId
13283
     * @param int $lpSessionId
13284
     *
13285
     * @return string
13286
     */
13287
    public static function rl_get_resource_link_for_learnpath(
13288
        $course_id,
13289
        $learningPathId,
13290
        $id_in_path,
13291
        $lpViewId,
13292
        $lpSessionId = 0
13293
    ) {
13294
        $session_id = api_get_session_id();
13295
        $course_info = api_get_course_info_by_id($course_id);
13296
13297
        $learningPathId = (int) $learningPathId;
13298
        $id_in_path = (int) $id_in_path;
13299
        $lpViewId = (int) $lpViewId;
13300
13301
        $em = Database::getManager();
13302
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13303
13304
        /** @var CLpItem $rowItem */
13305
        $rowItem = $lpItemRepo->findOneBy([
13306
            'cId' => $course_id,
13307
            'lpId' => $learningPathId,
13308
            'iid' => $id_in_path,
13309
        ]);
13310
13311
        if (!$rowItem) {
13312
            // Try one more time with "id"
13313
            /** @var CLpItem $rowItem */
13314
            $rowItem = $lpItemRepo->findOneBy([
13315
                'cId' => $course_id,
13316
                'lpId' => $learningPathId,
13317
                'id' => $id_in_path,
13318
            ]);
13319
13320
            if (!$rowItem) {
13321
                return -1;
13322
            }
13323
        }
13324
13325
        $course_code = $course_info['code'];
13326
        $type = $rowItem->getItemType();
13327
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13328
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13329
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13330
        $link = '';
13331
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13332
13333
        switch ($type) {
13334
            case 'dir':
13335
                return $main_dir_path.'lp/blank.php';
13336
            case TOOL_CALENDAR_EVENT:
13337
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13338
            case TOOL_ANNOUNCEMENT:
13339
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13340
            case TOOL_LINK:
13341
                $linkInfo = Link::getLinkInfo($id);
13342
                if ($linkInfo) {
13343
                    $itemPropertyInfo = api_get_item_property_info($course_id, TOOL_LINK, $id, $session_id);
13344
                    if ($itemPropertyInfo && 0 === (int) $itemPropertyInfo['visibility']) {
13345
                        return '';
13346
                    }
13347
                    if (isset($linkInfo['url'])) {
13348
                        return $linkInfo['url'];
13349
                    }
13350
                }
13351
13352
                return '';
13353
            case TOOL_QUIZ:
13354
                if (empty($id)) {
13355
                    return '';
13356
                }
13357
13358
                // Get the lp_item_view with the highest view_count.
13359
                $learnpathItemViewResult = $em
13360
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13361
                    ->findBy(
13362
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13363
                        ['viewCount' => 'DESC'],
13364
                        1
13365
                    );
13366
                /** @var CLpItemView $learnpathItemViewData */
13367
                $learnpathItemViewData = current($learnpathItemViewResult);
13368
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13369
13370
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13371
                    .http_build_query([
13372
                        'lp_init' => 1,
13373
                        'learnpath_item_view_id' => (int) $learnpathItemViewId,
13374
                        'learnpath_id' => (int) $learningPathId,
13375
                        'learnpath_item_id' => (int) $id_in_path,
13376
                        'exerciseId' => (int) $id,
13377
                    ]);
13378
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13379
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13380
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13381
                $myrow = Database::fetch_array($result);
13382
                $path = $myrow['path'];
13383
13384
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13385
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13386
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13387
            case TOOL_FORUM:
13388
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13389
            case TOOL_THREAD:
13390
                // forum post
13391
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13392
                if (empty($id)) {
13393
                    return '';
13394
                }
13395
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13396
                $result = Database::query($sql);
13397
                $myrow = Database::fetch_array($result);
13398
13399
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13400
                    .$extraParams;
13401
            case TOOL_POST:
13402
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13403
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13404
                $myrow = Database::fetch_array($result);
13405
13406
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13407
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13408
            case TOOL_READOUT_TEXT:
13409
                return api_get_path(WEB_CODE_PATH).
13410
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13411
            case TOOL_DOCUMENT:
13412
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13413
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13414
13415
                if (empty($document)) {
13416
                    // Try with normal id
13417
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13418
13419
                    if (empty($document)) {
13420
                        return '';
13421
                    }
13422
                }
13423
13424
                $documentPathInfo = pathinfo($document->getPath());
13425
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13426
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13427
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13428
13429
                $openmethod = 2;
13430
                $officedoc = false;
13431
                Session::write('openmethod', $openmethod);
13432
                Session::write('officedoc', $officedoc);
13433
13434
                // Same validation as in document/download.php
13435
                $isVisible = DocumentManager::is_visible(
13436
                    $document->getPath(),
13437
                    api_get_course_info(),
13438
                    api_get_session_id()
13439
                );
13440
13441
                if (!api_is_allowed_to_edit() && !$isVisible) {
13442
                    return '';
13443
                }
13444
13445
                if ($showDirectUrl) {
13446
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13447
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13448
                        if (Link::isPdfLink($file)) {
13449
                            return api_get_path(WEB_LIBRARY_PATH).
13450
                                'javascript/ViewerJS/index.html?zoom=page-width#'.$file;
13451
                        }
13452
                    }
13453
13454
                    return $file;
13455
                }
13456
13457
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13458
            case TOOL_LP_FINAL_ITEM:
13459
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13460
                    .$extraParams;
13461
            case 'assignments':
13462
                return $main_dir_path.'work/work.php?'.$extraParams;
13463
            case TOOL_DROPBOX:
13464
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13465
            case 'introduction_text': //DEPRECATED
13466
                return '';
13467
            case TOOL_COURSE_DESCRIPTION:
13468
                return $main_dir_path.'course_description?'.$extraParams;
13469
            case TOOL_GROUP:
13470
                return $main_dir_path.'group/group.php?'.$extraParams;
13471
            case TOOL_USER:
13472
                return $main_dir_path.'user/user.php?'.$extraParams;
13473
            case TOOL_STUDENTPUBLICATION:
13474
                if (!empty($rowItem->getPath())) {
13475
                    $workId = $rowItem->getPath();
13476
                    if (empty($lpSessionId) && !empty($session_id)) {
13477
                        // Check if a student publication with the same name exists in this session see BT#17700
13478
                        $title = Database::escape_string($rowItem->getTitle());
13479
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13480
                        $sql = "SELECT * FROM $table
13481
                                WHERE
13482
                                    active = 1 AND
13483
                                    parent_id = 0 AND
13484
                                    c_id = $course_id AND
13485
                                    session_id = $session_id AND
13486
                                    title = '$title'
13487
                                LIMIT 1";
13488
                        $result = Database::query($sql);
13489
                        if (Database::num_rows($result)) {
13490
                            $work = Database::fetch_array($result, 'ASSOC');
13491
                            if ($work) {
13492
                                $workId = $work['iid'];
13493
                            }
13494
                        }
13495
                    }
13496
13497
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13498
                }
13499
13500
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13501
            case TOOL_XAPI:
13502
                $toolLaunch = $em->find(ToolLaunch::class, $id);
13503
13504
                if (empty($toolLaunch)) {
13505
                    break;
13506
                }
13507
13508
                return api_get_path(WEB_PLUGIN_PATH).'xapi/'
13509
                    .('cmi5' === $toolLaunch->getActivityType() ? 'cmi5/view.php' : 'tincan/view.php')
13510
                    ."?id=$id&$extraParams";
13511
        }
13512
13513
        return $link;
13514
    }
13515
13516
    /**
13517
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13518
     *
13519
     * @author Yannick Warnier <[email protected]>
13520
     *
13521
     * @param string $course_code    Course code
13522
     * @param int    $learningPathId
13523
     * @param int    $id_in_path     The resource ID
13524
     *
13525
     * @return string
13526
     */
13527
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13528
    {
13529
        $_course = api_get_course_info($course_code);
13530
        if (empty($_course)) {
13531
            return '';
13532
        }
13533
        $course_id = $_course['real_id'];
13534
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13535
        $learningPathId = (int) $learningPathId;
13536
        $id_in_path = (int) $id_in_path;
13537
13538
        $sql = "SELECT item_type, title, ref
13539
                FROM $tbl_lp_item
13540
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13541
        $res_item = Database::query($sql);
13542
13543
        if (Database::num_rows($res_item) < 1) {
13544
            return '';
13545
        }
13546
        $row_item = Database::fetch_array($res_item);
13547
        $type = strtolower($row_item['item_type']);
13548
        $id = $row_item['ref'];
13549
        $output = '';
13550
13551
        switch ($type) {
13552
            case TOOL_CALENDAR_EVENT:
13553
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13554
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13555
                $myrow = Database::fetch_array($result);
13556
                $output = $myrow['title'];
13557
                break;
13558
            case TOOL_ANNOUNCEMENT:
13559
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13560
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13561
                $myrow = Database::fetch_array($result);
13562
                $output = $myrow['title'];
13563
                break;
13564
            case TOOL_LINK:
13565
                // Doesn't take $target into account.
13566
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13567
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13568
                $myrow = Database::fetch_array($result);
13569
                $output = $myrow['title'];
13570
                break;
13571
            case TOOL_QUIZ:
13572
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13573
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13574
                $myrow = Database::fetch_array($result);
13575
                $output = $myrow['title'];
13576
                break;
13577
            case TOOL_FORUM:
13578
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13579
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13580
                $myrow = Database::fetch_array($result);
13581
                $output = $myrow['forum_name'];
13582
                break;
13583
            case TOOL_THREAD:
13584
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13585
                // Grabbing the title of the post.
13586
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13587
                $result_title = Database::query($sql_title);
13588
                $myrow_title = Database::fetch_array($result_title);
13589
                $output = $myrow_title['post_title'];
13590
                break;
13591
            case TOOL_POST:
13592
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13593
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13594
                $result = Database::query($sql);
13595
                $post = Database::fetch_array($result);
13596
                $output = $post['post_title'];
13597
                break;
13598
            case 'dir':
13599
            case TOOL_DOCUMENT:
13600
                $title = $row_item['title'];
13601
                $output = '-';
13602
                if (!empty($title)) {
13603
                    $output = $title;
13604
                }
13605
                break;
13606
            case 'hotpotatoes':
13607
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13608
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13609
                $myrow = Database::fetch_array($result);
13610
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13611
                $last = count($pathname) - 1; // Making a correct name for the link.
13612
                $filename = $pathname[$last]; // Making a correct name for the link.
13613
                $myrow['path'] = rawurlencode($myrow['path']);
13614
                $output = $filename;
13615
                break;
13616
        }
13617
13618
        return stripslashes($output);
13619
    }
13620
13621
    /**
13622
     * Get the parent names for the current item.
13623
     *
13624
     * @param int $newItemId Optional. The item ID
13625
     *
13626
     * @return array
13627
     */
13628
    public function getCurrentItemParentNames($newItemId = 0)
13629
    {
13630
        $newItemId = $newItemId ?: $this->get_current_item_id();
13631
        $return = [];
13632
        $item = $this->getItem($newItemId);
13633
        $parent = $this->getItem($item->get_parent());
13634
13635
        while ($parent) {
13636
            $return[] = $parent->get_title();
13637
            $parent = $this->getItem($parent->get_parent());
13638
        }
13639
13640
        return array_reverse($return);
13641
    }
13642
13643
    /**
13644
     * Reads and process "lp_subscription_settings" setting.
13645
     *
13646
     * @return array
13647
     */
13648
    public static function getSubscriptionSettings()
13649
    {
13650
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13651
        if (empty($subscriptionSettings)) {
13652
            // By default allow both settings
13653
            $subscriptionSettings = [
13654
                'allow_add_users_to_lp' => true,
13655
                'allow_add_users_to_lp_category' => true,
13656
            ];
13657
        } else {
13658
            $subscriptionSettings = $subscriptionSettings['options'];
13659
        }
13660
13661
        return $subscriptionSettings;
13662
    }
13663
13664
    /**
13665
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13666
     */
13667
    public function exportToCourseBuildFormat()
13668
    {
13669
        if (!api_is_allowed_to_edit()) {
13670
            return false;
13671
        }
13672
13673
        $courseBuilder = new CourseBuilder();
13674
        $itemList = [];
13675
        /** @var learnpathItem $item */
13676
        foreach ($this->items as $item) {
13677
            $itemList[$item->get_type()][] = $item->get_path();
13678
        }
13679
13680
        if (empty($itemList)) {
13681
            return false;
13682
        }
13683
13684
        if (isset($itemList['document'])) {
13685
            // Get parents
13686
            foreach ($itemList['document'] as $documentId) {
13687
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13688
                if (!empty($documentInfo['parents'])) {
13689
                    foreach ($documentInfo['parents'] as $parentInfo) {
13690
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13691
                            continue;
13692
                        }
13693
                        $itemList['document'][] = $parentInfo['iid'];
13694
                    }
13695
                }
13696
            }
13697
13698
            $courseInfo = api_get_course_info();
13699
            foreach ($itemList['document'] as $documentId) {
13700
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13701
                $items = DocumentManager::get_resources_from_source_html(
13702
                    $documentInfo['absolute_path'],
13703
                    true,
13704
                    TOOL_DOCUMENT
13705
                );
13706
13707
                if (!empty($items)) {
13708
                    foreach ($items as $item) {
13709
                        // Get information about source url
13710
                        $url = $item[0]; // url
13711
                        $scope = $item[1]; // scope (local, remote)
13712
                        $type = $item[2]; // type (rel, abs, url)
13713
13714
                        $origParseUrl = parse_url($url);
13715
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13716
13717
                        if ($scope == 'local') {
13718
                            if ($type == 'abs' || $type == 'rel') {
13719
                                $documentFile = strstr($realOrigPath, 'document');
13720
                                if (strpos($realOrigPath, $documentFile) !== false) {
13721
                                    $documentFile = str_replace('document', '', $documentFile);
13722
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13723
                                    // Document found! Add it to the list
13724
                                    if ($itemDocumentId) {
13725
                                        $itemList['document'][] = $itemDocumentId;
13726
                                    }
13727
                                }
13728
                            }
13729
                        }
13730
                    }
13731
                }
13732
            }
13733
13734
            $courseBuilder->build_documents(
13735
                api_get_session_id(),
13736
                $this->get_course_int_id(),
13737
                true,
13738
                $itemList['document']
13739
            );
13740
        }
13741
13742
        if (isset($itemList['quiz'])) {
13743
            $courseBuilder->build_quizzes(
13744
                api_get_session_id(),
13745
                $this->get_course_int_id(),
13746
                true,
13747
                $itemList['quiz']
13748
            );
13749
        }
13750
13751
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13752
13753
        /*if (!empty($itemList['thread'])) {
13754
            $postList = [];
13755
            foreach ($itemList['thread'] as $postId) {
13756
                $post = get_post_information($postId);
13757
                if ($post) {
13758
                    if (!isset($itemList['forum'])) {
13759
                        $itemList['forum'] = [];
13760
                    }
13761
                    $itemList['forum'][] = $post['forum_id'];
13762
                    $postList[] = $postId;
13763
                }
13764
            }
13765
13766
            if (!empty($postList)) {
13767
                $courseBuilder->build_forum_posts(
13768
                    $this->get_course_int_id(),
13769
                    null,
13770
                    null,
13771
                    $postList
13772
                );
13773
            }
13774
        }*/
13775
13776
        if (!empty($itemList['thread'])) {
13777
            $threadList = [];
13778
            $em = Database::getManager();
13779
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13780
            foreach ($itemList['thread'] as $threadId) {
13781
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13782
                $thread = $repo->find($threadId);
13783
                if ($thread) {
13784
                    $itemList['forum'][] = $thread->getForumId();
13785
                    $threadList[] = $thread->getIid();
13786
                }
13787
            }
13788
13789
            if (!empty($threadList)) {
13790
                $courseBuilder->build_forum_topics(
13791
                    api_get_session_id(),
13792
                    $this->get_course_int_id(),
13793
                    null,
13794
                    $threadList
13795
                );
13796
            }
13797
        }
13798
13799
        $forumCategoryList = [];
13800
        if (isset($itemList['forum'])) {
13801
            foreach ($itemList['forum'] as $forumId) {
13802
                $forumInfo = get_forums($forumId);
13803
                $forumCategoryList[] = $forumInfo['forum_category'];
13804
            }
13805
        }
13806
13807
        if (!empty($forumCategoryList)) {
13808
            $courseBuilder->build_forum_category(
13809
                api_get_session_id(),
13810
                $this->get_course_int_id(),
13811
                true,
13812
                $forumCategoryList
13813
            );
13814
        }
13815
13816
        if (!empty($itemList['forum'])) {
13817
            $courseBuilder->build_forums(
13818
                api_get_session_id(),
13819
                $this->get_course_int_id(),
13820
                true,
13821
                $itemList['forum']
13822
            );
13823
        }
13824
13825
        if (isset($itemList['link'])) {
13826
            $courseBuilder->build_links(
13827
                api_get_session_id(),
13828
                $this->get_course_int_id(),
13829
                true,
13830
                $itemList['link']
13831
            );
13832
        }
13833
13834
        if (!empty($itemList['student_publication'])) {
13835
            $courseBuilder->build_works(
13836
                api_get_session_id(),
13837
                $this->get_course_int_id(),
13838
                true,
13839
                $itemList['student_publication']
13840
            );
13841
        }
13842
13843
        $courseBuilder->build_learnpaths(
13844
            api_get_session_id(),
13845
            $this->get_course_int_id(),
13846
            true,
13847
            [$this->get_id()],
13848
            false
13849
        );
13850
13851
        $courseBuilder->restoreDocumentsFromList();
13852
13853
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13854
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13855
        $result = DocumentManager::file_send_for_download(
13856
            $zipPath,
13857
            true,
13858
            $this->get_name().'.zip'
13859
        );
13860
13861
        if ($result) {
13862
            api_not_allowed();
13863
        }
13864
13865
        return true;
13866
    }
13867
13868
    /**
13869
     * Get whether this is a learning path with the accumulated work time or not.
13870
     *
13871
     * @return int
13872
     */
13873
    public function getAccumulateWorkTime()
13874
    {
13875
        return (int) $this->accumulateWorkTime;
13876
    }
13877
13878
    /**
13879
     * Get whether this is a learning path with the accumulated work time or not.
13880
     *
13881
     * @return int
13882
     */
13883
    public function getAccumulateWorkTimeTotalCourse()
13884
    {
13885
        $table = Database::get_course_table(TABLE_LP_MAIN);
13886
        $sql = "SELECT SUM(accumulate_work_time) AS total
13887
                FROM $table
13888
                WHERE c_id = ".$this->course_int_id;
13889
        $result = Database::query($sql);
13890
        $row = Database::fetch_array($result);
13891
13892
        return (int) $row['total'];
13893
    }
13894
13895
    /**
13896
     * Set whether this is a learning path with the accumulated work time or not.
13897
     *
13898
     * @param int $value (0 = false, 1 = true)
13899
     *
13900
     * @return bool
13901
     */
13902
    public function setAccumulateWorkTime($value)
13903
    {
13904
        if (!api_get_configuration_value('lp_minimum_time')) {
13905
            return false;
13906
        }
13907
13908
        $this->accumulateWorkTime = (int) $value;
13909
        $table = Database::get_course_table(TABLE_LP_MAIN);
13910
        $lp_id = $this->get_id();
13911
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13912
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13913
        Database::query($sql);
13914
13915
        return true;
13916
    }
13917
13918
    /**
13919
     * @param int $lpId
13920
     * @param int $courseId
13921
     *
13922
     * @return mixed
13923
     */
13924
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13925
    {
13926
        $lpId = (int) $lpId;
13927
        $courseId = (int) $courseId;
13928
13929
        $table = Database::get_course_table(TABLE_LP_MAIN);
13930
        $sql = "SELECT accumulate_work_time
13931
                FROM $table
13932
                WHERE c_id = $courseId AND id = $lpId";
13933
        $result = Database::query($sql);
13934
        $row = Database::fetch_array($result);
13935
13936
        return $row['accumulate_work_time'];
13937
    }
13938
13939
    /**
13940
     * @param int $courseId
13941
     *
13942
     * @return int
13943
     */
13944
    public static function getAccumulateWorkTimeTotal($courseId)
13945
    {
13946
        $table = Database::get_course_table(TABLE_LP_MAIN);
13947
        $courseId = (int) $courseId;
13948
        $sql = "SELECT SUM(accumulate_work_time) AS total
13949
                FROM $table
13950
                WHERE c_id = $courseId";
13951
        $result = Database::query($sql);
13952
        $row = Database::fetch_array($result);
13953
13954
        return (int) $row['total'];
13955
    }
13956
13957
    /**
13958
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13959
     * and put the images in.
13960
     *
13961
     * @return array
13962
     */
13963
    public static function getIconSelect()
13964
    {
13965
        $theme = api_get_visual_theme();
13966
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13967
        $icons = ['' => get_lang('SelectAnOption')];
13968
13969
        if (is_dir($path)) {
13970
            $finder = new Finder();
13971
            $finder->files()->in($path);
13972
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13973
            /** @var SplFileInfo $file */
13974
            foreach ($finder as $file) {
13975
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13976
                    $icons[$file->getFilename()] = $file->getFilename();
13977
                }
13978
            }
13979
        }
13980
13981
        return $icons;
13982
    }
13983
13984
    /**
13985
     * @param int $lpId
13986
     *
13987
     * @return string
13988
     */
13989
    public static function getSelectedIcon($lpId)
13990
    {
13991
        $extraFieldValue = new ExtraFieldValue('lp');
13992
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13993
        $icon = '';
13994
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13995
            $icon = $lpIcon['value'];
13996
        }
13997
13998
        return $icon;
13999
    }
14000
14001
    /**
14002
     * @param int $lpId
14003
     *
14004
     * @return string
14005
     */
14006
    public static function getSelectedIconHtml($lpId)
14007
    {
14008
        $icon = self::getSelectedIcon($lpId);
14009
14010
        if (empty($icon)) {
14011
            return '';
14012
        }
14013
14014
        $theme = api_get_visual_theme();
14015
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
14016
14017
        return Display::img($path);
14018
    }
14019
14020
    /**
14021
     * @param string $value
14022
     *
14023
     * @return string
14024
     */
14025
    public function cleanItemTitle($value)
14026
    {
14027
        $value = Security::remove_XSS(strip_tags($value));
14028
14029
        return $value;
14030
    }
14031
14032
    public function setItemTitle(FormValidator $form)
14033
    {
14034
        if (api_get_configuration_value('save_titles_as_html')) {
14035
            $form->addHtmlEditor(
14036
                'title',
14037
                get_lang('Title'),
14038
                true,
14039
                false,
14040
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
14041
            );
14042
        } else {
14043
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
14044
            $form->applyFilter('title', 'trim');
14045
            $form->applyFilter('title', 'html_filter');
14046
        }
14047
    }
14048
14049
    /**
14050
     * @return array
14051
     */
14052
    public function getItemsForForm($addParentCondition = false)
14053
    {
14054
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14055
        $course_id = api_get_course_int_id();
14056
14057
        $sql = "SELECT * FROM $tbl_lp_item
14058
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
14059
14060
        if ($addParentCondition) {
14061
            $sql .= ' AND parent_item_id = 0 ';
14062
        }
14063
        $sql .= ' ORDER BY display_order ASC';
14064
14065
        $result = Database::query($sql);
14066
        $arrLP = [];
14067
        while ($row = Database::fetch_array($result)) {
14068
            $arrLP[] = [
14069
                'iid' => $row['iid'],
14070
                'id' => $row['iid'],
14071
                'item_type' => $row['item_type'],
14072
                'title' => $this->cleanItemTitle($row['title']),
14073
                'title_raw' => $row['title'],
14074
                'path' => $row['path'],
14075
                'description' => Security::remove_XSS($row['description']),
14076
                'parent_item_id' => $row['parent_item_id'],
14077
                'previous_item_id' => $row['previous_item_id'],
14078
                'next_item_id' => $row['next_item_id'],
14079
                'display_order' => $row['display_order'],
14080
                'max_score' => $row['max_score'],
14081
                'min_score' => $row['min_score'],
14082
                'mastery_score' => $row['mastery_score'],
14083
                'prerequisite' => $row['prerequisite'],
14084
                'max_time_allowed' => $row['max_time_allowed'],
14085
                'prerequisite_min_score' => $row['prerequisite_min_score'],
14086
                'prerequisite_max_score' => $row['prerequisite_max_score'],
14087
            ];
14088
        }
14089
14090
        return $arrLP;
14091
    }
14092
14093
    /**
14094
     * Gets whether this SCORM learning path has been marked to use the score
14095
     * as progress. Takes into account whether the learnpath matches (SCORM
14096
     * content + less than 2 items).
14097
     *
14098
     * @return bool True if the score should be used as progress, false otherwise
14099
     */
14100
    public function getUseScoreAsProgress()
14101
    {
14102
        // If not a SCORM, we don't care about the setting
14103
        if ($this->get_type() != 2) {
14104
            return false;
14105
        }
14106
        // If more than one step in the SCORM, we don't care about the setting
14107
        if ($this->get_total_items_count() > 1) {
14108
            return false;
14109
        }
14110
        $extraFieldValue = new ExtraFieldValue('lp');
14111
        $doUseScore = false;
14112
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
14113
        if (!empty($useScore) && isset($useScore['value'])) {
14114
            $doUseScore = $useScore['value'];
14115
        }
14116
14117
        return $doUseScore;
14118
    }
14119
14120
    /**
14121
     * Get the user identifier (user_id or username
14122
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
14123
     *
14124
     * @return string User ID or username, depending on configuration setting
14125
     */
14126
    public static function getUserIdentifierForExternalServices()
14127
    {
14128
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
14129
            return api_get_user_info(api_get_user_id())['username'];
14130
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
14131
            $extraFieldValue = new ExtraFieldValue('user');
14132
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(api_get_user_id(), api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id'));
14133
14134
            return $extrafield['value'];
14135
        } else {
14136
            return api_get_user_id();
14137
        }
14138
    }
14139
14140
    /**
14141
     * Save the new order for learning path items.
14142
     *
14143
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
14144
     *
14145
     * @param array $orderList A associative array with item ID as key and parent ID as value.
14146
     * @param int   $courseId
14147
     */
14148
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
14149
    {
14150
        $courseId = $courseId ?: api_get_course_int_id();
14151
        $itemList = new LpItemOrderList();
14152
14153
        foreach ($orderList as $id => $parentId) {
14154
            $item = new LpOrderItem($id, $parentId);
14155
            $itemList->add($item);
14156
        }
14157
14158
        $parents = $itemList->getListOfParents();
14159
14160
        foreach ($parents as $parentId) {
14161
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
14162
            $previous_item_id = 0;
14163
            for ($i = 0; $i < count($sameParentLpItemList->list); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
14164
                $item_id = $sameParentLpItemList->list[$i]->id;
14165
                // display_order
14166
                $display_order = $i + 1;
14167
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14168
                // previous_item_id
14169
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14170
                $previous_item_id = $item_id;
14171
                // next_item_id
14172
                $next_item_id = 0;
14173
                if ($i < count($sameParentLpItemList->list) - 1) {
14174
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14175
                }
14176
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14177
            }
14178
        }
14179
14180
        $table = Database::get_course_table(TABLE_LP_ITEM);
14181
14182
        foreach ($itemList->list as $item) {
14183
            $params = [];
14184
            $params['display_order'] = $item->display_order;
14185
            $params['previous_item_id'] = $item->previous_item_id;
14186
            $params['next_item_id'] = $item->next_item_id;
14187
            $params['parent_item_id'] = $item->parent_item_id;
14188
14189
            Database::update(
14190
                $table,
14191
                $params,
14192
                [
14193
                    'iid = ? AND c_id = ? ' => [
14194
                        (int) $item->id,
14195
                        (int) $courseId,
14196
                    ],
14197
                ]
14198
            );
14199
        }
14200
    }
14201
14202
    public static function findLastView(int $lpId, int $studentId, int $courseId, int $sessionId = 0)
14203
    {
14204
        $tblLpView = Database::get_course_table(TABLE_LP_VIEW);
14205
14206
        $sessionCondition = api_get_session_condition($sessionId);
14207
14208
        $sql = "SELECT iid FROM $tblLpView
14209
            WHERE c_id = $courseId AND lp_id = $lpId AND user_id = $studentId $sessionCondition
14210
            ORDER BY view_count DESC";
14211
        $result = Database::query($sql);
14212
14213
        return Database::fetch_assoc($result);
14214
    }
14215
14216
    /**
14217
     * Get the depth level of LP item.
14218
     *
14219
     * @param array $items
14220
     * @param int   $currentItemId
14221
     *
14222
     * @return int
14223
     */
14224
    private static function get_level_for_item($items, $currentItemId)
14225
    {
14226
        $parentItemId = 0;
14227
        if (isset($items[$currentItemId])) {
14228
            $parentItemId = $items[$currentItemId]->parent;
14229
        }
14230
14231
        if ($parentItemId == 0) {
14232
            return 0;
14233
        } else {
14234
            return self::get_level_for_item($items, $parentItemId) + 1;
14235
        }
14236
    }
14237
14238
    /**
14239
     * Generate the link for a learnpath category as course tool.
14240
     *
14241
     * @param int $categoryId
14242
     *
14243
     * @return string
14244
     */
14245
    private static function getCategoryLinkForTool($categoryId)
14246
    {
14247
        $categoryId = (int) $categoryId;
14248
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14249
            .http_build_query(
14250
                [
14251
                    'action' => 'view_category',
14252
                    'id' => $categoryId,
14253
                ]
14254
            );
14255
14256
        return $link;
14257
    }
14258
14259
    /**
14260
     * Return the scorm item type object with spaces replaced with _
14261
     * The return result is use to build a css classname like scorm_type_$return.
14262
     *
14263
     * @param $in_type
14264
     *
14265
     * @return mixed
14266
     */
14267
    private static function format_scorm_type_item($in_type)
14268
    {
14269
        return str_replace(' ', '_', $in_type);
14270
    }
14271
14272
    /**
14273
     * Check and obtain the lp final item if exist.
14274
     *
14275
     * @return learnpathItem
14276
     */
14277
    private function getFinalItem()
14278
    {
14279
        if (empty($this->items)) {
14280
            return null;
14281
        }
14282
14283
        foreach ($this->items as $item) {
14284
            if ($item->type !== 'final_item') {
14285
                continue;
14286
            }
14287
14288
            return $item;
14289
        }
14290
    }
14291
14292
    /**
14293
     * Get the LP Final Item Template.
14294
     *
14295
     * @return string
14296
     */
14297
    private function getFinalItemTemplate()
14298
    {
14299
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14300
    }
14301
14302
    /**
14303
     * Get the LP Final Item Url.
14304
     *
14305
     * @return string
14306
     */
14307
    private function getSavedFinalItem()
14308
    {
14309
        $finalItem = $this->getFinalItem();
14310
        $doc = DocumentManager::get_document_data_by_id(
14311
            $finalItem->path,
14312
            $this->cc
14313
        );
14314
        if ($doc && file_exists($doc['absolute_path'])) {
14315
            return file_get_contents($doc['absolute_path']);
14316
        }
14317
14318
        return '';
14319
    }
14320
14321
    /**
14322
     * Gets the form to evaluate if it exists contains the extra field extra_authorlpitem
14323
     * to establish authors when editing an item of an LP.
14324
     */
14325
    private function setAuthorLpItem(FormValidator $form)
14326
    {
14327
        if ($form->hasElement('extra_authorlpitem')) {
14328
            /** @var HTML_QuickForm_select $author */
14329
            $author = $form->getElement('extra_authorlpitem');
14330
            $options = [];
14331
            $field = new ExtraField('user');
14332
            $authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
14333
            $extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
14334
            if ($extraFieldId != 0) {
14335
                $extraFieldValueUser = new ExtraFieldValue('user');
14336
                $values = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
14337
                    $authorLp['variable'],
14338
                    1,
14339
                    true,
14340
                    false,
14341
                    true
14342
                );
14343
14344
                if (!empty($values)) {
14345
                    foreach ($values as $item) {
14346
                        $teacher = api_get_user_info($item['item_id']);
14347
                        $options[$teacher['id']] = $teacher['complete_name'];
14348
                    }
14349
                }
14350
            }
14351
            $author->setOptions($options);
14352
        }
14353
    }
14354
}
14355