Passed
Push — master ( 45bdfa...db3c2b )
by Julito
10:09
created

learnpath::restart()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 27
nc 14
nop 0
dl 0
loc 40
rs 8.8657
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\CourseRepository;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
11
use Chamilo\CourseBundle\Entity\CDocument;
12
use Chamilo\CourseBundle\Entity\CLink;
13
use Chamilo\CourseBundle\Entity\CLp;
14
use Chamilo\CourseBundle\Entity\CLpCategory;
15
use Chamilo\CourseBundle\Entity\CLpItem;
16
use Chamilo\CourseBundle\Entity\CLpItemView;
17
use Chamilo\CourseBundle\Entity\CQuiz;
18
use Chamilo\CourseBundle\Entity\CShortcut;
19
use Chamilo\CourseBundle\Entity\CStudentPublication;
20
use Chamilo\CourseBundle\Entity\CTool;
21
use Chamilo\UserBundle\Entity\User;
22
use ChamiloSession as Session;
23
use Gedmo\Sortable\Entity\Repository\SortableRepository;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Symfony\Component\Finder\Finder;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
28
/**
29
 * Class learnpath
30
 * This class defines the parent attributes and methods for Chamilo learnpaths
31
 * and SCORM learnpaths. It is used by the scorm class.
32
 *
33
 * @todo decouple class
34
 * *
35
 *
36
 * @author  Yannick Warnier <[email protected]>
37
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
38
 */
39
class learnpath
40
{
41
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
42
43
    public $attempt = 0; // The number for the current ID view.
44
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
45
    public $current; // Id of the current item the user is viewing.
46
    public $current_score; // The score of the current item.
47
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
48
    public $current_time_stop; // The time the user closed this resource.
49
    public $default_status = 'not attempted';
50
    public $encoding = 'UTF-8';
51
    public $error = '';
52
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
53
    public $index; // The index of the active learnpath_item in $ordered_items array.
54
    public $items = [];
55
    public $last; // item_id of last item viewed in the learning path.
56
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
57
    public $license; // Which license this course has been given - not used yet on 20060522.
58
    public $lp_id; // DB iid for this learnpath.
59
    public $lp_view_id; // DB ID for lp_view
60
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
61
    public $message = '';
62
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
63
    public $name; // Learnpath name (they generally have one).
64
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
65
    public $path = ''; // Path inside the scorm directory (if scorm).
66
    public $theme; // The current theme of the learning path.
67
    public $preview_image; // The current image of the learning path.
68
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
69
    public $accumulateWorkTime; // The min time of learnpath
70
71
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
72
    public $prevent_reinit = 1;
73
74
    // Describes the mode of progress bar display.
75
    public $seriousgame_mode = 0;
76
    public $progress_bar_mode = '%';
77
78
    // Percentage progress as saved in the db.
79
    public $progress_db = 0;
80
    public $proximity; // Wether the content is distant or local or unknown.
81
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
82
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
83
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
84
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
85
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
86
    public $user_id; //ID of the user that is viewing/using the course
87
    public $update_queue = [];
88
    public $scorm_debug = 0;
89
    public $arrMenu = []; // Array for the menu items.
90
    public $debug = 0; // Logging level.
91
    public $lp_session_id = 0;
92
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
93
    public $prerequisite = 0;
94
    public $use_max_score = 1; // 1 or 0
95
    public $subscribeUsers = 0; // Subscribe users or not
96
    public $created_on = '';
97
    public $modified_on = '';
98
    public $publicated_on = '';
99
    public $expired_on = '';
100
    public $ref = null;
101
    public $course_int_id;
102
    public $course_info = [];
103
    public $categoryId;
104
    public $entity;
105
106
    /**
107
     * Constructor.
108
     * Needs a database handler, a course code and a learnpath id from the database.
109
     * Also builds the list of items into $this->items.
110
     *
111
     * @param string $course  Course code
112
     * @param int    $lp_id   c_lp.iid
113
     * @param int    $user_id
114
     */
115
    public function __construct($course, $lp_id, $user_id)
116
    {
117
        $debug = $this->debug;
118
        $this->encoding = api_get_system_encoding();
119
        if (empty($course)) {
120
            $course = api_get_course_id();
121
        }
122
        $course_info = api_get_course_info($course);
123
        if (!empty($course_info)) {
124
            $this->cc = $course_info['code'];
125
            $this->course_info = $course_info;
126
            $course_id = $course_info['real_id'];
127
        } else {
128
            $this->error = 'Course code does not exist in database.';
129
        }
130
131
        $lp_id = (int) $lp_id;
132
        $course_id = (int) $course_id;
133
        $this->set_course_int_id($course_id);
134
        // Check learnpath ID.
135
        if (empty($lp_id) || empty($course_id)) {
136
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
137
        } else {
138
            $repo = Container::getLpRepository();
139
            /** @var CLp $entity */
140
            $entity = $repo->find($lp_id);
141
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
142
                $this->entity = $entity;
143
                $this->lp_id = $lp_id;
144
                $this->type = $entity->getLpType();
145
                $this->name = stripslashes($entity->getName());
146
                $this->proximity = $entity->getContentLocal();
147
                $this->theme = $entity->getTheme();
148
                $this->maker = $entity->getContentLocal();
149
                $this->prevent_reinit = $entity->getPreventReinit();
150
                $this->seriousgame_mode = $entity->getSeriousgameMode();
151
                $this->license = $entity->getContentLicense();
152
                $this->scorm_debug = $entity->getDebug();
153
                $this->js_lib = $entity->getJsLib();
154
                $this->path = $entity->getPath();
155
                $this->preview_image = $entity->getPreviewImage();
156
                $this->author = $entity->getAuthor();
157
                $this->hide_toc_frame = $entity->getHideTocFrame();
158
                $this->lp_session_id = $entity->getSessionId();
159
                $this->use_max_score = $entity->getUseMaxScore();
160
                $this->subscribeUsers = $entity->getSubscribeUsers();
161
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
162
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
163
                $this->ref = $entity->getRef();
164
                $this->categoryId = $entity->getCategoryId();
165
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
166
167
                if (!empty($entity->getPublicatedOn())) {
168
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
169
                }
170
171
                if (!empty($entity->getExpiredOn())) {
172
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
173
                }
174
                if (2 == $this->type) {
175
                    if (1 == $entity->getForceCommit()) {
176
                        $this->force_commit = true;
177
                    }
178
                }
179
                $this->mode = $entity->getDefaultViewMod();
180
181
                // Check user ID.
182
                if (empty($user_id)) {
183
                    $this->error = 'User ID is empty';
184
                } else {
185
                    $userInfo = api_get_user_info($user_id);
186
                    if (!empty($userInfo)) {
187
                        $this->user_id = $userInfo['user_id'];
188
                    } else {
189
                        $this->error = 'User ID does not exist in database #'.$user_id;
190
                    }
191
                }
192
193
                // End of variables checking.
194
                $session_id = api_get_session_id();
195
                //  Get the session condition for learning paths of the base + session.
196
                $session = api_get_session_condition($session_id);
197
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
198
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
199
200
                // Selecting by view_count descending allows to get the highest view_count first.
201
                $sql = "SELECT * FROM $lp_table
202
                        WHERE
203
                            c_id = $course_id AND
204
                            lp_id = $lp_id AND
205
                            user_id = $user_id
206
                            $session
207
                        ORDER BY view_count DESC";
208
                $res = Database::query($sql);
209
                if ($debug) {
210
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
211
                }
212
213
                if (Database::num_rows($res) > 0) {
214
                    if ($debug) {
215
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
216
                    }
217
                    $row = Database::fetch_array($res);
218
                    $this->attempt = $row['view_count'];
219
                    $this->lp_view_id = $row['id'];
220
                    $this->last_item_seen = $row['last_item'];
221
                    $this->progress_db = $row['progress'];
222
                    $this->lp_view_session_id = $row['session_id'];
223
                } elseif (!api_is_invitee()) {
224
                    $this->attempt = 1;
225
                    $params = [
226
                        'c_id' => $course_id,
227
                        'lp_id' => $lp_id,
228
                        'user_id' => $user_id,
229
                        'view_count' => 1,
230
                        'session_id' => $session_id,
231
                        'last_item' => 0,
232
                    ];
233
                    $this->last_item_seen = 0;
234
                    $this->lp_view_session_id = $session_id;
235
                    $this->lp_view_id = Database::insert($lp_table, $params);
236
                    if (!empty($this->lp_view_id)) {
237
                        $sql = "UPDATE $lp_table SET id = iid
238
                                WHERE iid = ".$this->lp_view_id;
239
                        Database::query($sql);
240
                    }
241
                }
242
243
                // Initialise items.
244
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
245
                $sql = "SELECT * FROM $lp_item_table
246
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
247
                        ORDER BY parent_item_id, display_order";
248
                $res = Database::query($sql);
249
250
                $lp_item_id_list = [];
251
                while ($row = Database::fetch_array($res)) {
252
                    $lp_item_id_list[] = $row['iid'];
253
                    switch ($this->type) {
254
                        case 3: //aicc
255
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
256
                            if (is_object($oItem)) {
257
                                $my_item_id = $oItem->get_id();
258
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
259
                                $oItem->set_prevent_reinit($this->prevent_reinit);
260
                                // Don't use reference here as the next loop will make the pointed object change.
261
                                $this->items[$my_item_id] = $oItem;
262
                                $this->refs_list[$oItem->ref] = $my_item_id;
263
                                if ($debug) {
264
                                    error_log(
265
                                        'learnpath::__construct() - '.
266
                                        'aicc object with id '.$my_item_id.
267
                                        ' set in items[]',
268
                                        0
269
                                    );
270
                                }
271
                            }
272
                            break;
273
                        case 2:
274
                            $oItem = new scormItem('db', $row['iid'], $course_id);
275
                            if (is_object($oItem)) {
276
                                $my_item_id = $oItem->get_id();
277
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
278
                                $oItem->set_prevent_reinit($this->prevent_reinit);
279
                                // Don't use reference here as the next loop will make the pointed object change.
280
                                $this->items[$my_item_id] = $oItem;
281
                                $this->refs_list[$oItem->ref] = $my_item_id;
282
                                if ($debug) {
283
                                    error_log('object with id '.$my_item_id.' set in items[]');
284
                                }
285
                            }
286
                            break;
287
                        case 1:
288
                        default:
289
                            if ($debug) {
290
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
291
                            }
292
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
293
294
                            if ($debug) {
295
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
296
                            }
297
                            if (is_object($oItem)) {
298
                                $my_item_id = $oItem->get_id();
299
                                // Moved down to when we are sure the item_view exists.
300
                                //$oItem->set_lp_view($this->lp_view_id);
301
                                $oItem->set_prevent_reinit($this->prevent_reinit);
302
                                // Don't use reference here as the next loop will make the pointed object change.
303
                                $this->items[$my_item_id] = $oItem;
304
                                $this->refs_list[$my_item_id] = $my_item_id;
305
                                if ($debug) {
306
                                    error_log(
307
                                        'learnpath::__construct() '.__LINE__.
308
                                        ' - object with id '.$my_item_id.' set in items[]'
309
                                    );
310
                                }
311
                            }
312
                            break;
313
                    }
314
315
                    // Setting the object level with variable $this->items[$i][parent]
316
                    foreach ($this->items as $itemLPObject) {
317
                        $level = self::get_level_for_item(
318
                            $this->items,
319
                            $itemLPObject->db_id
320
                        );
321
                        $itemLPObject->level = $level;
322
                    }
323
324
                    // Setting the view in the item object.
325
                    if (is_object($this->items[$row['iid']])) {
326
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
327
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
328
                            $this->items[$row['iid']]->current_start_time = 0;
329
                            $this->items[$row['iid']]->current_stop_time = 0;
330
                        }
331
                    }
332
                }
333
334
                if (!empty($lp_item_id_list)) {
335
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
336
                    if (!empty($lp_item_id_list_to_string)) {
337
                        // Get last viewing vars.
338
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
339
                        // This query should only return one or zero result.
340
                        $sql = "SELECT lp_item_id, status
341
                                FROM $itemViewTable
342
                                WHERE
343
                                    c_id = $course_id AND
344
                                    lp_view_id = ".$this->get_view_id()." AND
345
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
346
                                ORDER BY view_count DESC ";
347
                        $status_list = [];
348
                        $res = Database::query($sql);
349
                        while ($row = Database:: fetch_array($res)) {
350
                            $status_list[$row['lp_item_id']] = $row['status'];
351
                        }
352
353
                        foreach ($lp_item_id_list as $item_id) {
354
                            if (isset($status_list[$item_id])) {
355
                                $status = $status_list[$item_id];
356
                                if (is_object($this->items[$item_id])) {
357
                                    $this->items[$item_id]->set_status($status);
358
                                    if (empty($status)) {
359
                                        $this->items[$item_id]->set_status(
360
                                            $this->default_status
361
                                        );
362
                                    }
363
                                }
364
                            } else {
365
                                if (!api_is_invitee()) {
366
                                    if (is_object($this->items[$item_id])) {
367
                                        $this->items[$item_id]->set_status(
368
                                            $this->default_status
369
                                        );
370
                                    }
371
372
                                    if (!empty($this->lp_view_id)) {
373
                                        // Add that row to the lp_item_view table so that
374
                                        // we have something to show in the stats page.
375
                                        $params = [
376
                                            'c_id' => $course_id,
377
                                            'lp_item_id' => $item_id,
378
                                            'lp_view_id' => $this->lp_view_id,
379
                                            'view_count' => 1,
380
                                            'status' => 'not attempted',
381
                                            'start_time' => time(),
382
                                            'total_time' => 0,
383
                                            'score' => 0,
384
                                        ];
385
                                        $insertId = Database::insert($itemViewTable, $params);
386
387
                                        if ($insertId) {
388
                                            $sql = "UPDATE $itemViewTable SET id = iid
389
                                                    WHERE iid = $insertId";
390
                                            Database::query($sql);
391
                                        }
392
393
                                        $this->items[$item_id]->set_lp_view(
394
                                            $this->lp_view_id,
395
                                            $course_id
396
                                        );
397
                                    }
398
                                }
399
                            }
400
                        }
401
                    }
402
                }
403
404
                $this->ordered_items = self::get_flat_ordered_items_list(
405
                    $this->get_id(),
406
                    0,
407
                    $course_id
408
                );
409
                $this->max_ordered_items = 0;
410
                foreach ($this->ordered_items as $index => $dummy) {
411
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
412
                        $this->max_ordered_items = $index;
413
                    }
414
                }
415
                // TODO: Define the current item better.
416
                $this->first();
417
                if ($debug) {
418
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
419
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
420
                }
421
            } else {
422
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
423
            }
424
        }
425
    }
426
427
    public function getEntity(): CLp
428
    {
429
        return $this->entity;
430
    }
431
432
    /**
433
     * @return string
434
     */
435
    public function getCourseCode()
436
    {
437
        return $this->cc;
438
    }
439
440
    /**
441
     * @return int
442
     */
443
    public function get_course_int_id()
444
    {
445
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
446
    }
447
448
    /**
449
     * @param $course_id
450
     *
451
     * @return int
452
     */
453
    public function set_course_int_id($course_id)
454
    {
455
        return $this->course_int_id = (int) $course_id;
456
    }
457
458
    /**
459
     * Function rewritten based on old_add_item() from Yannick Warnier.
460
     * Due the fact that users can decide where the item should come, I had to overlook this function and
461
     * I found it better to rewrite it. Old function is still available.
462
     * Added also the possibility to add a description.
463
     *
464
     * @param int    $parent
465
     * @param int    $previous
466
     * @param string $type
467
     * @param int    $id               resource ID (ref)
468
     * @param string $title
469
     * @param string $description
470
     * @param int    $prerequisites
471
     * @param int    $max_time_allowed
472
     * @param int    $userId
473
     *
474
     * @return int
475
     */
476
    public function add_item(
477
        $parent,
478
        $previous,
479
        $type = 'dir',
480
        $id,
481
        $title,
482
        $description,
483
        $prerequisites = 0,
484
        $max_time_allowed = 0,
485
        $userId = 0
486
    ) {
487
        $course_id = $this->course_info['real_id'];
488
        if (empty($course_id)) {
489
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
490
            $this->course_info = api_get_course_info($this->cc);
491
            $course_id = $this->course_info['real_id'];
492
        }
493
        $userId = empty($userId) ? api_get_user_id() : $userId;
494
        $sessionId = api_get_session_id();
495
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
496
        $_course = $this->course_info;
497
        $parent = (int) $parent;
498
        $previous = (int) $previous;
499
        $id = (int) $id;
500
        $max_time_allowed = htmlentities($max_time_allowed);
501
        if (empty($max_time_allowed)) {
502
            $max_time_allowed = 0;
503
        }
504
        $sql = "SELECT COUNT(iid) AS num
505
                FROM $tbl_lp_item
506
                WHERE
507
                    c_id = $course_id AND
508
                    lp_id = ".$this->get_id()." AND
509
                    parent_item_id = $parent ";
510
511
        $res_count = Database::query($sql);
512
        $row = Database::fetch_array($res_count);
513
        $num = $row['num'];
514
515
        $tmp_previous = 0;
516
        $display_order = 0;
517
        $next = 0;
518
        if ($num > 0) {
519
            if (empty($previous)) {
520
                $sql = "SELECT iid, next_item_id, display_order
521
                        FROM $tbl_lp_item
522
                        WHERE
523
                            c_id = $course_id AND
524
                            lp_id = ".$this->get_id()." AND
525
                            parent_item_id = $parent AND
526
                            previous_item_id = 0 OR
527
                            previous_item_id = $parent";
528
                $result = Database::query($sql);
529
                $row = Database::fetch_array($result);
530
                if ($row) {
531
                    $next = $row['iid'];
532
                }
533
            } else {
534
                $previous = (int) $previous;
535
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
536
						FROM $tbl_lp_item
537
                        WHERE
538
                            c_id = $course_id AND
539
                            lp_id = ".$this->get_id()." AND
540
                            id = $previous";
541
                $result = Database::query($sql);
542
                $row = Database::fetch_array($result);
543
                if ($row) {
544
                    $tmp_previous = $row['iid'];
545
                    $next = $row['next_item_id'];
546
                    $display_order = $row['display_order'];
547
                }
548
            }
549
        }
550
551
        $id = (int) $id;
552
        $typeCleaned = Database::escape_string($type);
553
        $max_score = 100;
554
        if ('quiz' === $type) {
555
            $sql = 'SELECT SUM(ponderation)
556
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
557
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
558
                    ON
559
                        quiz_question.id = quiz_rel_question.question_id AND
560
                        quiz_question.c_id = quiz_rel_question.c_id
561
                    WHERE
562
                        quiz_rel_question.exercice_id = '.$id." AND
563
                        quiz_question.c_id = $course_id AND
564
                        quiz_rel_question.c_id = $course_id ";
565
            $rsQuiz = Database::query($sql);
566
            $max_score = Database::result($rsQuiz, 0, 0);
567
568
            // Disabling the exercise if we add it inside a LP
569
            $exercise = new Exercise($course_id);
570
            $exercise->read($id);
571
            $exercise->disable();
572
            $exercise->save();
573
        }
574
575
        $params = [
576
            'c_id' => $course_id,
577
            'lp_id' => $this->get_id(),
578
            'item_type' => $typeCleaned,
579
            'ref' => '',
580
            'title' => $title,
581
            'description' => $description,
582
            'path' => $id,
583
            'max_score' => $max_score,
584
            'parent_item_id' => $parent,
585
            'previous_item_id' => $previous,
586
            'next_item_id' => (int) $next,
587
            'display_order' => $display_order + 1,
588
            'prerequisite' => $prerequisites,
589
            'max_time_allowed' => $max_time_allowed,
590
            'min_score' => 0,
591
            'launch_data' => '',
592
        ];
593
594
        if (0 != $prerequisites) {
595
            $params['prerequisite'] = $prerequisites;
596
        }
597
598
        $new_item_id = Database::insert($tbl_lp_item, $params);
599
        if ($new_item_id) {
600
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
601
            Database::query($sql);
602
603
            if (!empty($next)) {
604
                $sql = "UPDATE $tbl_lp_item
605
                        SET previous_item_id = $new_item_id
606
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
607
                Database::query($sql);
608
            }
609
610
            // Update the item that should be before the new item.
611
            if (!empty($tmp_previous)) {
612
                $sql = "UPDATE $tbl_lp_item
613
                        SET next_item_id = $new_item_id
614
                        WHERE c_id = $course_id AND id = $tmp_previous";
615
                Database::query($sql);
616
            }
617
618
            // Update all the items after the new item.
619
            $sql = "UPDATE $tbl_lp_item
620
                        SET display_order = display_order + 1
621
                    WHERE
622
                        c_id = $course_id AND
623
                        lp_id = ".$this->get_id()." AND
624
                        iid <> $new_item_id AND
625
                        parent_item_id = $parent AND
626
                        display_order > $display_order";
627
            Database::query($sql);
628
629
            // Update the item that should come after the new item.
630
            $sql = "UPDATE $tbl_lp_item
631
                    SET ref = $new_item_id
632
                    WHERE c_id = $course_id AND iid = $new_item_id";
633
            Database::query($sql);
634
635
            $sql = "UPDATE $tbl_lp_item
636
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
637
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
638
            Database::query($sql);
639
640
            // Upload audio.
641
            if (!empty($_FILES['mp3']['name'])) {
642
                // Create the audio folder if it does not exist yet.
643
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
644
                if (!is_dir($filepath.'audio')) {
645
                    mkdir(
646
                        $filepath.'audio',
647
                        api_get_permissions_for_new_directories()
648
                    );
649
                    $audio_id = DocumentManager::addDocument(
650
                        $_course,
651
                        '/audio',
652
                        'folder',
653
                        0,
654
                        'audio',
655
                        '',
656
                        0,
657
                        true,
658
                        null,
659
                        $sessionId,
660
                        $userId
661
                    );
662
                }
663
664
                $file_path = handle_uploaded_document(
665
                    $_course,
666
                    $_FILES['mp3'],
667
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
668
                    '/audio',
669
                    $userId,
670
                    '',
671
                    '',
672
                    '',
673
                    '',
674
                    false
675
                );
676
677
                // Getting the filename only.
678
                $file_components = explode('/', $file_path);
679
                $file = $file_components[count($file_components) - 1];
680
681
                // Store the mp3 file in the lp_item table.
682
                $sql = "UPDATE $tbl_lp_item SET
683
                          audio = '".Database::escape_string($file)."'
684
                        WHERE iid = '".intval($new_item_id)."'";
685
                Database::query($sql);
686
            }
687
        }
688
689
        return $new_item_id;
690
    }
691
692
    /**
693
     * Static admin function allowing addition of a learnpath to a course.
694
     *
695
     * @param string $courseCode
696
     * @param string $name
697
     * @param string $description
698
     * @param string $learnpath
699
     * @param string $origin
700
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
701
     * @param string $publicated_on
702
     * @param string $expired_on
703
     * @param int    $categoryId
704
     * @param int    $userId
705
     *
706
     * @return int The new learnpath ID on success, 0 on failure
707
     */
708
    public static function add_lp(
709
        $courseCode,
710
        $name,
711
        $description = '',
712
        $learnpath = 'guess',
713
        $origin = 'zip',
714
        $zipname = '',
715
        $publicated_on = '',
716
        $expired_on = '',
717
        $categoryId = 0,
718
        $userId = 0
719
    ) {
720
        global $charset;
721
722
        if (!empty($courseCode)) {
723
            $courseInfo = api_get_course_info($courseCode);
724
            $course_id = $courseInfo['real_id'];
725
        } else {
726
            $course_id = api_get_course_int_id();
727
            $courseInfo = api_get_course_info();
728
        }
729
730
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
731
        // Check course code exists.
732
        // Check lp_name doesn't exist, otherwise append something.
733
        $i = 0;
734
        $categoryId = (int) $categoryId;
735
        // Session id.
736
        $session_id = api_get_session_id();
737
        $userId = empty($userId) ? api_get_user_id() : $userId;
738
739
        if (empty($publicated_on)) {
740
            $publicated_on = null;
741
        } else {
742
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
743
        }
744
745
        if (empty($expired_on)) {
746
            $expired_on = null;
747
        } else {
748
            $expired_on = api_get_utc_datetime($expired_on, true, true);
749
        }
750
751
        $check_name = "SELECT * FROM $tbl_lp
752
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
753
        $res_name = Database::query($check_name);
754
755
        while (Database::num_rows($res_name)) {
756
            // There is already one such name, update the current one a bit.
757
            $i++;
758
            $name = $name.' - '.$i;
759
            $check_name = "SELECT * FROM $tbl_lp
760
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
761
            $res_name = Database::query($check_name);
762
        }
763
        // New name does not exist yet; keep it.
764
        // Escape description.
765
        // Kevin: added htmlentities().
766
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
767
        $type = 1;
768
        switch ($learnpath) {
769
            case 'guess':
770
            case 'aicc':
771
                break;
772
            case 'dokeos':
773
            case 'chamilo':
774
                $type = 1;
775
                break;
776
        }
777
778
        $id = null;
779
        switch ($origin) {
780
            case 'zip':
781
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
782
                break;
783
            case 'manual':
784
            default:
785
                $get_max = "SELECT MAX(display_order)
786
                            FROM $tbl_lp WHERE c_id = $course_id";
787
                $res_max = Database::query($get_max);
788
                if (Database::num_rows($res_max) < 1) {
789
                    $dsp = 1;
790
                } else {
791
                    $row = Database::fetch_array($res_max);
792
                    $dsp = $row[0] + 1;
793
                }
794
795
                $lp = new CLp();
796
                $lp
797
                    ->setCId($course_id)
798
                    ->setLpType($type)
799
                    ->setName($name)
800
                    ->setDescription($description)
801
                    ->setDisplayOrder($dsp)
802
                    ->setSessionId($session_id)
803
                    ->setCategoryId($categoryId)
804
                    ->setPublicatedOn($publicated_on)
805
                    ->setExpiredOn($expired_on)
806
                ;
807
808
                $repo = Container::getLpRepository();
809
                $em = $repo->getEntityManager();
810
                $em->persist($lp);
811
                $courseEntity = api_get_course_entity($courseInfo['real_id']);
812
813
                $repo->addResourceToCourse(
814
                    $lp,
815
                    ResourceLink::VISIBILITY_PUBLISHED,
816
                    api_get_user_entity(api_get_user_id()),
817
                    $courseEntity,
818
                    api_get_session_entity(),
819
                    api_get_group_entity()
820
                );
821
822
                $em->flush();
823
                if ($lp->getIid()) {
824
                    $id = $lp->getIid();
825
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
826
                    Database::query($sql);
827
                }
828
829
                // Insert into item_property.
830
                /*api_item_property_update(
831
                    $courseInfo,
832
                    TOOL_LEARNPATH,
833
                    $id,
834
                    'LearnpathAdded',
835
                    $userId
836
                );
837
                api_set_default_visibility(
838
                    $id,
839
                    TOOL_LEARNPATH,
840
                    0,
841
                    $courseInfo,
842
                    $session_id,
843
                    $userId
844
                );*/
845
846
                break;
847
        }
848
849
        return $id;
850
    }
851
852
    /**
853
     * Auto completes the parents of an item in case it's been completed or passed.
854
     *
855
     * @param int $item Optional ID of the item from which to look for parents
856
     */
857
    public function autocomplete_parents($item)
858
    {
859
        $debug = $this->debug;
860
861
        if (empty($item)) {
862
            $item = $this->current;
863
        }
864
865
        $currentItem = $this->getItem($item);
866
        if ($currentItem) {
867
            $parent_id = $currentItem->get_parent();
868
            $parent = $this->getItem($parent_id);
869
            if ($parent) {
870
                // if $item points to an object and there is a parent.
871
                if ($debug) {
872
                    error_log(
873
                        'Autocompleting parent of item '.$item.' '.
874
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
875
                        0
876
                    );
877
                }
878
879
                // New experiment including failed and browsed in completed status.
880
                //$current_status = $currentItem->get_status();
881
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
882
                // Fixes chapter auto complete
883
                if (true) {
884
                    // If the current item is completed or passes or succeeded.
885
                    $updateParentStatus = true;
886
                    if ($debug) {
887
                        error_log('Status of current item is alright');
888
                    }
889
890
                    foreach ($parent->get_children() as $childItemId) {
891
                        $childItem = $this->getItem($childItemId);
892
893
                        // If children was not set try to get the info
894
                        if (empty($childItem->db_item_view_id)) {
895
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
896
                        }
897
898
                        // Check all his brothers (parent's children) for completion status.
899
                        if ($childItemId != $item) {
900
                            if ($debug) {
901
                                error_log(
902
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
903
                                    0
904
                                );
905
                            }
906
                            // Trying completing parents of failed and browsed items as well.
907
                            if ($childItem->status_is(
908
                                [
909
                                    'completed',
910
                                    'passed',
911
                                    'succeeded',
912
                                    'browsed',
913
                                    'failed',
914
                                ]
915
                            )
916
                            ) {
917
                                // Keep completion status to true.
918
                                continue;
919
                            } else {
920
                                if ($debug > 2) {
921
                                    error_log(
922
                                        '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,
923
                                        0
924
                                    );
925
                                }
926
                                $updateParentStatus = false;
927
                                break;
928
                            }
929
                        }
930
                    }
931
932
                    if ($updateParentStatus) {
933
                        // If all the children were completed:
934
                        $parent->set_status('completed');
935
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
936
                        // Force the status to "completed"
937
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
938
                        $this->update_queue[$parent->get_id()] = 'completed';
939
                        if ($debug) {
940
                            error_log(
941
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
942
                                print_r($this->update_queue, 1),
943
                                0
944
                            );
945
                        }
946
                        // Recursive call.
947
                        $this->autocomplete_parents($parent->get_id());
948
                    }
949
                }
950
            } else {
951
                if ($debug) {
952
                    error_log("Parent #$parent_id does not exists");
953
                }
954
            }
955
        } else {
956
            if ($debug) {
957
                error_log("#$item is an item that doesn't have parents");
958
            }
959
        }
960
    }
961
962
    /**
963
     * Closes the current resource.
964
     *
965
     * Stops the timer
966
     * Saves into the database if required
967
     * Clears the current resource data from this object
968
     *
969
     * @return bool True on success, false on failure
970
     */
971
    public function close()
972
    {
973
        if (empty($this->lp_id)) {
974
            $this->error = 'Trying to close this learnpath but no ID is set';
975
976
            return false;
977
        }
978
        $this->current_time_stop = time();
979
        $this->ordered_items = [];
980
        $this->index = 0;
981
        unset($this->lp_id);
982
        //unset other stuff
983
        return true;
984
    }
985
986
    /**
987
     * Static admin function allowing removal of a learnpath.
988
     *
989
     * @param array  $courseInfo
990
     * @param int    $id         Learnpath ID
991
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
992
     *
993
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
994
     */
995
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
996
    {
997
        $course_id = api_get_course_int_id();
998
        if (!empty($courseInfo)) {
999
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1000
        }
1001
1002
        // TODO: Implement a way of getting this to work when the current object is not set.
1003
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1004
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1005
        if (!empty($id) && ($id != $this->lp_id)) {
1006
            return false;
1007
        }
1008
1009
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1010
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1011
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1012
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1013
1014
        // Delete lp item id.
1015
        foreach ($this->items as $lpItemId => $dummy) {
1016
            $sql = "DELETE FROM $lp_item_view
1017
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1018
            Database::query($sql);
1019
        }
1020
1021
        // Proposed by Christophe (nickname: clefevre)
1022
        $sql = "DELETE FROM $lp_item
1023
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1024
        Database::query($sql);
1025
1026
        $sql = "DELETE FROM $lp_view
1027
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1028
        Database::query($sql);
1029
1030
        //self::toggle_publish($this->lp_id, 'i');
1031
1032
        if (2 == $this->type || 3 == $this->type) {
1033
            // This is a scorm learning path, delete the files as well.
1034
            $sql = "SELECT path FROM $lp
1035
                    WHERE iid = ".$this->lp_id;
1036
            $res = Database::query($sql);
1037
            if (Database::num_rows($res) > 0) {
1038
                $row = Database::fetch_array($res);
1039
                $path = $row['path'];
1040
                $sql = "SELECT id FROM $lp
1041
                        WHERE
1042
                            c_id = $course_id AND
1043
                            path = '$path' AND
1044
                            iid != ".$this->lp_id;
1045
                $res = Database::query($sql);
1046
                if (Database::num_rows($res) > 0) {
1047
                    // Another learning path uses this directory, so don't delete it.
1048
                    if ($this->debug > 2) {
1049
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1050
                    }
1051
                } else {
1052
                    // No other LP uses that directory, delete it.
1053
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1054
                    // The absolute system path for this course.
1055
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1056
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1057
                        if ($this->debug > 2) {
1058
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1059
                        }
1060
                        // Proposed by Christophe (clefevre).
1061
                        if (0 == strcmp(substr($path, -2), "/.")) {
1062
                            $path = substr($path, 0, -1); // Remove "." at the end.
1063
                        }
1064
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1065
                        rmdirr($course_scorm_dir.$path);
1066
                    }
1067
                }
1068
            }
1069
        }
1070
1071
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1072
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1073
        // Delete tools
1074
        $sql = "DELETE FROM $tbl_tool
1075
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1076
        Database::query($sql);*/
1077
1078
        /*$sql = "DELETE FROM $lp
1079
                WHERE iid = ".$this->lp_id;
1080
        Database::query($sql);*/
1081
        $repo = Container::getLpRepository();
1082
        $lp = $repo->find($this->lp_id);
1083
        $repo->getEntityManager()->remove($lp);
1084
        $repo->getEntityManager()->flush();
1085
1086
        // Updates the display order of all lps.
1087
        $this->update_display_order();
1088
1089
        /*api_item_property_update(
1090
            api_get_course_info(),
1091
            TOOL_LEARNPATH,
1092
            $this->lp_id,
1093
            'delete',
1094
            api_get_user_id()
1095
        );*/
1096
1097
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1098
            api_get_course_id(),
1099
            4,
1100
            $id,
1101
            api_get_session_id()
1102
        );
1103
1104
        if (false !== $link_info) {
1105
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1106
        }
1107
1108
        if ('true' == api_get_setting('search_enabled')) {
1109
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1110
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1111
        }
1112
    }
1113
1114
    /**
1115
     * Removes all the children of one item - dangerous!
1116
     *
1117
     * @param int $id Element ID of which children have to be removed
1118
     *
1119
     * @return int Total number of children removed
1120
     */
1121
    public function delete_children_items($id)
1122
    {
1123
        $course_id = $this->course_info['real_id'];
1124
1125
        $num = 0;
1126
        $id = (int) $id;
1127
        if (empty($id) || empty($course_id)) {
1128
            return false;
1129
        }
1130
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1131
        $sql = "SELECT * FROM $lp_item
1132
                WHERE c_id = $course_id AND parent_item_id = $id";
1133
        $res = Database::query($sql);
1134
        while ($row = Database::fetch_array($res)) {
1135
            $num += $this->delete_children_items($row['iid']);
1136
            $sql = "DELETE FROM $lp_item
1137
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1138
            Database::query($sql);
1139
            $num++;
1140
        }
1141
1142
        return $num;
1143
    }
1144
1145
    /**
1146
     * Removes an item from the current learnpath.
1147
     *
1148
     * @param int $id Elem ID (0 if first)
1149
     *
1150
     * @return int Number of elements moved
1151
     *
1152
     * @todo implement resource removal
1153
     */
1154
    public function delete_item($id)
1155
    {
1156
        $course_id = api_get_course_int_id();
1157
        $id = (int) $id;
1158
        // TODO: Implement the resource removal.
1159
        if (empty($id) || empty($course_id)) {
1160
            return false;
1161
        }
1162
        // First select item to get previous, next, and display order.
1163
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1164
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1165
        $res_sel = Database::query($sql_sel);
1166
        if (Database::num_rows($res_sel) < 1) {
1167
            return false;
1168
        }
1169
        $row = Database::fetch_array($res_sel);
1170
        $previous = $row['previous_item_id'];
1171
        $next = $row['next_item_id'];
1172
        $display = $row['display_order'];
1173
        $parent = $row['parent_item_id'];
1174
        $lp = $row['lp_id'];
1175
        // Delete children items.
1176
        $this->delete_children_items($id);
1177
        // Now delete the item.
1178
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1179
        Database::query($sql_del);
1180
        // Now update surrounding items.
1181
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1182
                    WHERE iid = $previous";
1183
        Database::query($sql_upd);
1184
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1185
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1186
        Database::query($sql_upd);
1187
        // Now update all following items with new display order.
1188
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1189
                    WHERE
1190
                        c_id = $course_id AND
1191
                        lp_id = $lp AND
1192
                        parent_item_id = $parent AND
1193
                        display_order > $display";
1194
        Database::query($sql_all);
1195
1196
        //Removing prerequisites since the item will not longer exist
1197
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1198
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1199
        Database::query($sql_all);
1200
1201
        $sql = "UPDATE $lp_item
1202
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1203
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1204
        Database::query($sql);
1205
1206
        // Remove from search engine if enabled.
1207
        if ('true' === api_get_setting('search_enabled')) {
1208
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1209
            $sql = 'SELECT * FROM %s
1210
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1211
                    LIMIT 1';
1212
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1213
            $res = Database::query($sql);
1214
            if (Database::num_rows($res) > 0) {
1215
                $row2 = Database::fetch_array($res);
1216
                $di = new ChamiloIndexer();
1217
                $di->remove_document($row2['search_did']);
1218
            }
1219
            $sql = 'DELETE FROM %s
1220
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1221
                    LIMIT 1';
1222
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1223
            Database::query($sql);
1224
        }
1225
    }
1226
1227
    /**
1228
     * Updates an item's content in place.
1229
     *
1230
     * @param int    $id               Element ID
1231
     * @param int    $parent           Parent item ID
1232
     * @param int    $previous         Previous item ID
1233
     * @param string $title            Item title
1234
     * @param string $description      Item description
1235
     * @param string $prerequisites    Prerequisites (optional)
1236
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1237
     * @param int    $max_time_allowed
1238
     * @param string $url
1239
     *
1240
     * @return bool True on success, false on error
1241
     */
1242
    public function edit_item(
1243
        $id,
1244
        $parent,
1245
        $previous,
1246
        $title,
1247
        $description,
1248
        $prerequisites = '0',
1249
        $audio = [],
1250
        $max_time_allowed = 0,
1251
        $url = ''
1252
    ) {
1253
        $course_id = api_get_course_int_id();
1254
        $_course = api_get_course_info();
1255
        $id = (int) $id;
1256
1257
        if (empty($max_time_allowed)) {
1258
            $max_time_allowed = 0;
1259
        }
1260
1261
        if (empty($id) || empty($_course)) {
1262
            return false;
1263
        }
1264
1265
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1266
        $sql = "SELECT * FROM $tbl_lp_item
1267
                WHERE iid = $id";
1268
        $res_select = Database::query($sql);
1269
        $row_select = Database::fetch_array($res_select);
1270
        $audio_update_sql = '';
1271
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1272
            // Create the audio folder if it does not exist yet.
1273
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1274
            if (!is_dir($filepath.'audio')) {
1275
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1276
                $audio_id = DocumentManager::addDocument(
1277
                    $_course,
1278
                    '/audio',
1279
                    'folder',
1280
                    0,
1281
                    'audio'
1282
                );
1283
            }
1284
1285
            // Upload file in documents.
1286
            $pi = pathinfo($audio['name']);
1287
            if ('mp3' === $pi['extension']) {
1288
                $c_det = api_get_course_info($this->cc);
1289
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1290
                $path = handle_uploaded_document(
1291
                    $c_det,
1292
                    $audio,
1293
                    $bp,
1294
                    '/audio',
1295
                    api_get_user_id(),
1296
                    0,
1297
                    null,
1298
                    0,
1299
                    'rename',
1300
                    false,
1301
                    0
1302
                );
1303
                $path = substr($path, 7);
1304
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1305
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1306
            }
1307
        }
1308
1309
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1310
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1311
1312
        // TODO: htmlspecialchars to be checked for encoding related problems.
1313
        if ($same_parent && $same_previous) {
1314
            // Only update title and description.
1315
            $sql = "UPDATE $tbl_lp_item
1316
                    SET title = '".Database::escape_string($title)."',
1317
                        prerequisite = '".$prerequisites."',
1318
                        description = '".Database::escape_string($description)."'
1319
                        ".$audio_update_sql.",
1320
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1321
                    WHERE iid = $id";
1322
            Database::query($sql);
1323
        } else {
1324
            $old_parent = $row_select['parent_item_id'];
1325
            $old_previous = $row_select['previous_item_id'];
1326
            $old_next = $row_select['next_item_id'];
1327
            $old_order = $row_select['display_order'];
1328
            $old_prerequisite = $row_select['prerequisite'];
1329
            $old_max_time_allowed = $row_select['max_time_allowed'];
1330
1331
            /* BEGIN -- virtually remove the current item id */
1332
            /* for the next and previous item it is like the current item doesn't exist anymore */
1333
            if (0 != $old_previous) {
1334
                // Next
1335
                $sql = "UPDATE $tbl_lp_item
1336
                        SET next_item_id = $old_next
1337
                        WHERE iid = $old_previous";
1338
                Database::query($sql);
1339
            }
1340
1341
            if (!empty($old_next)) {
1342
                // Previous
1343
                $sql = "UPDATE $tbl_lp_item
1344
                        SET previous_item_id = $old_previous
1345
                        WHERE iid = $old_next";
1346
                Database::query($sql);
1347
            }
1348
1349
            // display_order - 1 for every item with a display_order
1350
            // bigger then the display_order of the current item.
1351
            $sql = "UPDATE $tbl_lp_item
1352
                    SET display_order = display_order - 1
1353
                    WHERE
1354
                        c_id = $course_id AND
1355
                        display_order > $old_order AND
1356
                        lp_id = ".$this->lp_id." AND
1357
                        parent_item_id = $old_parent";
1358
            Database::query($sql);
1359
            /* END -- virtually remove the current item id */
1360
1361
            /* BEGIN -- update the current item id to his new location */
1362
            if (0 == $previous) {
1363
                // Select the data of the item that should come after the current item.
1364
                $sql = "SELECT id, display_order
1365
                        FROM $tbl_lp_item
1366
                        WHERE
1367
                            c_id = $course_id AND
1368
                            lp_id = ".$this->lp_id." AND
1369
                            parent_item_id = $parent AND
1370
                            previous_item_id = $previous";
1371
                $res_select_old = Database::query($sql);
1372
                $row_select_old = Database::fetch_array($res_select_old);
1373
1374
                // If the new parent didn't have children before.
1375
                if (0 == Database::num_rows($res_select_old)) {
1376
                    $new_next = 0;
1377
                    $new_order = 1;
1378
                } else {
1379
                    $new_next = $row_select_old['id'];
1380
                    $new_order = $row_select_old['display_order'];
1381
                }
1382
            } else {
1383
                // Select the data of the item that should come before the current item.
1384
                $sql = "SELECT next_item_id, display_order
1385
                        FROM $tbl_lp_item
1386
                        WHERE iid = $previous";
1387
                $res_select_old = Database::query($sql);
1388
                $row_select_old = Database::fetch_array($res_select_old);
1389
                $new_next = $row_select_old['next_item_id'];
1390
                $new_order = $row_select_old['display_order'] + 1;
1391
            }
1392
1393
            // TODO: htmlspecialchars to be checked for encoding related problems.
1394
            // Update the current item with the new data.
1395
            $sql = "UPDATE $tbl_lp_item
1396
                    SET
1397
                        title = '".Database::escape_string($title)."',
1398
                        description = '".Database::escape_string($description)."',
1399
                        parent_item_id = $parent,
1400
                        previous_item_id = $previous,
1401
                        next_item_id = $new_next,
1402
                        display_order = $new_order
1403
                        $audio_update_sql
1404
                    WHERE iid = $id";
1405
            Database::query($sql);
1406
1407
            if (0 != $previous) {
1408
                // Update the previous item's next_item_id.
1409
                $sql = "UPDATE $tbl_lp_item
1410
                        SET next_item_id = $id
1411
                        WHERE iid = $previous";
1412
                Database::query($sql);
1413
            }
1414
1415
            if (!empty($new_next)) {
1416
                // Update the next item's previous_item_id.
1417
                $sql = "UPDATE $tbl_lp_item
1418
                        SET previous_item_id = $id
1419
                        WHERE iid = $new_next";
1420
                Database::query($sql);
1421
            }
1422
1423
            if ($old_prerequisite != $prerequisites) {
1424
                $sql = "UPDATE $tbl_lp_item
1425
                        SET prerequisite = '$prerequisites'
1426
                        WHERE iid = $id";
1427
                Database::query($sql);
1428
            }
1429
1430
            if ($old_max_time_allowed != $max_time_allowed) {
1431
                // update max time allowed
1432
                $sql = "UPDATE $tbl_lp_item
1433
                        SET max_time_allowed = $max_time_allowed
1434
                        WHERE iid = $id";
1435
                Database::query($sql);
1436
            }
1437
1438
            // Update all the items with the same or a bigger display_order than the current item.
1439
            $sql = "UPDATE $tbl_lp_item
1440
                    SET display_order = display_order + 1
1441
                    WHERE
1442
                       c_id = $course_id AND
1443
                       lp_id = ".$this->get_id()." AND
1444
                       iid <> $id AND
1445
                       parent_item_id = $parent AND
1446
                       display_order >= $new_order";
1447
            Database::query($sql);
1448
        }
1449
1450
        if ('link' == $row_select['item_type']) {
1451
            $link = new Link();
1452
            $linkId = $row_select['path'];
1453
            $link->updateLink($linkId, $url);
1454
        }
1455
    }
1456
1457
    /**
1458
     * Updates an item's prereq in place.
1459
     *
1460
     * @param int    $id              Element ID
1461
     * @param string $prerequisite_id Prerequisite Element ID
1462
     * @param int    $minScore        Prerequisite min score
1463
     * @param int    $maxScore        Prerequisite max score
1464
     *
1465
     * @return bool True on success, false on error
1466
     */
1467
    public function edit_item_prereq(
1468
        $id,
1469
        $prerequisite_id,
1470
        $minScore = 0,
1471
        $maxScore = 100
1472
    ) {
1473
        $id = (int) $id;
1474
        $prerequisite_id = (int) $prerequisite_id;
1475
1476
        if (empty($id)) {
1477
            return false;
1478
        }
1479
1480
        if (empty($minScore) || $minScore < 0) {
1481
            $minScore = 0;
1482
        }
1483
1484
        if (empty($maxScore) || $maxScore < 0) {
1485
            $maxScore = 100;
1486
        }
1487
1488
        $minScore = floatval($minScore);
1489
        $maxScore = floatval($maxScore);
1490
1491
        if (empty($prerequisite_id)) {
1492
            $prerequisite_id = 'NULL';
1493
            $minScore = 0;
1494
            $maxScore = 100;
1495
        }
1496
1497
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1498
        $sql = " UPDATE $tbl_lp_item
1499
                 SET
1500
                    prerequisite = $prerequisite_id ,
1501
                    prerequisite_min_score = $minScore ,
1502
                    prerequisite_max_score = $maxScore
1503
                 WHERE iid = $id";
1504
1505
        Database::query($sql);
1506
1507
        return true;
1508
    }
1509
1510
    /**
1511
     * Get the specific prefix index terms of this learning path.
1512
     *
1513
     * @param string $prefix
1514
     *
1515
     * @return array Array of terms
1516
     */
1517
    public function get_common_index_terms_by_prefix($prefix)
1518
    {
1519
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1520
        $terms = get_specific_field_values_list_by_prefix(
1521
            $prefix,
1522
            $this->cc,
1523
            TOOL_LEARNPATH,
1524
            $this->lp_id
1525
        );
1526
        $prefix_terms = [];
1527
        if (!empty($terms)) {
1528
            foreach ($terms as $term) {
1529
                $prefix_terms[] = $term['value'];
1530
            }
1531
        }
1532
1533
        return $prefix_terms;
1534
    }
1535
1536
    /**
1537
     * Gets the number of items currently completed.
1538
     *
1539
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1540
     *
1541
     * @return int The number of items currently completed
1542
     */
1543
    public function get_complete_items_count($failedStatusException = false)
1544
    {
1545
        $i = 0;
1546
        $completedStatusList = [
1547
            'completed',
1548
            'passed',
1549
            'succeeded',
1550
            'browsed',
1551
        ];
1552
1553
        if (!$failedStatusException) {
1554
            $completedStatusList[] = 'failed';
1555
        }
1556
1557
        foreach ($this->items as $id => $dummy) {
1558
            // Trying failed and browsed considered "progressed" as well.
1559
            if ($this->items[$id]->status_is($completedStatusList) &&
1560
                'dir' != $this->items[$id]->get_type()
1561
            ) {
1562
                $i++;
1563
            }
1564
        }
1565
1566
        return $i;
1567
    }
1568
1569
    /**
1570
     * Gets the current item ID.
1571
     *
1572
     * @return int The current learnpath item id
1573
     */
1574
    public function get_current_item_id()
1575
    {
1576
        $current = 0;
1577
        if (!empty($this->current)) {
1578
            $current = (int) $this->current;
1579
        }
1580
1581
        return $current;
1582
    }
1583
1584
    /**
1585
     * Force to get the first learnpath item id.
1586
     *
1587
     * @return int The current learnpath item id
1588
     */
1589
    public function get_first_item_id()
1590
    {
1591
        $current = 0;
1592
        if (is_array($this->ordered_items)) {
1593
            $current = $this->ordered_items[0];
1594
        }
1595
1596
        return $current;
1597
    }
1598
1599
    /**
1600
     * Gets the total number of items available for viewing in this SCORM.
1601
     *
1602
     * @return int The total number of items
1603
     */
1604
    public function get_total_items_count()
1605
    {
1606
        return count($this->items);
1607
    }
1608
1609
    /**
1610
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1611
     *
1612
     * @return int The total no-chapters number of items
1613
     */
1614
    public function getTotalItemsCountWithoutDirs()
1615
    {
1616
        $total = 0;
1617
        $typeListNotToCount = self::getChapterTypes();
1618
        foreach ($this->items as $temp2) {
1619
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1620
                $total++;
1621
            }
1622
        }
1623
1624
        return $total;
1625
    }
1626
1627
    /**
1628
     *  Sets the first element URL.
1629
     */
1630
    public function first()
1631
    {
1632
        if ($this->debug > 0) {
1633
            error_log('In learnpath::first()', 0);
1634
            error_log('$this->last_item_seen '.$this->last_item_seen);
1635
        }
1636
1637
        // Test if the last_item_seen exists and is not a dir.
1638
        if (0 == count($this->ordered_items)) {
1639
            $this->index = 0;
1640
        }
1641
1642
        if (!empty($this->last_item_seen) &&
1643
            !empty($this->items[$this->last_item_seen]) &&
1644
            'dir' != $this->items[$this->last_item_seen]->get_type()
1645
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1646
            //&& !$this->items[$this->last_item_seen]->is_done()
1647
        ) {
1648
            if ($this->debug > 2) {
1649
                error_log(
1650
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1651
                    $this->items[$this->last_item_seen]->get_type()
1652
                );
1653
            }
1654
            $index = -1;
1655
            foreach ($this->ordered_items as $myindex => $item_id) {
1656
                if ($item_id == $this->last_item_seen) {
1657
                    $index = $myindex;
1658
                    break;
1659
                }
1660
            }
1661
            if (-1 == $index) {
1662
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1663
                if ($this->debug > 2) {
1664
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1665
                }
1666
1667
                return false;
1668
            } else {
1669
                $this->last = $this->last_item_seen;
1670
                $this->current = $this->last_item_seen;
1671
                $this->index = $index;
1672
            }
1673
        } else {
1674
            if ($this->debug > 2) {
1675
                error_log('In learnpath::first() - No last item seen', 0);
1676
            }
1677
            $index = 0;
1678
            // Loop through all ordered items and stop at the first item that is
1679
            // not a directory *and* that has not been completed yet.
1680
            while (!empty($this->ordered_items[$index]) &&
1681
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1682
                (
1683
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1684
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1685
                ) && $index < $this->max_ordered_items) {
1686
                $index++;
1687
            }
1688
1689
            $this->last = $this->current;
1690
            // current is
1691
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1692
            $this->index = $index;
1693
            if ($this->debug > 2) {
1694
                error_log('$index '.$index);
1695
                error_log('In learnpath::first() - No last item seen');
1696
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1697
            }
1698
        }
1699
        if ($this->debug > 2) {
1700
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1701
        }
1702
    }
1703
1704
    /**
1705
     * Gets the js library from the database.
1706
     *
1707
     * @return string The name of the javascript library to be used
1708
     */
1709
    public function get_js_lib()
1710
    {
1711
        $lib = '';
1712
        if (!empty($this->js_lib)) {
1713
            $lib = $this->js_lib;
1714
        }
1715
1716
        return $lib;
1717
    }
1718
1719
    /**
1720
     * Gets the learnpath database ID.
1721
     *
1722
     * @return int Learnpath ID in the lp table
1723
     */
1724
    public function get_id()
1725
    {
1726
        if (!empty($this->lp_id)) {
1727
            return (int) $this->lp_id;
1728
        }
1729
1730
        return 0;
1731
    }
1732
1733
    /**
1734
     * Gets the last element URL.
1735
     *
1736
     * @return string URL to load into the viewer
1737
     */
1738
    public function get_last()
1739
    {
1740
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1741
        if (count($this->ordered_items) > 0) {
1742
            $this->index = count($this->ordered_items) - 1;
1743
1744
            return $this->ordered_items[$this->index];
1745
        }
1746
1747
        return false;
1748
    }
1749
1750
    /**
1751
     * Get the last element in the first level.
1752
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1753
     *
1754
     * @return mixed
1755
     */
1756
    public function getLastInFirstLevel()
1757
    {
1758
        try {
1759
            $lastId = Database::getManager()
1760
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1761
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1762
                ->setMaxResults(1)
1763
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1764
                ->getSingleScalarResult();
1765
1766
            return $lastId;
1767
        } catch (Exception $exception) {
1768
            return 0;
1769
        }
1770
    }
1771
1772
    /**
1773
     * Gets the navigation bar for the learnpath display screen.
1774
     *
1775
     * @param string $barId
1776
     *
1777
     * @return string The HTML string to use as a navigation bar
1778
     */
1779
    public function get_navigation_bar($barId = '')
1780
    {
1781
        if (empty($barId)) {
1782
            $barId = 'control-top';
1783
        }
1784
        $lpId = $this->lp_id;
1785
        $mycurrentitemid = $this->get_current_item_id();
1786
1787
        $reportingText = get_lang('Reporting');
1788
        $previousText = get_lang('Previous');
1789
        $nextText = get_lang('Next');
1790
        $fullScreenText = get_lang('Back to normal screen');
1791
1792
        $settings = api_get_configuration_value('lp_view_settings');
1793
        $display = isset($settings['display']) ? $settings['display'] : false;
1794
        $reportingIcon = '
1795
            <a class="icon-toolbar"
1796
                id="stats_link"
1797
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1798
                onclick="window.parent.API.save_asset(); return true;"
1799
                target="content_name" title="'.$reportingText.'">
1800
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1801
            </a>';
1802
1803
        if (!empty($display)) {
1804
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1805
            if (false === $showReporting) {
1806
                $reportingIcon = '';
1807
            }
1808
        }
1809
1810
        $hideArrows = false;
1811
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1812
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1813
        }
1814
1815
        $previousIcon = '';
1816
        $nextIcon = '';
1817
        if (false === $hideArrows) {
1818
            $previousIcon = '
1819
                <a class="icon-toolbar" id="scorm-previous" href="#"
1820
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1821
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1822
                </a>';
1823
1824
            $nextIcon = '
1825
                <a class="icon-toolbar" id="scorm-next" href="#"
1826
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1827
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1828
                </a>';
1829
        }
1830
1831
        if ('fullscreen' === $this->mode) {
1832
            $navbar = '
1833
                  <span id="'.$barId.'" class="buttons">
1834
                    '.$reportingIcon.'
1835
                    '.$previousIcon.'
1836
                    '.$nextIcon.'
1837
                    <a class="icon-toolbar" id="view-embedded"
1838
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1839
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1840
                    </a>
1841
                  </span>';
1842
        } else {
1843
            $navbar = '
1844
                 <span id="'.$barId.'" class="buttons text-right">
1845
                    '.$reportingIcon.'
1846
                    '.$previousIcon.'
1847
                    '.$nextIcon.'
1848
                </span>';
1849
        }
1850
1851
        return $navbar;
1852
    }
1853
1854
    /**
1855
     * Gets the next resource in queue (url).
1856
     *
1857
     * @return string URL to load into the viewer
1858
     */
1859
    public function get_next_index()
1860
    {
1861
        // TODO
1862
        $index = $this->index;
1863
        $index++;
1864
        while (
1865
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1866
            $index < $this->max_ordered_items
1867
        ) {
1868
            $index++;
1869
            if ($index == $this->max_ordered_items) {
1870
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1871
                    return $this->index;
1872
                }
1873
1874
                return $index;
1875
            }
1876
        }
1877
        if (empty($this->ordered_items[$index])) {
1878
            return $this->index;
1879
        }
1880
1881
        return $index;
1882
    }
1883
1884
    /**
1885
     * Gets item_id for the next element.
1886
     *
1887
     * @return int Next item (DB) ID
1888
     */
1889
    public function get_next_item_id()
1890
    {
1891
        $new_index = $this->get_next_index();
1892
        if (!empty($new_index)) {
1893
            if (isset($this->ordered_items[$new_index])) {
1894
                return $this->ordered_items[$new_index];
1895
            }
1896
        }
1897
1898
        return 0;
1899
    }
1900
1901
    /**
1902
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1903
     *
1904
     * Generally, the package provided is in the form of a zip file, so the function
1905
     * has been written to test a zip file. If not a zip, the function will return the
1906
     * default return value: ''
1907
     *
1908
     * @param string $file_path the path to the file
1909
     * @param string $file_name the original name of the file
1910
     *
1911
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1912
     */
1913
    public static function get_package_type($file_path, $file_name)
1914
    {
1915
        // Get name of the zip file without the extension.
1916
        $file_info = pathinfo($file_name);
1917
        $extension = $file_info['extension']; // Extension only.
1918
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1919
                'dll',
1920
                'exe',
1921
            ])) {
1922
            return 'oogie';
1923
        }
1924
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1925
                'dll',
1926
                'exe',
1927
            ])) {
1928
            return 'woogie';
1929
        }
1930
1931
        $zipFile = new PclZip($file_path);
1932
        // Check the zip content (real size and file extension).
1933
        $zipContentArray = $zipFile->listContent();
1934
        $package_type = '';
1935
        $manifest = '';
1936
        $aicc_match_crs = 0;
1937
        $aicc_match_au = 0;
1938
        $aicc_match_des = 0;
1939
        $aicc_match_cst = 0;
1940
1941
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1942
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
1943
            foreach ($zipContentArray as $thisContent) {
1944
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1945
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1946
                } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1947
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1948
                    $package_type = 'scorm';
1949
                    break; // Exit the foreach loop.
1950
                } elseif (
1951
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1952
                    in_array(
1953
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1954
                        ['crs', 'au', 'des', 'cst']
1955
                    )
1956
                ) {
1957
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1958
                    switch ($ext) {
1959
                        case 'crs':
1960
                            $aicc_match_crs = 1;
1961
                            break;
1962
                        case 'au':
1963
                            $aicc_match_au = 1;
1964
                            break;
1965
                        case 'des':
1966
                            $aicc_match_des = 1;
1967
                            break;
1968
                        case 'cst':
1969
                            $aicc_match_cst = 1;
1970
                            break;
1971
                        default:
1972
                            break;
1973
                    }
1974
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1975
                } else {
1976
                    $package_type = '';
1977
                }
1978
            }
1979
        }
1980
1981
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1982
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1983
            $package_type = 'aicc';
1984
        }
1985
1986
        // Try with chamilo course builder
1987
        if (empty($package_type)) {
1988
            $package_type = 'chamilo';
1989
        }
1990
1991
        return $package_type;
1992
    }
1993
1994
    /**
1995
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1996
     *
1997
     * @return string URL to load into the viewer
1998
     */
1999
    public function get_previous_index()
2000
    {
2001
        $index = $this->index;
2002
        if (isset($this->ordered_items[$index - 1])) {
2003
            $index--;
2004
            while (isset($this->ordered_items[$index]) &&
2005
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
2006
            ) {
2007
                $index--;
2008
                if ($index < 0) {
2009
                    return $this->index;
2010
                }
2011
            }
2012
        }
2013
2014
        return $index;
2015
    }
2016
2017
    /**
2018
     * Gets item_id for the next element.
2019
     *
2020
     * @return int Previous item (DB) ID
2021
     */
2022
    public function get_previous_item_id()
2023
    {
2024
        $index = $this->get_previous_index();
2025
2026
        return $this->ordered_items[$index];
2027
    }
2028
2029
    /**
2030
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2031
     *
2032
     * @param int    $lpItemId
2033
     * @param string $autostart
2034
     *
2035
     * @return string The mediaplayer HTML
2036
     */
2037
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2038
    {
2039
        $course_id = api_get_course_int_id();
2040
        $_course = api_get_course_info();
2041
        if (empty($_course)) {
2042
            return '';
2043
        }
2044
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2045
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2046
        $lpItemId = (int) $lpItemId;
2047
2048
        /** @var learnpathItem $item */
2049
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2050
        $itemViewId = 0;
2051
        if ($item) {
2052
            $itemViewId = (int) $item->db_item_view_id;
2053
        }
2054
2055
        // Getting all the information about the item.
2056
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2057
                FROM $tbl_lp_item as lpi
2058
                INNER JOIN $tbl_lp_item_view as lp_view
2059
                ON (lpi.iid = lp_view.lp_item_id)
2060
                WHERE
2061
                    lp_view.iid = $itemViewId AND
2062
                    lpi.iid = $lpItemId AND
2063
                    lp_view.c_id = $course_id";
2064
        $result = Database::query($sql);
2065
        $row = Database::fetch_assoc($result);
2066
        $output = '';
2067
2068
        if (!empty($row['audio'])) {
2069
            $list = $_SESSION['oLP']->get_toc();
2070
2071
            switch ($row['item_type']) {
2072
                case 'quiz':
2073
                    $type_quiz = false;
2074
                    foreach ($list as $toc) {
2075
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2076
                            $type_quiz = true;
2077
                        }
2078
                    }
2079
2080
                    if ($type_quiz) {
2081
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2082
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2083
                        } else {
2084
                            $autostart_audio = $autostart;
2085
                        }
2086
                    }
2087
                    break;
2088
                case TOOL_READOUT_TEXT:;
2089
                    $autostart_audio = 'false';
2090
                    break;
2091
                default:
2092
                    $autostart_audio = 'true';
2093
            }
2094
2095
            $courseInfo = api_get_course_info();
2096
            $audio = $row['audio'];
2097
2098
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2099
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2100
2101
            if (!file_exists($file)) {
2102
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2103
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2104
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2105
            }
2106
2107
            $player = Display::getMediaPlayer(
2108
                $file,
2109
                [
2110
                    'id' => 'lp_audio_media_player',
2111
                    'url' => $url,
2112
                    'autoplay' => $autostart_audio,
2113
                    'width' => '100%',
2114
                ]
2115
            );
2116
2117
            // The mp3 player.
2118
            $output = '<div id="container">';
2119
            $output .= $player;
2120
            $output .= '</div>';
2121
        }
2122
2123
        return $output;
2124
    }
2125
2126
    /**
2127
     * @param int   $studentId
2128
     * @param int   $prerequisite
2129
     * @param array $courseInfo
2130
     * @param int   $sessionId
2131
     *
2132
     * @return bool
2133
     */
2134
    public static function isBlockedByPrerequisite(
2135
        $studentId,
2136
        $prerequisite,
2137
        $courseInfo,
2138
        $sessionId
2139
    ) {
2140
        if (empty($courseInfo)) {
2141
            return false;
2142
        }
2143
2144
        $courseId = $courseInfo['real_id'];
2145
2146
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2147
        if ($allow) {
2148
            if (api_is_allowed_to_edit() ||
2149
                api_is_platform_admin(true) ||
2150
                api_is_drh() ||
2151
                api_is_coach($sessionId, $courseId, false)
2152
            ) {
2153
                return false;
2154
            }
2155
        }
2156
2157
        $isBlocked = false;
2158
        if (!empty($prerequisite)) {
2159
            $progress = self::getProgress(
2160
                $prerequisite,
2161
                $studentId,
2162
                $courseId,
2163
                $sessionId
2164
            );
2165
            if ($progress < 100) {
2166
                $isBlocked = true;
2167
            }
2168
2169
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2170
                // Block if it does not exceed minimum time
2171
                // Minimum time (in minutes) to pass the learning path
2172
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2173
2174
                if ($accumulateWorkTime > 0) {
2175
                    // Total time in course (sum of times in learning paths from course)
2176
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2177
2178
                    // Connect with the plugin_licences_course_session table
2179
                    // which indicates what percentage of the time applies
2180
                    // Minimum connection percentage
2181
                    $perc = 100;
2182
                    // Time from the course
2183
                    $tc = $accumulateWorkTimeTotal;
2184
2185
                    // Percentage of the learning paths
2186
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2187
                    // Minimum time for each learning path
2188
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2189
2190
                    // Spent time (in seconds) so far in the learning path
2191
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2192
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2193
2194
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2195
                        $isBlocked = true;
2196
                    }
2197
                }
2198
            }
2199
        }
2200
2201
        return $isBlocked;
2202
    }
2203
2204
    /**
2205
     * Checks if the learning path is visible for student after the progress
2206
     * of its prerequisite is completed, considering the time availability and
2207
     * the LP visibility.
2208
     *
2209
     * @param int   $lp_id
2210
     * @param int   $student_id
2211
     * @param array $courseInfo
2212
     * @param int   $sessionId
2213
     *
2214
     * @return bool
2215
     */
2216
    public static function is_lp_visible_for_student(
2217
        CLp $lp,
2218
        $student_id,
2219
        $courseInfo = [],
2220
        $sessionId = 0
2221
    ) {
2222
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2223
        $sessionId = (int) $sessionId;
2224
2225
        if (empty($courseInfo)) {
2226
            return false;
2227
        }
2228
2229
        if (empty($sessionId)) {
2230
            $sessionId = api_get_session_id();
2231
        }
2232
2233
        $courseId = $courseInfo['real_id'];
2234
2235
        /*$itemInfo = api_get_item_property_info(
2236
            $courseId,
2237
            TOOL_LEARNPATH,
2238
            $lp_id,
2239
            $sessionId
2240
        );*/
2241
2242
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2243
        // If the item was deleted.
2244
        if (false === $visibility) {
2245
            return false;
2246
        }
2247
2248
        $lp_id = $lp->getIid();
2249
        // @todo remove this query and load the row info as a parameter
2250
        $table = Database::get_course_table(TABLE_LP_MAIN);
2251
        // Get current prerequisite
2252
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2253
                FROM $table
2254
                WHERE iid = $lp_id";
2255
        $rs = Database::query($sql);
2256
        $now = time();
2257
        if (Database::num_rows($rs) > 0) {
2258
            $row = Database::fetch_array($rs, 'ASSOC');
2259
2260
            if (!empty($row['category_id'])) {
2261
                $em = Database::getManager();
2262
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2263
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2264
                    return false;
2265
                }
2266
            }
2267
2268
            $prerequisite = $row['prerequisite'];
2269
            $is_visible = true;
2270
2271
            $isBlocked = self::isBlockedByPrerequisite(
2272
                $student_id,
2273
                $prerequisite,
2274
                $courseInfo,
2275
                $sessionId
2276
            );
2277
2278
            if ($isBlocked) {
2279
                $is_visible = false;
2280
            }
2281
2282
            // Also check the time availability of the LP
2283
            if ($is_visible) {
2284
                // Adding visibility restrictions
2285
                if (!empty($row['publicated_on'])) {
2286
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2287
                        $is_visible = false;
2288
                    }
2289
                }
2290
                // Blocking empty start times see BT#2800
2291
                global $_custom;
2292
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2293
                    $_custom['lps_hidden_when_no_start_date']
2294
                ) {
2295
                    if (empty($row['publicated_on'])) {
2296
                        $is_visible = false;
2297
                    }
2298
                }
2299
2300
                if (!empty($row['expired_on'])) {
2301
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2302
                        $is_visible = false;
2303
                    }
2304
                }
2305
            }
2306
2307
            if ($is_visible) {
2308
                $subscriptionSettings = self::getSubscriptionSettings();
2309
2310
                // Check if the subscription users/group to a LP is ON
2311
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2312
                    true === $subscriptionSettings['allow_add_users_to_lp']
2313
                ) {
2314
                    // Try group
2315
                    $is_visible = false;
2316
                    // Checking only the user visibility
2317
                    $userVisibility = api_get_item_visibility(
2318
                        $courseInfo,
2319
                        'learnpath',
2320
                        $row['id'],
2321
                        $sessionId,
2322
                        $student_id,
2323
                        'LearnpathSubscription'
2324
                    );
2325
2326
                    if (1 == $userVisibility) {
2327
                        $is_visible = true;
2328
                    } else {
2329
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2330
                        if (!empty($userGroups)) {
2331
                            foreach ($userGroups as $groupInfo) {
2332
                                $groupId = $groupInfo['iid'];
2333
                                $userVisibility = api_get_item_visibility(
2334
                                    $courseInfo,
2335
                                    'learnpath',
2336
                                    $row['id'],
2337
                                    $sessionId,
2338
                                    null,
2339
                                    'LearnpathSubscription',
2340
                                    $groupId
2341
                                );
2342
2343
                                if (1 == $userVisibility) {
2344
                                    $is_visible = true;
2345
                                    break;
2346
                                }
2347
                            }
2348
                        }
2349
                    }
2350
                }
2351
            }
2352
2353
            return $is_visible;
2354
        }
2355
2356
        return false;
2357
    }
2358
2359
    /**
2360
     * @param int $lpId
2361
     * @param int $userId
2362
     * @param int $courseId
2363
     * @param int $sessionId
2364
     *
2365
     * @return int
2366
     */
2367
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2368
    {
2369
        $lpId = (int) $lpId;
2370
        $userId = (int) $userId;
2371
        $courseId = (int) $courseId;
2372
        $sessionId = (int) $sessionId;
2373
2374
        $sessionCondition = api_get_session_condition($sessionId);
2375
        $table = Database::get_course_table(TABLE_LP_VIEW);
2376
        $sql = "SELECT progress FROM $table
2377
                WHERE
2378
                    c_id = $courseId AND
2379
                    lp_id = $lpId AND
2380
                    user_id = $userId $sessionCondition ";
2381
        $res = Database::query($sql);
2382
2383
        $progress = 0;
2384
        if (Database::num_rows($res) > 0) {
2385
            $row = Database::fetch_array($res);
2386
            $progress = (int) $row['progress'];
2387
        }
2388
2389
        return $progress;
2390
    }
2391
2392
    /**
2393
     * @param array $lpList
2394
     * @param int   $userId
2395
     * @param int   $courseId
2396
     * @param int   $sessionId
2397
     *
2398
     * @return array
2399
     */
2400
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2401
    {
2402
        $lpList = array_map('intval', $lpList);
2403
        if (empty($lpList)) {
2404
            return [];
2405
        }
2406
2407
        $lpList = implode("','", $lpList);
2408
2409
        $userId = (int) $userId;
2410
        $courseId = (int) $courseId;
2411
        $sessionId = (int) $sessionId;
2412
2413
        $sessionCondition = api_get_session_condition($sessionId);
2414
        $table = Database::get_course_table(TABLE_LP_VIEW);
2415
        $sql = "SELECT lp_id, progress FROM $table
2416
                WHERE
2417
                    c_id = $courseId AND
2418
                    lp_id IN ('".$lpList."') AND
2419
                    user_id = $userId $sessionCondition ";
2420
        $res = Database::query($sql);
2421
2422
        if (Database::num_rows($res) > 0) {
2423
            $list = [];
2424
            while ($row = Database::fetch_array($res)) {
2425
                $list[$row['lp_id']] = $row['progress'];
2426
            }
2427
2428
            return $list;
2429
        }
2430
2431
        return [];
2432
    }
2433
2434
    /**
2435
     * Displays a progress bar
2436
     * completed so far.
2437
     *
2438
     * @param int    $percentage Progress value to display
2439
     * @param string $text_add   Text to display near the progress value
2440
     *
2441
     * @return string HTML string containing the progress bar
2442
     */
2443
    public static function get_progress_bar($percentage = -1, $text_add = '')
2444
    {
2445
        $text = $percentage.$text_add;
2446
        $output = '<div class="progress">
2447
            <div id="progress_bar_value"
2448
                class="progress-bar progress-bar-success" role="progressbar"
2449
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2450
            '.$text.'
2451
            </div>
2452
        </div>';
2453
2454
        return $output;
2455
    }
2456
2457
    /**
2458
     * @param string $mode can be '%' or 'abs'
2459
     *                     otherwise this value will be used $this->progress_bar_mode
2460
     *
2461
     * @return string
2462
     */
2463
    public function getProgressBar($mode = null)
2464
    {
2465
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2466
2467
        return self::get_progress_bar($percentage, $text_add);
2468
    }
2469
2470
    /**
2471
     * Gets the progress bar info to display inside the progress bar.
2472
     * Also used by scorm_api.php.
2473
     *
2474
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2475
     *                     we display a number of completed elements per total elements
2476
     * @param int    $add  Additional steps to fake as completed
2477
     *
2478
     * @return array Percentage or number and symbol (% or /xx)
2479
     */
2480
    public function get_progress_bar_text($mode = '', $add = 0)
2481
    {
2482
        if (empty($mode)) {
2483
            $mode = $this->progress_bar_mode;
2484
        }
2485
        $total_items = $this->getTotalItemsCountWithoutDirs();
2486
        $completeItems = $this->get_complete_items_count();
2487
        if (0 != $add) {
2488
            $completeItems += $add;
2489
        }
2490
        $text = '';
2491
        if ($completeItems > $total_items) {
2492
            $completeItems = $total_items;
2493
        }
2494
        $percentage = 0;
2495
        if ('%' == $mode) {
2496
            if ($total_items > 0) {
2497
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2498
            }
2499
            $percentage = number_format($percentage, 0);
2500
            $text = '%';
2501
        } elseif ('abs' === $mode) {
2502
            $percentage = $completeItems;
2503
            $text = '/'.$total_items;
2504
        }
2505
2506
        return [
2507
            $percentage,
2508
            $text,
2509
        ];
2510
    }
2511
2512
    /**
2513
     * Gets the progress bar mode.
2514
     *
2515
     * @return string The progress bar mode attribute
2516
     */
2517
    public function get_progress_bar_mode()
2518
    {
2519
        if (!empty($this->progress_bar_mode)) {
2520
            return $this->progress_bar_mode;
2521
        }
2522
2523
        return '%';
2524
    }
2525
2526
    /**
2527
     * Gets the learnpath theme (remote or local).
2528
     *
2529
     * @return string Learnpath theme
2530
     */
2531
    public function get_theme()
2532
    {
2533
        if (!empty($this->theme)) {
2534
            return $this->theme;
2535
        }
2536
2537
        return '';
2538
    }
2539
2540
    /**
2541
     * Gets the learnpath session id.
2542
     *
2543
     * @return int
2544
     */
2545
    public function get_lp_session_id()
2546
    {
2547
        if (!empty($this->lp_session_id)) {
2548
            return (int) $this->lp_session_id;
2549
        }
2550
2551
        return 0;
2552
    }
2553
2554
    /**
2555
     * Gets the learnpath image.
2556
     *
2557
     * @return string Web URL of the LP image
2558
     */
2559
    public function get_preview_image()
2560
    {
2561
        if (!empty($this->preview_image)) {
2562
            return $this->preview_image;
2563
        }
2564
2565
        return '';
2566
    }
2567
2568
    /**
2569
     * @param string $size
2570
     * @param string $path_type
2571
     *
2572
     * @return bool|string
2573
     */
2574
    public function get_preview_image_path($size = null, $path_type = 'web')
2575
    {
2576
        $preview_image = $this->get_preview_image();
2577
        if (isset($preview_image) && !empty($preview_image)) {
2578
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2579
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2580
2581
            if (isset($size)) {
2582
                $info = pathinfo($preview_image);
2583
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2584
2585
                if (file_exists($image_sys_path.$image_custom_size)) {
2586
                    if ('web' == $path_type) {
2587
                        return $image_path.$image_custom_size;
2588
                    } else {
2589
                        return $image_sys_path.$image_custom_size;
2590
                    }
2591
                }
2592
            } else {
2593
                if ('web' == $path_type) {
2594
                    return $image_path.$preview_image;
2595
                } else {
2596
                    return $image_sys_path.$preview_image;
2597
                }
2598
            }
2599
        }
2600
2601
        return false;
2602
    }
2603
2604
    /**
2605
     * Gets the learnpath author.
2606
     *
2607
     * @return string LP's author
2608
     */
2609
    public function get_author()
2610
    {
2611
        if (!empty($this->author)) {
2612
            return $this->author;
2613
        }
2614
2615
        return '';
2616
    }
2617
2618
    /**
2619
     * Gets hide table of contents.
2620
     *
2621
     * @return int
2622
     */
2623
    public function getHideTableOfContents()
2624
    {
2625
        return (int) $this->hide_toc_frame;
2626
    }
2627
2628
    /**
2629
     * Generate a new prerequisites string for a given item. If this item was a sco and
2630
     * its prerequisites were strings (instead of IDs), then transform those strings into
2631
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2632
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2633
     * same rule as the scormExport() method.
2634
     *
2635
     * @param int $item_id Item ID
2636
     *
2637
     * @return string Prerequisites string ready for the export as SCORM
2638
     */
2639
    public function get_scorm_prereq_string($item_id)
2640
    {
2641
        if ($this->debug > 0) {
2642
            error_log('In learnpath::get_scorm_prereq_string()');
2643
        }
2644
        if (!is_object($this->items[$item_id])) {
2645
            return false;
2646
        }
2647
        /** @var learnpathItem $oItem */
2648
        $oItem = $this->items[$item_id];
2649
        $prereq = $oItem->get_prereq_string();
2650
2651
        if (empty($prereq)) {
2652
            return '';
2653
        }
2654
        if (preg_match('/^\d+$/', $prereq) &&
2655
            isset($this->items[$prereq]) &&
2656
            is_object($this->items[$prereq])
2657
        ) {
2658
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2659
            // then simply return it (with the ITEM_ prefix).
2660
            //return 'ITEM_' . $prereq;
2661
            return $this->items[$prereq]->ref;
2662
        } else {
2663
            if (isset($this->refs_list[$prereq])) {
2664
                // It's a simple string item from which the ID can be found in the refs list,
2665
                // so we can transform it directly to an ID for export.
2666
                return $this->items[$this->refs_list[$prereq]]->ref;
2667
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2668
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2669
            } else {
2670
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2671
                // and replace them, one by one, by the internal IDs (chamilo db)
2672
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2673
                // by a space as well.
2674
                $find = [
2675
                    '&',
2676
                    '|',
2677
                    '~',
2678
                    '=',
2679
                    '<>',
2680
                    '{',
2681
                    '}',
2682
                    '*',
2683
                    '(',
2684
                    ')',
2685
                ];
2686
                $replace = [
2687
                    ' ',
2688
                    ' ',
2689
                    ' ',
2690
                    ' ',
2691
                    ' ',
2692
                    ' ',
2693
                    ' ',
2694
                    ' ',
2695
                    ' ',
2696
                    ' ',
2697
                ];
2698
                $prereq_mod = str_replace($find, $replace, $prereq);
2699
                $ids = explode(' ', $prereq_mod);
2700
                foreach ($ids as $id) {
2701
                    $id = trim($id);
2702
                    if (isset($this->refs_list[$id])) {
2703
                        $prereq = preg_replace(
2704
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2705
                            'ITEM_'.$this->refs_list[$id],
2706
                            $prereq
2707
                        );
2708
                    }
2709
                }
2710
2711
                return $prereq;
2712
            }
2713
        }
2714
    }
2715
2716
    /**
2717
     * Returns the XML DOM document's node.
2718
     *
2719
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2720
     * @param string   $id       The identifier to look for
2721
     *
2722
     * @return mixed The reference to the element found with that identifier. False if not found
2723
     */
2724
    public function get_scorm_xml_node(&$children, $id)
2725
    {
2726
        for ($i = 0; $i < $children->length; $i++) {
2727
            $item_temp = $children->item($i);
2728
            if ('item' == $item_temp->nodeName) {
2729
                if ($item_temp->getAttribute('identifier') == $id) {
2730
                    return $item_temp;
2731
                }
2732
            }
2733
            $subchildren = $item_temp->childNodes;
2734
            if ($subchildren && $subchildren->length > 0) {
2735
                $val = $this->get_scorm_xml_node($subchildren, $id);
2736
                if (is_object($val)) {
2737
                    return $val;
2738
                }
2739
            }
2740
        }
2741
2742
        return false;
2743
    }
2744
2745
    /**
2746
     * Gets the status list for all LP's items.
2747
     *
2748
     * @return array Array of [index] => [item ID => current status]
2749
     */
2750
    public function get_items_status_list()
2751
    {
2752
        $list = [];
2753
        foreach ($this->ordered_items as $item_id) {
2754
            $list[] = [
2755
                $item_id => $this->items[$item_id]->get_status(),
2756
            ];
2757
        }
2758
2759
        return $list;
2760
    }
2761
2762
    /**
2763
     * Return the number of interactions for the given learnpath Item View ID.
2764
     * This method can be used as static.
2765
     *
2766
     * @param int $lp_iv_id  Item View ID
2767
     * @param int $course_id course id
2768
     *
2769
     * @return int
2770
     */
2771
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2772
    {
2773
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2774
        $lp_iv_id = (int) $lp_iv_id;
2775
        $course_id = (int) $course_id;
2776
2777
        $sql = "SELECT count(*) FROM $table
2778
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2779
        $res = Database::query($sql);
2780
        $num = 0;
2781
        if (Database::num_rows($res)) {
2782
            $row = Database::fetch_array($res);
2783
            $num = $row[0];
2784
        }
2785
2786
        return $num;
2787
    }
2788
2789
    /**
2790
     * Return the interactions as an array for the given lp_iv_id.
2791
     * This method can be used as static.
2792
     *
2793
     * @param int $lp_iv_id Learnpath Item View ID
2794
     *
2795
     * @return array
2796
     *
2797
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2798
     */
2799
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2800
    {
2801
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2802
        $list = [];
2803
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2804
        $lp_iv_id = (int) $lp_iv_id;
2805
2806
        if (empty($lp_iv_id) || empty($course_id)) {
2807
            return [];
2808
        }
2809
2810
        $sql = "SELECT * FROM $table
2811
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2812
                ORDER BY order_id ASC";
2813
        $res = Database::query($sql);
2814
        $num = Database::num_rows($res);
2815
        if ($num > 0) {
2816
            $list[] = [
2817
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2818
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2819
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2820
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2821
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2822
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2823
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2824
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2825
                'student_response_formatted' => '',
2826
            ];
2827
            while ($row = Database::fetch_array($res)) {
2828
                $studentResponseFormatted = urldecode($row['student_response']);
2829
                $content_student_response = explode('__|', $studentResponseFormatted);
2830
                if (count($content_student_response) > 0) {
2831
                    if (count($content_student_response) >= 3) {
2832
                        // Pop the element off the end of array.
2833
                        array_pop($content_student_response);
2834
                    }
2835
                    $studentResponseFormatted = implode(',', $content_student_response);
2836
                }
2837
2838
                $list[] = [
2839
                    'order_id' => $row['order_id'] + 1,
2840
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2841
                    'type' => $row['interaction_type'],
2842
                    'time' => $row['completion_time'],
2843
                    'correct_responses' => '', // Hide correct responses from students.
2844
                    'student_response' => $row['student_response'],
2845
                    'result' => $row['result'],
2846
                    'latency' => $row['latency'],
2847
                    'student_response_formatted' => $studentResponseFormatted,
2848
                ];
2849
            }
2850
        }
2851
2852
        return $list;
2853
    }
2854
2855
    /**
2856
     * Return the number of objectives for the given learnpath Item View ID.
2857
     * This method can be used as static.
2858
     *
2859
     * @param int $lp_iv_id  Item View ID
2860
     * @param int $course_id Course ID
2861
     *
2862
     * @return int Number of objectives
2863
     */
2864
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2865
    {
2866
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2867
        $course_id = (int) $course_id;
2868
        $lp_iv_id = (int) $lp_iv_id;
2869
        $sql = "SELECT count(*) FROM $table
2870
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2871
        //@todo seems that this always returns 0
2872
        $res = Database::query($sql);
2873
        $num = 0;
2874
        if (Database::num_rows($res)) {
2875
            $row = Database::fetch_array($res);
2876
            $num = $row[0];
2877
        }
2878
2879
        return $num;
2880
    }
2881
2882
    /**
2883
     * Return the objectives as an array for the given lp_iv_id.
2884
     * This method can be used as static.
2885
     *
2886
     * @param int $lpItemViewId Learnpath Item View ID
2887
     * @param int $course_id
2888
     *
2889
     * @return array
2890
     *
2891
     * @todo    Translate labels
2892
     */
2893
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2894
    {
2895
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2896
        $lpItemViewId = (int) $lpItemViewId;
2897
2898
        if (empty($course_id) || empty($lpItemViewId)) {
2899
            return [];
2900
        }
2901
2902
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2903
        $sql = "SELECT * FROM $table
2904
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2905
                ORDER BY order_id ASC";
2906
        $res = Database::query($sql);
2907
        $num = Database::num_rows($res);
2908
        $list = [];
2909
        if ($num > 0) {
2910
            $list[] = [
2911
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2912
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2913
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2914
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2915
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2916
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2917
            ];
2918
            while ($row = Database::fetch_array($res)) {
2919
                $list[] = [
2920
                    'order_id' => $row['order_id'] + 1,
2921
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2922
                    'score_raw' => $row['score_raw'],
2923
                    'score_max' => $row['score_max'],
2924
                    'score_min' => $row['score_min'],
2925
                    'status' => $row['status'],
2926
                ];
2927
            }
2928
        }
2929
2930
        return $list;
2931
    }
2932
2933
    /**
2934
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2935
     * used by get_html_toc() to be ready to display.
2936
     *
2937
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2938
     */
2939
    public function get_toc()
2940
    {
2941
        $toc = [];
2942
        foreach ($this->ordered_items as $item_id) {
2943
            // TODO: Change this link generation and use new function instead.
2944
            $toc[] = [
2945
                'id' => $item_id,
2946
                'title' => $this->items[$item_id]->get_title(),
2947
                'status' => $this->items[$item_id]->get_status(),
2948
                'level' => $this->items[$item_id]->get_level(),
2949
                'type' => $this->items[$item_id]->get_type(),
2950
                'description' => $this->items[$item_id]->get_description(),
2951
                'path' => $this->items[$item_id]->get_path(),
2952
                'parent' => $this->items[$item_id]->get_parent(),
2953
            ];
2954
        }
2955
2956
        return $toc;
2957
    }
2958
2959
    /**
2960
     * Generate and return the table of contents for this learnpath. The JS
2961
     * table returned is used inside of scorm_api.php.
2962
     *
2963
     * @param string $varname
2964
     *
2965
     * @return string A JS array variable construction
2966
     */
2967
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2968
    {
2969
        $toc = $varname.' = new Array();';
2970
        foreach ($this->ordered_items as $item_id) {
2971
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2972
        }
2973
2974
        return $toc;
2975
    }
2976
2977
    /**
2978
     * Gets the learning path type.
2979
     *
2980
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2981
     *
2982
     * @return mixed Type ID or name, depending on the parameter
2983
     */
2984
    public function get_type($get_name = false)
2985
    {
2986
        $res = false;
2987
        if (!empty($this->type) && (!$get_name)) {
2988
            $res = $this->type;
2989
        }
2990
2991
        return $res;
2992
    }
2993
2994
    /**
2995
     * Gets the learning path type as static method.
2996
     *
2997
     * @param int $lp_id
2998
     *
2999
     * @return mixed Type ID or name, depending on the parameter
3000
     */
3001
    public static function get_type_static($lp_id = 0)
3002
    {
3003
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3004
        $lp_id = (int) $lp_id;
3005
        $sql = "SELECT lp_type FROM $tbl_lp
3006
                WHERE iid = $lp_id";
3007
        $res = Database::query($sql);
3008
        if (false === $res) {
3009
            return null;
3010
        }
3011
        if (Database::num_rows($res) <= 0) {
3012
            return null;
3013
        }
3014
        $row = Database::fetch_array($res);
3015
3016
        return $row['lp_type'];
3017
    }
3018
3019
    /**
3020
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3021
     * This method can be used as abstract and is recursive.
3022
     *
3023
     * @param int $lp        Learnpath ID
3024
     * @param int $parent    Parent ID of the items to look for
3025
     * @param int $course_id
3026
     *
3027
     * @return array Ordered list of item IDs (empty array on error)
3028
     */
3029
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3030
    {
3031
        if (empty($course_id)) {
3032
            $course_id = api_get_course_int_id();
3033
        } else {
3034
            $course_id = (int) $course_id;
3035
        }
3036
        $list = [];
3037
3038
        if (empty($lp)) {
3039
            return $list;
3040
        }
3041
3042
        $lp = (int) $lp;
3043
        $parent = (int) $parent;
3044
3045
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3046
        $sql = "SELECT iid FROM $tbl_lp_item
3047
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3048
                ORDER BY display_order";
3049
3050
        $res = Database::query($sql);
3051
        while ($row = Database::fetch_array($res)) {
3052
            $sublist = self::get_flat_ordered_items_list(
3053
                $lp,
3054
                $row['iid'],
3055
                $course_id
3056
            );
3057
            $list[] = $row['iid'];
3058
            foreach ($sublist as $item) {
3059
                $list[] = $item;
3060
            }
3061
        }
3062
3063
        return $list;
3064
    }
3065
3066
    /**
3067
     * @return array
3068
     */
3069
    public static function getChapterTypes()
3070
    {
3071
        return [
3072
            'dir',
3073
        ];
3074
    }
3075
3076
    /**
3077
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3078
     *
3079
     * @param $tree
3080
     *
3081
     * @return array HTML TOC ready to display
3082
     */
3083
    public function getParentToc($tree)
3084
    {
3085
        if (empty($tree)) {
3086
            $tree = $this->get_toc();
3087
        }
3088
        $dirTypes = self::getChapterTypes();
3089
        $myCurrentId = $this->get_current_item_id();
3090
        $listParent = [];
3091
        $listChildren = [];
3092
        $listNotParent = [];
3093
        $list = [];
3094
        foreach ($tree as $subtree) {
3095
            if (in_array($subtree['type'], $dirTypes)) {
3096
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3097
                $subtree['children'] = $listChildren;
3098
                if (!empty($subtree['children'])) {
3099
                    foreach ($subtree['children'] as $subItem) {
3100
                        if ($subItem['id'] == $this->current) {
3101
                            $subtree['parent_current'] = 'in';
3102
                            $subtree['current'] = 'on';
3103
                        }
3104
                    }
3105
                }
3106
                $listParent[] = $subtree;
3107
            }
3108
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3109
                $classStatus = [
3110
                    'not attempted' => 'scorm_not_attempted',
3111
                    'incomplete' => 'scorm_not_attempted',
3112
                    'failed' => 'scorm_failed',
3113
                    'completed' => 'scorm_completed',
3114
                    'passed' => 'scorm_completed',
3115
                    'succeeded' => 'scorm_completed',
3116
                    'browsed' => 'scorm_completed',
3117
                ];
3118
3119
                if (isset($classStatus[$subtree['status']])) {
3120
                    $cssStatus = $classStatus[$subtree['status']];
3121
                }
3122
3123
                $title = Security::remove_XSS($subtree['title']);
3124
                unset($subtree['title']);
3125
3126
                if (empty($title)) {
3127
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3128
                }
3129
                $classStyle = null;
3130
                if ($subtree['id'] == $this->current) {
3131
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3132
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3133
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3134
                }
3135
                $subtree['title'] = $title;
3136
                $subtree['class'] = $classStyle.' '.$cssStatus;
3137
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3138
                $subtree['current_id'] = $myCurrentId;
3139
                $listNotParent[] = $subtree;
3140
            }
3141
        }
3142
3143
        $list['are_parents'] = $listParent;
3144
        $list['not_parents'] = $listNotParent;
3145
3146
        return $list;
3147
    }
3148
3149
    /**
3150
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3151
     *
3152
     * @param array $tree
3153
     * @param int   $id
3154
     * @param bool  $parent
3155
     *
3156
     * @return array HTML TOC ready to display
3157
     */
3158
    public function getChildrenToc($tree, $id, $parent = true)
3159
    {
3160
        if (empty($tree)) {
3161
            $tree = $this->get_toc();
3162
        }
3163
3164
        $dirTypes = self::getChapterTypes();
3165
        $currentItemId = $this->get_current_item_id();
3166
        $list = [];
3167
        $classStatus = [
3168
            'not attempted' => 'scorm_not_attempted',
3169
            'incomplete' => 'scorm_not_attempted',
3170
            'failed' => 'scorm_failed',
3171
            'completed' => 'scorm_completed',
3172
            'passed' => 'scorm_completed',
3173
            'succeeded' => 'scorm_completed',
3174
            'browsed' => 'scorm_completed',
3175
        ];
3176
3177
        foreach ($tree as $subtree) {
3178
            $subtree['tree'] = null;
3179
3180
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3181
                if ($subtree['id'] == $this->current) {
3182
                    $subtree['current'] = 'active';
3183
                } else {
3184
                    $subtree['current'] = null;
3185
                }
3186
                if (isset($classStatus[$subtree['status']])) {
3187
                    $cssStatus = $classStatus[$subtree['status']];
3188
                }
3189
3190
                $title = Security::remove_XSS($subtree['title']);
3191
                unset($subtree['title']);
3192
                if (empty($title)) {
3193
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3194
                }
3195
3196
                $classStyle = null;
3197
                if ($subtree['id'] == $this->current) {
3198
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3199
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3200
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3201
                }
3202
3203
                if (in_array($subtree['type'], $dirTypes)) {
3204
                    $subtree['title'] = stripslashes($title);
3205
                } else {
3206
                    $subtree['title'] = $title;
3207
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3208
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3209
                    $subtree['current_id'] = $currentItemId;
3210
                }
3211
                $list[] = $subtree;
3212
            }
3213
        }
3214
3215
        return $list;
3216
    }
3217
3218
    /**
3219
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3220
     *
3221
     * @param array $toc_list
3222
     *
3223
     * @return array HTML TOC ready to display
3224
     */
3225
    public function getListArrayToc($toc_list = [])
3226
    {
3227
        if (empty($toc_list)) {
3228
            $toc_list = $this->get_toc();
3229
        }
3230
        // Temporary variables.
3231
        $currentItemId = $this->get_current_item_id();
3232
        $list = [];
3233
        $arrayList = [];
3234
        $classStatus = [
3235
            'not attempted' => 'scorm_not_attempted',
3236
            'incomplete' => 'scorm_not_attempted',
3237
            'failed' => 'scorm_failed',
3238
            'completed' => 'scorm_completed',
3239
            'passed' => 'scorm_completed',
3240
            'succeeded' => 'scorm_completed',
3241
            'browsed' => 'scorm_completed',
3242
        ];
3243
3244
        foreach ($toc_list as $item) {
3245
            $list['id'] = $item['id'];
3246
            $list['status'] = $item['status'];
3247
            $cssStatus = null;
3248
3249
            if (isset($classStatus[$item['status']])) {
3250
                $cssStatus = $classStatus[$item['status']];
3251
            }
3252
3253
            $classStyle = ' ';
3254
            $dirTypes = self::getChapterTypes();
3255
3256
            if (in_array($item['type'], $dirTypes)) {
3257
                $classStyle = 'scorm_item_section ';
3258
            }
3259
            if ($item['id'] == $this->current) {
3260
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3261
            } elseif (!in_array($item['type'], $dirTypes)) {
3262
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3263
            }
3264
            $title = $item['title'];
3265
            if (empty($title)) {
3266
                $title = self::rl_get_resource_name(
3267
                    api_get_course_id(),
3268
                    $this->get_id(),
3269
                    $item['id']
3270
                );
3271
            }
3272
            $title = Security::remove_XSS($item['title']);
3273
3274
            if (empty($item['description'])) {
3275
                $list['description'] = $title;
3276
            } else {
3277
                $list['description'] = $item['description'];
3278
            }
3279
3280
            $list['class'] = $classStyle.' '.$cssStatus;
3281
            $list['level'] = $item['level'];
3282
            $list['type'] = $item['type'];
3283
3284
            if (in_array($item['type'], $dirTypes)) {
3285
                $list['css_level'] = 'level_'.$item['level'];
3286
            } else {
3287
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3288
            }
3289
3290
            if (in_array($item['type'], $dirTypes)) {
3291
                $list['title'] = stripslashes($title);
3292
            } else {
3293
                $list['title'] = stripslashes($title);
3294
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3295
                $list['current_id'] = $currentItemId;
3296
            }
3297
            $arrayList[] = $list;
3298
        }
3299
3300
        return $arrayList;
3301
    }
3302
3303
    /**
3304
     * Returns an HTML-formatted string ready to display with teacher buttons
3305
     * in LP view menu.
3306
     *
3307
     * @return string HTML TOC ready to display
3308
     */
3309
    public function get_teacher_toc_buttons()
3310
    {
3311
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3312
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3313
        $html = '';
3314
        if ($isAllow && false == $hideIcons) {
3315
            if ($this->get_lp_session_id() == api_get_session_id()) {
3316
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3317
                $html .= '<div class="btn-group">';
3318
                $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'>".
3319
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3320
                $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'>".
3321
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3322
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3323
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3324
                $html .= '</div>';
3325
                $html .= '</div>';
3326
            }
3327
        }
3328
3329
        return $html;
3330
    }
3331
3332
    /**
3333
     * Gets the learnpath maker name - generally the editor's name.
3334
     *
3335
     * @return string Learnpath maker name
3336
     */
3337
    public function get_maker()
3338
    {
3339
        if (!empty($this->maker)) {
3340
            return $this->maker;
3341
        }
3342
3343
        return '';
3344
    }
3345
3346
    /**
3347
     * Gets the learnpath name/title.
3348
     *
3349
     * @return string Learnpath name/title
3350
     */
3351
    public function get_name()
3352
    {
3353
        if (!empty($this->name)) {
3354
            return $this->name;
3355
        }
3356
3357
        return 'N/A';
3358
    }
3359
3360
    /**
3361
     * @return string
3362
     */
3363
    public function getNameNoTags()
3364
    {
3365
        return strip_tags($this->get_name());
3366
    }
3367
3368
    /**
3369
     * Gets a link to the resource from the present location, depending on item ID.
3370
     *
3371
     * @param string $type         Type of link expected
3372
     * @param int    $item_id      Learnpath item ID
3373
     * @param bool   $provided_toc
3374
     *
3375
     * @return string $provided_toc Link to the lp_item resource
3376
     */
3377
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3378
    {
3379
        $course_id = $this->get_course_int_id();
3380
        $item_id = (int) $item_id;
3381
3382
        if (empty($item_id)) {
3383
            $item_id = $this->get_current_item_id();
3384
3385
            if (empty($item_id)) {
3386
                //still empty, this means there was no item_id given and we are not in an object context or
3387
                //the object property is empty, return empty link
3388
                $this->first();
3389
3390
                return '';
3391
            }
3392
        }
3393
3394
        $file = '';
3395
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3396
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3397
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3398
3399
        $sql = "SELECT
3400
                    l.lp_type as ltype,
3401
                    l.path as lpath,
3402
                    li.item_type as litype,
3403
                    li.path as lipath,
3404
                    li.parameters as liparams
3405
        		FROM $lp_table l
3406
                INNER JOIN $lp_item_table li
3407
                ON (li.lp_id = l.iid)
3408
        		WHERE
3409
        		    li.iid = $item_id
3410
        		";
3411
        $res = Database::query($sql);
3412
        if (Database::num_rows($res) > 0) {
3413
            $row = Database::fetch_array($res);
3414
            $lp_type = $row['ltype'];
3415
            $lp_path = $row['lpath'];
3416
            $lp_item_type = $row['litype'];
3417
            $lp_item_path = $row['lipath'];
3418
            $lp_item_params = $row['liparams'];
3419
3420
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3421
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3422
            }
3423
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3424
            if ('http' === $type) {
3425
                //web path
3426
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3427
            } else {
3428
                //$course_path = $sys_course_path; //system path
3429
            }
3430
3431
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3432
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3433
            if (in_array(
3434
                $lp_item_type,
3435
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3436
            )
3437
            ) {
3438
                $lp_type = 1;
3439
            }
3440
3441
            // Now go through the specific cases to get the end of the path
3442
            // @todo Use constants instead of int values.
3443
3444
            switch ($lp_type) {
3445
                case 1:
3446
                    $file = self::rl_get_resource_link_for_learnpath(
3447
                        $course_id,
3448
                        $this->get_id(),
3449
                        $item_id,
3450
                        $this->get_view_id()
3451
                    );
3452
                    switch ($lp_item_type) {
3453
                        case 'document':
3454
                            // Shows a button to download the file instead of just downloading the file directly.
3455
                            $documentPathInfo = pathinfo($file);
3456
                            if (isset($documentPathInfo['extension'])) {
3457
                                $parsed = parse_url($documentPathInfo['extension']);
3458
                                if (isset($parsed['path'])) {
3459
                                    $extension = $parsed['path'];
3460
                                    $extensionsToDownload = [
3461
                                        'zip',
3462
                                        'ppt',
3463
                                        'pptx',
3464
                                        'ods',
3465
                                        'xlsx',
3466
                                        'xls',
3467
                                        'csv',
3468
                                        'doc',
3469
                                        'docx',
3470
                                        'dot',
3471
                                    ];
3472
3473
                                    if (in_array($extension, $extensionsToDownload)) {
3474
                                        $file = api_get_path(WEB_CODE_PATH).
3475
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3476
                                    }
3477
                                }
3478
                            }
3479
                            break;
3480
                        case 'dir':
3481
                            $file = 'lp_content.php?type=dir';
3482
                            break;
3483
                        case 'link':
3484
                            if (Link::is_youtube_link($file)) {
3485
                                $src = Link::get_youtube_video_id($file);
3486
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3487
                            } elseif (Link::isVimeoLink($file)) {
3488
                                $src = Link::getVimeoLinkId($file);
3489
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3490
                            } else {
3491
                                // If the current site is HTTPS and the link is
3492
                                // HTTP, browsers will refuse opening the link
3493
                                $urlId = api_get_current_access_url_id();
3494
                                $url = api_get_access_url($urlId, false);
3495
                                $protocol = substr($url['url'], 0, 5);
3496
                                if ('https' === $protocol) {
3497
                                    $linkProtocol = substr($file, 0, 5);
3498
                                    if ('http:' === $linkProtocol) {
3499
                                        //this is the special intervention case
3500
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3501
                                    }
3502
                                }
3503
                            }
3504
                            break;
3505
                        case 'quiz':
3506
                            // Check how much attempts of a exercise exits in lp
3507
                            $lp_item_id = $this->get_current_item_id();
3508
                            $lp_view_id = $this->get_view_id();
3509
3510
                            $prevent_reinit = null;
3511
                            if (isset($this->items[$this->current])) {
3512
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3513
                            }
3514
3515
                            if (empty($provided_toc)) {
3516
                                if ($this->debug > 0) {
3517
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3518
                                }
3519
                                $list = $this->get_toc();
3520
                            } else {
3521
                                if ($this->debug > 0) {
3522
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3523
                                }
3524
                                $list = $provided_toc;
3525
                            }
3526
3527
                            $type_quiz = false;
3528
                            foreach ($list as $toc) {
3529
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3530
                                    $type_quiz = true;
3531
                                }
3532
                            }
3533
3534
                            if ($type_quiz) {
3535
                                $lp_item_id = (int) $lp_item_id;
3536
                                $lp_view_id = (int) $lp_view_id;
3537
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3538
                                        WHERE
3539
                                            c_id = $course_id AND
3540
                                            lp_item_id='".$lp_item_id."' AND
3541
                                            lp_view_id ='".$lp_view_id."' AND
3542
                                            status='completed'";
3543
                                $result = Database::query($sql);
3544
                                $row_count = Database:: fetch_row($result);
3545
                                $count_item_view = (int) $row_count[0];
3546
                                $not_multiple_attempt = 0;
3547
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3548
                                    $not_multiple_attempt = 1;
3549
                                }
3550
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3551
                            }
3552
                            break;
3553
                    }
3554
3555
                    $tmp_array = explode('/', $file);
3556
                    $document_name = $tmp_array[count($tmp_array) - 1];
3557
                    if (strpos($document_name, '_DELETED_')) {
3558
                        $file = 'blank.php?error=document_deleted';
3559
                    }
3560
                    break;
3561
                case 2:
3562
                    if ($this->debug > 2) {
3563
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3564
                    }
3565
3566
                    if ('dir' != $lp_item_type) {
3567
                        // Quite complex here:
3568
                        // We want to make sure 'http://' (and similar) links can
3569
                        // be loaded as is (withouth the Chamilo path in front) but
3570
                        // some contents use this form: resource.htm?resource=http://blablabla
3571
                        // which means we have to find a protocol at the path's start, otherwise
3572
                        // it should not be considered as an external URL.
3573
                        // if ($this->prerequisites_match($item_id)) {
3574
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3575
                            if ($this->debug > 2) {
3576
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3577
                            }
3578
                            // Distant url, return as is.
3579
                            $file = $lp_item_path;
3580
                        } else {
3581
                            if ($this->debug > 2) {
3582
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3583
                            }
3584
                            // Prevent getting untranslatable urls.
3585
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3586
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3587
                            // Prepare the path.
3588
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3589
                            // TODO: Fix this for urls with protocol header.
3590
                            $file = str_replace('//', '/', $file);
3591
                            $file = str_replace(':/', '://', $file);
3592
                            if ('/' == substr($lp_path, -1)) {
3593
                                $lp_path = substr($lp_path, 0, -1);
3594
                            }
3595
3596
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3597
                                // if file not found.
3598
                                $decoded = html_entity_decode($lp_item_path);
3599
                                list($decoded) = explode('?', $decoded);
3600
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3601
                                    $file = self::rl_get_resource_link_for_learnpath(
3602
                                        $course_id,
3603
                                        $this->get_id(),
3604
                                        $item_id,
3605
                                        $this->get_view_id()
3606
                                    );
3607
                                    if (empty($file)) {
3608
                                        $file = 'blank.php?error=document_not_found';
3609
                                    } else {
3610
                                        $tmp_array = explode('/', $file);
3611
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3612
                                        if (strpos($document_name, '_DELETED_')) {
3613
                                            $file = 'blank.php?error=document_deleted';
3614
                                        } else {
3615
                                            $file = 'blank.php?error=document_not_found';
3616
                                        }
3617
                                    }
3618
                                } else {
3619
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3620
                                }
3621
                            }
3622
                        }
3623
3624
                        // We want to use parameters if they were defined in the imsmanifest
3625
                        if (false === strpos($file, 'blank.php')) {
3626
                            $lp_item_params = ltrim($lp_item_params, '?');
3627
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3628
                        }
3629
                    } else {
3630
                        $file = 'lp_content.php?type=dir';
3631
                    }
3632
                    break;
3633
                case 3:
3634
                    if ($this->debug > 2) {
3635
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3636
                    }
3637
                    // Formatting AICC HACP append URL.
3638
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3639
                    if (!empty($lp_item_params)) {
3640
                        $aicc_append .= $lp_item_params.'&';
3641
                    }
3642
                    if ('dir' != $lp_item_type) {
3643
                        // Quite complex here:
3644
                        // We want to make sure 'http://' (and similar) links can
3645
                        // be loaded as is (withouth the Chamilo path in front) but
3646
                        // some contents use this form: resource.htm?resource=http://blablabla
3647
                        // which means we have to find a protocol at the path's start, otherwise
3648
                        // it should not be considered as an external URL.
3649
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3650
                            if ($this->debug > 2) {
3651
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3652
                            }
3653
                            // Distant url, return as is.
3654
                            $file = $lp_item_path;
3655
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3656
                            /*
3657
                            if (stristr($file,'<servername>') !== false) {
3658
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3659
                            }
3660
                            */
3661
                            if (false !== stripos($file, '<servername>')) {
3662
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3663
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3664
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3665
                            }
3666
3667
                            $file .= $aicc_append;
3668
                        } else {
3669
                            if ($this->debug > 2) {
3670
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3671
                            }
3672
                            // Prevent getting untranslatable urls.
3673
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3674
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3675
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3676
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3677
                            // TODO: Fix this for urls with protocol header.
3678
                            $file = str_replace('//', '/', $file);
3679
                            $file = str_replace(':/', '://', $file);
3680
                            $file .= $aicc_append;
3681
                        }
3682
                    } else {
3683
                        $file = 'lp_content.php?type=dir';
3684
                    }
3685
                    break;
3686
                case 4:
3687
                    break;
3688
                default:
3689
                    break;
3690
            }
3691
            // Replace &amp; by & because &amp; will break URL with params
3692
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3693
        }
3694
        if ($this->debug > 2) {
3695
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3696
        }
3697
3698
        return $file;
3699
    }
3700
3701
    /**
3702
     * Gets the latest usable view or generate a new one.
3703
     *
3704
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3705
     *
3706
     * @return int DB lp_view id
3707
     */
3708
    public function get_view($attempt_num = 0)
3709
    {
3710
        $search = '';
3711
        // Use $attempt_num to enable multi-views management (disabled so far).
3712
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3713
            $search = 'AND view_count = '.$attempt_num;
3714
        }
3715
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3716
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3717
3718
        $course_id = api_get_course_int_id();
3719
        $sessionId = api_get_session_id();
3720
3721
        $sql = "SELECT iid, view_count FROM $lp_view_table
3722
        		WHERE
3723
        		    c_id = $course_id AND
3724
        		    lp_id = ".$this->get_id()." AND
3725
        		    user_id = ".$this->get_user_id()." AND
3726
        		    session_id = $sessionId
3727
        		    $search
3728
                ORDER BY view_count DESC";
3729
        $res = Database::query($sql);
3730
        if (Database::num_rows($res) > 0) {
3731
            $row = Database::fetch_array($res);
3732
            $this->lp_view_id = $row['iid'];
3733
        } elseif (!api_is_invitee()) {
3734
            // There is no database record, create one.
3735
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3736
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3737
            Database::query($sql);
3738
            $id = Database::insert_id();
3739
            $this->lp_view_id = $id;
3740
3741
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3742
            Database::query($sql);
3743
        }
3744
3745
        return $this->lp_view_id;
3746
    }
3747
3748
    /**
3749
     * Gets the current view id.
3750
     *
3751
     * @return int View ID (from lp_view)
3752
     */
3753
    public function get_view_id()
3754
    {
3755
        if (!empty($this->lp_view_id)) {
3756
            return (int) $this->lp_view_id;
3757
        }
3758
3759
        return 0;
3760
    }
3761
3762
    /**
3763
     * Gets the update queue.
3764
     *
3765
     * @return array Array containing IDs of items to be updated by JavaScript
3766
     */
3767
    public function get_update_queue()
3768
    {
3769
        return $this->update_queue;
3770
    }
3771
3772
    /**
3773
     * Gets the user ID.
3774
     *
3775
     * @return int User ID
3776
     */
3777
    public function get_user_id()
3778
    {
3779
        if (!empty($this->user_id)) {
3780
            return (int) $this->user_id;
3781
        }
3782
3783
        return false;
3784
    }
3785
3786
    /**
3787
     * Checks if any of the items has an audio element attached.
3788
     *
3789
     * @return bool True or false
3790
     */
3791
    public function has_audio()
3792
    {
3793
        $has = false;
3794
        foreach ($this->items as $i => $item) {
3795
            if (!empty($this->items[$i]->audio)) {
3796
                $has = true;
3797
                break;
3798
            }
3799
        }
3800
3801
        return $has;
3802
    }
3803
3804
    /**
3805
     * Moves an item up and down at its level.
3806
     *
3807
     * @param int    $id        Item to move up and down
3808
     * @param string $direction Direction 'up' or 'down'
3809
     *
3810
     * @return bool|int
3811
     */
3812
    public function move_item($id, $direction)
3813
    {
3814
        $course_id = api_get_course_int_id();
3815
        if (empty($id) || empty($direction)) {
3816
            return false;
3817
        }
3818
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3819
        $sql_sel = "SELECT *
3820
                    FROM $tbl_lp_item
3821
                    WHERE
3822
                        iid = $id
3823
                    ";
3824
        $res_sel = Database::query($sql_sel);
3825
        // Check if elem exists.
3826
        if (Database::num_rows($res_sel) < 1) {
3827
            return false;
3828
        }
3829
        // Gather data.
3830
        $row = Database::fetch_array($res_sel);
3831
        $previous = $row['previous_item_id'];
3832
        $next = $row['next_item_id'];
3833
        $display = $row['display_order'];
3834
        $parent = $row['parent_item_id'];
3835
        $lp = $row['lp_id'];
3836
        // Update the item (switch with previous/next one).
3837
        switch ($direction) {
3838
            case 'up':
3839
                if ($display > 1) {
3840
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3841
                                 WHERE iid = $previous";
3842
                    $res_sel2 = Database::query($sql_sel2);
3843
                    if (Database::num_rows($res_sel2) < 1) {
3844
                        $previous_previous = 0;
3845
                    }
3846
                    // Gather data.
3847
                    $row2 = Database::fetch_array($res_sel2);
3848
                    $previous_previous = $row2['previous_item_id'];
3849
                    // Update previous_previous item (switch "next" with current).
3850
                    if (0 != $previous_previous) {
3851
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3852
                                        next_item_id = $id
3853
                                    WHERE iid = $previous_previous";
3854
                        Database::query($sql_upd2);
3855
                    }
3856
                    // Update previous item (switch with current).
3857
                    if (0 != $previous) {
3858
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3859
                                    next_item_id = $next,
3860
                                    previous_item_id = $id,
3861
                                    display_order = display_order +1
3862
                                    WHERE iid = $previous";
3863
                        Database::query($sql_upd2);
3864
                    }
3865
3866
                    // Update current item (switch with previous).
3867
                    if (0 != $id) {
3868
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3869
                                        next_item_id = $previous,
3870
                                        previous_item_id = $previous_previous,
3871
                                        display_order = display_order-1
3872
                                    WHERE c_id = ".$course_id." AND id = $id";
3873
                        Database::query($sql_upd2);
3874
                    }
3875
                    // Update next item (new previous item).
3876
                    if (!empty($next)) {
3877
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3878
                                     WHERE iid = $next";
3879
                        Database::query($sql_upd2);
3880
                    }
3881
                    $display = $display - 1;
3882
                }
3883
                break;
3884
            case 'down':
3885
                if (0 != $next) {
3886
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3887
                                 WHERE iid = $next";
3888
                    $res_sel2 = Database::query($sql_sel2);
3889
                    if (Database::num_rows($res_sel2) < 1) {
3890
                        $next_next = 0;
3891
                    }
3892
                    // Gather data.
3893
                    $row2 = Database::fetch_array($res_sel2);
3894
                    $next_next = $row2['next_item_id'];
3895
                    // Update previous item (switch with current).
3896
                    if (0 != $previous) {
3897
                        $sql_upd2 = "UPDATE $tbl_lp_item
3898
                                     SET next_item_id = $next
3899
                                     WHERE iid = $previous";
3900
                        Database::query($sql_upd2);
3901
                    }
3902
                    // Update current item (switch with previous).
3903
                    if (0 != $id) {
3904
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3905
                                     previous_item_id = $next,
3906
                                     next_item_id = $next_next,
3907
                                     display_order = display_order + 1
3908
                                     WHERE iid = $id";
3909
                        Database::query($sql_upd2);
3910
                    }
3911
3912
                    // Update next item (new previous item).
3913
                    if (0 != $next) {
3914
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3915
                                     previous_item_id = $previous,
3916
                                     next_item_id = $id,
3917
                                     display_order = display_order-1
3918
                                     WHERE iid = $next";
3919
                        Database::query($sql_upd2);
3920
                    }
3921
3922
                    // Update next_next item (switch "previous" with current).
3923
                    if (0 != $next_next) {
3924
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3925
                                     previous_item_id = $id
3926
                                     WHERE iid = $next_next";
3927
                        Database::query($sql_upd2);
3928
                    }
3929
                    $display = $display + 1;
3930
                }
3931
                break;
3932
            default:
3933
                return false;
3934
        }
3935
3936
        return $display;
3937
    }
3938
3939
    /**
3940
     * Move a LP up (display_order).
3941
     *
3942
     * @param int $lp_id      Learnpath ID
3943
     * @param int $categoryId Category ID
3944
     *
3945
     * @return bool
3946
     */
3947
    public static function move_up($lp_id, $categoryId = 0)
3948
    {
3949
        $courseId = api_get_course_int_id();
3950
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3951
3952
        $categoryCondition = '';
3953
        if (!empty($categoryId)) {
3954
            $categoryId = (int) $categoryId;
3955
            $categoryCondition = " AND category_id = $categoryId";
3956
        }
3957
        $sql = "SELECT * FROM $lp_table
3958
                WHERE c_id = $courseId
3959
                $categoryCondition
3960
                ORDER BY display_order";
3961
        $res = Database::query($sql);
3962
        if (false === $res) {
3963
            return false;
3964
        }
3965
3966
        $lps = [];
3967
        $lp_order = [];
3968
        $num = Database::num_rows($res);
3969
        // First check the order is correct, globally (might be wrong because
3970
        // of versions < 1.8.4)
3971
        if ($num > 0) {
3972
            $i = 1;
3973
            while ($row = Database::fetch_array($res)) {
3974
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3975
                    $sql = "UPDATE $lp_table SET display_order = $i
3976
                            WHERE iid = ".$row['iid'];
3977
                    Database::query($sql);
3978
                }
3979
                $row['display_order'] = $i;
3980
                $lps[$row['iid']] = $row;
3981
                $lp_order[$i] = $row['iid'];
3982
                $i++;
3983
            }
3984
        }
3985
        if ($num > 1) { // If there's only one element, no need to sort.
3986
            $order = $lps[$lp_id]['display_order'];
3987
            if ($order > 1) { // If it's the first element, no need to move up.
3988
                $sql = "UPDATE $lp_table SET display_order = $order
3989
                        WHERE iid = ".$lp_order[$order - 1];
3990
                Database::query($sql);
3991
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3992
                        WHERE iid = $lp_id";
3993
                Database::query($sql);
3994
            }
3995
        }
3996
3997
        return true;
3998
    }
3999
4000
    /**
4001
     * Move a learnpath down (display_order).
4002
     *
4003
     * @param int $lp_id      Learnpath ID
4004
     * @param int $categoryId Category ID
4005
     *
4006
     * @return bool
4007
     */
4008
    public static function move_down($lp_id, $categoryId = 0)
4009
    {
4010
        $courseId = api_get_course_int_id();
4011
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4012
4013
        $categoryCondition = '';
4014
        if (!empty($categoryId)) {
4015
            $categoryId = (int) $categoryId;
4016
            $categoryCondition = " AND category_id = $categoryId";
4017
        }
4018
4019
        $sql = "SELECT * FROM $lp_table
4020
                WHERE c_id = $courseId
4021
                $categoryCondition
4022
                ORDER BY display_order";
4023
        $res = Database::query($sql);
4024
        if (false === $res) {
4025
            return false;
4026
        }
4027
        $lps = [];
4028
        $lp_order = [];
4029
        $num = Database::num_rows($res);
4030
        $max = 0;
4031
        // First check the order is correct, globally (might be wrong because
4032
        // of versions < 1.8.4).
4033
        if ($num > 0) {
4034
            $i = 1;
4035
            while ($row = Database::fetch_array($res)) {
4036
                $max = $i;
4037
                if ($row['display_order'] != $i) {
4038
                    // If we find a gap in the order, we need to fix it.
4039
                    $sql = "UPDATE $lp_table SET display_order = $i
4040
                              WHERE iid = ".$row['iid'];
4041
                    Database::query($sql);
4042
                }
4043
                $row['display_order'] = $i;
4044
                $lps[$row['iid']] = $row;
4045
                $lp_order[$i] = $row['iid'];
4046
                $i++;
4047
            }
4048
        }
4049
        if ($num > 1) { // If there's only one element, no need to sort.
4050
            $order = $lps[$lp_id]['display_order'];
4051
            if ($order < $max) { // If it's the first element, no need to move up.
4052
                $sql = "UPDATE $lp_table SET display_order = $order
4053
                        WHERE iid = ".$lp_order[$order + 1];
4054
                Database::query($sql);
4055
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4056
                        WHERE iid = $lp_id";
4057
                Database::query($sql);
4058
            }
4059
        }
4060
4061
        return true;
4062
    }
4063
4064
    /**
4065
     * Updates learnpath attributes to point to the next element
4066
     * The last part is similar to set_current_item but processing the other way around.
4067
     */
4068
    public function next()
4069
    {
4070
        if ($this->debug > 0) {
4071
            error_log('In learnpath::next()', 0);
4072
        }
4073
        $this->last = $this->get_current_item_id();
4074
        $this->items[$this->last]->save(
4075
            false,
4076
            $this->prerequisites_match($this->last)
4077
        );
4078
        $this->autocomplete_parents($this->last);
4079
        $new_index = $this->get_next_index();
4080
        if ($this->debug > 2) {
4081
            error_log('New index: '.$new_index, 0);
4082
        }
4083
        $this->index = $new_index;
4084
        if ($this->debug > 2) {
4085
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4086
        }
4087
        $this->current = $this->ordered_items[$new_index];
4088
        if ($this->debug > 2) {
4089
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4090
        }
4091
    }
4092
4093
    /**
4094
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4095
     * class, this might be redefined to allow several behaviours depending on the document type.
4096
     *
4097
     * @param int $id Resource ID
4098
     */
4099
    public function open($id)
4100
    {
4101
        // TODO:
4102
        // set the current resource attribute to this resource
4103
        // switch on element type (redefine in child class?)
4104
        // set status for this item to "opened"
4105
        // start timer
4106
        // initialise score
4107
        $this->index = 0; //or = the last item seen (see $this->last)
4108
    }
4109
4110
    /**
4111
     * Check that all prerequisites are fulfilled. Returns true and an
4112
     * empty string on success, returns false
4113
     * and the prerequisite string on error.
4114
     * This function is based on the rules for aicc_script language as
4115
     * described in the SCORM 1.2 CAM documentation page 108.
4116
     *
4117
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4118
     *
4119
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4120
     *              string otherwise
4121
     */
4122
    public function prerequisites_match($itemId = null)
4123
    {
4124
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4125
        if ($allow) {
4126
            if (api_is_allowed_to_edit() ||
4127
                api_is_platform_admin(true) ||
4128
                api_is_drh() ||
4129
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4130
            ) {
4131
                return true;
4132
            }
4133
        }
4134
4135
        $debug = $this->debug;
4136
        if ($debug > 0) {
4137
            error_log('In learnpath::prerequisites_match()');
4138
        }
4139
4140
        if (empty($itemId)) {
4141
            $itemId = $this->current;
4142
        }
4143
4144
        $currentItem = $this->getItem($itemId);
4145
4146
        if ($currentItem) {
4147
            if (2 == $this->type) {
4148
                // Getting prereq from scorm
4149
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4150
            } else {
4151
                $prereq_string = $currentItem->get_prereq_string();
4152
            }
4153
4154
            if (empty($prereq_string)) {
4155
                if ($debug > 0) {
4156
                    error_log('Found prereq_string is empty return true');
4157
                }
4158
4159
                return true;
4160
            }
4161
4162
            // Clean spaces.
4163
            $prereq_string = str_replace(' ', '', $prereq_string);
4164
            if ($debug > 0) {
4165
                error_log('Found prereq_string: '.$prereq_string, 0);
4166
            }
4167
4168
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4169
            $result = $currentItem->parse_prereq(
4170
                $prereq_string,
4171
                $this->items,
4172
                $this->refs_list,
4173
                $this->get_user_id()
4174
            );
4175
4176
            if (false === $result) {
4177
                $this->set_error_msg($currentItem->prereq_alert);
4178
            }
4179
        } else {
4180
            $result = true;
4181
            if ($debug > 1) {
4182
                error_log('$this->items['.$itemId.'] was not an object', 0);
4183
            }
4184
        }
4185
4186
        if ($debug > 1) {
4187
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4188
        }
4189
4190
        return $result;
4191
    }
4192
4193
    /**
4194
     * Updates learnpath attributes to point to the previous element
4195
     * The last part is similar to set_current_item but processing the other way around.
4196
     */
4197
    public function previous()
4198
    {
4199
        $this->last = $this->get_current_item_id();
4200
        $this->items[$this->last]->save(
4201
            false,
4202
            $this->prerequisites_match($this->last)
4203
        );
4204
        $this->autocomplete_parents($this->last);
4205
        $new_index = $this->get_previous_index();
4206
        $this->index = $new_index;
4207
        $this->current = $this->ordered_items[$new_index];
4208
    }
4209
4210
    /**
4211
     * Publishes a learnpath. This basically means show or hide the learnpath
4212
     * to normal users.
4213
     * Can be used as abstract.
4214
     *
4215
     * @param int $lp_id          Learnpath ID
4216
     * @param int $set_visibility New visibility
4217
     *
4218
     * @return bool
4219
     */
4220
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4221
    {
4222
        $action = 'visible';
4223
        if (1 != $set_visibility) {
4224
            $action = 'invisible';
4225
            self::toggle_publish($lp_id, 'i');
4226
        }
4227
4228
        return api_item_property_update(
4229
            api_get_course_info(),
4230
            TOOL_LEARNPATH,
4231
            $lp_id,
4232
            $action,
4233
            api_get_user_id()
4234
        );
4235
    }
4236
4237
    /**
4238
     * Publishes a learnpath category.
4239
     * This basically means show or hide the learnpath category to normal users.
4240
     *
4241
     * @param int $id
4242
     * @param int $visibility
4243
     *
4244
     * @throws \Doctrine\ORM\NonUniqueResultException
4245
     * @throws \Doctrine\ORM\ORMException
4246
     * @throws \Doctrine\ORM\OptimisticLockException
4247
     * @throws \Doctrine\ORM\TransactionRequiredException
4248
     *
4249
     * @return bool
4250
     */
4251
    public static function toggleCategoryVisibility($id, $visibility = 1)
4252
    {
4253
        $action = 'visible';
4254
        if (1 != $visibility) {
4255
            self::toggleCategoryPublish($id, 0);
4256
            $action = 'invisible';
4257
        }
4258
4259
        return api_item_property_update(
4260
            api_get_course_info(),
4261
            TOOL_LEARNPATH_CATEGORY,
4262
            $id,
4263
            $action,
4264
            api_get_user_id()
4265
        );
4266
    }
4267
4268
    /**
4269
     * Publishes a learnpath. This basically means show or hide the learnpath
4270
     * on the course homepage
4271
     * Can be used as abstract.
4272
     *
4273
     * @param int    $lp_id          Learnpath id
4274
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4275
     *
4276
     * @return bool
4277
     */
4278
    public static function toggle_publish($id, $setVisibility = 'v')
4279
    {
4280
        $addShortcut = false;
4281
        if ('v' === $setVisibility) {
4282
            $addShortcut = true;
4283
        }
4284
        $repo = Container::getLpRepository();
4285
        /** @var CLp $lp */
4286
        $lp = $repo->find($id);
4287
        $repoShortcut = Container::getShortcutRepository();
4288
        if ($addShortcut) {
4289
            $shortcut = new CShortcut();
4290
            $shortcut->setName($lp->getName());
4291
            $shortcut->setShortCutNode($lp->getResourceNode());
4292
4293
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4294
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4295
            $repoShortcut->getEntityManager()->flush();
4296
        } else {
4297
            $shortcut = $repoShortcut->getShortcutFromResource($lp);
4298
            if (null !== $shortcut) {
4299
                $repoShortcut->getEntityManager()->remove($shortcut);
4300
                $repoShortcut->getEntityManager()->flush();
4301
            }
4302
        }
4303
4304
        return true;
4305
        /*
4306
        $course_id = api_get_course_int_id();
4307
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4308
        $lp_id = (int) $lp_id;
4309
        $sql = "SELECT * FROM $tbl_lp
4310
                WHERE iid = $lp_id";
4311
        $result = Database::query($sql);
4312
4313
        if (Database::num_rows($result)) {
4314
            $row = Database::fetch_array($result);
4315
            $name = Database::escape_string($row['name']);
4316
            if ($set_visibility == 'i') {
4317
                $v = 0;
4318
            }
4319
            if ($set_visibility == 'v') {
4320
                $v = 1;
4321
            }
4322
4323
            $session_id = api_get_session_id();
4324
            $session_condition = api_get_session_condition($session_id);
4325
4326
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4327
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4328
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4329
4330
            $sql = "SELECT * FROM $tbl_tool
4331
                    WHERE
4332
                        c_id = $course_id AND
4333
                        (link = '$link' OR link = '$oldLink') AND
4334
                        image = 'scormbuilder.gif' AND
4335
                        (
4336
                            link LIKE '$link%' OR
4337
                            link LIKE '$oldLink%'
4338
                        )
4339
                        $session_condition
4340
                    ";
4341
4342
            $result = Database::query($sql);
4343
            $num = Database::num_rows($result);
4344
            if ($set_visibility == 'i' && $num > 0) {
4345
                $sql = "DELETE FROM $tbl_tool
4346
                        WHERE
4347
                            c_id = $course_id AND
4348
                            (link = '$link' OR link = '$oldLink') AND
4349
                            image='scormbuilder.gif'
4350
                            $session_condition";
4351
                Database::query($sql);
4352
            } elseif ($set_visibility == 'v' && $num == 0) {
4353
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4354
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4355
                Database::query($sql);
4356
                $insertId = Database::insert_id();
4357
                if ($insertId) {
4358
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4359
                    Database::query($sql);
4360
                }
4361
            } elseif ($set_visibility == 'v' && $num > 0) {
4362
                $sql = "UPDATE $tbl_tool SET
4363
                            c_id = $course_id,
4364
                            name = '$name',
4365
                            link = '$link',
4366
                            image = 'scormbuilder.gif',
4367
                            visibility = '$v',
4368
                            admin = '0',
4369
                            address = 'pastillegris.gif',
4370
                            added_tool = 0,
4371
                            session_id = $session_id
4372
                        WHERE
4373
                            c_id = ".$course_id." AND
4374
                            (link = '$link' OR link = '$oldLink') AND
4375
                            image='scormbuilder.gif'
4376
                            $session_condition
4377
                        ";
4378
                Database::query($sql);
4379
            } else {
4380
                // Parameter and database incompatible, do nothing, exit.
4381
                return false;
4382
            }
4383
        } else {
4384
            return false;
4385
        }*/
4386
    }
4387
4388
    /**
4389
     * Publishes a learnpath.
4390
     * Show or hide the learnpath category on the course homepage.
4391
     *
4392
     * @param int $id
4393
     * @param int $setVisibility
4394
     *
4395
     * @throws \Doctrine\ORM\NonUniqueResultException
4396
     * @throws \Doctrine\ORM\ORMException
4397
     * @throws \Doctrine\ORM\OptimisticLockException
4398
     * @throws \Doctrine\ORM\TransactionRequiredException
4399
     *
4400
     * @return bool
4401
     */
4402
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4403
    {
4404
        $courseId = api_get_course_int_id();
4405
        $sessionId = api_get_session_id();
4406
        $sessionCondition = api_get_session_condition(
4407
            $sessionId,
4408
            true,
4409
            false,
4410
            't.sessionId'
4411
        );
4412
4413
        $em = Database::getManager();
4414
4415
        /** @var CLpCategory $category */
4416
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4417
4418
        if (!$category) {
4419
            return false;
4420
        }
4421
4422
        if (empty($courseId)) {
4423
            return false;
4424
        }
4425
4426
        $link = self::getCategoryLinkForTool($id);
4427
4428
        /** @var CTool $tool */
4429
        $tool = $em->createQuery("
4430
                SELECT t FROM ChamiloCourseBundle:CTool t
4431
                WHERE
4432
                    t.course = :course AND
4433
                    t.link = :link1 AND
4434
                    t.image LIKE 'lp_category.%' AND
4435
                    t.link LIKE :link2
4436
                    $sessionCondition
4437
            ")
4438
            ->setParameters([
4439
                'course' => $courseId,
4440
                'link1' => $link,
4441
                'link2' => "$link%",
4442
            ])
4443
            ->getOneOrNullResult();
4444
4445
        if (0 == $setVisibility && $tool) {
4446
            $em->remove($tool);
4447
            $em->flush();
4448
4449
            return true;
4450
        }
4451
4452
        if (1 == $setVisibility && !$tool) {
4453
            $tool = new CTool();
4454
            $tool
4455
                ->setCategory('authoring')
4456
                ->setCourse(api_get_course_entity($courseId))
4457
                ->setName(strip_tags($category->getName()))
4458
                ->setLink($link)
4459
                ->setImage('lp_category.png')
4460
                ->setVisibility(1)
4461
                ->setAdmin(0)
4462
                ->setAddress('pastillegris.gif')
4463
                ->setAddedTool(0)
4464
                ->setSessionId($sessionId)
4465
                ->setTarget('_self');
4466
4467
            $em->persist($tool);
4468
            $em->flush();
4469
4470
            $tool->setId($tool->getIid());
4471
4472
            $em->persist($tool);
4473
            $em->flush();
4474
4475
            return true;
4476
        }
4477
4478
        if (1 == $setVisibility && $tool) {
4479
            $tool
4480
                ->setName(strip_tags($category->getName()))
4481
                ->setVisibility(1);
4482
4483
            $em->persist($tool);
4484
            $em->flush();
4485
4486
            return true;
4487
        }
4488
4489
        return false;
4490
    }
4491
4492
    /**
4493
     * Check if the learnpath category is visible for a user.
4494
     *
4495
     * @param int
4496
     * @param int
4497
     *
4498
     * @return bool
4499
     */
4500
    public static function categoryIsVisibleForStudent(
4501
        CLpCategory $category,
4502
        User $user,
4503
        $courseId = 0,
4504
        $sessionId = 0
4505
    ) {
4506
        if (empty($category)) {
4507
            return false;
4508
        }
4509
4510
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4511
4512
        if ($isAllowedToEdit) {
4513
            return true;
4514
        }
4515
4516
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4517
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4518
4519
        $courseInfo = api_get_course_info_by_id($courseId);
4520
4521
        $categoryVisibility = api_get_item_visibility(
4522
            $courseInfo,
4523
            TOOL_LEARNPATH_CATEGORY,
4524
            $category->getId(),
4525
            $sessionId
4526
        );
4527
4528
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4529
            return false;
4530
        }
4531
4532
        $subscriptionSettings = self::getSubscriptionSettings();
4533
4534
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4535
            return true;
4536
        }
4537
4538
        $users = $category->getUsers();
4539
4540
        if (empty($users) || !$users->count()) {
4541
            return true;
4542
        }
4543
4544
        if ($category->hasUserAdded($user)) {
4545
            return true;
4546
        }
4547
4548
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4549
        if (!empty($groups)) {
4550
            $em = Database::getManager();
4551
4552
            /** @var ItemPropertyRepository $itemRepo */
4553
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4554
4555
            /** @var CourseRepository $courseRepo */
4556
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4557
            $session = null;
4558
            if (!empty($sessionId)) {
4559
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4560
            }
4561
4562
            if (0 != $courseId) {
4563
                $course = $courseRepo->find($courseId);
4564
4565
                // Subscribed groups to a LP
4566
                $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4567
                    TOOL_LEARNPATH_CATEGORY,
4568
                    $category->getId(),
4569
                    $course,
4570
                    $session
4571
                );
4572
            }
4573
4574
            if (!empty($subscribedGroupsInLp)) {
4575
                $groups = array_column($groups, 'iid');
4576
                /** @var CItemProperty $item */
4577
                foreach ($subscribedGroupsInLp as $item) {
4578
                    if ($item->getGroup() &&
4579
                        in_array($item->getGroup()->getId(), $groups)
4580
                    ) {
4581
                        return true;
4582
                    }
4583
                }
4584
            }
4585
        }
4586
4587
        return false;
4588
    }
4589
4590
    /**
4591
     * Check if a learnpath category is published as course tool.
4592
     *
4593
     * @param int $courseId
4594
     *
4595
     * @return bool
4596
     */
4597
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4598
    {
4599
        return false;
4600
        $link = self::getCategoryLinkForTool($category->getId());
0 ignored issues
show
Unused Code introduced by
$link = self::getCategor...ool($category->getId()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
4601
        $em = Database::getManager();
4602
4603
        $tools = $em
4604
            ->createQuery("
4605
                SELECT t FROM ChamiloCourseBundle:CTool t
4606
                WHERE t.course = :course AND
4607
                    t.name = :name AND
4608
                    t.image LIKE 'lp_category.%' AND
4609
                    t.link LIKE :link
4610
            ")
4611
            ->setParameters([
4612
                'course' => $courseId,
4613
                'name' => strip_tags($category->getName()),
4614
                'link' => "$link%",
4615
            ])
4616
            ->getResult();
4617
4618
        /** @var CTool $tool */
4619
        $tool = current($tools);
4620
4621
        return $tool ? $tool->getVisibility() : false;
4622
    }
4623
4624
    /**
4625
     * Restart the whole learnpath. Return the URL of the first element.
4626
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4627
     * To use a similar method  statically, use the create_new_attempt() method.
4628
     *
4629
     * @return bool
4630
     */
4631
    public function restart()
4632
    {
4633
        if ($this->debug > 0) {
4634
            error_log('In learnpath::restart()', 0);
4635
        }
4636
        // TODO
4637
        // Call autosave method to save the current progress.
4638
        //$this->index = 0;
4639
        if (api_is_invitee()) {
4640
            return false;
4641
        }
4642
        $session_id = api_get_session_id();
4643
        $course_id = api_get_course_int_id();
4644
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4645
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4646
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4647
        if ($this->debug > 2) {
4648
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4649
        }
4650
        Database::query($sql);
4651
        $view_id = Database::insert_id();
4652
4653
        if ($view_id) {
4654
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4655
            Database::query($sql);
4656
            $this->lp_view_id = $view_id;
4657
            $this->attempt = $this->attempt + 1;
4658
        } else {
4659
            $this->error = 'Could not insert into item_view table...';
4660
4661
            return false;
4662
        }
4663
        $this->autocomplete_parents($this->current);
4664
        foreach ($this->items as $index => $dummy) {
4665
            $this->items[$index]->restart();
4666
            $this->items[$index]->set_lp_view($this->lp_view_id);
4667
        }
4668
        $this->first();
4669
4670
        return true;
4671
    }
4672
4673
    /**
4674
     * Saves the current item.
4675
     *
4676
     * @return bool
4677
     */
4678
    public function save_current()
4679
    {
4680
        $debug = $this->debug;
4681
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4682
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4683
        if ($debug) {
4684
            error_log('save_current() saving item '.$this->current, 0);
4685
            error_log(''.print_r($this->items, true), 0);
4686
        }
4687
        if (isset($this->items[$this->current]) &&
4688
            is_object($this->items[$this->current])
4689
        ) {
4690
            if ($debug) {
4691
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4692
            }
4693
4694
            $res = $this->items[$this->current]->save(
4695
                false,
4696
                $this->prerequisites_match($this->current)
4697
            );
4698
            $this->autocomplete_parents($this->current);
4699
            $status = $this->items[$this->current]->get_status();
4700
            $this->update_queue[$this->current] = $status;
4701
4702
            if ($debug) {
4703
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4704
            }
4705
4706
            return $res;
4707
        }
4708
4709
        return false;
4710
    }
4711
4712
    /**
4713
     * Saves the given item.
4714
     *
4715
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4716
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4717
     *
4718
     * @return bool
4719
     */
4720
    public function save_item($item_id = null, $from_outside = true)
4721
    {
4722
        $debug = $this->debug;
4723
        if ($debug) {
4724
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4725
        }
4726
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4727
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4728
        if (empty($item_id)) {
4729
            $item_id = (int) $_REQUEST['id'];
4730
        }
4731
4732
        if (empty($item_id)) {
4733
            $item_id = $this->get_current_item_id();
4734
        }
4735
        if (isset($this->items[$item_id]) &&
4736
            is_object($this->items[$item_id])
4737
        ) {
4738
            if ($debug) {
4739
                error_log('Object exists');
4740
            }
4741
4742
            // Saving the item.
4743
            $res = $this->items[$item_id]->save(
4744
                $from_outside,
4745
                $this->prerequisites_match($item_id)
4746
            );
4747
4748
            if ($debug) {
4749
                error_log('update_queue before:');
4750
                error_log(print_r($this->update_queue, 1));
4751
            }
4752
            $this->autocomplete_parents($item_id);
4753
4754
            $status = $this->items[$item_id]->get_status();
4755
            $this->update_queue[$item_id] = $status;
4756
4757
            if ($debug) {
4758
                error_log('get_status(): '.$status);
4759
                error_log('update_queue after:');
4760
                error_log(print_r($this->update_queue, 1));
4761
            }
4762
4763
            return $res;
4764
        }
4765
4766
        return false;
4767
    }
4768
4769
    /**
4770
     * Saves the last item seen's ID only in case.
4771
     */
4772
    public function save_last()
4773
    {
4774
        $course_id = api_get_course_int_id();
4775
        $debug = $this->debug;
4776
        if ($debug) {
4777
            error_log('In learnpath::save_last()', 0);
4778
        }
4779
        $session_condition = api_get_session_condition(
4780
            api_get_session_id(),
4781
            true,
4782
            false
4783
        );
4784
        $table = Database::get_course_table(TABLE_LP_VIEW);
4785
4786
        if (isset($this->current) && !api_is_invitee()) {
4787
            if ($debug) {
4788
                error_log('Saving current item ('.$this->current.') for later review', 0);
4789
            }
4790
            $sql = "UPDATE $table SET
4791
                        last_item = ".$this->get_current_item_id()."
4792
                    WHERE
4793
                        c_id = $course_id AND
4794
                        lp_id = ".$this->get_id()." AND
4795
                        user_id = ".$this->get_user_id()." ".$session_condition;
4796
4797
            if ($debug) {
4798
                error_log('Saving last item seen : '.$sql, 0);
4799
            }
4800
            Database::query($sql);
4801
        }
4802
4803
        if (!api_is_invitee()) {
4804
            // Save progress.
4805
            list($progress) = $this->get_progress_bar_text('%');
4806
            if ($progress >= 0 && $progress <= 100) {
4807
                $progress = (int) $progress;
4808
                $sql = "UPDATE $table SET
4809
                            progress = $progress
4810
                        WHERE
4811
                            c_id = $course_id AND
4812
                            lp_id = ".$this->get_id()." AND
4813
                            user_id = ".$this->get_user_id()." ".$session_condition;
4814
                // Ignore errors as some tables might not have the progress field just yet.
4815
                Database::query($sql);
4816
                $this->progress_db = $progress;
4817
            }
4818
        }
4819
    }
4820
4821
    /**
4822
     * Sets the current item ID (checks if valid and authorized first).
4823
     *
4824
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4825
     */
4826
    public function set_current_item($item_id = null)
4827
    {
4828
        $debug = $this->debug;
4829
        if ($debug) {
4830
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4831
        }
4832
        if (empty($item_id)) {
4833
            if ($debug) {
4834
                error_log('No new current item given, ignore...', 0);
4835
            }
4836
            // Do nothing.
4837
        } else {
4838
            if ($debug) {
4839
                error_log('New current item given is '.$item_id.'...', 0);
4840
            }
4841
            if (is_numeric($item_id)) {
4842
                $item_id = (int) $item_id;
4843
                // TODO: Check in database here.
4844
                $this->last = $this->current;
4845
                $this->current = $item_id;
4846
                // TODO: Update $this->index as well.
4847
                foreach ($this->ordered_items as $index => $item) {
4848
                    if ($item == $this->current) {
4849
                        $this->index = $index;
4850
                        break;
4851
                    }
4852
                }
4853
                if ($debug) {
4854
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4855
                }
4856
            } else {
4857
                if ($debug) {
4858
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4859
                }
4860
            }
4861
        }
4862
    }
4863
4864
    /**
4865
     * Sets the encoding.
4866
     *
4867
     * @param string $enc New encoding
4868
     *
4869
     * @return bool
4870
     *
4871
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4872
     */
4873
    public function set_encoding($enc = 'UTF-8')
4874
    {
4875
        $enc = api_refine_encoding_id($enc);
4876
        if (empty($enc)) {
4877
            $enc = api_get_system_encoding();
4878
        }
4879
        if (api_is_encoding_supported($enc)) {
4880
            $lp = $this->get_id();
4881
            if (0 != $lp) {
4882
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4883
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4884
                        WHERE iid = ".$lp;
4885
                $res = Database::query($sql);
4886
4887
                return $res;
4888
            }
4889
        }
4890
4891
        return false;
4892
    }
4893
4894
    /**
4895
     * Sets the JS lib setting in the database directly.
4896
     * This is the JavaScript library file this lp needs to load on startup.
4897
     *
4898
     * @param string $lib Proximity setting
4899
     *
4900
     * @return bool True on update success. False otherwise.
4901
     */
4902
    public function set_jslib($lib = '')
4903
    {
4904
        $lp = $this->get_id();
4905
4906
        if (0 != $lp) {
4907
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4908
            $lib = Database::escape_string($lib);
4909
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4910
                    WHERE iid = $lp";
4911
            $res = Database::query($sql);
4912
4913
            return $res;
4914
        }
4915
4916
        return false;
4917
    }
4918
4919
    /**
4920
     * Sets the name of the LP maker (publisher) (and save).
4921
     *
4922
     * @param string $name Optional string giving the new content_maker of this learnpath
4923
     *
4924
     * @return bool True
4925
     */
4926
    public function set_maker($name = '')
4927
    {
4928
        if (empty($name)) {
4929
            return false;
4930
        }
4931
        $this->maker = $name;
4932
        $table = Database::get_course_table(TABLE_LP_MAIN);
4933
        $lp_id = $this->get_id();
4934
        $sql = "UPDATE $table SET
4935
                content_maker = '".Database::escape_string($this->maker)."'
4936
                WHERE iid = $lp_id";
4937
        Database::query($sql);
4938
4939
        return true;
4940
    }
4941
4942
    /**
4943
     * Sets the name of the current learnpath (and save).
4944
     *
4945
     * @param string $name Optional string giving the new name of this learnpath
4946
     *
4947
     * @return bool True/False
4948
     */
4949
    public function set_name($name = null)
4950
    {
4951
        if (empty($name)) {
4952
            return false;
4953
        }
4954
        $this->name = $name;
4955
4956
        $lp_id = $this->get_id();
4957
4958
        $repo = Container::getLpRepository();
4959
        /** @var CLp $lp */
4960
        $lp = $repo->find($lp_id);
4961
        $lp->setName($name);
4962
        $repo->updateNodeForResource($lp);
4963
4964
        /*
4965
        $course_id = $this->course_info['real_id'];
4966
        $sql = "UPDATE $lp_table SET
4967
            name = '$name'
4968
            WHERE iid = $lp_id";
4969
        $result = Database::query($sql);
4970
        // If the lp is visible on the homepage, change his name there.
4971
        if (Database::affected_rows($result)) {
4972
        $session_id = api_get_session_id();
4973
        $session_condition = api_get_session_condition($session_id);
4974
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4975
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4976
        $sql = "UPDATE $tbl_tool SET name = '$name'
4977
        	    WHERE
4978
        	        c_id = $course_id AND
4979
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4980
        Database::query($sql);*/
4981
4982
        //return true;
4983
        //}
4984
4985
        return false;
4986
    }
4987
4988
    /**
4989
     * Set index specified prefix terms for all items in this path.
4990
     *
4991
     * @param string $terms_string Comma-separated list of terms
4992
     * @param string $prefix       Xapian term prefix
4993
     *
4994
     * @return bool False on error, true otherwise
4995
     */
4996
    public function set_terms_by_prefix($terms_string, $prefix)
4997
    {
4998
        $course_id = api_get_course_int_id();
4999
        if ('true' !== api_get_setting('search_enabled')) {
5000
            return false;
5001
        }
5002
5003
        if (!extension_loaded('xapian')) {
5004
            return false;
5005
        }
5006
5007
        $terms_string = trim($terms_string);
5008
        $terms = explode(',', $terms_string);
5009
        array_walk($terms, 'trim_value');
5010
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5011
5012
        // Don't do anything if no change, verify only at DB, not the search engine.
5013
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5014
            return false;
5015
        }
5016
5017
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5018
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5019
5020
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5021
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5022
        $lp_id = (int) $_POST['lp_id'];
5023
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5024
        $result = Database::query($sql);
5025
        $di = new ChamiloIndexer();
5026
5027
        while ($lp_item = Database::fetch_array($result)) {
5028
            // Get search_did.
5029
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5030
            $sql = 'SELECT * FROM %s
5031
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5032
                    LIMIT 1';
5033
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5034
5035
            //echo $sql; echo '<br>';
5036
            $res = Database::query($sql);
5037
            if (Database::num_rows($res) > 0) {
5038
                $se_ref = Database::fetch_array($res);
5039
                // Compare terms.
5040
                $doc = $di->get_document($se_ref['search_did']);
5041
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5042
                $xterms = [];
5043
                foreach ($xapian_terms as $xapian_term) {
5044
                    $xterms[] = substr($xapian_term['name'], 1);
5045
                }
5046
5047
                $dterms = $terms;
5048
                $missing_terms = array_diff($dterms, $xterms);
5049
                $deprecated_terms = array_diff($xterms, $dterms);
5050
5051
                // Save it to search engine.
5052
                foreach ($missing_terms as $term) {
5053
                    $doc->add_term($prefix.$term, 1);
5054
                }
5055
                foreach ($deprecated_terms as $term) {
5056
                    $doc->remove_term($prefix.$term);
5057
                }
5058
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5059
                $di->getDb()->flush();
5060
            }
5061
        }
5062
5063
        return true;
5064
    }
5065
5066
    /**
5067
     * Sets the theme of the LP (local/remote) (and save).
5068
     *
5069
     * @param string $name Optional string giving the new theme of this learnpath
5070
     *
5071
     * @return bool Returns true if theme name is not empty
5072
     */
5073
    public function set_theme($name = '')
5074
    {
5075
        $this->theme = $name;
5076
        $table = Database::get_course_table(TABLE_LP_MAIN);
5077
        $lp_id = $this->get_id();
5078
        $sql = "UPDATE $table
5079
                SET theme = '".Database::escape_string($this->theme)."'
5080
                WHERE iid = $lp_id";
5081
        Database::query($sql);
5082
5083
        return true;
5084
    }
5085
5086
    /**
5087
     * Sets the image of an LP (and save).
5088
     *
5089
     * @param string $name Optional string giving the new image of this learnpath
5090
     *
5091
     * @return bool Returns true if theme name is not empty
5092
     */
5093
    public function set_preview_image($name = '')
5094
    {
5095
        $this->preview_image = $name;
5096
        $table = Database::get_course_table(TABLE_LP_MAIN);
5097
        $lp_id = $this->get_id();
5098
        $sql = "UPDATE $table SET
5099
                preview_image = '".Database::escape_string($this->preview_image)."'
5100
                WHERE iid = $lp_id";
5101
        Database::query($sql);
5102
5103
        return true;
5104
    }
5105
5106
    /**
5107
     * Sets the author of a LP (and save).
5108
     *
5109
     * @param string $name Optional string giving the new author of this learnpath
5110
     *
5111
     * @return bool Returns true if author's name is not empty
5112
     */
5113
    public function set_author($name = '')
5114
    {
5115
        $this->author = $name;
5116
        $table = Database::get_course_table(TABLE_LP_MAIN);
5117
        $lp_id = $this->get_id();
5118
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5119
                WHERE iid = $lp_id";
5120
        Database::query($sql);
5121
5122
        return true;
5123
    }
5124
5125
    /**
5126
     * Sets the hide_toc_frame parameter of a LP (and save).
5127
     *
5128
     * @param int $hide 1 if frame is hidden 0 then else
5129
     *
5130
     * @return bool Returns true if author's name is not empty
5131
     */
5132
    public function set_hide_toc_frame($hide)
5133
    {
5134
        if (intval($hide) == $hide) {
5135
            $this->hide_toc_frame = $hide;
5136
            $table = Database::get_course_table(TABLE_LP_MAIN);
5137
            $lp_id = $this->get_id();
5138
            $sql = "UPDATE $table SET
5139
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5140
                    WHERE iid = $lp_id";
5141
            Database::query($sql);
5142
5143
            return true;
5144
        }
5145
5146
        return false;
5147
    }
5148
5149
    /**
5150
     * Sets the prerequisite of a LP (and save).
5151
     *
5152
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5153
     *
5154
     * @return bool returns true if prerequisite is not empty
5155
     */
5156
    public function set_prerequisite($prerequisite)
5157
    {
5158
        $this->prerequisite = (int) $prerequisite;
5159
        $table = Database::get_course_table(TABLE_LP_MAIN);
5160
        $lp_id = $this->get_id();
5161
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5162
                WHERE iid = $lp_id";
5163
        Database::query($sql);
5164
5165
        return true;
5166
    }
5167
5168
    /**
5169
     * Sets the location/proximity of the LP (local/remote) (and save).
5170
     *
5171
     * @param string $name Optional string giving the new location of this learnpath
5172
     *
5173
     * @return bool True on success / False on error
5174
     */
5175
    public function set_proximity($name = '')
5176
    {
5177
        if (empty($name)) {
5178
            return false;
5179
        }
5180
5181
        $this->proximity = $name;
5182
        $table = Database::get_course_table(TABLE_LP_MAIN);
5183
        $lp_id = $this->get_id();
5184
        $sql = "UPDATE $table SET
5185
                    content_local = '".Database::escape_string($name)."'
5186
                WHERE iid = $lp_id";
5187
        Database::query($sql);
5188
5189
        return true;
5190
    }
5191
5192
    /**
5193
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5194
     *
5195
     * @param int $id DB ID of the item
5196
     */
5197
    public function set_previous_item($id)
5198
    {
5199
        if ($this->debug > 0) {
5200
            error_log('In learnpath::set_previous_item()', 0);
5201
        }
5202
        $this->last = $id;
5203
    }
5204
5205
    /**
5206
     * Sets use_max_score.
5207
     *
5208
     * @param int $use_max_score Optional string giving the new location of this learnpath
5209
     *
5210
     * @return bool True on success / False on error
5211
     */
5212
    public function set_use_max_score($use_max_score = 1)
5213
    {
5214
        $use_max_score = (int) $use_max_score;
5215
        $this->use_max_score = $use_max_score;
5216
        $table = Database::get_course_table(TABLE_LP_MAIN);
5217
        $lp_id = $this->get_id();
5218
        $sql = "UPDATE $table SET
5219
                    use_max_score = '".$this->use_max_score."'
5220
                WHERE iid = $lp_id";
5221
        Database::query($sql);
5222
5223
        return true;
5224
    }
5225
5226
    /**
5227
     * Sets and saves the expired_on date.
5228
     *
5229
     * @param string $expired_on Optional string giving the new author of this learnpath
5230
     *
5231
     * @throws \Doctrine\ORM\OptimisticLockException
5232
     *
5233
     * @return bool Returns true if author's name is not empty
5234
     */
5235
    public function set_expired_on($expired_on)
5236
    {
5237
        $em = Database::getManager();
5238
        /** @var CLp $lp */
5239
        $lp = $em
5240
            ->getRepository('ChamiloCourseBundle:CLp')
5241
            ->findOneBy(
5242
                [
5243
                    'iid' => $this->get_id(),
5244
                ]
5245
            );
5246
5247
        if (!$lp) {
5248
            return false;
5249
        }
5250
5251
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5252
5253
        $lp->setExpiredOn($this->expired_on);
5254
        $em->persist($lp);
5255
        $em->flush();
5256
5257
        return true;
5258
    }
5259
5260
    /**
5261
     * Sets and saves the publicated_on date.
5262
     *
5263
     * @param string $publicated_on Optional string giving the new author of this learnpath
5264
     *
5265
     * @throws \Doctrine\ORM\OptimisticLockException
5266
     *
5267
     * @return bool Returns true if author's name is not empty
5268
     */
5269
    public function set_publicated_on($publicated_on)
5270
    {
5271
        $em = Database::getManager();
5272
        /** @var CLp $lp */
5273
        $lp = $em
5274
            ->getRepository('ChamiloCourseBundle:CLp')
5275
            ->findOneBy(
5276
                [
5277
                    'iid' => $this->get_id(),
5278
                ]
5279
            );
5280
5281
        if (!$lp) {
5282
            return false;
5283
        }
5284
5285
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5286
        $lp->setPublicatedOn($this->publicated_on);
5287
        $em->persist($lp);
5288
        $em->flush();
5289
5290
        return true;
5291
    }
5292
5293
    /**
5294
     * Sets and saves the expired_on date.
5295
     *
5296
     * @return bool Returns true if author's name is not empty
5297
     */
5298
    public function set_modified_on()
5299
    {
5300
        $this->modified_on = api_get_utc_datetime();
5301
        $table = Database::get_course_table(TABLE_LP_MAIN);
5302
        $lp_id = $this->get_id();
5303
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5304
                WHERE iid = $lp_id";
5305
        Database::query($sql);
5306
5307
        return true;
5308
    }
5309
5310
    /**
5311
     * Sets the object's error message.
5312
     *
5313
     * @param string $error Error message. If empty, reinits the error string
5314
     */
5315
    public function set_error_msg($error = '')
5316
    {
5317
        if ($this->debug > 0) {
5318
            error_log('In learnpath::set_error_msg()', 0);
5319
        }
5320
        if (empty($error)) {
5321
            $this->error = '';
5322
        } else {
5323
            $this->error .= $error;
5324
        }
5325
    }
5326
5327
    /**
5328
     * Launches the current item if not 'sco'
5329
     * (starts timer and make sure there is a record ready in the DB).
5330
     *
5331
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5332
     *
5333
     * @return bool
5334
     */
5335
    public function start_current_item($allow_new_attempt = false)
5336
    {
5337
        $debug = $this->debug;
5338
        if ($debug) {
5339
            error_log('In learnpath::start_current_item()');
5340
            error_log('current: '.$this->current);
5341
        }
5342
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5343
            $type = $this->get_type();
5344
            $item_type = $this->items[$this->current]->get_type();
5345
            if ((2 == $type && 'sco' != $item_type) ||
5346
                (3 == $type && 'au' != $item_type) ||
5347
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5348
            ) {
5349
                if ($debug) {
5350
                    error_log('item type: '.$item_type);
5351
                    error_log('lp type: '.$type);
5352
                }
5353
                $this->items[$this->current]->open($allow_new_attempt);
5354
                $this->autocomplete_parents($this->current);
5355
                $prereq_check = $this->prerequisites_match($this->current);
5356
                if ($debug) {
5357
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5358
                }
5359
                $this->items[$this->current]->save(false, $prereq_check);
5360
            }
5361
            // If sco, then it is supposed to have been updated by some other call.
5362
            if ('sco' == $item_type) {
5363
                $this->items[$this->current]->restart();
5364
            }
5365
        }
5366
        if ($debug) {
5367
            error_log('lp_view_session_id');
5368
            error_log($this->lp_view_session_id);
5369
            error_log('api session id');
5370
            error_log(api_get_session_id());
5371
            error_log('End of learnpath::start_current_item()');
5372
        }
5373
5374
        return true;
5375
    }
5376
5377
    /**
5378
     * Stops the processing and counters for the old item (as held in $this->last).
5379
     *
5380
     * @return bool True/False
5381
     */
5382
    public function stop_previous_item()
5383
    {
5384
        $debug = $this->debug;
5385
        if ($debug) {
5386
            error_log('In learnpath::stop_previous_item()', 0);
5387
        }
5388
5389
        if (0 != $this->last && $this->last != $this->current &&
5390
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5391
        ) {
5392
            if ($debug) {
5393
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5394
            }
5395
            switch ($this->get_type()) {
5396
                case '3':
5397
                    if ('au' != $this->items[$this->last]->get_type()) {
5398
                        if ($debug) {
5399
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5400
                        }
5401
                        $this->items[$this->last]->close();
5402
                    } else {
5403
                        if ($debug) {
5404
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5405
                        }
5406
                    }
5407
                    break;
5408
                case '2':
5409
                    if ('sco' != $this->items[$this->last]->get_type()) {
5410
                        if ($debug) {
5411
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5412
                        }
5413
                        $this->items[$this->last]->close();
5414
                    } else {
5415
                        if ($debug) {
5416
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5417
                        }
5418
                    }
5419
                    break;
5420
                case '1':
5421
                default:
5422
                    if ($debug) {
5423
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5424
                    }
5425
                    $this->items[$this->last]->close();
5426
                    break;
5427
            }
5428
        } else {
5429
            if ($debug) {
5430
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5431
            }
5432
5433
            return false;
5434
        }
5435
5436
        return true;
5437
    }
5438
5439
    /**
5440
     * Updates the default view mode from fullscreen to embedded and inversely.
5441
     *
5442
     * @return string The current default view mode ('fullscreen' or 'embedded')
5443
     */
5444
    public function update_default_view_mode()
5445
    {
5446
        $table = Database::get_course_table(TABLE_LP_MAIN);
5447
        $sql = "SELECT * FROM $table
5448
                WHERE iid = ".$this->get_id();
5449
        $res = Database::query($sql);
5450
        if (Database::num_rows($res) > 0) {
5451
            $row = Database::fetch_array($res);
5452
            $default_view_mode = $row['default_view_mod'];
5453
            $view_mode = $default_view_mode;
5454
            switch ($default_view_mode) {
5455
                case 'fullscreen': // default with popup
5456
                    $view_mode = 'embedded';
5457
                    break;
5458
                case 'embedded': // default view with left menu
5459
                    $view_mode = 'embedframe';
5460
                    break;
5461
                case 'embedframe': //folded menu
5462
                    $view_mode = 'impress';
5463
                    break;
5464
                case 'impress':
5465
                    $view_mode = 'fullscreen';
5466
                    break;
5467
            }
5468
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5469
                    WHERE iid = ".$this->get_id();
5470
            Database::query($sql);
5471
            $this->mode = $view_mode;
5472
5473
            return $view_mode;
5474
        }
5475
5476
        return -1;
5477
    }
5478
5479
    /**
5480
     * Updates the default behaviour about auto-commiting SCORM updates.
5481
     *
5482
     * @return bool True if auto-commit has been set to 'on', false otherwise
5483
     */
5484
    public function update_default_scorm_commit()
5485
    {
5486
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5487
        $sql = "SELECT * FROM $lp_table
5488
                WHERE iid = ".$this->get_id();
5489
        $res = Database::query($sql);
5490
        if (Database::num_rows($res) > 0) {
5491
            $row = Database::fetch_array($res);
5492
            $force = $row['force_commit'];
5493
            if (1 == $force) {
5494
                $force = 0;
5495
                $force_return = false;
5496
            } elseif (0 == $force) {
5497
                $force = 1;
5498
                $force_return = true;
5499
            }
5500
            $sql = "UPDATE $lp_table SET force_commit = $force
5501
                    WHERE iid = ".$this->get_id();
5502
            Database::query($sql);
5503
            $this->force_commit = $force_return;
5504
5505
            return $force_return;
5506
        }
5507
5508
        return -1;
5509
    }
5510
5511
    /**
5512
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5513
     *
5514
     * @return bool True on success, false on failure
5515
     */
5516
    public function update_display_order()
5517
    {
5518
        $course_id = api_get_course_int_id();
5519
        $table = Database::get_course_table(TABLE_LP_MAIN);
5520
        $sql = "SELECT * FROM $table
5521
                WHERE c_id = $course_id
5522
                ORDER BY display_order";
5523
        $res = Database::query($sql);
5524
        if (false === $res) {
5525
            return false;
5526
        }
5527
5528
        $num = Database::num_rows($res);
5529
        // First check the order is correct, globally (might be wrong because
5530
        // of versions < 1.8.4).
5531
        if ($num > 0) {
5532
            $i = 1;
5533
            while ($row = Database::fetch_array($res)) {
5534
                if ($row['display_order'] != $i) {
5535
                    // If we find a gap in the order, we need to fix it.
5536
                    $sql = "UPDATE $table SET display_order = $i
5537
                            WHERE iid = ".$row['iid'];
5538
                    Database::query($sql);
5539
                }
5540
                $i++;
5541
            }
5542
        }
5543
5544
        return true;
5545
    }
5546
5547
    /**
5548
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5549
     *
5550
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5551
     */
5552
    public function update_reinit()
5553
    {
5554
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5555
        $sql = "SELECT * FROM $lp_table
5556
                WHERE iid = ".$this->get_id();
5557
        $res = Database::query($sql);
5558
        if (Database::num_rows($res) > 0) {
5559
            $row = Database::fetch_array($res);
5560
            $force = $row['prevent_reinit'];
5561
            if (1 == $force) {
5562
                $force = 0;
5563
            } elseif (0 == $force) {
5564
                $force = 1;
5565
            }
5566
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5567
                    WHERE iid = ".$this->get_id();
5568
            Database::query($sql);
5569
            $this->prevent_reinit = $force;
5570
5571
            return $force;
5572
        }
5573
5574
        return -1;
5575
    }
5576
5577
    /**
5578
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5579
     *
5580
     * @return string 'single', 'multi' or 'seriousgame'
5581
     *
5582
     * @author ndiechburg <[email protected]>
5583
     */
5584
    public function get_attempt_mode()
5585
    {
5586
        //Set default value for seriousgame_mode
5587
        if (!isset($this->seriousgame_mode)) {
5588
            $this->seriousgame_mode = 0;
5589
        }
5590
        // Set default value for prevent_reinit
5591
        if (!isset($this->prevent_reinit)) {
5592
            $this->prevent_reinit = 1;
5593
        }
5594
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5595
            return 'seriousgame';
5596
        }
5597
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5598
            return 'single';
5599
        }
5600
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5601
            return 'multiple';
5602
        }
5603
5604
        return 'single';
5605
    }
5606
5607
    /**
5608
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5609
     *
5610
     * @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...
5611
     *
5612
     * @return bool
5613
     *
5614
     * @author ndiechburg <[email protected]>
5615
     */
5616
    public function set_attempt_mode($mode)
5617
    {
5618
        switch ($mode) {
5619
            case 'seriousgame':
5620
                $sg_mode = 1;
5621
                $prevent_reinit = 1;
5622
                break;
5623
            case 'single':
5624
                $sg_mode = 0;
5625
                $prevent_reinit = 1;
5626
                break;
5627
            case 'multiple':
5628
                $sg_mode = 0;
5629
                $prevent_reinit = 0;
5630
                break;
5631
            default:
5632
                $sg_mode = 0;
5633
                $prevent_reinit = 0;
5634
                break;
5635
        }
5636
        $this->prevent_reinit = $prevent_reinit;
5637
        $this->seriousgame_mode = $sg_mode;
5638
        $table = Database::get_course_table(TABLE_LP_MAIN);
5639
        $sql = "UPDATE $table SET
5640
                prevent_reinit = $prevent_reinit ,
5641
                seriousgame_mode = $sg_mode
5642
                WHERE iid = ".$this->get_id();
5643
        $res = Database::query($sql);
5644
        if ($res) {
5645
            return true;
5646
        } else {
5647
            return false;
5648
        }
5649
    }
5650
5651
    /**
5652
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5653
     *
5654
     * @author ndiechburg <[email protected]>
5655
     */
5656
    public function switch_attempt_mode()
5657
    {
5658
        $mode = $this->get_attempt_mode();
5659
        switch ($mode) {
5660
            case 'single':
5661
                $next_mode = 'multiple';
5662
                break;
5663
            case 'multiple':
5664
                $next_mode = 'seriousgame';
5665
                break;
5666
            case 'seriousgame':
5667
            default:
5668
                $next_mode = 'single';
5669
                break;
5670
        }
5671
        $this->set_attempt_mode($next_mode);
5672
    }
5673
5674
    /**
5675
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5676
     * but possibility to do again a completed item.
5677
     *
5678
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5679
     *
5680
     * @author ndiechburg <[email protected]>
5681
     */
5682
    public function set_seriousgame_mode()
5683
    {
5684
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5685
        $sql = "SELECT * FROM $lp_table
5686
                WHERE iid = ".$this->get_id();
5687
        $res = Database::query($sql);
5688
        if (Database::num_rows($res) > 0) {
5689
            $row = Database::fetch_array($res);
5690
            $force = $row['seriousgame_mode'];
5691
            if (1 == $force) {
5692
                $force = 0;
5693
            } elseif (0 == $force) {
5694
                $force = 1;
5695
            }
5696
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5697
			        WHERE iid = ".$this->get_id();
5698
            Database::query($sql);
5699
            $this->seriousgame_mode = $force;
5700
5701
            return $force;
5702
        }
5703
5704
        return -1;
5705
    }
5706
5707
    /**
5708
     * Updates the "scorm_debug" value that shows or hide the debug window.
5709
     *
5710
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5711
     */
5712
    public function update_scorm_debug()
5713
    {
5714
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5715
        $sql = "SELECT * FROM $lp_table
5716
                WHERE iid = ".$this->get_id();
5717
        $res = Database::query($sql);
5718
        if (Database::num_rows($res) > 0) {
5719
            $row = Database::fetch_array($res);
5720
            $force = $row['debug'];
5721
            if (1 == $force) {
5722
                $force = 0;
5723
            } elseif (0 == $force) {
5724
                $force = 1;
5725
            }
5726
            $sql = "UPDATE $lp_table SET debug = $force
5727
                    WHERE iid = ".$this->get_id();
5728
            Database::query($sql);
5729
            $this->scorm_debug = $force;
5730
5731
            return $force;
5732
        }
5733
5734
        return -1;
5735
    }
5736
5737
    /**
5738
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5739
     *
5740
     * @author Kevin Van Den Haute
5741
     *
5742
     * @param  array
5743
     */
5744
    public function tree_array($array)
5745
    {
5746
        $array = $this->sort_tree_array($array);
5747
        $this->create_tree_array($array);
5748
    }
5749
5750
    /**
5751
     * Creates an array with the elements of the learning path tree in it.
5752
     *
5753
     * @author Kevin Van Den Haute
5754
     *
5755
     * @param array $array
5756
     * @param int   $parent
5757
     * @param int   $depth
5758
     * @param array $tmp
5759
     */
5760
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5761
    {
5762
        if (is_array($array)) {
5763
            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...
5764
                if ($array[$i]['parent_item_id'] == $parent) {
5765
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5766
                        $tmp[] = $array[$i]['parent_item_id'];
5767
                        $depth++;
5768
                    }
5769
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5770
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5771
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5772
5773
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5774
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5775
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5776
                    $this->arrMenu[] = [
5777
                        'id' => $array[$i]['id'],
5778
                        'ref' => $ref,
5779
                        'item_type' => $array[$i]['item_type'],
5780
                        'title' => $array[$i]['title'],
5781
                        'title_raw' => $array[$i]['title_raw'],
5782
                        'path' => $path,
5783
                        'description' => $array[$i]['description'],
5784
                        'parent_item_id' => $array[$i]['parent_item_id'],
5785
                        'previous_item_id' => $array[$i]['previous_item_id'],
5786
                        'next_item_id' => $array[$i]['next_item_id'],
5787
                        'min_score' => $array[$i]['min_score'],
5788
                        'max_score' => $array[$i]['max_score'],
5789
                        'mastery_score' => $array[$i]['mastery_score'],
5790
                        'display_order' => $array[$i]['display_order'],
5791
                        'prerequisite' => $preq,
5792
                        'depth' => $depth,
5793
                        'audio' => $audio,
5794
                        'prerequisite_min_score' => $prerequisiteMinScore,
5795
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5796
                    ];
5797
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5798
                }
5799
            }
5800
        }
5801
    }
5802
5803
    /**
5804
     * Sorts a multi dimensional array by parent id and display order.
5805
     *
5806
     * @author Kevin Van Den Haute
5807
     *
5808
     * @param array $array (array with al the learning path items in it)
5809
     *
5810
     * @return array
5811
     */
5812
    public function sort_tree_array($array)
5813
    {
5814
        foreach ($array as $key => $row) {
5815
            $parent[$key] = $row['parent_item_id'];
5816
            $position[$key] = $row['display_order'];
5817
        }
5818
5819
        if (count($array) > 0) {
5820
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5821
        }
5822
5823
        return $array;
5824
    }
5825
5826
    /**
5827
     * Function that creates a html list of learning path items so that we can add audio files to them.
5828
     *
5829
     * @author Kevin Van Den Haute
5830
     *
5831
     * @return string
5832
     */
5833
    public function overview()
5834
    {
5835
        $return = '';
5836
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5837
5838
        // we need to start a form when we want to update all the mp3 files
5839
        if ('true' == $update_audio) {
5840
            $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">';
5841
        }
5842
        $return .= '<div id="message"></div>';
5843
        if (0 == count($this->items)) {
5844
            $return .= Display::return_message(get_lang('You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'), 'normal');
5845
        } else {
5846
            $return_audio = '<table class="data_table">';
5847
            $return_audio .= '<tr>';
5848
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5849
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5850
            $return_audio .= '</tr>';
5851
5852
            if ('true' != $update_audio) {
5853
                $return .= '<div class="col-md-12">';
5854
                $return .= self::return_new_tree($update_audio);
5855
                $return .= '</div>';
5856
                $return .= Display::div(
5857
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5858
                    ['style' => 'float:left; margin-top:15px;width:100%']
5859
                );
5860
            } else {
5861
                $return_audio .= self::return_new_tree($update_audio);
5862
                $return .= $return_audio.'</table>';
5863
            }
5864
5865
            // We need to close the form when we are updating the mp3 files.
5866
            if ('true' == $update_audio) {
5867
                $return .= '<div class="footer-audio">';
5868
                $return .= Display::button(
5869
                    'save_audio',
5870
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5871
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5872
                );
5873
                $return .= '</div>';
5874
            }
5875
        }
5876
5877
        // We need to close the form when we are updating the mp3 files.
5878
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5879
            $return .= '</form>';
5880
        }
5881
5882
        return $return;
5883
    }
5884
5885
    /**
5886
     * @param string $update_audio
5887
     *
5888
     * @return array
5889
     */
5890
    public function processBuildMenuElements($update_audio = 'false')
5891
    {
5892
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5893
        $arrLP = $this->getItemsForForm();
5894
5895
        $this->tree_array($arrLP);
5896
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5897
        unset($this->arrMenu);
5898
        $default_data = null;
5899
        $default_content = null;
5900
        $elements = [];
5901
        $return_audio = null;
5902
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5903
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5904
        $countItems = count($arrLP);
5905
5906
        $upIcon = Display::return_icon(
5907
            'up.png',
5908
            get_lang('Up'),
5909
            [],
5910
            ICON_SIZE_TINY
5911
        );
5912
5913
        $disableUpIcon = Display::return_icon(
5914
            'up_na.png',
5915
            get_lang('Up'),
5916
            [],
5917
            ICON_SIZE_TINY
5918
        );
5919
5920
        $downIcon = Display::return_icon(
5921
            'down.png',
5922
            get_lang('Down'),
5923
            [],
5924
            ICON_SIZE_TINY
5925
        );
5926
5927
        $disableDownIcon = Display::return_icon(
5928
            'down_na.png',
5929
            get_lang('Down'),
5930
            [],
5931
            ICON_SIZE_TINY
5932
        );
5933
5934
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5935
5936
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5937
        $plugin = null;
5938
        if ($pluginCalendar) {
5939
            $plugin = LearningCalendarPlugin::create();
5940
        }
5941
5942
        for ($i = 0; $i < $countItems; $i++) {
5943
            $parent_id = $arrLP[$i]['parent_item_id'];
5944
            $title = $arrLP[$i]['title'];
5945
            $title_cut = $arrLP[$i]['title_raw'];
5946
            if (false === $show) {
5947
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5948
            }
5949
            // Link for the documents
5950
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT == $arrLP[$i]['item_type']) {
5951
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5952
                $title_cut = Display::url(
5953
                    $title_cut,
5954
                    $url,
5955
                    [
5956
                        'class' => 'ajax moved',
5957
                        'data-title' => $title,
5958
                        'title' => $title,
5959
                    ]
5960
                );
5961
            }
5962
5963
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5964
            if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5965
                Session::write('pathItem', $arrLP[$i]['path']);
5966
            }
5967
5968
            $oddClass = 'row_even';
5969
            if (0 == ($i % 2)) {
5970
                $oddClass = 'row_odd';
5971
            }
5972
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5973
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5974
5975
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5976
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5977
            } else {
5978
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5979
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5980
                } else {
5981
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5982
                        $icon = Display::return_icon('certificate.png');
5983
                    } else {
5984
                        $icon = Display::return_icon('folder_document.png');
5985
                    }
5986
                }
5987
            }
5988
5989
            // The audio column.
5990
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5991
            $audio = '';
5992
            if (!$update_audio || 'true' != $update_audio) {
5993
                if (empty($arrLP[$i]['audio'])) {
5994
                    $audio .= '';
5995
                }
5996
            } else {
5997
                $types = self::getChapterTypes();
5998
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5999
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6000
                    if (!empty($arrLP[$i]['audio'])) {
6001
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6002
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6003
                    }
6004
                }
6005
            }
6006
6007
            $return_audio .= Display::span($icon.' '.$title).
6008
                Display::tag(
6009
                    'td',
6010
                    $audio,
6011
                    ['style' => '']
6012
                );
6013
            $return_audio .= '</td>';
6014
            $move_icon = '';
6015
            $move_item_icon = '';
6016
            $edit_icon = '';
6017
            $delete_icon = '';
6018
            $audio_icon = '';
6019
            $prerequisities_icon = '';
6020
            $forumIcon = '';
6021
            $previewIcon = '';
6022
            $pluginCalendarIcon = '';
6023
            $orderIcons = '';
6024
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6025
6026
            if ($is_allowed_to_edit) {
6027
                if (!$update_audio || 'true' != $update_audio) {
6028
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
6029
                        $move_icon .= '<a class="moved" href="#">';
6030
                        $move_icon .= Display::return_icon(
6031
                            'move_everywhere.png',
6032
                            get_lang('Move'),
6033
                            [],
6034
                            ICON_SIZE_TINY
6035
                        );
6036
                        $move_icon .= '</a>';
6037
                    }
6038
                }
6039
6040
                // No edit for this item types
6041
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6042
                    if ('dir' != $arrLP[$i]['item_type']) {
6043
                        $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">';
6044
                        $edit_icon .= Display::return_icon(
6045
                            'edit.png',
6046
                            get_lang('Edit section description/name'),
6047
                            [],
6048
                            ICON_SIZE_TINY
6049
                        );
6050
                        $edit_icon .= '</a>';
6051
6052
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6053
                            $forumThread = null;
6054
                            if (isset($this->items[$arrLP[$i]['id']])) {
6055
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6056
                                    $this->course_int_id,
6057
                                    $this->lp_session_id
6058
                                );
6059
                            }
6060
                            if ($forumThread) {
6061
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6062
                                        'action' => 'dissociate_forum',
6063
                                        'id' => $arrLP[$i]['id'],
6064
                                        'lp_id' => $this->lp_id,
6065
                                    ]);
6066
                                $forumIcon = Display::url(
6067
                                    Display::return_icon(
6068
                                        'forum.png',
6069
                                        get_lang('Dissociate the forum of this learning path item'),
6070
                                        [],
6071
                                        ICON_SIZE_TINY
6072
                                    ),
6073
                                    $forumIconUrl,
6074
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6075
                                );
6076
                            } else {
6077
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6078
                                        'action' => 'create_forum',
6079
                                        'id' => $arrLP[$i]['id'],
6080
                                        'lp_id' => $this->lp_id,
6081
                                    ]);
6082
                                $forumIcon = Display::url(
6083
                                    Display::return_icon(
6084
                                        'forum.png',
6085
                                        get_lang('Associate a forum to this learning path item'),
6086
                                        [],
6087
                                        ICON_SIZE_TINY
6088
                                    ),
6089
                                    $forumIconUrl,
6090
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6091
                                );
6092
                            }
6093
                        }
6094
                    } else {
6095
                        $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">';
6096
                        $edit_icon .= Display::return_icon(
6097
                            'edit.png',
6098
                            get_lang('Edit section description/name'),
6099
                            [],
6100
                            ICON_SIZE_TINY
6101
                        );
6102
                        $edit_icon .= '</a>';
6103
                    }
6104
                } else {
6105
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6106
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6107
                        $edit_icon .= Display::return_icon(
6108
                            'edit.png',
6109
                            get_lang('Edit'),
6110
                            [],
6111
                            ICON_SIZE_TINY
6112
                        );
6113
                        $edit_icon .= '</a>';
6114
                    }
6115
                }
6116
6117
                if ($pluginCalendar) {
6118
                    $pluginLink = $pluginUrl.
6119
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6120
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6121
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6122
                    if ($itemInfo && 1 == $itemInfo['value']) {
6123
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6124
                    }
6125
                    $pluginCalendarIcon = Display::url(
6126
                        $iconCalendar,
6127
                        $pluginLink,
6128
                        ['class' => 'btn btn-default']
6129
                    );
6130
                }
6131
6132
                if ('final_item' != $arrLP[$i]['item_type']) {
6133
                    $orderIcons = Display::url(
6134
                        $upIcon,
6135
                        'javascript:void(0)',
6136
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6137
                    );
6138
                    $orderIcons .= Display::url(
6139
                        $downIcon,
6140
                        'javascript:void(0)',
6141
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6142
                    );
6143
                }
6144
6145
                $delete_icon .= ' <a
6146
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6147
                    onclick="return confirmation(\''.addslashes($title).'\');"
6148
                    class="btn btn-default">';
6149
                $delete_icon .= Display::return_icon(
6150
                    'delete.png',
6151
                    get_lang('Delete section'),
6152
                    [],
6153
                    ICON_SIZE_TINY
6154
                );
6155
                $delete_icon .= '</a>';
6156
6157
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6158
                $previewImage = Display::return_icon(
6159
                    'preview_view.png',
6160
                    get_lang('Preview'),
6161
                    [],
6162
                    ICON_SIZE_TINY
6163
                );
6164
6165
                switch ($arrLP[$i]['item_type']) {
6166
                    case TOOL_DOCUMENT:
6167
                    case TOOL_LP_FINAL_ITEM:
6168
                    case TOOL_READOUT_TEXT:
6169
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6170
                        $previewIcon = Display::url(
6171
                            $previewImage,
6172
                            $urlPreviewLink,
6173
                            [
6174
                                'target' => '_blank',
6175
                                'class' => 'btn btn-default',
6176
                                'data-title' => $arrLP[$i]['title'],
6177
                                'title' => $arrLP[$i]['title'],
6178
                            ]
6179
                        );
6180
                        break;
6181
                    case TOOL_THREAD:
6182
                    case TOOL_FORUM:
6183
                    case TOOL_QUIZ:
6184
                    case TOOL_STUDENTPUBLICATION:
6185
                    case TOOL_LP_FINAL_ITEM:
6186
                    case TOOL_LINK:
6187
                        $class = 'btn btn-default';
6188
                        $target = '_blank';
6189
                        $link = self::rl_get_resource_link_for_learnpath(
6190
                            $this->course_int_id,
6191
                            $this->lp_id,
6192
                            $arrLP[$i]['id'],
6193
                            0
6194
                        );
6195
                        $previewIcon = Display::url(
6196
                            $previewImage,
6197
                            $link,
6198
                            [
6199
                                'class' => $class,
6200
                                'data-title' => $arrLP[$i]['title'],
6201
                                'title' => $arrLP[$i]['title'],
6202
                                'target' => $target,
6203
                            ]
6204
                        );
6205
                        break;
6206
                    default:
6207
                        $previewIcon = Display::url(
6208
                            $previewImage,
6209
                            $url.'&action=view_item',
6210
                            ['class' => 'btn btn-default', 'target' => '_blank']
6211
                        );
6212
                        break;
6213
                }
6214
6215
                if ('dir' != $arrLP[$i]['item_type']) {
6216
                    $prerequisities_icon = Display::url(
6217
                        Display::return_icon(
6218
                            'accept.png',
6219
                            get_lang('Prerequisites'),
6220
                            [],
6221
                            ICON_SIZE_TINY
6222
                        ),
6223
                        $url.'&action=edit_item_prereq',
6224
                        ['class' => 'btn btn-default']
6225
                    );
6226
                    if ('final_item' != $arrLP[$i]['item_type']) {
6227
                        $move_item_icon = Display::url(
6228
                            Display::return_icon(
6229
                                'move.png',
6230
                                get_lang('Move'),
6231
                                [],
6232
                                ICON_SIZE_TINY
6233
                            ),
6234
                            $url.'&action=move_item',
6235
                            ['class' => 'btn btn-default']
6236
                        );
6237
                    }
6238
                    $audio_icon = Display::url(
6239
                        Display::return_icon(
6240
                            'audio.png',
6241
                            get_lang('Upload'),
6242
                            [],
6243
                            ICON_SIZE_TINY
6244
                        ),
6245
                        $url.'&action=add_audio',
6246
                        ['class' => 'btn btn-default']
6247
                    );
6248
                }
6249
            }
6250
            if ('true' != $update_audio) {
6251
                $row = $move_icon.' '.$icon.
6252
                    Display::span($title_cut).
6253
                    Display::tag(
6254
                        'div',
6255
                        "<div class=\"btn-group btn-group-xs\">
6256
                                    $previewIcon
6257
                                    $audio
6258
                                    $edit_icon
6259
                                    $pluginCalendarIcon
6260
                                    $forumIcon
6261
                                    $prerequisities_icon
6262
                                    $move_item_icon
6263
                                    $audio_icon
6264
                                    $orderIcons
6265
                                    $delete_icon
6266
                                </div>",
6267
                        ['class' => 'btn-toolbar button_actions']
6268
                    );
6269
            } else {
6270
                $row =
6271
                    Display::span($title.$icon).
6272
                    Display::span($audio, ['class' => 'button_actions']);
6273
            }
6274
6275
            $default_data[$arrLP[$i]['id']] = $row;
6276
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6277
6278
            if (empty($parent_id)) {
6279
                $elements[$arrLP[$i]['id']]['data'] = $row;
6280
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6281
            } else {
6282
                $parent_arrays = [];
6283
                if ($arrLP[$i]['depth'] > 1) {
6284
                    // Getting list of parents
6285
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6286
                        foreach ($arrLP as $item) {
6287
                            if ($item['id'] == $parent_id) {
6288
                                if (0 == $item['parent_item_id']) {
6289
                                    $parent_id = $item['id'];
6290
                                    break;
6291
                                } else {
6292
                                    $parent_id = $item['parent_item_id'];
6293
                                    if (empty($parent_arrays)) {
6294
                                        $parent_arrays[] = intval($item['id']);
6295
                                    }
6296
                                    $parent_arrays[] = $parent_id;
6297
                                    break;
6298
                                }
6299
                            }
6300
                        }
6301
                    }
6302
                }
6303
6304
                if (!empty($parent_arrays)) {
6305
                    $parent_arrays = array_reverse($parent_arrays);
6306
                    $val = '$elements';
6307
                    $x = 0;
6308
                    foreach ($parent_arrays as $item) {
6309
                        if ($x != count($parent_arrays) - 1) {
6310
                            $val .= '["'.$item.'"]["children"]';
6311
                        } else {
6312
                            $val .= '["'.$item.'"]["children"]';
6313
                        }
6314
                        $x++;
6315
                    }
6316
                    $val .= "";
6317
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6318
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6319
                } else {
6320
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6321
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6322
                }
6323
            }
6324
        }
6325
6326
        return [
6327
            'elements' => $elements,
6328
            'default_data' => $default_data,
6329
            'default_content' => $default_content,
6330
            'return_audio' => $return_audio,
6331
        ];
6332
    }
6333
6334
    /**
6335
     * @param string $updateAudio true/false strings
6336
     *
6337
     * @return string
6338
     */
6339
    public function returnLpItemList($updateAudio)
6340
    {
6341
        $result = $this->processBuildMenuElements($updateAudio);
6342
6343
        $html = self::print_recursive(
6344
            $result['elements'],
6345
            $result['default_data'],
6346
            $result['default_content']
6347
        );
6348
6349
        if (!empty($html)) {
6350
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6351
        }
6352
6353
        return $html;
6354
    }
6355
6356
    /**
6357
     * @param string $update_audio
6358
     * @param bool   $drop_element_here
6359
     *
6360
     * @return string
6361
     */
6362
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6363
    {
6364
        $result = $this->processBuildMenuElements($update_audio);
6365
6366
        $list = '<ul id="lp_item_list">';
6367
        $tree = $this->print_recursive(
6368
            $result['elements'],
6369
            $result['default_data'],
6370
            $result['default_content']
6371
        );
6372
6373
        if (!empty($tree)) {
6374
            $list .= $tree;
6375
        } else {
6376
            if ($drop_element_here) {
6377
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6378
            }
6379
        }
6380
        $list .= '</ul>';
6381
6382
        $return = Display::panelCollapse(
6383
            $this->name,
6384
            $list,
6385
            'scorm-list',
6386
            null,
6387
            'scorm-list-accordion',
6388
            'scorm-list-collapse'
6389
        );
6390
6391
        if ('true' === $update_audio) {
6392
            $return = $result['return_audio'];
6393
        }
6394
6395
        return $return;
6396
    }
6397
6398
    /**
6399
     * @param array $elements
6400
     * @param array $default_data
6401
     * @param array $default_content
6402
     *
6403
     * @return string
6404
     */
6405
    public function print_recursive($elements, $default_data, $default_content)
6406
    {
6407
        $return = '';
6408
        foreach ($elements as $key => $item) {
6409
            if (isset($item['load_data']) || empty($item['data'])) {
6410
                $item['data'] = $default_data[$item['load_data']];
6411
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6412
            }
6413
            $sub_list = '';
6414
            if (isset($item['type']) && 'dir' === $item['type']) {
6415
                // empty value
6416
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6417
            }
6418
            if (empty($item['children'])) {
6419
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6420
                $active = null;
6421
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6422
                    $active = 'active';
6423
                }
6424
                $return .= Display::tag(
6425
                    'li',
6426
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6427
                    ['id' => $key, 'class' => 'record li_container']
6428
                );
6429
            } else {
6430
                // Sections
6431
                $data = '';
6432
                if (isset($item['children'])) {
6433
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6434
                }
6435
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6436
                $return .= Display::tag(
6437
                    'li',
6438
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6439
                    ['id' => $key, 'class' => 'record li_container']
6440
                );
6441
            }
6442
        }
6443
6444
        return $return;
6445
    }
6446
6447
    /**
6448
     * This function builds the action menu.
6449
     *
6450
     * @param bool $returnContent          Optional
6451
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6452
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6453
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6454
     *
6455
     * @return string
6456
     */
6457
    public function build_action_menu(
6458
        $returnContent = false,
6459
        $showRequirementButtons = true,
6460
        $isConfigPage = false,
6461
        $allowExpand = true
6462
    ) {
6463
        $actionsRight = '';
6464
        $actionsLeft = Display::url(
6465
            Display::return_icon(
6466
                'back.png',
6467
                get_lang('Back to learning paths'),
6468
                '',
6469
                ICON_SIZE_MEDIUM
6470
            ),
6471
            'lp_controller.php?'.api_get_cidreq()
6472
        );
6473
        $actionsLeft .= Display::url(
6474
            Display::return_icon(
6475
                'preview_view.png',
6476
                get_lang('Preview'),
6477
                '',
6478
                ICON_SIZE_MEDIUM
6479
            ),
6480
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6481
                'action' => 'view',
6482
                'lp_id' => $this->lp_id,
6483
                'isStudentView' => 'true',
6484
            ])
6485
        );
6486
6487
        $actionsLeft .= Display::url(
6488
            Display::return_icon(
6489
                'upload_audio.png',
6490
                get_lang('Add audio'),
6491
                '',
6492
                ICON_SIZE_MEDIUM
6493
            ),
6494
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6495
                'action' => 'admin_view',
6496
                'lp_id' => $this->lp_id,
6497
                'updateaudio' => 'true',
6498
            ])
6499
        );
6500
6501
        $subscriptionSettings = self::getSubscriptionSettings();
6502
6503
        $request = api_request_uri();
6504
        if (false === strpos($request, 'edit')) {
6505
            $actionsLeft .= Display::url(
6506
                Display::return_icon(
6507
                    'settings.png',
6508
                    get_lang('Course settings'),
6509
                    '',
6510
                    ICON_SIZE_MEDIUM
6511
                ),
6512
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6513
                    'action' => 'edit',
6514
                    'lp_id' => $this->lp_id,
6515
                ])
6516
            );
6517
        }
6518
6519
        if (false === strpos($request, 'build') && false === strpos($request, 'add_item')) {
6520
            $actionsLeft .= Display::url(
6521
                Display::return_icon(
6522
                    'edit.png',
6523
                    get_lang('Edit'),
6524
                    '',
6525
                    ICON_SIZE_MEDIUM
6526
                ),
6527
                'lp_controller.php?'.http_build_query([
6528
                    'action' => 'build',
6529
                    'lp_id' => $this->lp_id,
6530
                ]).'&'.api_get_cidreq()
6531
            );
6532
        }
6533
6534
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6535
            if (1 == $this->subscribeUsers &&
6536
                $subscriptionSettings['allow_add_users_to_lp']) {
6537
                $actionsLeft .= Display::url(
6538
                    Display::return_icon(
6539
                        'user.png',
6540
                        get_lang('Subscribe users to learning path'),
6541
                        '',
6542
                        ICON_SIZE_MEDIUM
6543
                    ),
6544
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6545
                );
6546
            }
6547
        }
6548
6549
        if ($allowExpand) {
6550
            /*$actionsLeft .= Display::url(
6551
                Display::return_icon(
6552
                    'expand.png',
6553
                    get_lang('Expand'),
6554
                    ['id' => 'expand'],
6555
                    ICON_SIZE_MEDIUM
6556
                ).
6557
                Display::return_icon(
6558
                    'contract.png',
6559
                    get_lang('Collapse'),
6560
                    ['id' => 'contract', 'class' => 'hide'],
6561
                    ICON_SIZE_MEDIUM
6562
                ),
6563
                '#',
6564
                ['role' => 'button', 'id' => 'hide_bar_template']
6565
            );*/
6566
        }
6567
6568
        if ($showRequirementButtons) {
6569
            $buttons = [
6570
                [
6571
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6572
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6573
                        'action' => 'set_previous_step_as_prerequisite',
6574
                        'lp_id' => $this->lp_id,
6575
                    ]),
6576
                ],
6577
                [
6578
                    'title' => get_lang('Clear all prerequisites'),
6579
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6580
                        'action' => 'clear_prerequisites',
6581
                        'lp_id' => $this->lp_id,
6582
                    ]),
6583
                ],
6584
            ];
6585
            $actionsRight = Display::groupButtonWithDropDown(
6586
                get_lang('Prerequisites options'),
6587
                $buttons,
6588
                true
6589
            );
6590
        }
6591
6592
        $toolbar = Display::toolbarAction(
6593
            'actions-lp-controller',
6594
            [$actionsLeft, $actionsRight]
6595
        );
6596
6597
        if ($returnContent) {
6598
            return $toolbar;
6599
        }
6600
6601
        echo $toolbar;
6602
    }
6603
6604
    /**
6605
     * Creates the default learning path folder.
6606
     *
6607
     * @param array $course
6608
     * @param int   $creatorId
6609
     *
6610
     * @return bool
6611
     */
6612
    public static function generate_learning_path_folder($course, $creatorId = 0)
6613
    {
6614
        // Creating learning_path folder
6615
        $dir = 'learning_path';
6616
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6617
        $folder = false;
6618
        $folderData = create_unexisting_directory(
6619
            $course,
6620
            $creatorId,
6621
            0,
6622
            null,
6623
            0,
6624
            '',
6625
            $dir,
6626
            get_lang('Learning paths'),
6627
            0
6628
        );
6629
6630
        if (!empty($folderData)) {
6631
            $folder = true;
6632
        }
6633
6634
        return $folder;
6635
    }
6636
6637
    /**
6638
     * @param array  $course
6639
     * @param string $lp_name
6640
     * @param int    $creatorId
6641
     *
6642
     * @return array
6643
     */
6644
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6645
    {
6646
        $filepath = '';
6647
        $dir = '/learning_path/';
6648
6649
        if (empty($lp_name)) {
6650
            $lp_name = $this->name;
6651
        }
6652
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6653
        $folder = self::generate_learning_path_folder($course, $creatorId);
6654
6655
        // Limits title size
6656
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6657
        $dir = $dir.$title;
6658
6659
        // Creating LP folder
6660
        $documentId = null;
6661
        if ($folder) {
6662
            $folderData = create_unexisting_directory(
6663
                $course,
6664
                $creatorId,
6665
                0,
6666
                0,
6667
                0,
6668
                $filepath,
6669
                $dir,
6670
                $lp_name
6671
            );
6672
            if (!empty($folderData)) {
6673
                $folder = true;
6674
            }
6675
6676
            $documentId = $folderData->getIid();
6677
            $dir = $dir.'/';
6678
            if ($folder) {
6679
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6680
            }
6681
        }
6682
6683
        if (empty($documentId)) {
6684
            $dir = api_remove_trailing_slash($dir);
6685
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6686
        }
6687
6688
        $array = [
6689
            'dir' => $dir,
6690
            'filepath' => $filepath,
6691
            'folder' => $folder,
6692
            'id' => $documentId,
6693
        ];
6694
6695
        return $array;
6696
    }
6697
6698
    /**
6699
     * Create a new document //still needs some finetuning.
6700
     *
6701
     * @param array  $courseInfo
6702
     * @param string $content
6703
     * @param string $title
6704
     * @param string $extension
6705
     * @param int    $parentId
6706
     * @param int    $creatorId  creator id
6707
     *
6708
     * @return int
6709
     */
6710
    public function create_document(
6711
        $courseInfo,
6712
        $content = '',
6713
        $title = '',
6714
        $extension = 'html',
6715
        $parentId = 0,
6716
        $creatorId = 0
6717
    ) {
6718
        if (!empty($courseInfo)) {
6719
            $course_id = $courseInfo['real_id'];
6720
        } else {
6721
            $course_id = api_get_course_int_id();
6722
        }
6723
6724
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6725
        $sessionId = api_get_session_id();
6726
6727
        // Generates folder
6728
        $result = $this->generate_lp_folder($courseInfo);
6729
        $dir = $result['dir'];
6730
6731
        if (empty($parentId) || '/' == $parentId) {
6732
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6733
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6734
6735
            if ('/' === $parentId) {
6736
                $dir = '/';
6737
            }
6738
6739
            // Please, do not modify this dirname formatting.
6740
            if (strstr($dir, '..')) {
6741
                $dir = '/';
6742
            }
6743
6744
            if (!empty($dir[0]) && '.' == $dir[0]) {
6745
                $dir = substr($dir, 1);
6746
            }
6747
            if (!empty($dir[0]) && '/' != $dir[0]) {
6748
                $dir = '/'.$dir;
6749
            }
6750
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6751
                $dir .= '/';
6752
            }
6753
        } else {
6754
            $parentInfo = DocumentManager::get_document_data_by_id(
6755
                $parentId,
6756
                $courseInfo['code']
6757
            );
6758
            if (!empty($parentInfo)) {
6759
                $dir = $parentInfo['path'].'/';
6760
            }
6761
        }
6762
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6763
        // is already escaped twice when it gets here.
6764
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6765
        if (!empty($title)) {
6766
            $title = api_replace_dangerous_char(stripslashes($title));
6767
        } else {
6768
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6769
        }
6770
6771
        $title = disable_dangerous_file($title);
6772
        $filename = $title;
6773
        $content = !empty($content) ? $content : $_POST['content_lp'];
6774
        $tmp_filename = $filename;
6775
        $filename = $tmp_filename.'.'.$extension;
6776
6777
        if ('html' === $extension) {
6778
            $content = stripslashes($content);
6779
            $content = str_replace(
6780
                api_get_path(WEB_COURSE_PATH),
6781
                api_get_path(REL_PATH).'courses/',
6782
                $content
6783
            );
6784
6785
            // Change the path of mp3 to absolute.
6786
            // The first regexp deals with :// urls.
6787
            $content = preg_replace(
6788
                "|(flashvars=\"file=)([^:/]+)/|",
6789
                "$1".api_get_path(
6790
                    REL_COURSE_PATH
6791
                ).$courseInfo['path'].'/document/',
6792
                $content
6793
            );
6794
            // The second regexp deals with audio/ urls.
6795
            $content = preg_replace(
6796
                "|(flashvars=\"file=)([^/]+)/|",
6797
                "$1".api_get_path(
6798
                    REL_COURSE_PATH
6799
                ).$courseInfo['path'].'/document/$2/',
6800
                $content
6801
            );
6802
            // For flv player: To prevent edition problem with firefox,
6803
            // we have to use a strange tip (don't blame me please).
6804
            $content = str_replace(
6805
                '</body>',
6806
                '<style type="text/css">body{}</style></body>',
6807
                $content
6808
            );
6809
        }
6810
6811
        $save_file_path = $dir.$filename;
6812
6813
        $document = DocumentManager::addDocument(
6814
            $courseInfo,
6815
            $save_file_path,
6816
            'file',
6817
            '',
6818
            $tmp_filename,
6819
            '',
6820
            0, //readonly
6821
            true,
6822
            null,
6823
            $sessionId,
6824
            $creatorId,
6825
            false,
6826
            $content,
6827
            $parentId
6828
        );
6829
6830
        $document_id = $document->getIid();
6831
        if ($document_id) {
6832
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6833
            $new_title = $originalTitle;
6834
6835
            if ($new_comment || $new_title) {
6836
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6837
                $ct = '';
6838
                if ($new_comment) {
6839
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6840
                }
6841
                if ($new_title) {
6842
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6843
                }
6844
6845
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6846
                        WHERE c_id = $course_id AND id = $document_id ";
6847
                Database::query($sql);
6848
            }
6849
        }
6850
6851
        return $document_id;
6852
    }
6853
6854
    /**
6855
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6856
     *
6857
     */
6858
    public function edit_document()
6859
    {
6860
        $repo = Container::getDocumentRepository();
6861
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6862
            $id = (int) $_REQUEST['document_id'];
6863
            /** @var CDocument $document */
6864
            $document = $repo->find($id);
6865
6866
            if ($document->getResourceNode()->hasEditableContent()) {
6867
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6868
            }
6869
6870
            $document->setTitle($_REQUEST['title']);
6871
            $repo->getEntityManager()->persist($document);
6872
            $repo->getEntityManager()->flush();
6873
        }
6874
    }
6875
6876
    /**
6877
     * Displays the selected item, with a panel for manipulating the item.
6878
     *
6879
     * @param CLpItem $lpItem
6880
     * @param string  $msg
6881
     * @param bool    $show_actions
6882
     *
6883
     * @return string
6884
     */
6885
    public function display_item($lpItem, $msg = null, $show_actions = true)
6886
    {
6887
        $course_id = api_get_course_int_id();
6888
        $return = '';
6889
6890
        if (empty($lpItem)) {
6891
            return '';
6892
        }
6893
        $item_id = $lpItem->getIid();
6894
        $itemType = $lpItem->getItemType();
6895
        $lpId = $lpItem->getLpId();
6896
        $path = $lpItem->getPath();
6897
6898
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6899
6900
        // Prevents wrong parent selection for document, see Bug#1251.
6901
        if ('dir' !== $itemType) {
6902
            Session::write('parent_item_id', $lpItem->getParentItemId());
6903
        }
6904
6905
        if ($show_actions) {
6906
            $return .= $this->display_manipulate($lpItem);
6907
        }
6908
        $return .= '<div style="padding:10px;">';
6909
6910
        if ('' != $msg) {
6911
            $return .= $msg;
6912
        }
6913
6914
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6915
6916
        switch ($itemType) {
6917
            case TOOL_THREAD:
6918
                $link = $this->rl_get_resource_link_for_learnpath(
6919
                    $course_id,
6920
                    $lpId,
6921
                    $item_id,
6922
                    0
6923
                );
6924
                $return .= Display::url(
6925
                    get_lang('Go to thread'),
6926
                    $link,
6927
                    ['class' => 'btn btn-primary']
6928
                );
6929
                break;
6930
            case TOOL_FORUM:
6931
                $return .= Display::url(
6932
                    get_lang('Go to the forum'),
6933
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6934
                    ['class' => 'btn btn-primary']
6935
                );
6936
                break;
6937
            case TOOL_QUIZ:
6938
                if (!empty($path)) {
6939
                    $exercise = new Exercise();
6940
                    $exercise->read($path);
6941
                    $return .= $exercise->description.'<br />';
6942
                    $return .= Display::url(
6943
                        get_lang('Go to exercise'),
6944
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6945
                        ['class' => 'btn btn-primary']
6946
                    );
6947
                }
6948
                break;
6949
            case TOOL_LP_FINAL_ITEM:
6950
                $return .= $this->getSavedFinalItem();
6951
                break;
6952
            case TOOL_DOCUMENT:
6953
            case TOOL_READOUT_TEXT:
6954
                $repo = Container::getDocumentRepository();
6955
                /** @var CDocument $document */
6956
                $document = $repo->find($lpItem->getPath());
6957
                $return .= $this->display_document($document, true, true);
6958
                break;
6959
            case TOOL_HOTPOTATOES:
6960
                $return .= $this->display_document($document, false, true);
6961
                break;
6962
        }
6963
        $return .= '</div>';
6964
6965
        return $return;
6966
    }
6967
6968
    /**
6969
     * Shows the needed forms for editing a specific item.
6970
     *
6971
     * @param CLpItem $lpItem
6972
     *
6973
     * @throws Exception
6974
     * @throws HTML_QuickForm_Error
6975
     *
6976
     * @return string
6977
     */
6978
    public function display_edit_item($lpItem)
6979
    {
6980
        $course_id = api_get_course_int_id();
6981
        $return = '';
6982
6983
        if (empty($lpItem)) {
6984
            return '';
6985
        }
6986
        $item_id  = $lpItem->getIid();
6987
        $itemType = $lpItem->getItemType();
6988
        $path = $lpItem->getPath();
6989
6990
        switch ($itemType) {
6991
            case 'dir':
6992
            case 'asset':
6993
            case 'sco':
6994
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6995
                    $return .= $this->display_manipulate($lpItem);
6996
                    $return .= $this->display_item_form(
6997
                        $lpItem,
6998
                        get_lang('Edit the current section').' :',
6999
                        'edit'
7000
                    );
7001
                } else {
7002
                    $return .= $this->display_item_form(
7003
                        $lpItem,
7004
                        get_lang('Edit the current section').' :',
7005
                        'edit_item'
7006
                    );
7007
                }
7008
                break;
7009
            case TOOL_DOCUMENT:
7010
            case TOOL_READOUT_TEXT:
7011
                $return .= $this->display_manipulate($lpItem);
7012
                if (TOOL_DOCUMENT === $itemType) {
7013
                    $return .= $this->display_document_form('edit', $lpItem);
7014
                }
7015
7016
                if (TOOL_READOUT_TEXT === $itemType) {
7017
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7018
                }
7019
                break;
7020
            case TOOL_LINK:
7021
                $link = null;
7022
                if (!empty($path)) {
7023
                    $repo = Container::getLinkRepository();
7024
                    $link = $repo->find($path);
7025
                }
7026
                $return .= $this->display_manipulate($lpItem);
7027
                $return .= $this->display_link_form('edit', $lpItem, $link);
7028
                break;
7029
            case TOOL_LP_FINAL_ITEM:
7030
                Session::write('finalItem', true);
7031
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7032
                $sql = "SELECT lp.*, doc.path as dir
7033
                        FROM $tbl_lp_item as lp
7034
                        LEFT JOIN $tbl_doc as doc
7035
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7036
                        WHERE
7037
                            doc.c_id = $course_id AND
7038
                            lp.iid = ".$item_id;
7039
                $res_step = Database::query($sql);
7040
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7041
                $return .= $this->display_manipulate($lpItem);
7042
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7043
                break;
7044
            case TOOL_QUIZ:
7045
                if (!empty($path)) {
7046
                    $repo = Container::getExerciseRepository();
7047
                    $resource = $repo->find($path);
7048
                }
7049
                $return .= $this->display_manipulate($lpItem);
7050
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7051
                break;
7052
            case TOOL_HOTPOTATOES:
7053
                $return .= $this->display_manipulate($lpItem);
7054
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7055
                break;
7056
            case TOOL_STUDENTPUBLICATION:
7057
                if (!empty($path)) {
7058
                    $repo = Container::getStudentPublicationRepository();
7059
                    $resource = $repo->find($path);
7060
                }
7061
                $return .= $this->display_manipulate($lpItem);
7062
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7063
                break;
7064
            case TOOL_FORUM:
7065
                if (!empty($path)) {
7066
                    $repo = Container::getForumRepository();
7067
                    $resource = $repo->find($path);
7068
                }
7069
                $return .= $this->display_manipulate($lpItem);
7070
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7071
                break;
7072
            case TOOL_THREAD:
7073
                if (!empty($path)) {
7074
                    $repo = Container::getForumPostRepository();
7075
                    $resource = $repo->find($path);
7076
                }
7077
                $return .= $this->display_manipulate($lpItem);
7078
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7079
                break;
7080
        }
7081
7082
        return $return;
7083
    }
7084
7085
    /**
7086
     * Function that displays a list with al the resources that
7087
     * could be added to the learning path.
7088
     *
7089
     * @throws Exception
7090
     * @throws HTML_QuickForm_Error
7091
     *
7092
     * @return bool
7093
     */
7094
    public function display_resources()
7095
    {
7096
        $course_code = api_get_course_id();
7097
7098
        // Get all the docs.
7099
        $documents = $this->get_documents(true);
7100
7101
        // Get all the exercises.
7102
        $exercises = $this->get_exercises();
7103
7104
        // Get all the links.
7105
        $links = $this->get_links();
7106
7107
        // Get all the student publications.
7108
        $works = $this->get_student_publications();
7109
7110
        // Get all the forums.
7111
        $forums = $this->get_forums(null, $course_code);
7112
7113
        // Get the final item form (see BT#11048) .
7114
        $finish = $this->getFinalItemForm();
7115
7116
        $headers = [
7117
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7118
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7119
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7120
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7121
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7122
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7123
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7124
        ];
7125
7126
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7127
        //$dir = $this->display_item_form('dir', get_lang('EnterDataAdd section'), 'add_item');
7128
        $dir = $this->displayNewSectionForm();
7129
7130
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7131
7132
        echo Display::tabs(
7133
            $headers,
7134
            [
7135
                $documents,
7136
                $exercises,
7137
                $links,
7138
                $works,
7139
                $forums,
7140
                $dir,
7141
                $finish,
7142
            ],
7143
            'resource_tab',
7144
            [],
7145
            [],
7146
            $selected
7147
        );
7148
7149
        return true;
7150
    }
7151
7152
    /**
7153
     * Returns the extension of a document.
7154
     *
7155
     * @param string $filename
7156
     *
7157
     * @return string Extension (part after the last dot)
7158
     */
7159
    public function get_extension($filename)
7160
    {
7161
        $explode = explode('.', $filename);
7162
7163
        return $explode[count($explode) - 1];
7164
    }
7165
7166
    /**
7167
     * @return string
7168
     */
7169
    public function getCurrentBuildingModeURL()
7170
    {
7171
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7172
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7173
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7174
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7175
7176
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7177
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7178
7179
        return $currentUrl;
7180
    }
7181
7182
    /**
7183
     * Displays a document by id.
7184
     *
7185
     * @param CDocument  $document
7186
     * @param bool $show_title
7187
     * @param bool $iframe
7188
     * @param bool $edit_link
7189
     *
7190
     * @return string
7191
     */
7192
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7193
    {
7194
        $return = '';
7195
        if (!$document) {
7196
            return '';
7197
        }
7198
7199
        $repo = Container::getDocumentRepository();
7200
7201
        // TODO: Add a path filter.
7202
        if ($iframe) {
7203
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7204
            $url = $repo->getResourceFileUrl($document);
7205
7206
            $return .= '<iframe
7207
                id="learnpath_preview_frame"
7208
                frameborder="0"
7209
                height="400"
7210
                width="100%"
7211
                scrolling="auto"
7212
                src="'.$url.'"></iframe>';
7213
        } else {
7214
            $return = $repo->getResourceFileContent($document);
7215
        }
7216
7217
        return $return;
7218
    }
7219
7220
    /**
7221
     * Return HTML form to add/edit a link item.
7222
     *
7223
     * @param string $action     (add/edit)
7224
     * @param CLpItem    $lpItem
7225
     * @param CLink  $link
7226
     *
7227
     * @throws Exception
7228
     * @throws HTML_QuickForm_Error
7229
     *
7230
     * @return string HTML form
7231
     */
7232
    public function display_link_form($action = 'add', $lpItem, $link)
7233
    {
7234
        $item_url = '';
7235
        if ($link) {
7236
            $item_url = stripslashes($link->getUrl());
7237
        }
7238
        $form = new FormValidator(
7239
            'edit_link',
7240
            'POST',
7241
            $this->getCurrentBuildingModeURL()
7242
        );
7243
7244
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7245
7246
        $urlAttributes = ['class' => 'learnpath_item_form'];
7247
        $urlAttributes['disabled'] = 'disabled';
7248
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7249
        $form->setDefault('url', $item_url);
7250
        $form->addHidden('type', TOOL_LINK);
7251
7252
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7253
7254
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7255
    }
7256
7257
    /**
7258
     * Return HTML form to add/edit a quiz.
7259
     *
7260
     * @param string $action     Action (add/edit)
7261
     * @param CLpItem    $lpItem         Item ID if already exists
7262
     * @param CQuiz  $exercise Extra information (quiz ID if integer)
7263
     *
7264
     * @throws Exception
7265
     *
7266
     * @return string HTML form
7267
     */
7268
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7269
    {
7270
        $form = new FormValidator(
7271
            'quiz_form',
7272
            'POST',
7273
            $this->getCurrentBuildingModeURL()
7274
        );
7275
7276
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7277
7278
        $form->addHidden('type', TOOL_QUIZ);
7279
7280
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7281
7282
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7283
    }
7284
7285
    /**
7286
     * Addition of Hotpotatoes tests.
7287
     *
7288
     * @param string $action
7289
     * @param int    $id         Internal ID of the item
7290
     * @param string $extra_info
7291
     *
7292
     * @return string HTML structure to display the hotpotatoes addition formular
7293
     */
7294
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7295
    {
7296
        $course_id = api_get_course_int_id();
7297
        $uploadPath = DIR_HOTPOTATOES;
7298
7299
        if (0 != $id && is_array($extra_info)) {
7300
            $item_title = stripslashes($extra_info['title']);
7301
            $item_description = stripslashes($extra_info['description']);
7302
        } elseif (is_numeric($extra_info)) {
7303
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7304
7305
            $sql = "SELECT * FROM $TBL_DOCUMENT
7306
                    WHERE
7307
                        c_id = $course_id AND
7308
                        path LIKE '".$uploadPath."/%/%htm%' AND
7309
                        iid = ".(int) $extra_info."
7310
                    ORDER BY iid ASC";
7311
7312
            $res_hot = Database::query($sql);
7313
            $row = Database::fetch_array($res_hot);
7314
7315
            $item_title = $row['title'];
7316
            $item_description = $row['description'];
7317
7318
            if (!empty($row['comment'])) {
7319
                $item_title = $row['comment'];
7320
            }
7321
        } else {
7322
            $item_title = '';
7323
            $item_description = '';
7324
        }
7325
7326
        $parent = 0;
7327
        if (0 != $id && is_array($extra_info)) {
7328
            $parent = $extra_info['parent_item_id'];
7329
        }
7330
7331
        $arrLP = $this->getItemsForForm();
7332
        $legend = '<legend>';
7333
        if ('add' == $action) {
7334
            $legend .= get_lang('Adding a test to the course');
7335
        } elseif ('move' == $action) {
7336
            $legend .= get_lang('Move the current test');
7337
        } else {
7338
            $legend .= get_lang('Edit the current test');
7339
        }
7340
        if (isset($_GET['edit']) && 'true' == $_GET['edit']) {
7341
            $legend .= Display:: return_message(
7342
                get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument')
7343
            );
7344
        }
7345
        $legend .= '</legend>';
7346
7347
        $return = '<form method="POST">';
7348
        $return .= $legend;
7349
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7350
        $return .= '<tr>';
7351
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7352
        $return .= '<td class="input">';
7353
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7354
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7355
        $arrHide = [$id];
7356
7357
        if (count($arrLP) > 0) {
7358
            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...
7359
                if ('add' != $action) {
7360
                    if ('dir' == $arrLP[$i]['item_type'] &&
7361
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7362
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7363
                    ) {
7364
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7365
                    } else {
7366
                        $arrHide[] = $arrLP[$i]['id'];
7367
                    }
7368
                } else {
7369
                    if ('dir' == $arrLP[$i]['item_type']) {
7370
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7371
                    }
7372
                }
7373
            }
7374
            reset($arrLP);
7375
        }
7376
7377
        $return .= '</select>';
7378
        $return .= '</td>';
7379
        $return .= '</tr>';
7380
        $return .= '<tr>';
7381
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7382
        $return .= '<td class="input">';
7383
        $return .= '<select id="previous" name="previous" size="1">';
7384
        $return .= '<option class="top" value="0">'.get_lang('First position').'</option>';
7385
7386
        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...
7387
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7388
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7389
                    $selected = 'selected="selected" ';
7390
                } elseif ('add' == $action) {
7391
                    $selected = 'selected="selected" ';
7392
                } else {
7393
                    $selected = '';
7394
                }
7395
7396
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7397
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7398
            }
7399
        }
7400
7401
        $return .= '</select>';
7402
        $return .= '</td>';
7403
        $return .= '</tr>';
7404
7405
        if ('move' != $action) {
7406
            $return .= '<tr>';
7407
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7408
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7409
            $return .= '</tr>';
7410
            $id_prerequisite = 0;
7411
            if (is_array($arrLP) && count($arrLP) > 0) {
7412
                foreach ($arrLP as $key => $value) {
7413
                    if ($value['id'] == $id) {
7414
                        $id_prerequisite = $value['prerequisite'];
7415
                        break;
7416
                    }
7417
                }
7418
7419
                $arrHide = [];
7420
                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...
7421
                    if ($arrLP[$i]['id'] != $id && 'dir' != $arrLP[$i]['item_type']) {
7422
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7423
                    }
7424
                }
7425
            }
7426
        }
7427
7428
        $return .= '<tr>';
7429
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7430
            get_lang('Save hotpotatoes').'</button></td>';
7431
        $return .= '</tr>';
7432
        $return .= '</table>';
7433
7434
        if ('move' == $action) {
7435
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7436
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7437
        }
7438
7439
        if (is_numeric($extra_info)) {
7440
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7441
        } elseif (is_array($extra_info)) {
7442
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7443
        }
7444
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7445
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7446
        $return .= '</form>';
7447
7448
        return $return;
7449
    }
7450
7451
    /**
7452
     * Return the form to display the forum edit/add option.
7453
     *
7454
     * @param CLpItem $lpItem
7455
     * @param int    $id         ID of the lp_item if already exists
7456
     * @param string $extra_info
7457
     *
7458
     * @throws Exception
7459
     *
7460
     * @return string HTML form
7461
     */
7462
    public function display_forum_form($action = 'add', $lpItem, $resource)
7463
    {
7464
        $form = new FormValidator(
7465
            'forum_form',
7466
            'POST',
7467
            $this->getCurrentBuildingModeURL()
7468
        );
7469
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7470
7471
        if ('add' == $action) {
7472
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7473
        } else {
7474
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7475
        }
7476
7477
        $form->addHidden('type', TOOL_FORUM);
7478
7479
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7480
    }
7481
7482
    /**
7483
     * Return HTML form to add/edit forum threads.
7484
     *
7485
     * @param string $action
7486
     * @param CLpItem    $lpItem
7487
     * @param string $resource
7488
     *
7489
     * @throws Exception
7490
     *
7491
     * @return string HTML form
7492
     */
7493
    public function display_thread_form($action = 'add', $lpItem, $resource)
7494
    {
7495
        $form = new FormValidator(
7496
            'thread_form',
7497
            'POST',
7498
            $this->getCurrentBuildingModeURL()
7499
        );
7500
7501
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7502
7503
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7504
7505
        $form->addHidden('type', TOOL_THREAD);
7506
7507
        return $form->returnForm();
7508
    }
7509
7510
    /**
7511
     * Return the HTML form to display an item (generally a dir item).
7512
     *
7513
     * @param CLpItem $lpItem
7514
     * @param string $title
7515
     * @param string $action
7516
     * @param string $extra_info
7517
     *
7518
     * @throws Exception
7519
     * @throws HTML_QuickForm_Error
7520
     *
7521
     * @return string HTML form
7522
     */
7523
    public function display_item_form(
7524
        $lpItem,
7525
        $title = '',
7526
        $action = 'add_item'
7527
    ) {
7528
        $item_type = $lpItem->getItemType();
7529
7530
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7531
7532
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7533
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7534
7535
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7536
7537
        //assets can't be modified
7538
        //$item_type == 'asset' ||
7539
        if (('sco' == $item_type) && ('html' == $extension || 'htm' == $extension)) {
7540
            if ('sco' == $item_type) {
7541
                $form->addElement(
7542
                    'html',
7543
                    '<script>alert("'.get_lang('Warning ! ! !WhenEditingScorm').'")</script>'
7544
                );
7545
            }
7546
            $renderer = $form->defaultRenderer();
7547
            $renderer->setElementTemplate(
7548
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
7549
                'content_lp'
7550
            );
7551
7552
            $relative_prefix = '';
7553
            $editor_config = [
7554
                'ToolbarSet' => 'LearningPathDocuments',
7555
                'Width' => '100%',
7556
                'Height' => '500',
7557
                'FullPage' => true,
7558
                'CreateDocumentDir' => $relative_prefix,
7559
                //'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
7560
                //'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
7561
            ];
7562
7563
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
7564
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
7565
            $defaults['content_lp'] = file_get_contents($content_path);
7566
        }
7567
7568
        return $form->returnForm();
7569
    }
7570
7571
7572
    /**
7573
     * Return HTML form to add/edit a student publication (work).
7574
     *
7575
     * @param string $action
7576
     * @param CLpItem    $lpItem
7577
     * @param CStudentPublication $resource
7578
     *
7579
     * @throws Exception
7580
     *
7581
     * @return string HTML form
7582
     */
7583
    public function display_student_publication_form(
7584
        $action = 'add',
7585
        CLpItem $lpItem,
7586
        $resource
7587
    ) {
7588
        $form = new FormValidator('frm_student_publication', 'post', '#');
7589
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7590
7591
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
7592
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7593
7594
        $return = '<div class="sectioncomment">';
7595
        $return .= $form->returnForm();
7596
        $return .= '</div>';
7597
7598
        return $return;
7599
    }
7600
7601
    public function displayNewSectionForm()
7602
    {
7603
        $action = 'add_item';
7604
        $item_type = 'dir';
7605
7606
        $lpItem = new CLpItem();
7607
        $lpItem->setItemType('dir');
7608
7609
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7610
7611
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7612
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7613
7614
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7615
        $form->addElement('hidden', 'type', 'dir');
7616
7617
        return $form->returnForm();
7618
    }
7619
7620
7621
    /**
7622
     * Returns the form to update or create a document.
7623
     *
7624
     * @param string  $action (add/edit)
7625
     * @param CLpItem $lpItem
7626
     *
7627
     * @return string HTML form
7628
     * @throws HTML_QuickForm_Error
7629
     *
7630
     * @throws Exception
7631
     */
7632
    public function display_document_form($action = 'add', $lpItem = null)
7633
    {
7634
        $_course = api_get_course_info();
7635
7636
        if (empty($lpItem)) {
7637
            return '';
7638
        }
7639
7640
        $item_title = $lpItem->getTitle();
7641
        $item_description = $lpItem->getDescription();
7642
        $item_type = $lpItem->getItemType();
7643
7644
        $form = new FormValidator(
7645
            'form',
7646
            'POST',
7647
            $this->getCurrentBuildingModeURL(),
7648
            '',
7649
            ['enctype' => 'multipart/form-data']
7650
        );
7651
7652
        $data = $this->generate_lp_folder($_course);
7653
7654
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7655
7656
        switch ($action) {
7657
            case 'add':
7658
                $folders = DocumentManager::get_all_document_folders(
7659
                    $_course,
7660
                    0,
7661
                    true
7662
                );
7663
                DocumentManager::build_directory_selector(
7664
                    $folders,
7665
                    '',
7666
                    [],
7667
                    true,
7668
                    $form,
7669
                    'directory_parent_id'
7670
                );
7671
7672
                if (isset($data['id'])) {
7673
                    $defaults['directory_parent_id'] = $data['id'];
7674
                }
7675
7676
                break;
7677
        }
7678
7679
        $showButton = false;
7680
        if ('move' !== $action) {
7681
            $arrHide = [];
7682
            /*for ($i = 0; $i < $count; $i++) {
7683
                if ($arrLP[$i]['id'] != $itemId && 'dir' !== $arrLP[$i]['item_type'] &&
7684
                    TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']
7685
                ) {
7686
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7687
                }
7688
            }*/
7689
7690
            //$edit = isset($_GET['edit']) ? $_GET['edit'] : null;
7691
            $repo = Container::getDocumentRepository();
7692
            /** @var CDocument $document */
7693
            $document = $repo->find($lpItem->getPath());
7694
            if (TOOL_DOCUMENT == $item_type || TOOL_LP_FINAL_ITEM == $item_type) {
7695
                /*if (isset($_POST['content'])) {
7696
                    $content = stripslashes($_POST['content']);
7697
                } else {
7698
                    $content = $this->display_document(
7699
                        $lpItem->getPath(),
7700
                        false,
7701
                        false
7702
                    );
7703
                }*/
7704
7705
                ///if (!$no_display_edit_textarea) {
7706
                    // We need to calculate here some specific settings for the online editor.
7707
                    // The calculated settings work for documents in the Documents tool
7708
                    // (on the root or in subfolders).
7709
                    // For documents in native scorm packages it is unclear whether the
7710
                    // online editor should be activated or not.
7711
7712
                    // A new document, it is in the root of the repository.
7713
                   /* if (is_array($extra_info) && 'new' !== $extra_info) {
7714
                        // The document already exists. Whe have to determine its relative path towards the repository root.
7715
                        $relative_path = explode('/', $extra_info['dir']);
7716
                        $cnt = count($relative_path) - 2;
7717
                        if ($cnt < 0) {
7718
                            $cnt = 0;
7719
                        }
7720
                        $relative_prefix = str_repeat('../', $cnt);
7721
                        $relative_path = array_slice($relative_path, 1, $cnt);
7722
                        $relative_path = implode('/', $relative_path);
7723
                        if (strlen($relative_path) > 0) {
7724
                            $relative_path = $relative_path.'/';
7725
                        }
7726
                    } else {
7727
                        $result = $this->generate_lp_folder($_course);
7728
                        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
7729
                        $relative_prefix = '../../';
7730
                    }*/
7731
                    $editorConfig = [
7732
                        'ToolbarSet' => 'LearningPathDocuments',
7733
                        'Width' => '100%',
7734
                        'Height' => '500',
7735
                        'FullPage' => true,
7736
                     //   'CreateDocumentDir' => $relative_prefix,
7737
                        'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
7738
                        //'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
7739
                    ];
7740
7741
                    if ('add_item' === $action) {
7742
                        $text = get_lang('Add this document to the course');
7743
                    }
7744
7745
                    if ('edit_item' === $action) {
7746
                        $text = get_lang('Save document');
7747
                    }
7748
7749
                    $form->addButtonSave(get_lang('Save'), 'submit_button');
7750
7751
                    if ($document->getResourceNode()->hasEditableContent()) {
7752
                        $form->addHidden('document_id', $document->getIid());
7753
                        $showButton = true;
7754
                        $renderer = $form->defaultRenderer();
7755
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
7756
7757
                        $form->addElement('html', '<div class="editor-lp">');
7758
                        $form->addHtmlEditor('content_lp', null, null, true, $editorConfig, true);
7759
                        $form->addElement('html', '</div>');
7760
7761
                        $content = $this->display_document(
7762
                            $document,
7763
                            false,
7764
                            false
7765
                        );
7766
                        $defaults['content_lp'] = $content;
7767
                    }
7768
                //}
7769
            } elseif ($lpItem) {
7770
                $form->addButtonSave(get_lang('Save document'), 'submit_button');
7771
                $return = $this->display_document($document, true, true, true);
7772
                $form->addElement('html', $return);
7773
            }
7774
        }
7775
7776
        if (TOOL_LP_FINAL_ITEM == $item_type) {
7777
            $parentSelect->freeze();
7778
            $position->freeze();
7779
        }
7780
7781
        if ($showButton) {
7782
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
7783
            //$form->addElement('hidden', 'path', $extra_info);
7784
        }
7785
        /* elseif (is_array($extra_info)) {
7786
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
7787
            $form->addElement('hidden', 'path', $extra_info['path']);
7788
        }*/
7789
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
7790
        $form->setDefaults($defaults);
7791
7792
        return $form->returnForm();
7793
    }
7794
7795
    /**
7796
     * Returns the form to update or create a read-out text.
7797
     *
7798
     * @param string $action     "add" or "edit"
7799
     * @param int    $id         ID of the lp_item (if already exists)
7800
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
7801
     *
7802
     * @throws Exception
7803
     * @throws HTML_QuickForm_Error
7804
     *
7805
     * @return string HTML form
7806
     */
7807
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
7808
    {
7809
        $course_id = api_get_course_int_id();
7810
        $_course = api_get_course_info();
7811
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7812
7813
        $no_display_edit_textarea = false;
7814
        if ('edit' == $action) {
7815
            if (is_array($extra_info)) {
7816
                $path_parts = pathinfo($extra_info['dir']);
7817
                if ("txt" != $path_parts['extension'] && "html" != $path_parts['extension']) {
7818
                    $no_display_edit_textarea = true;
7819
                }
7820
            }
7821
        }
7822
        $no_display_add = false;
7823
7824
        $item_title = '';
7825
        $item_description = '';
7826
        if (0 != $id && is_array($extra_info)) {
7827
            $item_title = stripslashes($extra_info['title']);
7828
            $item_description = stripslashes($extra_info['description']);
7829
            $item_terms = stripslashes($extra_info['terms']);
7830
            if (empty($item_title)) {
7831
                $path_parts = pathinfo($extra_info['path']);
7832
                $item_title = stripslashes($path_parts['filename']);
7833
            }
7834
        } elseif (is_numeric($extra_info)) {
7835
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
7836
            $result = Database::query($sql);
7837
            $row = Database::fetch_array($result);
7838
            $item_title = $row['title'];
7839
            $item_title = str_replace('_', ' ', $item_title);
7840
            if (empty($item_title)) {
7841
                $path_parts = pathinfo($row['path']);
7842
                $item_title = stripslashes($path_parts['filename']);
7843
            }
7844
        }
7845
7846
        $parent = 0;
7847
        if (0 != $id && is_array($extra_info)) {
7848
            $parent = $extra_info['parent_item_id'];
7849
        }
7850
7851
        $arrLP = $this->getItemsForForm();
7852
        $this->tree_array($arrLP);
7853
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7854
        unset($this->arrMenu);
7855
7856
        if ('add' === $action) {
7857
            $formHeader = get_lang('Create a new document');
7858
        } else {
7859
            $formHeader = get_lang('Edit the current document');
7860
        }
7861
7862
        if ('edit' === $action) {
7863
            $urlAudioIcon = Display::url(
7864
                Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY),
7865
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
7866
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
7867
            );
7868
        } else {
7869
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY);
7870
        }
7871
7872
        $form = new FormValidator(
7873
            'frm_add_reading',
7874
            'POST',
7875
            $this->getCurrentBuildingModeURL(),
7876
            '',
7877
            ['enctype' => 'multipart/form-data']
7878
        );
7879
        $form->addHeader($formHeader);
7880
        $form->addHtml(
7881
            Display::return_message(
7882
                sprintf(get_lang('You need attach a audio file according to the text, clicking on the %s icon.'), $urlAudioIcon),
7883
                'normal',
7884
                false
7885
            )
7886
        );
7887
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
7888
        $defaults['description'] = $item_description;
7889
7890
        $data = $this->generate_lp_folder($_course);
7891
7892
        if ('edit' != $action) {
7893
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
7894
            DocumentManager::build_directory_selector(
7895
                $folders,
7896
                '',
7897
                [],
7898
                true,
7899
                $form,
7900
                'directory_parent_id'
7901
            );
7902
        }
7903
7904
        if (isset($data['id'])) {
7905
            $defaults['directory_parent_id'] = $data['id'];
7906
        }
7907
        $this->setItemTitle($form);
7908
7909
        $arrHide[0]['value'] = $this->name;
7910
        $arrHide[0]['padding'] = 20;
7911
7912
        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...
7913
            if ('add' != $action) {
7914
                if ('dir' == $arrLP[$i]['item_type'] &&
7915
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7916
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7917
                ) {
7918
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7919
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
7920
                }
7921
            } else {
7922
                if ('dir' == $arrLP[$i]['item_type']) {
7923
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7924
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
7925
                }
7926
            }
7927
        }
7928
7929
        $parent_select = $form->addSelect(
7930
            'parent',
7931
            get_lang('Parent'),
7932
            [],
7933
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
7934
        );
7935
7936
        $my_count = 0;
7937
        foreach ($arrHide as $key => $value) {
7938
            if (0 != $my_count) {
7939
                // The LP name is also the first section and is not in the same charset like the other sections.
7940
                $value['value'] = Security::remove_XSS($value['value']);
7941
                $parent_select->addOption(
7942
                    $value['value'],
7943
                    $key,
7944
                    'style="padding-left:'.$value['padding'].'px;"'
7945
                );
7946
            } else {
7947
                $value['value'] = Security::remove_XSS($value['value']);
7948
                $parent_select->addOption(
7949
                    $value['value'],
7950
                    $key,
7951
                    'style="padding-left:'.$value['padding'].'px;"'
7952
                );
7953
            }
7954
            $my_count++;
7955
        }
7956
7957
        if (!empty($id)) {
7958
            $parent_select->setSelected($parent);
7959
        } else {
7960
            $parent_item_id = Session::read('parent_item_id', 0);
7961
            $parent_select->setSelected($parent_item_id);
7962
        }
7963
7964
        if (is_array($arrLP)) {
7965
            reset($arrLP);
7966
        }
7967
7968
        $arrHide = [];
7969
        $s_selected_position = null;
7970
7971
        // POSITION
7972
        $lastPosition = null;
7973
7974
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7975
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
7976
                TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']
7977
            ) {
7978
                if ((isset($extra_info['previous_item_id']) &&
7979
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || 'add' == $action
7980
                ) {
7981
                    $s_selected_position = $arrLP[$i]['id'];
7982
                }
7983
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
7984
            }
7985
            $lastPosition = $arrLP[$i]['id'];
7986
        }
7987
7988
        if (empty($s_selected_position)) {
7989
            $s_selected_position = $lastPosition;
7990
        }
7991
7992
        $position = $form->addSelect(
7993
            'previous',
7994
            get_lang('Position'),
7995
            []
7996
        );
7997
        $position->addOption(get_lang('First position'), 0);
7998
7999
        foreach ($arrHide as $key => $value) {
8000
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8001
            $position->addOption(
8002
                $value['value'],
8003
                $key,
8004
                'style="padding-left:'.$padding.'px;"'
8005
            );
8006
        }
8007
        $position->setSelected($s_selected_position);
8008
8009
        if (is_array($arrLP)) {
8010
            reset($arrLP);
8011
        }
8012
8013
        $arrHide = [];
8014
8015
        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...
8016
            if ($arrLP[$i]['id'] != $id && 'dir' != $arrLP[$i]['item_type'] &&
8017
                TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']
8018
            ) {
8019
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8020
            }
8021
        }
8022
8023
        if (!$no_display_add) {
8024
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8025
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8026
8027
            if ('new' == $extra_info || TOOL_READOUT_TEXT == $item_type || 'true' == $edit) {
8028
                if (!$no_display_edit_textarea) {
8029
                    $content = '';
8030
8031
                    if (isset($_POST['content'])) {
8032
                        $content = stripslashes($_POST['content']);
8033
                    } elseif (is_array($extra_info)) {
8034
                        $content = $this->display_document($extra_info['path'], false, false);
8035
                    } elseif (is_numeric($extra_info)) {
8036
                        $content = $this->display_document($extra_info, false, false);
8037
                    }
8038
8039
                    // A new document, it is in the root of the repository.
8040
                    if (is_array($extra_info) && 'new' != $extra_info) {
8041
                    } else {
8042
                        $this->generate_lp_folder($_course);
8043
                    }
8044
8045
                    if ('add_item' == $_GET['action']) {
8046
                        $text = get_lang('Add this document to the course');
8047
                    } else {
8048
                        $text = get_lang('Save document');
8049
                    }
8050
8051
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8052
                    $form
8053
                        ->defaultRenderer()
8054
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8055
                    $form->addButtonSave($text, 'submit_button');
8056
                    $defaults['content_lp'] = $content;
8057
                }
8058
            } elseif (is_numeric($extra_info)) {
8059
                $form->addButtonSave(get_lang('Save document'), 'submit_button');
8060
8061
                $return = $this->display_document($extra_info, true, true, true);
8062
                $form->addElement('html', $return);
8063
            }
8064
        }
8065
8066
        if (is_numeric($extra_info)) {
8067
            $form->addElement('hidden', 'path', $extra_info);
8068
        } elseif (is_array($extra_info)) {
8069
            $form->addElement('hidden', 'path', $extra_info['path']);
8070
        }
8071
8072
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8073
        $form->addElement('hidden', 'post_time', time());
8074
        $form->setDefaults($defaults);
8075
8076
        return $form->returnForm();
8077
    }
8078
8079
    /**
8080
     * @param array  $courseInfo
8081
     * @param string $content
8082
     * @param string $title
8083
     * @param int    $parentId
8084
     *
8085
     * @throws \Doctrine\ORM\ORMException
8086
     * @throws \Doctrine\ORM\OptimisticLockException
8087
     * @throws \Doctrine\ORM\TransactionRequiredException
8088
     *
8089
     * @return int
8090
     */
8091
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8092
    {
8093
        $creatorId = api_get_user_id();
8094
        $sessionId = api_get_session_id();
8095
8096
        // Generates folder
8097
        $result = $this->generate_lp_folder($courseInfo);
8098
        $dir = $result['dir'];
8099
8100
        if (empty($parentId) || '/' == $parentId) {
8101
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8102
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8103
8104
            if ('/' === $parentId) {
8105
                $dir = '/';
8106
            }
8107
8108
            // Please, do not modify this dirname formatting.
8109
            if (strstr($dir, '..')) {
8110
                $dir = '/';
8111
            }
8112
8113
            if (!empty($dir[0]) && '.' == $dir[0]) {
8114
                $dir = substr($dir, 1);
8115
            }
8116
            if (!empty($dir[0]) && '/' != $dir[0]) {
8117
                $dir = '/'.$dir;
8118
            }
8119
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
8120
                $dir .= '/';
8121
            }
8122
        } else {
8123
            $parentInfo = DocumentManager::get_document_data_by_id(
8124
                $parentId,
8125
                $courseInfo['code']
8126
            );
8127
            if (!empty($parentInfo)) {
8128
                $dir = $parentInfo['path'].'/';
8129
            }
8130
        }
8131
8132
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8133
8134
        if (!is_dir($filepath)) {
8135
            $dir = '/';
8136
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8137
        }
8138
8139
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8140
8141
        if (!empty($title)) {
8142
            $title = api_replace_dangerous_char(stripslashes($title));
8143
        } else {
8144
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
8145
        }
8146
8147
        $title = disable_dangerous_file($title);
8148
        $filename = $title;
8149
        $content = !empty($content) ? $content : $_POST['content_lp'];
8150
        $tmpFileName = $filename;
8151
8152
        $i = 0;
8153
        while (file_exists($filepath.$tmpFileName.'.html')) {
8154
            $tmpFileName = $filename.'_'.++$i;
8155
        }
8156
8157
        $filename = $tmpFileName.'.html';
8158
        $content = stripslashes($content);
8159
8160
        if (file_exists($filepath.$filename)) {
8161
            return 0;
8162
        }
8163
8164
        $putContent = file_put_contents($filepath.$filename, $content);
8165
8166
        if (false === $putContent) {
8167
            return 0;
8168
        }
8169
8170
        $fileSize = filesize($filepath.$filename);
8171
        $saveFilePath = $dir.$filename;
8172
8173
        $document = DocumentManager::addDocument(
8174
            $courseInfo,
8175
            $saveFilePath,
8176
            'file',
8177
            $fileSize,
8178
            $tmpFileName,
8179
            '',
8180
            0, //readonly
8181
            true,
8182
            null,
8183
            $sessionId,
8184
            $creatorId
8185
        );
8186
8187
        $documentId = $document->getId();
8188
8189
        if (!$document) {
8190
            return 0;
8191
        }
8192
8193
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
8194
        $newTitle = $originalTitle;
8195
8196
        if ($newComment || $newTitle) {
8197
            $em = Database::getManager();
8198
8199
            if ($newComment) {
8200
                $document->setComment($newComment);
8201
            }
8202
8203
            if ($newTitle) {
8204
                $document->setTitle($newTitle);
8205
            }
8206
8207
            $em->persist($document);
8208
            $em->flush();
8209
        }
8210
8211
        return $documentId;
8212
    }
8213
8214
    /**
8215
     * Displays the menu for manipulating a step.
8216
     *
8217
     * @return string
8218
     */
8219
    public function display_manipulate(CLpItem $lpItem)
8220
    {
8221
        $course_code = api_get_course_id();
8222
        $item_id = $lpItem->getIid();
8223
        $audio = $lpItem->getAudio();
8224
        $itemType = $lpItem->getItemType();
8225
        $path = $lpItem->getPath();
8226
        $return = '<div class="actions">';
8227
8228
        $audio_player = null;
8229
        // We display an audio player if needed.
8230
        if (!empty($audio)) {
8231
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
8232
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
8233
                .'<audio src="'.$webAudioPath.'" controls>'
8234
                .'</div><br>';*/
8235
        }
8236
8237
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
8238
8239
        if (TOOL_LP_FINAL_ITEM != $itemType) {
8240
            $return .= Display::url(
8241
                Display::return_icon(
8242
                    'edit.png',
8243
                    get_lang('Edit'),
8244
                    [],
8245
                    ICON_SIZE_SMALL
8246
                ),
8247
                $url.'&action=edit_item&path_item='.$path
8248
            );
8249
8250
            $return .= Display::url(
8251
                Display::return_icon(
8252
                    'move.png',
8253
                    get_lang('Move'),
8254
                    [],
8255
                    ICON_SIZE_SMALL
8256
                ),
8257
                $url.'&action=move_item'
8258
            );
8259
        }
8260
8261
        // Commented for now as prerequisites cannot be added to chapters.
8262
        if ('dir' != $itemType) {
8263
            $return .= Display::url(
8264
                Display::return_icon(
8265
                    'accept.png',
8266
                    get_lang('Prerequisites'),
8267
                    [],
8268
                    ICON_SIZE_SMALL
8269
                ),
8270
                $url.'&action=edit_item_prereq'
8271
            );
8272
        }
8273
        $return .= Display::url(
8274
            Display::return_icon(
8275
                'delete.png',
8276
                get_lang('Delete'),
8277
                [],
8278
                ICON_SIZE_SMALL
8279
            ),
8280
            $url.'&action=delete_item'
8281
        );
8282
8283
        if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
8284
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
8285
            if (empty($documentData)) {
8286
                // Try with iid
8287
                $table = Database::get_course_table(TABLE_DOCUMENT);
8288
                $sql = "SELECT path FROM $table
8289
                        WHERE
8290
                              c_id = ".api_get_course_int_id()." AND
8291
                              iid = ".$path." AND
8292
                              path NOT LIKE '%_DELETED_%'";
8293
                $result = Database::query($sql);
8294
                $documentData = Database::fetch_array($result);
8295
                if ($documentData) {
8296
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
8297
                }
8298
            }
8299
            if (isset($documentData['absolute_path_from_document'])) {
8300
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
8301
            }
8302
        }
8303
8304
        $return .= '</div>';
8305
8306
        if (!empty($audio_player)) {
8307
            $return .= $audio_player;
8308
        }
8309
8310
        return $return;
8311
    }
8312
8313
    /**
8314
     * Creates the javascript needed for filling up the checkboxes without page reload.
8315
     *
8316
     * @return string
8317
     */
8318
    public function get_js_dropdown_array()
8319
    {
8320
        $course_id = api_get_course_int_id();
8321
        $return = 'var child_name = new Array();'."\n";
8322
        $return .= 'var child_value = new Array();'."\n\n";
8323
        $return .= 'child_name[0] = new Array();'."\n";
8324
        $return .= 'child_value[0] = new Array();'."\n\n";
8325
8326
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8327
        $sql = "SELECT * FROM ".$tbl_lp_item."
8328
                WHERE
8329
                    c_id = $course_id AND
8330
                    lp_id = ".$this->lp_id." AND
8331
                    parent_item_id = 0
8332
                ORDER BY display_order ASC";
8333
        $res_zero = Database::query($sql);
8334
        $i = 0;
8335
8336
        $list = $this->getItemsForForm(true);
8337
8338
        foreach ($list as $row_zero) {
8339
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
8340
                if (TOOL_QUIZ == $row_zero['item_type']) {
8341
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
8342
                }
8343
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
8344
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
8345
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
8346
            }
8347
        }
8348
8349
        $return .= "\n";
8350
        $sql = "SELECT * FROM $tbl_lp_item
8351
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8352
        $res = Database::query($sql);
8353
        while ($row = Database::fetch_array($res)) {
8354
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
8355
                           WHERE
8356
                                c_id = ".$course_id." AND
8357
                                parent_item_id = ".$row['iid']."
8358
                           ORDER BY display_order ASC";
8359
            $res_parent = Database::query($sql_parent);
8360
            $i = 0;
8361
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
8362
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
8363
8364
            while ($row_parent = Database::fetch_array($res_parent)) {
8365
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
8366
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
8367
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
8368
            }
8369
            $return .= "\n";
8370
        }
8371
8372
        $return .= "
8373
            function load_cbo(id) {
8374
                if (!id) {
8375
                    return false;
8376
                }
8377
8378
                var cbo = document.getElementById('previous');
8379
                for(var i = cbo.length - 1; i > 0; i--) {
8380
                    cbo.options[i] = null;
8381
                }
8382
8383
                var k=0;
8384
                for(var i = 1; i <= child_name[id].length; i++){
8385
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
8386
                    option.style.paddingLeft = '40px';
8387
                    cbo.options[i] = option;
8388
                    k = i;
8389
                }
8390
8391
                cbo.options[k].selected = true;
8392
                $('#previous').selectpicker('refresh');
8393
            }";
8394
8395
        return $return;
8396
    }
8397
8398
    /**
8399
     * Display the form to allow moving an item.
8400
     *
8401
     * @param CLpItem $lpItem
8402
     *
8403
     * @throws Exception
8404
     * @throws HTML_QuickForm_Error
8405
     *
8406
     * @return string HTML form
8407
     */
8408
    public function display_move_item($lpItem)
8409
    {
8410
        $return = '';
8411
        $path = $lpItem->getPath();
8412
8413
        if ($lpItem) {
8414
            $itemType = $lpItem->getItemType();
8415
            switch ($itemType) {
8416
                case 'dir':
8417
                case 'asset':
8418
                    $return .= $this->display_manipulate($lpItem);
8419
                    $return .= $this->display_item_form(
8420
                        $lpItem,
8421
                        get_lang('Move the current section'),
8422
                        'move',
8423
                        $row
8424
                    );
8425
                    break;
8426
                case TOOL_DOCUMENT:
8427
                    $return .= $this->display_manipulate($lpItem);
8428
                    $return .= $this->display_document_form('move', $lpItem);
8429
                    break;
8430
                case TOOL_LINK:
8431
                    $link = null;
8432
                    if (!empty($path)) {
8433
                        $repo = Container::getLinkRepository();
8434
                        $link = $repo->find($path);
8435
                    }
8436
                    $return .= $this->display_manipulate($lpItem);
8437
                    $return .= $this->display_link_form('move', $lpItem, $link);
8438
                    break;
8439
                case TOOL_HOTPOTATOES:
8440
                    $return .= $this->display_manipulate($lpItem);
8441
                    $return .= $this->display_link_form('move', $lpItem, $row);
8442
                    break;
8443
                case TOOL_QUIZ:
8444
                    $return .= $this->display_manipulate($lpItem);
8445
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
8446
                    break;
8447
                case TOOL_STUDENTPUBLICATION:
8448
                    $return .= $this->display_manipulate($lpItem);
8449
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
8450
                    break;
8451
                case TOOL_FORUM:
8452
                    $return .= $this->display_manipulate($lpItem);
8453
                    $return .= $this->display_forum_form('move', $lpItem, $row);
8454
                    break;
8455
                case TOOL_THREAD:
8456
                    $return .= $this->display_manipulate($lpItem);
8457
                    $return .= $this->display_forum_form('move', $lpItem, $row);
8458
                    break;
8459
            }
8460
        }
8461
8462
        return $return;
8463
    }
8464
8465
    /**
8466
     * Return HTML form to allow prerequisites selection.
8467
     *
8468
     * @todo use FormValidator
8469
     *
8470
     * @param CLpItem $lpItem
8471
     *
8472
     * @return string HTML form
8473
     */
8474
    public function display_item_prerequisites_form(CLpItem $lpItem)
8475
    {
8476
        $course_id = api_get_course_int_id();
8477
        $prerequisiteId = $lpItem->getPrerequisite();
8478
        $itemId = $lpItem->getIid();
8479
8480
        $return = '<legend>';
8481
        $return .= get_lang('Add/edit prerequisites');
8482
        $return .= '</legend>';
8483
        $return .= '<form method="POST">';
8484
        $return .= '<div class="table-responsive">';
8485
        $return .= '<table class="table table-hover">';
8486
        $return .= '<thead>';
8487
        $return .= '<tr>';
8488
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
8489
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
8490
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
8491
        $return .= '</tr>';
8492
        $return .= '</thead>';
8493
8494
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
8495
        $return .= '<tbody>';
8496
        $return .= '<tr>';
8497
        $return .= '<td colspan="3">';
8498
        $return .= '<div class="radio learnpath"><label for="idnone">';
8499
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
8500
        $return .= get_lang('none').'</label>';
8501
        $return .= '</div>';
8502
        $return .= '</tr>';
8503
8504
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8505
        $sql = "SELECT * FROM $tbl_lp_item
8506
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8507
        $result = Database::query($sql);
8508
8509
        $selectedMinScore = [];
8510
        $selectedMaxScore = [];
8511
        $masteryScore = [];
8512
        while ($row = Database::fetch_array($result)) {
8513
            if ($row['iid'] == $itemId) {
8514
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
8515
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
8516
            }
8517
            $masteryScore[$row['iid']] = $row['mastery_score'];
8518
        }
8519
8520
        $arrLP = $this->getItemsForForm();
8521
        $this->tree_array($arrLP);
8522
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8523
        unset($this->arrMenu);
8524
8525
        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...
8526
            $item = $arrLP[$i];
8527
8528
            if ($item['id'] == $itemId) {
8529
                break;
8530
            }
8531
8532
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
8533
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
8534
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
8535
8536
            $return .= '<tr>';
8537
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
8538
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
8539
            $return .= '<label for="id'.$item['id'].'">';
8540
            $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').('dir' == $item['item_type'] ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
8541
8542
            $icon_name = str_replace(' ', '', $item['item_type']);
8543
8544
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
8545
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
8546
            } else {
8547
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
8548
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
8549
                } else {
8550
                    $return .= Display::return_icon('folder_document.png');
8551
                }
8552
            }
8553
8554
            $return .= $item['title'].'</label>';
8555
            $return .= '</div>';
8556
            $return .= '</td>';
8557
8558
            if (TOOL_QUIZ == $item['item_type']) {
8559
                // lets update max_score Tests information depending of the Tests Advanced properties
8560
                $lpItemObj = new LpItem($course_id, $item['id']);
8561
                $exercise = new Exercise($course_id);
8562
                $exercise->read($lpItemObj->path);
8563
                $lpItemObj->max_score = $exercise->get_max_score();
8564
                $lpItemObj->update();
8565
                $item['max_score'] = $lpItemObj->max_score;
8566
8567
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
8568
                    // Backwards compatibility with 1.9.x use mastery_score as min value
8569
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
8570
                }
8571
8572
                $return .= '<td>';
8573
                $return .= '<input
8574
                    class="form-control"
8575
                    size="4" maxlength="3"
8576
                    name="min_'.$item['id'].'"
8577
                    type="number"
8578
                    min="0"
8579
                    step="1"
8580
                    max="'.$item['max_score'].'"
8581
                    value="'.$selectedMinScoreValue.'"
8582
                />';
8583
                $return .= '</td>';
8584
                $return .= '<td>';
8585
                $return .= '<input
8586
                    class="form-control"
8587
                    size="4"
8588
                    maxlength="3"
8589
                    name="max_'.$item['id'].'"
8590
                    type="number"
8591
                    min="0"
8592
                    step="1"
8593
                    max="'.$item['max_score'].'"
8594
                    value="'.$selectedMaxScoreValue.'"
8595
                />';
8596
                $return .= '</td>';
8597
            }
8598
8599
            if (TOOL_HOTPOTATOES == $item['item_type']) {
8600
                $return .= '<td>';
8601
                $return .= '<input
8602
                    size="4"
8603
                    maxlength="3"
8604
                    name="min_'.$item['id'].'"
8605
                    type="number"
8606
                    min="0"
8607
                    step="1"
8608
                    max="'.$item['max_score'].'"
8609
                    value="'.$selectedMinScoreValue.'"
8610
                />';
8611
                $return .= '</td>';
8612
                $return .= '<td>';
8613
                $return .= '<input
8614
                    size="4"
8615
                    maxlength="3"
8616
                    name="max_'.$item['id'].'"
8617
                    type="number"
8618
                    min="0"
8619
                    step="1"
8620
                    max="'.$item['max_score'].'"
8621
                    value="'.$selectedMaxScoreValue.'"
8622
                />';
8623
                $return .= '</td>';
8624
            }
8625
            $return .= '</tr>';
8626
        }
8627
        $return .= '<tr>';
8628
        $return .= '</tr>';
8629
        $return .= '</tbody>';
8630
        $return .= '</table>';
8631
        $return .= '</div>';
8632
        $return .= '<div class="form-group">';
8633
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
8634
            get_lang('Save prerequisites settings').'</button>';
8635
        $return .= '</form>';
8636
8637
        return $return;
8638
    }
8639
8640
    /**
8641
     * Return HTML list to allow prerequisites selection for lp.
8642
     *
8643
     * @return string HTML form
8644
     */
8645
    public function display_lp_prerequisites_list()
8646
    {
8647
        $course_id = api_get_course_int_id();
8648
        $lp_id = $this->lp_id;
8649
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8650
8651
        // get current prerequisite
8652
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8653
        $result = Database::query($sql);
8654
        $row = Database::fetch_array($result);
8655
        $prerequisiteId = $row['prerequisite'];
8656
        $session_id = api_get_session_id();
8657
        $session_condition = api_get_session_condition($session_id, true, true);
8658
        $sql = "SELECT * FROM $tbl_lp
8659
                WHERE c_id = $course_id $session_condition
8660
                ORDER BY display_order ";
8661
        $rs = Database::query($sql);
8662
        $return = '';
8663
        $return .= '<select name="prerequisites" class="form-control">';
8664
        $return .= '<option value="0">'.get_lang('none').'</option>';
8665
        if (Database::num_rows($rs) > 0) {
8666
            while ($row = Database::fetch_array($rs)) {
8667
                if ($row['id'] == $lp_id) {
8668
                    continue;
8669
                }
8670
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
8671
            }
8672
        }
8673
        $return .= '</select>';
8674
8675
        return $return;
8676
    }
8677
8678
    /**
8679
     * Creates a list with all the documents in it.
8680
     *
8681
     * @param bool $showInvisibleFiles
8682
     *
8683
     * @throws Exception
8684
     * @throws HTML_QuickForm_Error
8685
     *
8686
     * @return string
8687
     */
8688
    public function get_documents($showInvisibleFiles = false)
8689
    {
8690
        $course_info = api_get_course_info();
8691
        $sessionId = api_get_session_id();
8692
        $documentTree = DocumentManager::get_document_preview(
8693
            $course_info,
8694
            $this->lp_id,
8695
            null,
8696
            $sessionId,
8697
            true,
8698
            null,
8699
            null,
8700
            $showInvisibleFiles,
8701
            true
8702
        );
8703
8704
        $headers = [
8705
            get_lang('Files'),
8706
            get_lang('Create a new document'),
8707
            get_lang('Create read-out text'),
8708
            get_lang('Upload'),
8709
        ];
8710
8711
        $form = new FormValidator(
8712
            'form_upload',
8713
            'POST',
8714
            $this->getCurrentBuildingModeURL(),
8715
            '',
8716
            ['enctype' => 'multipart/form-data']
8717
        );
8718
8719
        $folders = DocumentManager::get_all_document_folders(
8720
            api_get_course_info(),
8721
            0,
8722
            true
8723
        );
8724
8725
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8726
8727
        DocumentManager::build_directory_selector(
8728
            $folders,
8729
            $lpPathInfo['id'],
8730
            [],
8731
            true,
8732
            $form,
8733
            'directory_parent_id'
8734
        );
8735
8736
        $group = [
8737
            $form->createElement(
8738
                'radio',
8739
                'if_exists',
8740
                get_lang('If file exists:'),
8741
                get_lang('Do nothing'),
8742
                'nothing'
8743
            ),
8744
            $form->createElement(
8745
                'radio',
8746
                'if_exists',
8747
                null,
8748
                get_lang('Overwrite the existing file'),
8749
                'overwrite'
8750
            ),
8751
            $form->createElement(
8752
                'radio',
8753
                'if_exists',
8754
                null,
8755
                get_lang('Rename the uploaded file if it exists'),
8756
                'rename'
8757
            ),
8758
        ];
8759
        $form->addGroup($group, null, get_lang('If file exists:'));
8760
8761
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8762
        $defaultFileExistsOption = 'rename';
8763
        if (!empty($fileExistsOption)) {
8764
            $defaultFileExistsOption = $fileExistsOption;
8765
        }
8766
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8767
8768
        // Check box options
8769
        $form->addElement(
8770
            'checkbox',
8771
            'unzip',
8772
            get_lang('Options'),
8773
            get_lang('Uncompress zip')
8774
        );
8775
8776
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8777
        $form->addMultipleUpload($url);
8778
        $new = $this->display_document_form('add');
8779
        $frmReadOutText = $this->displayFrmReadOutText('add');
8780
        $tabs = Display::tabs(
8781
            $headers,
8782
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
8783
            'subtab'
8784
        );
8785
8786
        return $tabs;
8787
    }
8788
8789
    /**
8790
     * Creates a list with all the exercises (quiz) in it.
8791
     *
8792
     * @return string
8793
     */
8794
    public function get_exercises()
8795
    {
8796
        $course_id = api_get_course_int_id();
8797
        $session_id = api_get_session_id();
8798
        $userInfo = api_get_user_info();
8799
8800
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8801
        $condition_session = api_get_session_condition($session_id, true, true);
8802
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8803
8804
        $activeCondition = ' active <> -1 ';
8805
        if ($setting) {
8806
            $activeCondition = ' active = 1 ';
8807
        }
8808
8809
        $categoryCondition = '';
8810
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8811
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8812
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8813
        }
8814
8815
        $keywordCondition = '';
8816
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8817
8818
        if (!empty($keyword)) {
8819
            $keyword = Database::escape_string($keyword);
8820
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8821
        }
8822
8823
        $sql_quiz = "SELECT * FROM $tbl_quiz
8824
                     WHERE
8825
                            c_id = $course_id AND
8826
                            $activeCondition
8827
                            $condition_session
8828
                            $categoryCondition
8829
                            $keywordCondition
8830
                     ORDER BY title ASC";
8831
        $res_quiz = Database::query($sql_quiz);
8832
8833
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8834
8835
        // Create a search-box
8836
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8837
        $form->addHidden('action', 'add_item');
8838
        $form->addHidden('type', 'step');
8839
        $form->addHidden('lp_id', $this->lp_id);
8840
        $form->addHidden('lp_build_selected', '2');
8841
8842
        $form->addCourseHiddenParams();
8843
        $form->addText(
8844
            'keyword',
8845
            get_lang('Search'),
8846
            false,
8847
            [
8848
                'aria-label' => get_lang('Search'),
8849
            ]
8850
        );
8851
8852
        if (api_get_configuration_value('allow_exercise_categories')) {
8853
            $manager = new ExerciseCategoryManager();
8854
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8855
            if (!empty($options)) {
8856
                $form->addSelect(
8857
                    'category_id',
8858
                    get_lang('Category'),
8859
                    $options,
8860
                    ['placeholder' => get_lang('Please select an option')]
8861
                );
8862
            }
8863
        }
8864
8865
        $form->addButtonSearch(get_lang('Search'));
8866
        $return = $form->returnForm();
8867
8868
        $return .= '<ul class="lp_resource">';
8869
        $return .= '<li class="lp_resource_element">';
8870
        $return .= Display::return_icon('new_exercice.png');
8871
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8872
            get_lang('New test').'</a>';
8873
        $return .= '</li>';
8874
8875
        $previewIcon = Display::return_icon(
8876
            'preview_view.png',
8877
            get_lang('Preview')
8878
        );
8879
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8880
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8881
8882
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8883
        $repo = Container::getExerciseRepository();
8884
        $courseEntity = api_get_course_entity();
8885
        $sessionEntity = api_get_session_entity();
8886
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8887
            /** @var CQuiz $exercise */
8888
            $exercise = $repo->find($row_quiz['id']);
8889
            $title = strip_tags(
8890
                api_html_entity_decode($row_quiz['title'])
8891
            );
8892
8893
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8894
            /*$visibility = api_get_item_visibility(
8895
                ['real_id' => $course_id],
8896
                TOOL_QUIZ,
8897
                $row_quiz['iid'],
8898
                $session_id
8899
            );*/
8900
8901
            $link = Display::url(
8902
                $previewIcon,
8903
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
8904
                ['target' => '_blank']
8905
            );
8906
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
8907
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8908
            $return .= $quizIcon;
8909
            $sessionStar = api_get_session_image(
8910
                $row_quiz['session_id'],
8911
                $userInfo['status']
8912
            );
8913
            $return .= Display::url(
8914
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8915
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
8916
                [
8917
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8918
                ]
8919
            );
8920
            $return .= '</li>';
8921
        }
8922
8923
        $return .= '</ul>';
8924
8925
        return $return;
8926
    }
8927
8928
    /**
8929
     * Creates a list with all the links in it.
8930
     *
8931
     * @return string
8932
     */
8933
    public function get_links()
8934
    {
8935
        $sessionId = api_get_session_id();
8936
        $repo = Container::getLinkRepository();
8937
8938
        $course = api_get_course_entity();
8939
        $session = api_get_session_entity($sessionId);
8940
        $qb = $repo->getResourcesByCourse($course, $session);
8941
        /** @var CLink[] $links */
8942
        $links = $qb->getQuery()->getResult();
8943
8944
        $selfUrl = api_get_self();
8945
        $courseIdReq = api_get_cidreq();
8946
        $userInfo = api_get_user_info();
8947
8948
        $moveEverywhereIcon = Display::return_icon(
8949
            'move_everywhere.png',
8950
            get_lang('Move'),
8951
            [],
8952
            ICON_SIZE_TINY
8953
        );
8954
8955
        /*$condition_session = api_get_session_condition(
8956
            $session_id,
8957
            true,
8958
            true,
8959
            'link.session_id'
8960
        );
8961
8962
        $sql = "SELECT
8963
                    link.id as link_id,
8964
                    link.title as link_title,
8965
                    link.session_id as link_session_id,
8966
                    link.category_id as category_id,
8967
                    link_category.category_title as category_title
8968
                FROM $tbl_link as link
8969
                LEFT JOIN $linkCategoryTable as link_category
8970
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8971
                WHERE link.c_id = $course_id $condition_session
8972
                ORDER BY link_category.category_title ASC, link.title ASC";
8973
        $result = Database::query($sql);*/
8974
        $categorizedLinks = [];
8975
        $categories = [];
8976
8977
        foreach ($links as $link) {
8978
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8979
8980
            if (empty($categoryId)) {
8981
                $categories[0] = get_lang('Uncategorized');
8982
            } else {
8983
                $category = $link->getCategory();
8984
                $categories[$categoryId] = $category->getCategoryTitle();
8985
            }
8986
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8987
        }
8988
8989
        $linksHtmlCode =
8990
            '<script>
8991
            function toggle_tool(tool, id) {
8992
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8993
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8994
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8995
                } else {
8996
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8997
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8998
                }
8999
            }
9000
        </script>
9001
9002
        <ul class="lp_resource">
9003
            <li class="lp_resource_element">
9004
                '.Display::return_icon('linksnew.gif').'
9005
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('Add a link').'">'.
9006
                get_lang('Add a link').'
9007
                </a>
9008
            </li>';
9009
9010
        foreach ($categorizedLinks as $categoryId => $links) {
9011
            $linkNodes = null;
9012
            /** @var CLink $link */
9013
            foreach ($links as $key => $link) {
9014
                $title = $link->getTitle();
9015
                $linkSessionId = $link->getSessionId();
9016
9017
                $linkUrl = Display::url(
9018
                    Display::return_icon('preview_view.png', get_lang('Preview')),
9019
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
9020
                    ['target' => '_blank']
9021
                );
9022
9023
                if ($link->isVisible($course, $session)) {
9024
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
9025
                    $linkNodes .=
9026
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
9027
                        <a class="moved" href="#">'.
9028
                            $moveEverywhereIcon.
9029
                        '</a>
9030
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
9031
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
9032
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
9033
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
9034
                        '</a>
9035
                    </li>';
9036
                }
9037
            }
9038
            $linksHtmlCode .=
9039
                '<li>
9040
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
9041
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
9042
                    align="absbottom" />
9043
                </a>
9044
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
9045
            </li>
9046
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
9047
        }
9048
        $linksHtmlCode .= '</ul>';
9049
9050
        return $linksHtmlCode;
9051
    }
9052
9053
    /**
9054
     * Creates a list with all the student publications in it.
9055
     *
9056
     * @return string
9057
     */
9058
    public function get_student_publications()
9059
    {
9060
        $return = '<ul class="lp_resource">';
9061
        $return .= '<li class="lp_resource_element">';
9062
        $return .= Display::return_icon('works_new.gif');
9063
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
9064
            get_lang('Add the Assignments tool to the course').'</a>';
9065
        $return .= '</li>';
9066
9067
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
9068
        $works = getWorkListTeacher(0, 100, null, null, null);
9069
        if (!empty($works)) {
9070
            foreach ($works as $work) {
9071
                $link = Display::url(
9072
                    Display::return_icon('preview_view.png', get_lang('Preview')),
9073
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
9074
                    ['target' => '_blank']
9075
                );
9076
9077
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
9078
                $return .= '<a class="moved" href="#">';
9079
                $return .= Display::return_icon(
9080
                    'move_everywhere.png',
9081
                    get_lang('Move'),
9082
                    [],
9083
                    ICON_SIZE_TINY
9084
                );
9085
                $return .= '</a> ';
9086
9087
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
9088
                $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.'">'.
9089
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
9090
                </a>';
9091
9092
                $return .= '</li>';
9093
            }
9094
        }
9095
9096
        $return .= '</ul>';
9097
9098
        return $return;
9099
    }
9100
9101
    /**
9102
     * Creates a list with all the forums in it.
9103
     *
9104
     * @return string
9105
     */
9106
    public function get_forums()
9107
    {
9108
        require_once '../forum/forumfunction.inc.php';
9109
9110
        $forumCategories = get_forum_categories();
9111
        $forumsInNoCategory = get_forums_in_category(0);
9112
        if (!empty($forumsInNoCategory)) {
9113
            $forumCategories = array_merge(
9114
                $forumCategories,
9115
                [
9116
                    [
9117
                        'cat_id' => 0,
9118
                        'session_id' => 0,
9119
                        'visibility' => 1,
9120
                        'cat_comment' => null,
9121
                    ],
9122
                ]
9123
            );
9124
        }
9125
9126
        $forumList = get_forums();
9127
        $a_forums = [];
9128
        foreach ($forumCategories as $forumCategory) {
9129
            // The forums in this category.
9130
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
9131
            if (!empty($forumsInCategory)) {
9132
                foreach ($forumList as $forum) {
9133
                    $a_forums[] = $forum;
9134
                }
9135
            }
9136
        }
9137
9138
        $return = '<ul class="lp_resource">';
9139
9140
        // First add link
9141
        $return .= '<li class="lp_resource_element">';
9142
        $return .= Display::return_icon('new_forum.png');
9143
        $return .= Display::url(
9144
            get_lang('Create a new forum'),
9145
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
9146
                'action' => 'add',
9147
                'content' => 'forum',
9148
                'lp_id' => $this->lp_id,
9149
            ]),
9150
            ['title' => get_lang('Create a new forum')]
9151
        );
9152
        $return .= '</li>';
9153
9154
        $return .= '<script>
9155
            function toggle_forum(forum_id) {
9156
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
9157
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
9158
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
9159
                } else {
9160
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
9161
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
9162
                }
9163
            }
9164
        </script>';
9165
9166
        foreach ($a_forums as $forum) {
9167
            $forumId = $forum->getIid();
9168
            $title = Security::remove_XSS($forum->getForumTitle());
9169
            $link = Display::url(
9170
                Display::return_icon('preview_view.png', get_lang('Preview')),
9171
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
9172
                ['target' => '_blank']
9173
            );
9174
9175
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
9176
            $return .= '<a class="moved" href="#">';
9177
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
9178
            $return .= ' </a>';
9179
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
9180
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
9181
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
9182
                        </a>
9183
                        <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
9184
                $title.' '.$link.'</a>';
9185
9186
            $return .= '</li>';
9187
9188
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
9189
            $a_threads = get_threads($forumId);
9190
            if (is_array($a_threads)) {
9191
                foreach ($a_threads as $thread) {
9192
                    $threadId = $thread->getIid();
9193
                    $link = Display::url(
9194
                        Display::return_icon('preview_view.png', get_lang('Preview')),
9195
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
9196
                        ['target' => '_blank']
9197
                    );
9198
9199
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
9200
                    $return .= '&nbsp;<a class="moved" href="#">';
9201
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
9202
                    $return .= ' </a>';
9203
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
9204
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'">'.
9205
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
9206
                    $return .= '</li>';
9207
                }
9208
            }
9209
            $return .= '</div>';
9210
        }
9211
        $return .= '</ul>';
9212
9213
        return $return;
9214
    }
9215
9216
    /**
9217
     * // TODO: The output encoding should be equal to the system encoding.
9218
     *
9219
     * Exports the learning path as a SCORM package. This is the main function that
9220
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
9221
     * whole thing and returns the zip.
9222
     *
9223
     * This method needs to be called in PHP5, as it will fail with non-adequate
9224
     * XML package (like the ones for PHP4), and it is *not* a static method, so
9225
     * you need to call it on a learnpath object.
9226
     *
9227
     * @TODO The method might be redefined later on in the scorm class itself to avoid
9228
     * creating a SCORM structure if there is one already. However, if the initial SCORM
9229
     * path has been modified, it should use the generic method here below.
9230
     *
9231
     * @return string Returns the zip package string, or null if error
9232
     */
9233
    public function scormExport()
9234
    {
9235
        api_set_more_memory_and_time_limits();
9236
9237
        $_course = api_get_course_info();
9238
        $course_id = $_course['real_id'];
9239
        // Create the zip handler (this will remain available throughout the method).
9240
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
9241
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
9242
        $temp_dir_short = uniqid('scorm_export', true);
9243
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
9244
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
9245
        $zip_folder = new PclZip($temp_zip_file);
9246
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
9247
        $root_path = $main_path = api_get_path(SYS_PATH);
9248
        $files_cleanup = [];
9249
9250
        // Place to temporarily stash the zip file.
9251
        // create the temp dir if it doesn't exist
9252
        // or do a cleanup before creating the zip file.
9253
        if (!is_dir($temp_zip_dir)) {
9254
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
9255
        } else {
9256
            // Cleanup: Check the temp dir for old files and delete them.
9257
            $handle = opendir($temp_zip_dir);
9258
            while (false !== ($file = readdir($handle))) {
9259
                if ('.' != $file && '..' != $file) {
9260
                    unlink("$temp_zip_dir/$file");
9261
                }
9262
            }
9263
            closedir($handle);
9264
        }
9265
        $zip_files = $zip_files_abs = $zip_files_dist = [];
9266
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
9267
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
9268
        ) {
9269
            // Remove the possible . at the end of the path.
9270
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
9271
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
9272
            mkdir(
9273
                $dest_path_to_scorm_folder,
9274
                api_get_permissions_for_new_directories(),
9275
                true
9276
            );
9277
            copyr(
9278
                $current_course_path.'/scorm/'.$this->path,
9279
                $dest_path_to_scorm_folder,
9280
                ['imsmanifest'],
9281
                $zip_files
9282
            );
9283
        }
9284
9285
        // Build a dummy imsmanifest structure.
9286
        // Do not add to the zip yet (we still need it).
9287
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
9288
        // Aggregation Model official document, section "2.3 Content Packaging".
9289
        // We are going to build a UTF-8 encoded manifest.
9290
        // Later we will recode it to the desired (and supported) encoding.
9291
        $xmldoc = new DOMDocument('1.0');
9292
        $root = $xmldoc->createElement('manifest');
9293
        $root->setAttribute('identifier', 'SingleCourseManifest');
9294
        $root->setAttribute('version', '1.1');
9295
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
9296
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
9297
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
9298
        $root->setAttribute(
9299
            'xsi:schemaLocation',
9300
            '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'
9301
        );
9302
        // Build mandatory sub-root container elements.
9303
        $metadata = $xmldoc->createElement('metadata');
9304
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
9305
        $metadata->appendChild($md_schema);
9306
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
9307
        $metadata->appendChild($md_schemaversion);
9308
        $root->appendChild($metadata);
9309
9310
        $organizations = $xmldoc->createElement('organizations');
9311
        $resources = $xmldoc->createElement('resources');
9312
9313
        // Build the only organization we will use in building our learnpaths.
9314
        $organizations->setAttribute('default', 'chamilo_scorm_export');
9315
        $organization = $xmldoc->createElement('organization');
9316
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
9317
        // To set the title of the SCORM entity (=organization), we take the name given
9318
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
9319
        // learning path charset) as it is the encoding that defines how it is stored
9320
        // in the database. Then we convert it to HTML entities again as the "&" character
9321
        // alone is not authorized in XML (must be &amp;).
9322
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
9323
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
9324
        $organization->appendChild($org_title);
9325
        $folder_name = 'document';
9326
9327
        // Removes the learning_path/scorm_folder path when exporting see #4841
9328
        $path_to_remove = '';
9329
        $path_to_replace = '';
9330
        $result = $this->generate_lp_folder($_course);
9331
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
9332
            $path_to_remove = 'document'.$result['dir'];
9333
            $path_to_replace = $folder_name.'/';
9334
        }
9335
9336
        // Fixes chamilo scorm exports
9337
        if ('chamilo_scorm_export' === $this->ref) {
9338
            $path_to_remove = 'scorm/'.$this->path.'/document/';
9339
        }
9340
9341
        // For each element, add it to the imsmanifest structure, then add it to the zip.
9342
        $link_updates = [];
9343
        $links_to_create = [];
9344
        foreach ($this->ordered_items as $index => $itemId) {
9345
            /** @var learnpathItem $item */
9346
            $item = $this->items[$itemId];
9347
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
9348
                // Get included documents from this item.
9349
                if ('sco' === $item->type) {
9350
                    $inc_docs = $item->get_resources_from_source(
9351
                        null,
9352
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
9353
                    );
9354
                } else {
9355
                    $inc_docs = $item->get_resources_from_source();
9356
                }
9357
9358
                // Give a child element <item> to the <organization> element.
9359
                $my_item_id = $item->get_id();
9360
                $my_item = $xmldoc->createElement('item');
9361
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
9362
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
9363
                $my_item->setAttribute('isvisible', 'true');
9364
                // Give a child element <title> to the <item> element.
9365
                $my_title = $xmldoc->createElement(
9366
                    'title',
9367
                    htmlspecialchars(
9368
                        api_utf8_encode($item->get_title()),
9369
                        ENT_QUOTES,
9370
                        'UTF-8'
9371
                    )
9372
                );
9373
                $my_item->appendChild($my_title);
9374
                // Give a child element <adlcp:prerequisites> to the <item> element.
9375
                $my_prereqs = $xmldoc->createElement(
9376
                    'adlcp:prerequisites',
9377
                    $this->get_scorm_prereq_string($my_item_id)
9378
                );
9379
                $my_prereqs->setAttribute('type', 'aicc_script');
9380
                $my_item->appendChild($my_prereqs);
9381
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9382
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
9383
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9384
                //$xmldoc->createElement('adlcp:timelimitaction','');
9385
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9386
                //$xmldoc->createElement('adlcp:datafromlms','');
9387
                // Give a child element <adlcp:masteryscore> to the <item> element.
9388
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9389
                $my_item->appendChild($my_masteryscore);
9390
9391
                // Attach this item to the organization element or hits parent if there is one.
9392
                if (!empty($item->parent) && 0 != $item->parent) {
9393
                    $children = $organization->childNodes;
9394
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9395
                    if (is_object($possible_parent)) {
9396
                        $possible_parent->appendChild($my_item);
9397
                    } else {
9398
                        if ($this->debug > 0) {
9399
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
9400
                        }
9401
                    }
9402
                } else {
9403
                    if ($this->debug > 0) {
9404
                        error_log('No parent');
9405
                    }
9406
                    $organization->appendChild($my_item);
9407
                }
9408
9409
                // Get the path of the file(s) from the course directory root.
9410
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9411
                $my_xml_file_path = $my_file_path;
9412
                if (!empty($path_to_remove)) {
9413
                    // From docs
9414
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
9415
9416
                    // From quiz
9417
                    if ('chamilo_scorm_export' === $this->ref) {
9418
                        $path_to_remove = 'scorm/'.$this->path.'/';
9419
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
9420
                    }
9421
                }
9422
9423
                $my_sub_dir = dirname($my_file_path);
9424
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9425
                $my_xml_sub_dir = $my_sub_dir;
9426
                // Give a <resource> child to the <resources> element
9427
                $my_resource = $xmldoc->createElement('resource');
9428
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9429
                $my_resource->setAttribute('type', 'webcontent');
9430
                $my_resource->setAttribute('href', $my_xml_file_path);
9431
                // adlcp:scormtype can be either 'sco' or 'asset'.
9432
                if ('sco' === $item->type) {
9433
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
9434
                } else {
9435
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
9436
                }
9437
                // xml:base is the base directory to find the files declared in this resource.
9438
                $my_resource->setAttribute('xml:base', '');
9439
                // Give a <file> child to the <resource> element.
9440
                $my_file = $xmldoc->createElement('file');
9441
                $my_file->setAttribute('href', $my_xml_file_path);
9442
                $my_resource->appendChild($my_file);
9443
9444
                // Dependency to other files - not yet supported.
9445
                $i = 1;
9446
                if ($inc_docs) {
9447
                    foreach ($inc_docs as $doc_info) {
9448
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
9449
                            continue;
9450
                        }
9451
                        $my_dep = $xmldoc->createElement('resource');
9452
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9453
                        $my_dep->setAttribute('identifier', $res_id);
9454
                        $my_dep->setAttribute('type', 'webcontent');
9455
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
9456
                        $my_dep_file = $xmldoc->createElement('file');
9457
                        // Check type of URL.
9458
                        if ('remote' == $doc_info[1]) {
9459
                            // Remote file. Save url as is.
9460
                            $my_dep_file->setAttribute('href', $doc_info[0]);
9461
                            $my_dep->setAttribute('xml:base', '');
9462
                        } elseif ('local' === $doc_info[1]) {
9463
                            switch ($doc_info[2]) {
9464
                                case 'url':
9465
                                    // Local URL - save path as url for now, don't zip file.
9466
                                    $abs_path = api_get_path(SYS_PATH).
9467
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9468
                                    $current_dir = dirname($abs_path);
9469
                                    $current_dir = str_replace('\\', '/', $current_dir);
9470
                                    $file_path = realpath($abs_path);
9471
                                    $file_path = str_replace('\\', '/', $file_path);
9472
                                    $my_dep_file->setAttribute('href', $file_path);
9473
                                    $my_dep->setAttribute('xml:base', '');
9474
                                    if (false !== strstr($file_path, $main_path)) {
9475
                                        // The calculated real path is really inside Chamilo's root path.
9476
                                        // Reduce file path to what's under the DocumentRoot.
9477
                                        $replace = $file_path;
9478
                                        $file_path = substr($file_path, strlen($root_path) - 1);
9479
                                        $destinationFile = $file_path;
9480
9481
                                        if (false !== strstr($file_path, 'upload/users')) {
9482
                                            $pos = strpos($file_path, 'my_files/');
9483
                                            if (false !== $pos) {
9484
                                                $onlyDirectory = str_replace(
9485
                                                    'upload/users/',
9486
                                                    '',
9487
                                                    substr($file_path, $pos, strlen($file_path))
9488
                                                );
9489
                                            }
9490
                                            $replace = $onlyDirectory;
9491
                                            $destinationFile = $replace;
9492
                                        }
9493
                                        $zip_files_abs[] = $file_path;
9494
                                        $link_updates[$my_file_path][] = [
9495
                                            'orig' => $doc_info[0],
9496
                                            'dest' => $destinationFile,
9497
                                            'replace' => $replace,
9498
                                        ];
9499
                                        $my_dep_file->setAttribute('href', $file_path);
9500
                                        $my_dep->setAttribute('xml:base', '');
9501
                                    } elseif (empty($file_path)) {
9502
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9503
                                        $file_path = str_replace('//', '/', $file_path);
9504
                                        if (file_exists($file_path)) {
9505
                                            // We get the relative path.
9506
                                            $file_path = substr($file_path, strlen($current_dir));
9507
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
9508
                                            $link_updates[$my_file_path][] = [
9509
                                                'orig' => $doc_info[0],
9510
                                                'dest' => $file_path,
9511
                                            ];
9512
                                            $my_dep_file->setAttribute('href', $file_path);
9513
                                            $my_dep->setAttribute('xml:base', '');
9514
                                        }
9515
                                    }
9516
                                    break;
9517
                                case 'abs':
9518
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9519
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9520
                                    $my_dep->setAttribute('xml:base', '');
9521
9522
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
9523
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
9524
                                    $abs_img_path_without_subdir = $doc_info[0];
9525
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
9526
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
9527
                                    if (0 === $pos) {
9528
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
9529
                                    }
9530
9531
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
9532
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
9533
9534
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
9535
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
9536
                                    // Check if the current document is in that path.
9537
                                    if (false !== strstr($file_path, $cur_path)) {
9538
                                        $destinationFile = substr($file_path, strlen($cur_path));
9539
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
9540
9541
                                        $fileToTest = $cur_path.$my_file_path;
9542
                                        if (!empty($path_to_remove)) {
9543
                                            $fileToTest = str_replace(
9544
                                                $path_to_remove.'/',
9545
                                                $path_to_replace,
9546
                                                $cur_path.$my_file_path
9547
                                            );
9548
                                        }
9549
9550
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
9551
9552
                                        // Put the current document in the zip (this array is the array
9553
                                        // that will manage documents already in the course folder - relative).
9554
                                        $zip_files[] = $filePathNoCoursePart;
9555
                                        // Update the links to the current document in the
9556
                                        // containing document (make them relative).
9557
                                        $link_updates[$my_file_path][] = [
9558
                                            'orig' => $doc_info[0],
9559
                                            'dest' => $destinationFile,
9560
                                            'replace' => $relative_path,
9561
                                        ];
9562
9563
                                        $my_dep_file->setAttribute('href', $file_path);
9564
                                        $my_dep->setAttribute('xml:base', '');
9565
                                    } elseif (false !== strstr($file_path, $main_path)) {
9566
                                        // The calculated real path is really inside Chamilo's root path.
9567
                                        // Reduce file path to what's under the DocumentRoot.
9568
                                        $file_path = substr($file_path, strlen($root_path));
9569
                                        $zip_files_abs[] = $file_path;
9570
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9571
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9572
                                        $my_dep->setAttribute('xml:base', '');
9573
                                    } elseif (empty($file_path)) {
9574
                                        // Probably this is an image inside "/main" directory
9575
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
9576
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9577
9578
                                        if (file_exists($file_path)) {
9579
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
9580
                                                // We get the relative path.
9581
                                                $pos = strpos($file_path, 'main/default_course_document/');
9582
                                                if (false !== $pos) {
9583
                                                    $onlyDirectory = str_replace(
9584
                                                        'main/default_course_document/',
9585
                                                        '',
9586
                                                        substr($file_path, $pos, strlen($file_path))
9587
                                                    );
9588
                                                }
9589
9590
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
9591
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
9592
                                                $zip_files_abs[] = $fileAbs;
9593
                                                $link_updates[$my_file_path][] = [
9594
                                                    'orig' => $doc_info[0],
9595
                                                    'dest' => $destinationFile,
9596
                                                ];
9597
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9598
                                                $my_dep->setAttribute('xml:base', '');
9599
                                            }
9600
                                        }
9601
                                    }
9602
                                    break;
9603
                                case 'rel':
9604
                                    // Path relative to the current document.
9605
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
9606
                                    if ('..' === substr($doc_info[0], 0, 2)) {
9607
                                        // Relative path going up.
9608
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9609
                                        $current_dir = str_replace('\\', '/', $current_dir);
9610
                                        $file_path = realpath($current_dir.$doc_info[0]);
9611
                                        $file_path = str_replace('\\', '/', $file_path);
9612
                                        if (false !== strstr($file_path, $main_path)) {
9613
                                            // The calculated real path is really inside Chamilo's root path.
9614
                                            // Reduce file path to what's under the DocumentRoot.
9615
                                            $file_path = substr($file_path, strlen($root_path));
9616
                                            $zip_files_abs[] = $file_path;
9617
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9618
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9619
                                            $my_dep->setAttribute('xml:base', '');
9620
                                        }
9621
                                    } else {
9622
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9623
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
9624
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9625
                                    }
9626
                                    break;
9627
                                default:
9628
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9629
                                    $my_dep->setAttribute('xml:base', '');
9630
                                    break;
9631
                            }
9632
                        }
9633
                        $my_dep->appendChild($my_dep_file);
9634
                        $resources->appendChild($my_dep);
9635
                        $dependency = $xmldoc->createElement('dependency');
9636
                        $dependency->setAttribute('identifierref', $res_id);
9637
                        $my_resource->appendChild($dependency);
9638
                        $i++;
9639
                    }
9640
                }
9641
                $resources->appendChild($my_resource);
9642
                $zip_files[] = $my_file_path;
9643
            } else {
9644
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
9645
                switch ($item->type) {
9646
                    case TOOL_LINK:
9647
                        $my_item = $xmldoc->createElement('item');
9648
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9649
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9650
                        $my_item->setAttribute('isvisible', 'true');
9651
                        // Give a child element <title> to the <item> element.
9652
                        $my_title = $xmldoc->createElement(
9653
                            'title',
9654
                            htmlspecialchars(
9655
                                api_utf8_encode($item->get_title()),
9656
                                ENT_QUOTES,
9657
                                'UTF-8'
9658
                            )
9659
                        );
9660
                        $my_item->appendChild($my_title);
9661
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9662
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9663
                        $my_prereqs->setAttribute('type', 'aicc_script');
9664
                        $my_item->appendChild($my_prereqs);
9665
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9666
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9667
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9668
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9669
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9670
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9671
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9672
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9673
                        $my_item->appendChild($my_masteryscore);
9674
9675
                        // Attach this item to the organization element or its parent if there is one.
9676
                        if (!empty($item->parent) && 0 != $item->parent) {
9677
                            $children = $organization->childNodes;
9678
                            for ($i = 0; $i < $children->length; $i++) {
9679
                                $item_temp = $children->item($i);
9680
                                if ('item' == $item_temp->nodeName) {
9681
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9682
                                        $item_temp->appendChild($my_item);
9683
                                    }
9684
                                }
9685
                            }
9686
                        } else {
9687
                            $organization->appendChild($my_item);
9688
                        }
9689
9690
                        $my_file_path = 'link_'.$item->get_id().'.html';
9691
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9692
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9693
                        $rs = Database::query($sql);
9694
                        if ($link = Database::fetch_array($rs)) {
9695
                            $url = $link['url'];
9696
                            $title = stripslashes($link['title']);
9697
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9698
                            $my_xml_file_path = $my_file_path;
9699
                            $my_sub_dir = dirname($my_file_path);
9700
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9701
                            $my_xml_sub_dir = $my_sub_dir;
9702
                            // Give a <resource> child to the <resources> element.
9703
                            $my_resource = $xmldoc->createElement('resource');
9704
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9705
                            $my_resource->setAttribute('type', 'webcontent');
9706
                            $my_resource->setAttribute('href', $my_xml_file_path);
9707
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9708
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9709
                            // xml:base is the base directory to find the files declared in this resource.
9710
                            $my_resource->setAttribute('xml:base', '');
9711
                            // give a <file> child to the <resource> element.
9712
                            $my_file = $xmldoc->createElement('file');
9713
                            $my_file->setAttribute('href', $my_xml_file_path);
9714
                            $my_resource->appendChild($my_file);
9715
                            $resources->appendChild($my_resource);
9716
                        }
9717
                        break;
9718
                    case TOOL_QUIZ:
9719
                        $exe_id = $item->path;
9720
                        // Should be using ref when everything will be cleaned up in this regard.
9721
                        $exe = new Exercise();
9722
                        $exe->read($exe_id);
9723
                        $my_item = $xmldoc->createElement('item');
9724
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9725
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9726
                        $my_item->setAttribute('isvisible', 'true');
9727
                        // Give a child element <title> to the <item> element.
9728
                        $my_title = $xmldoc->createElement(
9729
                            'title',
9730
                            htmlspecialchars(
9731
                                api_utf8_encode($item->get_title()),
9732
                                ENT_QUOTES,
9733
                                'UTF-8'
9734
                            )
9735
                        );
9736
                        $my_item->appendChild($my_title);
9737
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9738
                        $my_item->appendChild($my_max_score);
9739
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9740
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9741
                        $my_prereqs->setAttribute('type', 'aicc_script');
9742
                        $my_item->appendChild($my_prereqs);
9743
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9744
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9745
                        $my_item->appendChild($my_masteryscore);
9746
9747
                        // Attach this item to the organization element or hits parent if there is one.
9748
                        if (!empty($item->parent) && 0 != $item->parent) {
9749
                            $children = $organization->childNodes;
9750
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9751
                            if ($possible_parent) {
9752
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9753
                                    $possible_parent->appendChild($my_item);
9754
                                }
9755
                            }
9756
                        } else {
9757
                            $organization->appendChild($my_item);
9758
                        }
9759
9760
                        // Get the path of the file(s) from the course directory root
9761
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9762
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9763
                        // Write the contents of the exported exercise into a (big) html file
9764
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9765
                        $scormExercise = new ScormExercise($exe, true);
9766
                        $contents = $scormExercise->export();
9767
9768
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9769
                        $res = file_put_contents($tmp_file_path, $contents);
9770
                        if (false === $res) {
9771
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9772
                        }
9773
                        $files_cleanup[] = $tmp_file_path;
9774
                        $my_xml_file_path = $my_file_path;
9775
                        $my_sub_dir = dirname($my_file_path);
9776
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9777
                        $my_xml_sub_dir = $my_sub_dir;
9778
                        // Give a <resource> child to the <resources> element.
9779
                        $my_resource = $xmldoc->createElement('resource');
9780
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9781
                        $my_resource->setAttribute('type', 'webcontent');
9782
                        $my_resource->setAttribute('href', $my_xml_file_path);
9783
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9784
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9785
                        // xml:base is the base directory to find the files declared in this resource.
9786
                        $my_resource->setAttribute('xml:base', '');
9787
                        // Give a <file> child to the <resource> element.
9788
                        $my_file = $xmldoc->createElement('file');
9789
                        $my_file->setAttribute('href', $my_xml_file_path);
9790
                        $my_resource->appendChild($my_file);
9791
9792
                        // Get included docs.
9793
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9794
9795
                        // Dependency to other files - not yet supported.
9796
                        $i = 1;
9797
                        foreach ($inc_docs as $doc_info) {
9798
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9799
                                continue;
9800
                            }
9801
                            $my_dep = $xmldoc->createElement('resource');
9802
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9803
                            $my_dep->setAttribute('identifier', $res_id);
9804
                            $my_dep->setAttribute('type', 'webcontent');
9805
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9806
                            $my_dep_file = $xmldoc->createElement('file');
9807
                            // Check type of URL.
9808
                            if ('remote' == $doc_info[1]) {
9809
                                // Remote file. Save url as is.
9810
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9811
                                $my_dep->setAttribute('xml:base', '');
9812
                            } elseif ('local' == $doc_info[1]) {
9813
                                switch ($doc_info[2]) {
9814
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9815
                                        // Save file but as local file (retrieve from URL).
9816
                                        $abs_path = api_get_path(SYS_PATH).
9817
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9818
                                        $current_dir = dirname($abs_path);
9819
                                        $current_dir = str_replace('\\', '/', $current_dir);
9820
                                        $file_path = realpath($abs_path);
9821
                                        $file_path = str_replace('\\', '/', $file_path);
9822
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9823
                                        $my_dep->setAttribute('xml:base', '');
9824
                                        if (false !== strstr($file_path, $main_path)) {
9825
                                            // The calculated real path is really inside the chamilo root path.
9826
                                            // Reduce file path to what's under the DocumentRoot.
9827
                                            $file_path = substr($file_path, strlen($root_path));
9828
                                            $zip_files_abs[] = $file_path;
9829
                                            $link_updates[$my_file_path][] = [
9830
                                                'orig' => $doc_info[0],
9831
                                                'dest' => 'document/'.$file_path,
9832
                                            ];
9833
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9834
                                            $my_dep->setAttribute('xml:base', '');
9835
                                        } elseif (empty($file_path)) {
9836
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9837
                                            $file_path = str_replace('//', '/', $file_path);
9838
                                            if (file_exists($file_path)) {
9839
                                                $file_path = substr($file_path, strlen($current_dir));
9840
                                                // We get the relative path.
9841
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9842
                                                $link_updates[$my_file_path][] = [
9843
                                                    'orig' => $doc_info[0],
9844
                                                    'dest' => 'document/'.$file_path,
9845
                                                ];
9846
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9847
                                                $my_dep->setAttribute('xml:base', '');
9848
                                            }
9849
                                        }
9850
                                        break;
9851
                                    case 'abs':
9852
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9853
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9854
                                        $current_dir = str_replace('\\', '/', $current_dir);
9855
                                        $file_path = realpath($doc_info[0]);
9856
                                        $file_path = str_replace('\\', '/', $file_path);
9857
                                        $my_dep_file->setAttribute('href', $file_path);
9858
                                        $my_dep->setAttribute('xml:base', '');
9859
9860
                                        if (false !== strstr($file_path, $main_path)) {
9861
                                            // The calculated real path is really inside the chamilo root path.
9862
                                            // Reduce file path to what's under the DocumentRoot.
9863
                                            $file_path = substr($file_path, strlen($root_path));
9864
                                            $zip_files_abs[] = $file_path;
9865
                                            $link_updates[$my_file_path][] = [
9866
                                                'orig' => $doc_info[0],
9867
                                                'dest' => $file_path,
9868
                                            ];
9869
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9870
                                            $my_dep->setAttribute('xml:base', '');
9871
                                        } elseif (empty($file_path)) {
9872
                                            $docSysPartPath = str_replace(
9873
                                                api_get_path(REL_COURSE_PATH),
9874
                                                '',
9875
                                                $doc_info[0]
9876
                                            );
9877
9878
                                            $docSysPartPathNoCourseCode = str_replace(
9879
                                                $_course['directory'].'/',
9880
                                                '',
9881
                                                $docSysPartPath
9882
                                            );
9883
9884
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9885
                                            if (file_exists($docSysPath)) {
9886
                                                $file_path = $docSysPartPathNoCourseCode;
9887
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9888
                                                $link_updates[$my_file_path][] = [
9889
                                                    'orig' => $doc_info[0],
9890
                                                    'dest' => $file_path,
9891
                                                ];
9892
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9893
                                                $my_dep->setAttribute('xml:base', '');
9894
                                            }
9895
                                        }
9896
                                        break;
9897
                                    case 'rel':
9898
                                        // Path relative to the current document. Save xml:base as current document's
9899
                                        // directory and save file in zip as subdir.file_path
9900
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9901
                                            // Relative path going up.
9902
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9903
                                            $current_dir = str_replace('\\', '/', $current_dir);
9904
                                            $file_path = realpath($current_dir.$doc_info[0]);
9905
                                            $file_path = str_replace('\\', '/', $file_path);
9906
                                            if (false !== strstr($file_path, $main_path)) {
9907
                                                // The calculated real path is really inside Chamilo's root path.
9908
                                                // Reduce file path to what's under the DocumentRoot.
9909
9910
                                                $file_path = substr($file_path, strlen($root_path));
9911
                                                $file_path_dest = $file_path;
9912
9913
                                                // File path is courses/CHAMILO/document/....
9914
                                                $info_file_path = explode('/', $file_path);
9915
                                                if ('courses' == $info_file_path[0]) {
9916
                                                    // Add character "/" in file path.
9917
                                                    $file_path_dest = 'document/'.$file_path;
9918
                                                }
9919
                                                $zip_files_abs[] = $file_path;
9920
9921
                                                $link_updates[$my_file_path][] = [
9922
                                                    'orig' => $doc_info[0],
9923
                                                    'dest' => $file_path_dest,
9924
                                                ];
9925
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9926
                                                $my_dep->setAttribute('xml:base', '');
9927
                                            }
9928
                                        } else {
9929
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9930
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9931
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9932
                                        }
9933
                                        break;
9934
                                    default:
9935
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9936
                                        $my_dep->setAttribute('xml:base', '');
9937
                                        break;
9938
                                }
9939
                            }
9940
                            $my_dep->appendChild($my_dep_file);
9941
                            $resources->appendChild($my_dep);
9942
                            $dependency = $xmldoc->createElement('dependency');
9943
                            $dependency->setAttribute('identifierref', $res_id);
9944
                            $my_resource->appendChild($dependency);
9945
                            $i++;
9946
                        }
9947
                        $resources->appendChild($my_resource);
9948
                        $zip_files[] = $my_file_path;
9949
                        break;
9950
                    default:
9951
                        // Get the path of the file(s) from the course directory root
9952
                        $my_file_path = 'non_exportable.html';
9953
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9954
                        $my_xml_file_path = $my_file_path;
9955
                        $my_sub_dir = dirname($my_file_path);
9956
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9957
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9958
                        $my_xml_sub_dir = $my_sub_dir;
9959
                        // Give a <resource> child to the <resources> element.
9960
                        $my_resource = $xmldoc->createElement('resource');
9961
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9962
                        $my_resource->setAttribute('type', 'webcontent');
9963
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9964
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9965
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9966
                        // xml:base is the base directory to find the files declared in this resource.
9967
                        $my_resource->setAttribute('xml:base', '');
9968
                        // Give a <file> child to the <resource> element.
9969
                        $my_file = $xmldoc->createElement('file');
9970
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9971
                        $my_resource->appendChild($my_file);
9972
                        $resources->appendChild($my_resource);
9973
                        break;
9974
                }
9975
            }
9976
        }
9977
        $organizations->appendChild($organization);
9978
        $root->appendChild($organizations);
9979
        $root->appendChild($resources);
9980
        $xmldoc->appendChild($root);
9981
9982
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9983
9984
        // then add the file to the zip, then destroy the file (this is done automatically).
9985
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9986
        foreach ($zip_files as $file_path) {
9987
            if (empty($file_path)) {
9988
                continue;
9989
            }
9990
9991
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9992
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9993
9994
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9995
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9996
            }
9997
9998
            $this->create_path($dest_file);
9999
            @copy($filePath, $dest_file);
10000
10001
            // Check if the file needs a link update.
10002
            if (in_array($file_path, array_keys($link_updates))) {
10003
                $string = file_get_contents($dest_file);
10004
                unlink($dest_file);
10005
                foreach ($link_updates[$file_path] as $old_new) {
10006
                    // This is an ugly hack that allows .flv files to be found by the flv player that
10007
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
10008
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
10009
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
10010
                    if ('flv' === substr($old_new['dest'], -3) &&
10011
                        'main/' === substr($old_new['dest'], 0, 5)
10012
                    ) {
10013
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
10014
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
10015
                        'video/' === substr($old_new['dest'], 0, 6)
10016
                    ) {
10017
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
10018
                    }
10019
10020
                    // Fix to avoid problems with default_course_document
10021
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
10022
                        $newDestination = $old_new['dest'];
10023
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
10024
                            $newDestination = $old_new['replace'];
10025
                        }
10026
                    } else {
10027
                        $newDestination = str_replace('document/', '', $old_new['dest']);
10028
                    }
10029
                    $string = str_replace($old_new['orig'], $newDestination, $string);
10030
10031
                    // Add files inside the HTMLs
10032
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
10033
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
10034
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
10035
                        copy(
10036
                            $sys_course_path.$new_path,
10037
                            $destinationFile
10038
                        );
10039
                    }
10040
                }
10041
                file_put_contents($dest_file, $string);
10042
            }
10043
10044
            if (file_exists($filePath) && $copyAll) {
10045
                $extension = $this->get_extension($filePath);
10046
                if (in_array($extension, ['html', 'html'])) {
10047
                    $containerOrigin = dirname($filePath);
10048
                    $containerDestination = dirname($dest_file);
10049
10050
                    $finder = new Finder();
10051
                    $finder->files()->in($containerOrigin)
10052
                        ->notName('*_DELETED_*')
10053
                        ->exclude('share_folder')
10054
                        ->exclude('chat_files')
10055
                        ->exclude('certificates')
10056
                    ;
10057
10058
                    if (is_dir($containerOrigin) &&
10059
                        is_dir($containerDestination)
10060
                    ) {
10061
                        $fs = new Filesystem();
10062
                        $fs->mirror(
10063
                            $containerOrigin,
10064
                            $containerDestination,
10065
                            $finder
10066
                        );
10067
                    }
10068
                }
10069
            }
10070
        }
10071
10072
        foreach ($zip_files_abs as $file_path) {
10073
            if (empty($file_path)) {
10074
                continue;
10075
            }
10076
10077
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
10078
                continue;
10079
            }
10080
10081
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
10082
            if (false !== strstr($file_path, 'upload/users')) {
10083
                $pos = strpos($file_path, 'my_files/');
10084
                if (false !== $pos) {
10085
                    $onlyDirectory = str_replace(
10086
                        'upload/users/',
10087
                        '',
10088
                        substr($file_path, $pos, strlen($file_path))
10089
                    );
10090
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
10091
                }
10092
            }
10093
10094
            if (false !== strstr($file_path, 'default_course_document/')) {
10095
                $replace = str_replace('/main', '', $file_path);
10096
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
10097
            }
10098
10099
            if (empty($dest_file)) {
10100
                continue;
10101
            }
10102
10103
            $this->create_path($dest_file);
10104
            copy($main_path.$file_path, $dest_file);
10105
            // Check if the file needs a link update.
10106
            if (in_array($file_path, array_keys($link_updates))) {
10107
                $string = file_get_contents($dest_file);
10108
                unlink($dest_file);
10109
                foreach ($link_updates[$file_path] as $old_new) {
10110
                    // This is an ugly hack that allows .flv files to be found by the flv player that
10111
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
10112
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
10113
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
10114
                    if ('flv' == substr($old_new['dest'], -3) &&
10115
                        'main/' == substr($old_new['dest'], 0, 5)
10116
                    ) {
10117
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
10118
                    }
10119
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
10120
                }
10121
                file_put_contents($dest_file, $string);
10122
            }
10123
        }
10124
10125
        if (is_array($links_to_create)) {
10126
            foreach ($links_to_create as $file => $link) {
10127
                $content = '<!DOCTYPE html><head>
10128
                            <meta charset="'.api_get_language_isocode().'" />
10129
                            <title>'.$link['title'].'</title>
10130
                            </head>
10131
                            <body dir="'.api_get_text_direction().'">
10132
                            <div style="text-align:center">
10133
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
10134
                            </body>
10135
                            </html>';
10136
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
10137
            }
10138
        }
10139
10140
        // Add non exportable message explanation.
10141
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
10142
        $file_content = '<!DOCTYPE html><head>
10143
                        <meta charset="'.api_get_language_isocode().'" />
10144
                        <title>'.$lang_not_exportable.'</title>
10145
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
10146
                        </head>
10147
                        <body dir="'.api_get_text_direction().'">';
10148
        $file_content .=
10149
            <<<EOD
10150
                    <style>
10151
            .error-message {
10152
                font-family: arial, verdana, helvetica, sans-serif;
10153
                border-width: 1px;
10154
                border-style: solid;
10155
                left: 50%;
10156
                margin: 10px auto;
10157
                min-height: 30px;
10158
                padding: 5px;
10159
                right: 50%;
10160
                width: 500px;
10161
                background-color: #FFD1D1;
10162
                border-color: #FF0000;
10163
                color: #000;
10164
            }
10165
        </style>
10166
    <body>
10167
        <div class="error-message">
10168
            $lang_not_exportable
10169
        </div>
10170
    </body>
10171
</html>
10172
EOD;
10173
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
10174
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
10175
        }
10176
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
10177
10178
        // Add the extra files that go along with a SCORM package.
10179
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
10180
10181
        $fs = new Filesystem();
10182
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
10183
10184
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
10185
        $manifest = @$xmldoc->saveXML();
10186
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
10187
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
10188
        $zip_folder->add(
10189
            $archivePath.'/'.$temp_dir_short,
10190
            PCLZIP_OPT_REMOVE_PATH,
10191
            $archivePath.'/'.$temp_dir_short.'/'
10192
        );
10193
10194
        // Clean possible temporary files.
10195
        foreach ($files_cleanup as $file) {
10196
            $res = unlink($file);
10197
            if (false === $res) {
10198
                error_log(
10199
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
10200
                    0
10201
                );
10202
            }
10203
        }
10204
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
10205
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
10206
    }
10207
10208
    /**
10209
     * @param int $lp_id
10210
     *
10211
     * @return bool
10212
     */
10213
    public function scorm_export_to_pdf($lp_id)
10214
    {
10215
        $lp_id = (int) $lp_id;
10216
        $files_to_export = [];
10217
10218
        $sessionId = api_get_session_id();
10219
        $course_data = api_get_course_info($this->cc);
10220
10221
        if (!empty($course_data)) {
10222
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
10223
            $list = self::get_flat_ordered_items_list($lp_id);
10224
            if (!empty($list)) {
10225
                foreach ($list as $item_id) {
10226
                    $item = $this->items[$item_id];
10227
                    switch ($item->type) {
10228
                        case 'document':
10229
                            // Getting documents from a LP with chamilo documents
10230
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
10231
                            // Try loading document from the base course.
10232
                            if (empty($file_data) && !empty($sessionId)) {
10233
                                $file_data = DocumentManager::get_document_data_by_id(
10234
                                    $item->path,
10235
                                    $this->cc,
10236
                                    false,
10237
                                    0
10238
                                );
10239
                            }
10240
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
10241
                            if (file_exists($file_path)) {
10242
                                $files_to_export[] = [
10243
                                    'title' => $item->get_title(),
10244
                                    'path' => $file_path,
10245
                                ];
10246
                            }
10247
                            break;
10248
                        case 'asset': //commes from a scorm package generated by chamilo
10249
                        case 'sco':
10250
                            $file_path = $scorm_path.'/'.$item->path;
10251
                            if (file_exists($file_path)) {
10252
                                $files_to_export[] = [
10253
                                    'title' => $item->get_title(),
10254
                                    'path' => $file_path,
10255
                                ];
10256
                            }
10257
                            break;
10258
                        case 'dir':
10259
                            $files_to_export[] = [
10260
                                'title' => $item->get_title(),
10261
                                'path' => null,
10262
                            ];
10263
                            break;
10264
                    }
10265
                }
10266
            }
10267
10268
            $pdf = new PDF();
10269
            $result = $pdf->html_to_pdf(
10270
                $files_to_export,
10271
                $this->name,
10272
                $this->cc,
10273
                true,
10274
                true,
10275
                true,
10276
                $this->get_name()
10277
            );
10278
10279
            return $result;
10280
        }
10281
10282
        return false;
10283
    }
10284
10285
    /**
10286
     * Temp function to be moved in main_api or the best place around for this.
10287
     * Creates a file path if it doesn't exist.
10288
     *
10289
     * @param string $path
10290
     */
10291
    public function create_path($path)
10292
    {
10293
        $path_bits = explode('/', dirname($path));
10294
10295
        // IS_WINDOWS_OS has been defined in main_api.lib.php
10296
        $path_built = IS_WINDOWS_OS ? '' : '/';
10297
        foreach ($path_bits as $bit) {
10298
            if (!empty($bit)) {
10299
                $new_path = $path_built.$bit;
10300
                if (is_dir($new_path)) {
10301
                    $path_built = $new_path.'/';
10302
                } else {
10303
                    mkdir($new_path, api_get_permissions_for_new_directories());
10304
                    $path_built = $new_path.'/';
10305
                }
10306
            }
10307
        }
10308
    }
10309
10310
    /**
10311
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
10312
     *
10313
     * @return bool The results of the unlink function, or false if there was no image to start with
10314
     */
10315
    public function delete_lp_image()
10316
    {
10317
        $img = $this->get_preview_image();
10318
        if ('' != $img) {
10319
            $del_file = $this->get_preview_image_path(null, 'sys');
10320
            if (isset($del_file) && file_exists($del_file)) {
10321
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
10322
                if (file_exists($del_file_2)) {
10323
                    unlink($del_file_2);
10324
                }
10325
                $this->set_preview_image('');
10326
10327
                return @unlink($del_file);
10328
            }
10329
        }
10330
10331
        return false;
10332
    }
10333
10334
    /**
10335
     * Uploads an author image to the upload/learning_path/images directory.
10336
     *
10337
     * @param array    The image array, coming from the $_FILES superglobal
10338
     *
10339
     * @return bool True on success, false on error
10340
     */
10341
    public function upload_image($image_array)
10342
    {
10343
        if (!empty($image_array['name'])) {
10344
            $upload_ok = process_uploaded_file($image_array);
10345
            $has_attachment = true;
10346
        }
10347
10348
        if ($upload_ok && $has_attachment) {
10349
            $courseDir = api_get_course_path().'/upload/learning_path/images';
10350
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
10351
            $updir = $sys_course_path.$courseDir;
10352
            // Try to add an extension to the file if it hasn't one.
10353
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
10354
10355
            if (filter_extension($new_file_name)) {
10356
                $file_extension = explode('.', $image_array['name']);
10357
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
10358
                $filename = uniqid('');
10359
                $new_file_name = $filename.'.'.$file_extension;
10360
                $new_path = $updir.'/'.$new_file_name;
10361
10362
                // Resize the image.
10363
                $temp = new Image($image_array['tmp_name']);
10364
                $temp->resize(104);
10365
                $result = $temp->send_image($new_path);
10366
10367
                // Storing the image filename.
10368
                if ($result) {
10369
                    $this->set_preview_image($new_file_name);
10370
10371
                    //Resize to 64px to use on course homepage
10372
                    $temp->resize(64);
10373
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
10374
10375
                    return true;
10376
                }
10377
            }
10378
        }
10379
10380
        return false;
10381
    }
10382
10383
    /**
10384
     * @param int    $lp_id
10385
     * @param string $status
10386
     */
10387
    public function set_autolaunch($lp_id, $status)
10388
    {
10389
        $course_id = api_get_course_int_id();
10390
        $lp_id = (int) $lp_id;
10391
        $status = (int) $status;
10392
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
10393
10394
        // Setting everything to autolaunch = 0
10395
        $attributes['autolaunch'] = 0;
10396
        $where = [
10397
            'session_id = ? AND c_id = ? ' => [
10398
                api_get_session_id(),
10399
                $course_id,
10400
            ],
10401
        ];
10402
        Database::update($lp_table, $attributes, $where);
10403
        if (1 == $status) {
10404
            //Setting my lp_id to autolaunch = 1
10405
            $attributes['autolaunch'] = 1;
10406
            $where = [
10407
                'iid = ? AND session_id = ? AND c_id = ?' => [
10408
                    $lp_id,
10409
                    api_get_session_id(),
10410
                    $course_id,
10411
                ],
10412
            ];
10413
            Database::update($lp_table, $attributes, $where);
10414
        }
10415
    }
10416
10417
    /**
10418
     * Gets previous_item_id for the next element of the lp_item table.
10419
     *
10420
     * @author Isaac flores paz
10421
     *
10422
     * @return int Previous item ID
10423
     */
10424
    public function select_previous_item_id()
10425
    {
10426
        $course_id = api_get_course_int_id();
10427
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10428
10429
        // Get the max order of the items
10430
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
10431
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10432
        $rs_max_order = Database::query($sql);
10433
        $row_max_order = Database::fetch_object($rs_max_order);
10434
        $max_order = $row_max_order->display_order;
10435
        // Get the previous item ID
10436
        $sql = "SELECT iid as previous FROM $table_lp_item
10437
                WHERE
10438
                    c_id = $course_id AND
10439
                    lp_id = ".$this->lp_id." AND
10440
                    display_order = '$max_order' ";
10441
        $rs_max = Database::query($sql);
10442
        $row_max = Database::fetch_object($rs_max);
10443
10444
        // Return the previous item ID
10445
        return $row_max->previous;
10446
    }
10447
10448
    /**
10449
     * Copies an LP.
10450
     */
10451
    public function copy()
10452
    {
10453
        // Course builder
10454
        $cb = new CourseBuilder();
10455
10456
        //Setting tools that will be copied
10457
        $cb->set_tools_to_build(['learnpaths']);
10458
10459
        //Setting elements that will be copied
10460
        $cb->set_tools_specific_id_list(
10461
            ['learnpaths' => [$this->lp_id]]
10462
        );
10463
10464
        $course = $cb->build();
10465
10466
        //Course restorer
10467
        $course_restorer = new CourseRestorer($course);
10468
        $course_restorer->set_add_text_in_items(true);
10469
        $course_restorer->set_tool_copy_settings(
10470
            ['learnpaths' => ['reset_dates' => true]]
10471
        );
10472
        $course_restorer->restore(
10473
            api_get_course_id(),
10474
            api_get_session_id(),
10475
            false,
10476
            false
10477
        );
10478
    }
10479
10480
    /**
10481
     * Verify document size.
10482
     *
10483
     * @param string $s
10484
     *
10485
     * @return bool
10486
     */
10487
    public static function verify_document_size($s)
10488
    {
10489
        $post_max = ini_get('post_max_size');
10490
        if ('M' == substr($post_max, -1, 1)) {
10491
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
10492
        } elseif ('G' == substr($post_max, -1, 1)) {
10493
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
10494
        }
10495
        $upl_max = ini_get('upload_max_filesize');
10496
        if ('M' == substr($upl_max, -1, 1)) {
10497
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
10498
        } elseif ('G' == substr($upl_max, -1, 1)) {
10499
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
10500
        }
10501
10502
        $repo = Container::getDocumentRepository();
10503
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
10504
10505
        $course_max_space = DocumentManager::get_course_quota();
10506
        $total_size = filesize($s) + $documents_total_space;
10507
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
10508
            return true;
10509
        }
10510
10511
        return false;
10512
    }
10513
10514
    /**
10515
     * Clear LP prerequisites.
10516
     */
10517
    public function clear_prerequisites()
10518
    {
10519
        $course_id = $this->get_course_int_id();
10520
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10521
        $lp_id = $this->get_id();
10522
        // Cleaning prerequisites
10523
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
10524
                WHERE c_id = $course_id AND lp_id = $lp_id";
10525
        Database::query($sql);
10526
10527
        // Cleaning mastery score for exercises
10528
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
10529
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
10530
        Database::query($sql);
10531
    }
10532
10533
    public function set_previous_step_as_prerequisite_for_all_items()
10534
    {
10535
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10536
        $course_id = $this->get_course_int_id();
10537
        $lp_id = $this->get_id();
10538
10539
        if (!empty($this->items)) {
10540
            $previous_item_id = null;
10541
            $previous_item_max = 0;
10542
            $previous_item_type = null;
10543
            $last_item_not_dir = null;
10544
            $last_item_not_dir_type = null;
10545
            $last_item_not_dir_max = null;
10546
10547
            foreach ($this->ordered_items as $itemId) {
10548
                $item = $this->getItem($itemId);
10549
                // if there was a previous item... (otherwise jump to set it)
10550
                if (!empty($previous_item_id)) {
10551
                    $current_item_id = $item->get_id(); //save current id
10552
                    if ('dir' != $item->get_type()) {
10553
                        // Current item is not a folder, so it qualifies to get a prerequisites
10554
                        if ('quiz' == $last_item_not_dir_type) {
10555
                            // if previous is quiz, mark its max score as default score to be achieved
10556
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
10557
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
10558
                            Database::query($sql);
10559
                        }
10560
                        // now simply update the prerequisite to set it to the last non-chapter item
10561
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
10562
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
10563
                        Database::query($sql);
10564
                        // record item as 'non-chapter' reference
10565
                        $last_item_not_dir = $item->get_id();
10566
                        $last_item_not_dir_type = $item->get_type();
10567
                        $last_item_not_dir_max = $item->get_max();
10568
                    }
10569
                } else {
10570
                    if ('dir' != $item->get_type()) {
10571
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
10572
                        $last_item_not_dir = $item->get_id();
10573
                        $last_item_not_dir_type = $item->get_type();
10574
                        $last_item_not_dir_max = $item->get_max();
10575
                    }
10576
                }
10577
                // Saving the item as "previous item" for the next loop
10578
                $previous_item_id = $item->get_id();
10579
                $previous_item_max = $item->get_max();
10580
                $previous_item_type = $item->get_type();
10581
            }
10582
        }
10583
    }
10584
10585
    /**
10586
     * @param array $params
10587
     *
10588
     * @throws \Doctrine\ORM\OptimisticLockException
10589
     *
10590
     * @return int
10591
     */
10592
    public static function createCategory($params)
10593
    {
10594
        $item = new CLpCategory();
10595
        $item->setName($params['name']);
10596
        $item->setCId($params['c_id']);
10597
10598
        $repo = Container::getLpCategoryRepository();
10599
        $em = $repo->getEntityManager();
10600
        $em->persist($item);
10601
        $courseEntity = api_get_course_entity(api_get_course_int_id());
10602
10603
        $repo->addResourceToCourse(
10604
            $item,
10605
            ResourceLink::VISIBILITY_PUBLISHED,
10606
            api_get_user_entity(api_get_user_id()),
10607
            $courseEntity,
10608
            api_get_session_entity(),
10609
            api_get_group_entity()
10610
        );
10611
10612
        $em->flush();
10613
10614
        /*api_item_property_update(
10615
            api_get_course_info(),
10616
            TOOL_LEARNPATH_CATEGORY,
10617
            $item->getId(),
10618
            'visible',
10619
            api_get_user_id()
10620
        );*/
10621
10622
        return $item->getId();
10623
    }
10624
10625
    /**
10626
     * @param array $params
10627
     *
10628
     * @throws \Doctrine\ORM\ORMException
10629
     * @throws \Doctrine\ORM\OptimisticLockException
10630
     * @throws \Doctrine\ORM\TransactionRequiredException
10631
     */
10632
    public static function updateCategory($params)
10633
    {
10634
        $em = Database::getManager();
10635
        /** @var CLpCategory $item */
10636
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
10637
        if ($item) {
10638
            $item->setName($params['name']);
10639
            $em->merge($item);
10640
            $em->flush();
10641
        }
10642
    }
10643
10644
    /**
10645
     * @param int $id
10646
     *
10647
     * @throws \Doctrine\ORM\ORMException
10648
     * @throws \Doctrine\ORM\OptimisticLockException
10649
     * @throws \Doctrine\ORM\TransactionRequiredException
10650
     */
10651
    public static function moveUpCategory($id)
10652
    {
10653
        $id = (int) $id;
10654
        $em = Database::getManager();
10655
        /** @var CLpCategory $item */
10656
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10657
        if ($item) {
10658
            $position = $item->getPosition() - 1;
10659
            $item->setPosition($position);
10660
            $em->persist($item);
10661
            $em->flush();
10662
        }
10663
    }
10664
10665
    /**
10666
     * @param int $id
10667
     *
10668
     * @throws \Doctrine\ORM\ORMException
10669
     * @throws \Doctrine\ORM\OptimisticLockException
10670
     * @throws \Doctrine\ORM\TransactionRequiredException
10671
     */
10672
    public static function moveDownCategory($id)
10673
    {
10674
        $id = (int) $id;
10675
        $em = Database::getManager();
10676
        /** @var CLpCategory $item */
10677
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10678
        if ($item) {
10679
            $position = $item->getPosition() + 1;
10680
            $item->setPosition($position);
10681
            $em->persist($item);
10682
            $em->flush();
10683
        }
10684
    }
10685
10686
    /**
10687
     * @param int $courseId
10688
     *
10689
     * @throws \Doctrine\ORM\Query\QueryException
10690
     *
10691
     * @return int|mixed
10692
     */
10693
    public static function getCountCategories($courseId)
10694
    {
10695
        if (empty($courseId)) {
10696
            return 0;
10697
        }
10698
        $em = Database::getManager();
10699
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10700
        $query->setParameter('id', $courseId);
10701
10702
        return $query->getSingleScalarResult();
10703
    }
10704
10705
    /**
10706
     * @param int $courseId
10707
     *
10708
     * @return mixed
10709
     */
10710
    public static function getCategories($courseId)
10711
    {
10712
        $em = Database::getManager();
10713
10714
        // Using doctrine extensions
10715
        /** @var SortableRepository $repo */
10716
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10717
        $items = $repo
10718
            ->getBySortableGroupsQuery(['cId' => $courseId])
10719
            ->getResult();
10720
10721
        return $items;
10722
    }
10723
10724
    /**
10725
     * @param int $id
10726
     *
10727
     * @throws \Doctrine\ORM\ORMException
10728
     * @throws \Doctrine\ORM\OptimisticLockException
10729
     * @throws \Doctrine\ORM\TransactionRequiredException
10730
     *
10731
     * @return CLpCategory
10732
     */
10733
    public static function getCategory($id)
10734
    {
10735
        $id = (int) $id;
10736
        $em = Database::getManager();
10737
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10738
10739
        return $item;
10740
    }
10741
10742
    /**
10743
     * @param int $courseId
10744
     *
10745
     * @return array
10746
     */
10747
    public static function getCategoryByCourse($courseId)
10748
    {
10749
        $em = Database::getManager();
10750
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10751
            ['cId' => $courseId]
10752
        );
10753
10754
        return $items;
10755
    }
10756
10757
    /**
10758
     * @param int $id
10759
     *
10760
     * @throws \Doctrine\ORM\ORMException
10761
     * @throws \Doctrine\ORM\OptimisticLockException
10762
     * @throws \Doctrine\ORM\TransactionRequiredException
10763
     *
10764
     * @return mixed
10765
     */
10766
    public static function deleteCategory($id)
10767
    {
10768
        $em = Database::getManager();
10769
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10770
        if ($item) {
10771
            $courseId = $item->getCId();
10772
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
10773
            $query->setParameter('id', $courseId);
10774
            $query->setParameter('catId', $item->getId());
10775
            $lps = $query->getResult();
10776
10777
            // Setting category = 0.
10778
            if ($lps) {
10779
                foreach ($lps as $lpItem) {
10780
                    $lpItem->setCategoryId(0);
10781
                }
10782
            }
10783
10784
            // Removing category.
10785
            $em->remove($item);
10786
            $em->flush();
10787
10788
            $courseInfo = api_get_course_info_by_id($courseId);
10789
            $sessionId = api_get_session_id();
10790
10791
            // Delete link tool
10792
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
10793
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
10794
            // Delete tools
10795
            $sql = "DELETE FROM $tbl_tool
10796
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
10797
            Database::query($sql);*/
10798
10799
            return true;
10800
        }
10801
10802
        return false;
10803
    }
10804
10805
    /**
10806
     * @param int  $courseId
10807
     * @param bool $addSelectOption
10808
     *
10809
     * @return mixed
10810
     */
10811
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10812
    {
10813
        $items = self::getCategoryByCourse($courseId);
10814
        $cats = [];
10815
        if ($addSelectOption) {
10816
            $cats = [get_lang('Select a category')];
10817
        }
10818
10819
        if (!empty($items)) {
10820
            foreach ($items as $cat) {
10821
                $cats[$cat->getId()] = $cat->getName();
10822
            }
10823
        }
10824
10825
        return $cats;
10826
    }
10827
10828
    /**
10829
     * @param string $courseCode
10830
     * @param int    $lpId
10831
     * @param int    $user_id
10832
     *
10833
     * @return learnpath
10834
     */
10835
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10836
    {
10837
        $debug = 0;
10838
        $learnPath = null;
10839
        $lpObject = Session::read('lpobject');
10840
        if (null !== $lpObject) {
10841
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10842
            if ($debug) {
10843
                error_log('getLpFromSession: unserialize');
10844
                error_log('------getLpFromSession------');
10845
                error_log('------unserialize------');
10846
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10847
                error_log("api_get_sessionid: ".api_get_session_id());
10848
            }
10849
        }
10850
10851
        if (!is_object($learnPath)) {
10852
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10853
            if ($debug) {
10854
                error_log('------getLpFromSession------');
10855
                error_log('getLpFromSession: create new learnpath');
10856
                error_log("create new LP with $courseCode - $lpId - $user_id");
10857
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10858
                error_log("api_get_sessionid: ".api_get_session_id());
10859
            }
10860
        }
10861
10862
        return $learnPath;
10863
    }
10864
10865
    /**
10866
     * @param int $itemId
10867
     *
10868
     * @return learnpathItem|false
10869
     */
10870
    public function getItem($itemId)
10871
    {
10872
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10873
            return $this->items[$itemId];
10874
        }
10875
10876
        return false;
10877
    }
10878
10879
    /**
10880
     * @return int
10881
     */
10882
    public function getCurrentAttempt()
10883
    {
10884
        $attempt = $this->getItem($this->get_current_item_id());
10885
        if ($attempt) {
10886
            $attemptId = $attempt->get_attempt_id();
10887
10888
            return $attemptId;
10889
        }
10890
10891
        return 0;
10892
    }
10893
10894
    /**
10895
     * @return int
10896
     */
10897
    public function getCategoryId()
10898
    {
10899
        return (int) $this->categoryId;
10900
    }
10901
10902
    /**
10903
     * @param int $categoryId
10904
     *
10905
     * @return bool
10906
     */
10907
    public function setCategoryId($categoryId)
10908
    {
10909
        $this->categoryId = (int) $categoryId;
10910
        $table = Database::get_course_table(TABLE_LP_MAIN);
10911
        $lp_id = $this->get_id();
10912
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10913
                WHERE iid = $lp_id";
10914
        Database::query($sql);
10915
10916
        return true;
10917
    }
10918
10919
    /**
10920
     * Get whether this is a learning path with the possibility to subscribe
10921
     * users or not.
10922
     *
10923
     * @return int
10924
     */
10925
    public function getSubscribeUsers()
10926
    {
10927
        return $this->subscribeUsers;
10928
    }
10929
10930
    /**
10931
     * Set whether this is a learning path with the possibility to subscribe
10932
     * users or not.
10933
     *
10934
     * @param int $value (0 = false, 1 = true)
10935
     *
10936
     * @return bool
10937
     */
10938
    public function setSubscribeUsers($value)
10939
    {
10940
        $this->subscribeUsers = (int) $value;
10941
        $table = Database::get_course_table(TABLE_LP_MAIN);
10942
        $lp_id = $this->get_id();
10943
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10944
                WHERE iid = $lp_id";
10945
        Database::query($sql);
10946
10947
        return true;
10948
    }
10949
10950
    /**
10951
     * Calculate the count of stars for a user in this LP
10952
     * This calculation is based on the following rules:
10953
     * - the student gets one star when he gets to 50% of the learning path
10954
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10955
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10956
     * - the student gets the final star when the score for the *last* test is >= 80%.
10957
     *
10958
     * @param int $sessionId Optional. The session ID
10959
     *
10960
     * @return int The count of stars
10961
     */
10962
    public function getCalculateStars($sessionId = 0)
10963
    {
10964
        $stars = 0;
10965
        $progress = self::getProgress(
10966
            $this->lp_id,
10967
            $this->user_id,
10968
            $this->course_int_id,
10969
            $sessionId
10970
        );
10971
10972
        if ($progress >= 50) {
10973
            $stars++;
10974
        }
10975
10976
        // Calculate stars chapters evaluation
10977
        $exercisesItems = $this->getExercisesItems();
10978
10979
        if (!empty($exercisesItems)) {
10980
            $totalResult = 0;
10981
10982
            foreach ($exercisesItems as $exerciseItem) {
10983
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10984
                    $this->user_id,
10985
                    $exerciseItem->path,
10986
                    $this->course_int_id,
10987
                    $sessionId,
10988
                    $this->lp_id,
10989
                    $exerciseItem->db_id
10990
                );
10991
10992
                $exerciseResultInfo = end($exerciseResultInfo);
10993
10994
                if (!$exerciseResultInfo) {
10995
                    continue;
10996
                }
10997
10998
                if (!empty($exerciseResultInfo['max_score'])) {
10999
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
11000
                } else {
11001
                    $exerciseResult = 0;
11002
                }
11003
                $totalResult += $exerciseResult;
11004
            }
11005
11006
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
11007
11008
            if ($totalExerciseAverage >= 50) {
11009
                $stars++;
11010
            }
11011
11012
            if ($totalExerciseAverage >= 80) {
11013
                $stars++;
11014
            }
11015
        }
11016
11017
        // Calculate star for final evaluation
11018
        $finalEvaluationItem = $this->getFinalEvaluationItem();
11019
11020
        if (!empty($finalEvaluationItem)) {
11021
            $evaluationResultInfo = Event::getExerciseResultsByUser(
11022
                $this->user_id,
11023
                $finalEvaluationItem->path,
11024
                $this->course_int_id,
11025
                $sessionId,
11026
                $this->lp_id,
11027
                $finalEvaluationItem->db_id
11028
            );
11029
11030
            $evaluationResultInfo = end($evaluationResultInfo);
11031
11032
            if ($evaluationResultInfo) {
11033
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
11034
11035
                if ($evaluationResult >= 80) {
11036
                    $stars++;
11037
                }
11038
            }
11039
        }
11040
11041
        return $stars;
11042
    }
11043
11044
    /**
11045
     * Get the items of exercise type.
11046
     *
11047
     * @return array The items. Otherwise return false
11048
     */
11049
    public function getExercisesItems()
11050
    {
11051
        $exercises = [];
11052
        foreach ($this->items as $item) {
11053
            if ('quiz' != $item->type) {
11054
                continue;
11055
            }
11056
            $exercises[] = $item;
11057
        }
11058
11059
        array_pop($exercises);
11060
11061
        return $exercises;
11062
    }
11063
11064
    /**
11065
     * Get the item of exercise type (evaluation type).
11066
     *
11067
     * @return array The final evaluation. Otherwise return false
11068
     */
11069
    public function getFinalEvaluationItem()
11070
    {
11071
        $exercises = [];
11072
        foreach ($this->items as $item) {
11073
            if ('quiz' != $item->type) {
11074
                continue;
11075
            }
11076
11077
            $exercises[] = $item;
11078
        }
11079
11080
        return array_pop($exercises);
11081
    }
11082
11083
    /**
11084
     * Calculate the total points achieved for the current user in this learning path.
11085
     *
11086
     * @param int $sessionId Optional. The session Id
11087
     *
11088
     * @return int
11089
     */
11090
    public function getCalculateScore($sessionId = 0)
11091
    {
11092
        // Calculate stars chapters evaluation
11093
        $exercisesItems = $this->getExercisesItems();
11094
        $finalEvaluationItem = $this->getFinalEvaluationItem();
11095
        $totalExercisesResult = 0;
11096
        $totalEvaluationResult = 0;
11097
11098
        if (false !== $exercisesItems) {
11099
            foreach ($exercisesItems as $exerciseItem) {
11100
                $exerciseResultInfo = Event::getExerciseResultsByUser(
11101
                    $this->user_id,
11102
                    $exerciseItem->path,
11103
                    $this->course_int_id,
11104
                    $sessionId,
11105
                    $this->lp_id,
11106
                    $exerciseItem->db_id
11107
                );
11108
11109
                $exerciseResultInfo = end($exerciseResultInfo);
11110
11111
                if (!$exerciseResultInfo) {
11112
                    continue;
11113
                }
11114
11115
                $totalExercisesResult += $exerciseResultInfo['score'];
11116
            }
11117
        }
11118
11119
        if (!empty($finalEvaluationItem)) {
11120
            $evaluationResultInfo = Event::getExerciseResultsByUser(
11121
                $this->user_id,
11122
                $finalEvaluationItem->path,
11123
                $this->course_int_id,
11124
                $sessionId,
11125
                $this->lp_id,
11126
                $finalEvaluationItem->db_id
11127
            );
11128
11129
            $evaluationResultInfo = end($evaluationResultInfo);
11130
11131
            if ($evaluationResultInfo) {
11132
                $totalEvaluationResult += $evaluationResultInfo['score'];
11133
            }
11134
        }
11135
11136
        return $totalExercisesResult + $totalEvaluationResult;
11137
    }
11138
11139
    /**
11140
     * Check if URL is not allowed to be show in a iframe.
11141
     *
11142
     * @param string $src
11143
     *
11144
     * @return string
11145
     */
11146
    public function fixBlockedLinks($src)
11147
    {
11148
        $urlInfo = parse_url($src);
11149
11150
        $platformProtocol = 'https';
11151
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
11152
            $platformProtocol = 'http';
11153
        }
11154
11155
        $protocolFixApplied = false;
11156
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
11157
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
11158
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
11159
11160
        if ($platformProtocol != $scheme) {
11161
            Session::write('x_frame_source', $src);
11162
            $src = 'blank.php?error=x_frames_options';
11163
            $protocolFixApplied = true;
11164
        }
11165
11166
        if (false == $protocolFixApplied) {
11167
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
11168
                // Check X-Frame-Options
11169
                $ch = curl_init();
11170
                $options = [
11171
                    CURLOPT_URL => $src,
11172
                    CURLOPT_RETURNTRANSFER => true,
11173
                    CURLOPT_HEADER => true,
11174
                    CURLOPT_FOLLOWLOCATION => true,
11175
                    CURLOPT_ENCODING => "",
11176
                    CURLOPT_AUTOREFERER => true,
11177
                    CURLOPT_CONNECTTIMEOUT => 120,
11178
                    CURLOPT_TIMEOUT => 120,
11179
                    CURLOPT_MAXREDIRS => 10,
11180
                ];
11181
11182
                $proxySettings = api_get_configuration_value('proxy_settings');
11183
                if (!empty($proxySettings) &&
11184
                    isset($proxySettings['curl_setopt_array'])
11185
                ) {
11186
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
11187
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
11188
                }
11189
11190
                curl_setopt_array($ch, $options);
11191
                $response = curl_exec($ch);
11192
                $httpCode = curl_getinfo($ch);
11193
                $headers = substr($response, 0, $httpCode['header_size']);
11194
11195
                $error = false;
11196
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
11197
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
11198
                ) {
11199
                    $error = true;
11200
                }
11201
11202
                if ($error) {
11203
                    Session::write('x_frame_source', $src);
11204
                    $src = 'blank.php?error=x_frames_options';
11205
                }
11206
            }
11207
        }
11208
11209
        return $src;
11210
    }
11211
11212
    /**
11213
     * Check if this LP has a created forum in the basis course.
11214
     *
11215
     * @return bool
11216
     */
11217
    public function lpHasForum()
11218
    {
11219
        $forumTable = Database::get_course_table(TABLE_FORUM);
11220
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
11221
11222
        $fakeFrom = "
11223
            $forumTable f
11224
            INNER JOIN $itemProperty ip
11225
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
11226
        ";
11227
11228
        $resultData = Database::select(
11229
            'COUNT(f.iid) AS qty',
11230
            $fakeFrom,
11231
            [
11232
                'where' => [
11233
                    'ip.visibility != ? AND ' => 2,
11234
                    'ip.tool = ? AND ' => TOOL_FORUM,
11235
                    'f.c_id = ? AND ' => intval($this->course_int_id),
11236
                    'f.lp_id = ?' => intval($this->lp_id),
11237
                ],
11238
            ],
11239
            'first'
11240
        );
11241
11242
        return $resultData['qty'] > 0;
11243
    }
11244
11245
    /**
11246
     * Get the forum for this learning path.
11247
     *
11248
     * @param int $sessionId
11249
     *
11250
     * @return array
11251
     */
11252
    public function getForum($sessionId = 0)
11253
    {
11254
        $repo = Container::getForumRepository();
11255
11256
        $course = api_get_course_entity();
11257
        $session = api_get_session_entity($sessionId);
11258
        $qb = $repo->getResourcesByCourse($course, $session);
11259
11260
        return $qb->getQuery()->getResult();
11261
    }
11262
11263
    /**
11264
     * Create a forum for this learning path.
11265
     *
11266
     * @param int $forumCategoryId
11267
     *
11268
     * @return int The forum ID if was created. Otherwise return false
11269
     */
11270
    public function createForum($forumCategoryId)
11271
    {
11272
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
11273
11274
        $forumId = store_forum(
11275
            [
11276
                'lp_id' => $this->lp_id,
11277
                'forum_title' => $this->name,
11278
                'forum_comment' => null,
11279
                'forum_category' => (int) $forumCategoryId,
11280
                'students_can_edit_group' => ['students_can_edit' => 0],
11281
                'allow_new_threads_group' => ['allow_new_threads' => 0],
11282
                'default_view_type_group' => ['default_view_type' => 'flat'],
11283
                'group_forum' => 0,
11284
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
11285
            ],
11286
            [],
11287
            true
11288
        );
11289
11290
        return $forumId;
11291
    }
11292
11293
    /**
11294
     * Get the LP Final Item form.
11295
     *
11296
     * @throws Exception
11297
     * @throws HTML_QuickForm_Error
11298
     *
11299
     * @return string
11300
     */
11301
    public function getFinalItemForm()
11302
    {
11303
        $finalItem = $this->getFinalItem();
11304
        $title = '';
11305
11306
        if ($finalItem) {
11307
            $title = $finalItem->get_title();
11308
            $buttonText = get_lang('Save');
11309
            $content = $this->getSavedFinalItem();
11310
        } else {
11311
            $buttonText = get_lang('Add this document to the course');
11312
            $content = $this->getFinalItemTemplate();
11313
        }
11314
11315
        $courseInfo = api_get_course_info();
11316
        $result = $this->generate_lp_folder($courseInfo);
11317
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
11318
        $relative_prefix = '../../';
11319
11320
        $editorConfig = [
11321
            'ToolbarSet' => 'LearningPathDocuments',
11322
            'Width' => '100%',
11323
            'Height' => '500',
11324
            'FullPage' => true,
11325
            'CreateDocumentDir' => $relative_prefix,
11326
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
11327
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
11328
        ];
11329
11330
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
11331
            'type' => 'document',
11332
            'lp_id' => $this->lp_id,
11333
        ]);
11334
11335
        $form = new FormValidator('final_item', 'POST', $url);
11336
        $form->addText('title', get_lang('Title'));
11337
        $form->addButtonSave($buttonText);
11338
        $form->addHtml(
11339
            Display::return_message(
11340
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
11341
                'normal',
11342
                false
11343
            )
11344
        );
11345
11346
        $renderer = $form->defaultRenderer();
11347
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
11348
11349
        $form->addHtmlEditor(
11350
            'content_lp_certificate',
11351
            null,
11352
            true,
11353
            false,
11354
            $editorConfig,
11355
            true
11356
        );
11357
        $form->addHidden('action', 'add_final_item');
11358
        $form->addHidden('path', Session::read('pathItem'));
11359
        $form->addHidden('previous', $this->get_last());
11360
        $form->setDefaults(
11361
            ['title' => $title, 'content_lp_certificate' => $content]
11362
        );
11363
11364
        if ($form->validate()) {
11365
            $values = $form->exportValues();
11366
            $lastItemId = $this->getLastInFirstLevel();
11367
11368
            if (!$finalItem) {
11369
                $documentId = $this->create_document(
11370
                    $this->course_info,
11371
                    $values['content_lp_certificate'],
11372
                    $values['title']
11373
                );
11374
                $this->add_item(
11375
                    0,
11376
                    $lastItemId,
11377
                    'final_item',
11378
                    $documentId,
11379
                    $values['title'],
11380
                    ''
11381
                );
11382
11383
                Display::addFlash(
11384
                    Display::return_message(get_lang('Added'))
11385
                );
11386
            } else {
11387
                $this->edit_document($this->course_info);
11388
            }
11389
        }
11390
11391
        return $form->returnForm();
11392
    }
11393
11394
    /**
11395
     * Check if the current lp item is first, both, last or none from lp list.
11396
     *
11397
     * @param int $currentItemId
11398
     *
11399
     * @return string
11400
     */
11401
    public function isFirstOrLastItem($currentItemId)
11402
    {
11403
        $lpItemId = [];
11404
        $typeListNotToVerify = self::getChapterTypes();
11405
11406
        // Using get_toc() function instead $this->items because returns the correct order of the items
11407
        foreach ($this->get_toc() as $item) {
11408
            if (!in_array($item['type'], $typeListNotToVerify)) {
11409
                $lpItemId[] = $item['id'];
11410
            }
11411
        }
11412
11413
        $lastLpItemIndex = count($lpItemId) - 1;
11414
        $position = array_search($currentItemId, $lpItemId);
11415
11416
        switch ($position) {
11417
            case 0:
11418
                if (!$lastLpItemIndex) {
11419
                    $answer = 'both';
11420
                    break;
11421
                }
11422
11423
                $answer = 'first';
11424
                break;
11425
            case $lastLpItemIndex:
11426
                $answer = 'last';
11427
                break;
11428
            default:
11429
                $answer = 'none';
11430
        }
11431
11432
        return $answer;
11433
    }
11434
11435
    /**
11436
     * Get whether this is a learning path with the accumulated SCORM time or not.
11437
     *
11438
     * @return int
11439
     */
11440
    public function getAccumulateScormTime()
11441
    {
11442
        return $this->accumulateScormTime;
11443
    }
11444
11445
    /**
11446
     * Set whether this is a learning path with the accumulated SCORM time or not.
11447
     *
11448
     * @param int $value (0 = false, 1 = true)
11449
     *
11450
     * @return bool Always returns true
11451
     */
11452
    public function setAccumulateScormTime($value)
11453
    {
11454
        $this->accumulateScormTime = (int) $value;
11455
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11456
        $lp_id = $this->get_id();
11457
        $sql = "UPDATE $lp_table
11458
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
11459
                WHERE iid = $lp_id";
11460
        Database::query($sql);
11461
11462
        return true;
11463
    }
11464
11465
    /**
11466
     * Returns an HTML-formatted link to a resource, to incorporate directly into
11467
     * the new learning path tool.
11468
     *
11469
     * The function is a big switch on tool type.
11470
     * In each case, we query the corresponding table for information and build the link
11471
     * with that information.
11472
     *
11473
     * @author Yannick Warnier <[email protected]> - rebranding based on
11474
     * previous work (display_addedresource_link_in_learnpath())
11475
     *
11476
     * @param int $course_id      Course code
11477
     * @param int $learningPathId The learning path ID (in lp table)
11478
     * @param int $id_in_path     the unique index in the items table
11479
     * @param int $lpViewId
11480
     *
11481
     * @return string
11482
     */
11483
    public static function rl_get_resource_link_for_learnpath(
11484
        $course_id,
11485
        $learningPathId,
11486
        $id_in_path,
11487
        $lpViewId
11488
    ) {
11489
        $session_id = api_get_session_id();
11490
        $course_info = api_get_course_info_by_id($course_id);
11491
11492
        $learningPathId = (int) $learningPathId;
11493
        $id_in_path = (int) $id_in_path;
11494
        $lpViewId = (int) $lpViewId;
11495
11496
        $em = Database::getManager();
11497
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
11498
11499
        /** @var CLpItem $rowItem */
11500
        $rowItem = $lpItemRepo->findOneBy([
11501
            'cId' => $course_id,
11502
            'lpId' => $learningPathId,
11503
            'iid' => $id_in_path,
11504
        ]);
11505
11506
        if (!$rowItem) {
11507
            // Try one more time with "id"
11508
            /** @var CLpItem $rowItem */
11509
            $rowItem = $lpItemRepo->findOneBy([
11510
                'cId' => $course_id,
11511
                'lpId' => $learningPathId,
11512
                'id' => $id_in_path,
11513
            ]);
11514
11515
            if (!$rowItem) {
11516
                return -1;
11517
            }
11518
        }
11519
11520
        $type = $rowItem->getItemType();
11521
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
11522
        $main_dir_path = api_get_path(WEB_CODE_PATH);
11523
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
11524
        $link = '';
11525
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
11526
11527
        switch ($type) {
11528
            case 'dir':
11529
                return $main_dir_path.'lp/blank.php';
11530
            case TOOL_CALENDAR_EVENT:
11531
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
11532
            case TOOL_ANNOUNCEMENT:
11533
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
11534
            case TOOL_LINK:
11535
                $linkInfo = Link::getLinkInfo($id);
11536
                if (isset($linkInfo['url'])) {
11537
                    return $linkInfo['url'];
11538
                }
11539
11540
                return '';
11541
            case TOOL_QUIZ:
11542
                if (empty($id)) {
11543
                    return '';
11544
                }
11545
11546
                // Get the lp_item_view with the highest view_count.
11547
                $learnpathItemViewResult = $em
11548
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
11549
                    ->findBy(
11550
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
11551
                        ['viewCount' => 'DESC'],
11552
                        1
11553
                    );
11554
                /** @var CLpItemView $learnpathItemViewData */
11555
                $learnpathItemViewData = current($learnpathItemViewResult);
11556
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
11557
11558
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
11559
                    .http_build_query([
11560
                        'lp_init' => 1,
11561
                        'learnpath_item_view_id' => $learnpathItemViewId,
11562
                        'learnpath_id' => $learningPathId,
11563
                        'learnpath_item_id' => $id_in_path,
11564
                        'exerciseId' => $id,
11565
                    ]);
11566
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
11567
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
11568
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
11569
                $myrow = Database::fetch_array($result);
11570
                $path = $myrow['path'];
11571
11572
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
11573
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
11574
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
11575
            case TOOL_FORUM:
11576
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
11577
            case TOOL_THREAD:
11578
                // forum post
11579
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
11580
                if (empty($id)) {
11581
                    return '';
11582
                }
11583
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
11584
                $result = Database::query($sql);
11585
                $myrow = Database::fetch_array($result);
11586
11587
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
11588
                    .$extraParams;
11589
            case TOOL_POST:
11590
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11591
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
11592
                $myrow = Database::fetch_array($result);
11593
11594
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
11595
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
11596
            case TOOL_READOUT_TEXT:
11597
                return api_get_path(WEB_CODE_PATH).
11598
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
11599
            case TOOL_DOCUMENT:
11600
                $repo = Container::getDocumentRepository();
11601
                $document = $repo->find($rowItem->getPath());
11602
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
11603
11604
                return $file;
11605
11606
                $documentPathInfo = pathinfo($document->getPath());
0 ignored issues
show
Unused Code introduced by
$documentPathInfo = path...o($document->getPath()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
11607
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
11608
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
11609
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
11610
11611
                $openmethod = 2;
11612
                $officedoc = false;
11613
                Session::write('openmethod', $openmethod);
11614
                Session::write('officedoc', $officedoc);
11615
11616
                if ($showDirectUrl) {
11617
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
11618
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
11619
                        if (Link::isPdfLink($file)) {
11620
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
11621
11622
                            return $pdfUrl;
11623
                        }
11624
                    }
11625
11626
                    return $file;
11627
                }
11628
11629
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
11630
            case TOOL_LP_FINAL_ITEM:
11631
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
11632
                    .$extraParams;
11633
            case 'assignments':
11634
                return $main_dir_path.'work/work.php?'.$extraParams;
11635
            case TOOL_DROPBOX:
11636
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
11637
            case 'introduction_text': //DEPRECATED
11638
                return '';
11639
            case TOOL_COURSE_DESCRIPTION:
11640
                return $main_dir_path.'course_description?'.$extraParams;
11641
            case TOOL_GROUP:
11642
                return $main_dir_path.'group/group.php?'.$extraParams;
11643
            case TOOL_USER:
11644
                return $main_dir_path.'user/user.php?'.$extraParams;
11645
            case TOOL_STUDENTPUBLICATION:
11646
                if (!empty($rowItem->getPath())) {
11647
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
11648
                }
11649
11650
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
11651
        }
11652
11653
        return $link;
11654
    }
11655
11656
    /**
11657
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11658
     *
11659
     * @author Yannick Warnier <[email protected]>
11660
     *
11661
     * @param string $course_code    Course code
11662
     * @param int    $learningPathId
11663
     * @param int    $id_in_path     The resource ID
11664
     *
11665
     * @return string
11666
     */
11667
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11668
    {
11669
        $_course = api_get_course_info($course_code);
11670
        if (empty($_course)) {
11671
            return '';
11672
        }
11673
        $course_id = $_course['real_id'];
11674
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11675
        $learningPathId = (int) $learningPathId;
11676
        $id_in_path = (int) $id_in_path;
11677
11678
        $sql = "SELECT item_type, title, ref
11679
                FROM $tbl_lp_item
11680
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11681
        $res_item = Database::query($sql);
11682
11683
        if (Database::num_rows($res_item) < 1) {
11684
            return '';
11685
        }
11686
        $row_item = Database::fetch_array($res_item);
11687
        $type = strtolower($row_item['item_type']);
11688
        $id = $row_item['ref'];
11689
        $output = '';
11690
11691
        switch ($type) {
11692
            case TOOL_CALENDAR_EVENT:
11693
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11694
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11695
                $myrow = Database::fetch_array($result);
11696
                $output = $myrow['title'];
11697
                break;
11698
            case TOOL_ANNOUNCEMENT:
11699
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11700
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11701
                $myrow = Database::fetch_array($result);
11702
                $output = $myrow['title'];
11703
                break;
11704
            case TOOL_LINK:
11705
                // Doesn't take $target into account.
11706
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11707
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11708
                $myrow = Database::fetch_array($result);
11709
                $output = $myrow['title'];
11710
                break;
11711
            case TOOL_QUIZ:
11712
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11713
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11714
                $myrow = Database::fetch_array($result);
11715
                $output = $myrow['title'];
11716
                break;
11717
            case TOOL_FORUM:
11718
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11719
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11720
                $myrow = Database::fetch_array($result);
11721
                $output = $myrow['forum_name'];
11722
                break;
11723
            case TOOL_THREAD:
11724
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11725
                // Grabbing the title of the post.
11726
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11727
                $result_title = Database::query($sql_title);
11728
                $myrow_title = Database::fetch_array($result_title);
11729
                $output = $myrow_title['post_title'];
11730
                break;
11731
            case TOOL_POST:
11732
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11733
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11734
                $result = Database::query($sql);
11735
                $post = Database::fetch_array($result);
11736
                $output = $post['post_title'];
11737
                break;
11738
            case 'dir':
11739
            case TOOL_DOCUMENT:
11740
                $title = $row_item['title'];
11741
                $output = '-';
11742
                if (!empty($title)) {
11743
                    $output = $title;
11744
                }
11745
                break;
11746
            case 'hotpotatoes':
11747
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11748
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11749
                $myrow = Database::fetch_array($result);
11750
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11751
                $last = count($pathname) - 1; // Making a correct name for the link.
11752
                $filename = $pathname[$last]; // Making a correct name for the link.
11753
                $myrow['path'] = rawurlencode($myrow['path']);
11754
                $output = $filename;
11755
                break;
11756
        }
11757
11758
        return stripslashes($output);
11759
    }
11760
11761
    /**
11762
     * Get the parent names for the current item.
11763
     *
11764
     * @param int $newItemId Optional. The item ID
11765
     *
11766
     * @return array
11767
     */
11768
    public function getCurrentItemParentNames($newItemId = 0)
11769
    {
11770
        $newItemId = $newItemId ?: $this->get_current_item_id();
11771
        $return = [];
11772
        $item = $this->getItem($newItemId);
11773
        $parent = $this->getItem($item->get_parent());
11774
11775
        while ($parent) {
11776
            $return[] = $parent->get_title();
11777
            $parent = $this->getItem($parent->get_parent());
11778
        }
11779
11780
        return array_reverse($return);
11781
    }
11782
11783
    /**
11784
     * Reads and process "lp_subscription_settings" setting.
11785
     *
11786
     * @return array
11787
     */
11788
    public static function getSubscriptionSettings()
11789
    {
11790
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11791
        if (empty($subscriptionSettings)) {
11792
            // By default allow both settings
11793
            $subscriptionSettings = [
11794
                'allow_add_users_to_lp' => true,
11795
                'allow_add_users_to_lp_category' => true,
11796
            ];
11797
        } else {
11798
            $subscriptionSettings = $subscriptionSettings['options'];
11799
        }
11800
11801
        return $subscriptionSettings;
11802
    }
11803
11804
    /**
11805
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11806
     */
11807
    public function exportToCourseBuildFormat()
11808
    {
11809
        if (!api_is_allowed_to_edit()) {
11810
            return false;
11811
        }
11812
11813
        $courseBuilder = new CourseBuilder();
11814
        $itemList = [];
11815
        /** @var learnpathItem $item */
11816
        foreach ($this->items as $item) {
11817
            $itemList[$item->get_type()][] = $item->get_path();
11818
        }
11819
11820
        if (empty($itemList)) {
11821
            return false;
11822
        }
11823
11824
        if (isset($itemList['document'])) {
11825
            // Get parents
11826
            foreach ($itemList['document'] as $documentId) {
11827
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11828
                if (!empty($documentInfo['parents'])) {
11829
                    foreach ($documentInfo['parents'] as $parentInfo) {
11830
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11831
                            continue;
11832
                        }
11833
                        $itemList['document'][] = $parentInfo['iid'];
11834
                    }
11835
                }
11836
            }
11837
11838
            $courseInfo = api_get_course_info();
11839
            foreach ($itemList['document'] as $documentId) {
11840
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11841
                $items = DocumentManager::get_resources_from_source_html(
11842
                    $documentInfo['absolute_path'],
11843
                    true,
11844
                    TOOL_DOCUMENT
11845
                );
11846
11847
                if (!empty($items)) {
11848
                    foreach ($items as $item) {
11849
                        // Get information about source url
11850
                        $url = $item[0]; // url
11851
                        $scope = $item[1]; // scope (local, remote)
11852
                        $type = $item[2]; // type (rel, abs, url)
11853
11854
                        $origParseUrl = parse_url($url);
11855
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11856
11857
                        if ('local' == $scope) {
11858
                            if ('abs' == $type || 'rel' == $type) {
11859
                                $documentFile = strstr($realOrigPath, 'document');
11860
                                if (false !== strpos($realOrigPath, $documentFile)) {
11861
                                    $documentFile = str_replace('document', '', $documentFile);
11862
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11863
                                    // Document found! Add it to the list
11864
                                    if ($itemDocumentId) {
11865
                                        $itemList['document'][] = $itemDocumentId;
11866
                                    }
11867
                                }
11868
                            }
11869
                        }
11870
                    }
11871
                }
11872
            }
11873
11874
            $courseBuilder->build_documents(
11875
                api_get_session_id(),
11876
                $this->get_course_int_id(),
11877
                true,
11878
                $itemList['document']
11879
            );
11880
        }
11881
11882
        if (isset($itemList['quiz'])) {
11883
            $courseBuilder->build_quizzes(
11884
                api_get_session_id(),
11885
                $this->get_course_int_id(),
11886
                true,
11887
                $itemList['quiz']
11888
            );
11889
        }
11890
11891
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11892
11893
        /*if (!empty($itemList['thread'])) {
11894
            $postList = [];
11895
            foreach ($itemList['thread'] as $postId) {
11896
                $post = get_post_information($postId);
11897
                if ($post) {
11898
                    if (!isset($itemList['forum'])) {
11899
                        $itemList['forum'] = [];
11900
                    }
11901
                    $itemList['forum'][] = $post['forum_id'];
11902
                    $postList[] = $postId;
11903
                }
11904
            }
11905
11906
            if (!empty($postList)) {
11907
                $courseBuilder->build_forum_posts(
11908
                    $this->get_course_int_id(),
11909
                    null,
11910
                    null,
11911
                    $postList
11912
                );
11913
            }
11914
        }*/
11915
11916
        if (!empty($itemList['thread'])) {
11917
            $threadList = [];
11918
            $em = Database::getManager();
11919
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11920
            foreach ($itemList['thread'] as $threadId) {
11921
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11922
                $thread = $repo->find($threadId);
11923
                if ($thread) {
11924
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11925
                    $threadList[] = $thread->getIid();
11926
                }
11927
            }
11928
11929
            if (!empty($threadList)) {
11930
                $courseBuilder->build_forum_topics(
11931
                    api_get_session_id(),
11932
                    $this->get_course_int_id(),
11933
                    null,
11934
                    $threadList
11935
                );
11936
            }
11937
        }
11938
11939
        $forumCategoryList = [];
11940
        if (isset($itemList['forum'])) {
11941
            foreach ($itemList['forum'] as $forumId) {
11942
                $forumInfo = get_forums($forumId);
11943
                $forumCategoryList[] = $forumInfo['forum_category'];
11944
            }
11945
        }
11946
11947
        if (!empty($forumCategoryList)) {
11948
            $courseBuilder->build_forum_category(
11949
                api_get_session_id(),
11950
                $this->get_course_int_id(),
11951
                true,
11952
                $forumCategoryList
11953
            );
11954
        }
11955
11956
        if (!empty($itemList['forum'])) {
11957
            $courseBuilder->build_forums(
11958
                api_get_session_id(),
11959
                $this->get_course_int_id(),
11960
                true,
11961
                $itemList['forum']
11962
            );
11963
        }
11964
11965
        if (isset($itemList['link'])) {
11966
            $courseBuilder->build_links(
11967
                api_get_session_id(),
11968
                $this->get_course_int_id(),
11969
                true,
11970
                $itemList['link']
11971
            );
11972
        }
11973
11974
        if (!empty($itemList['student_publication'])) {
11975
            $courseBuilder->build_works(
11976
                api_get_session_id(),
11977
                $this->get_course_int_id(),
11978
                true,
11979
                $itemList['student_publication']
11980
            );
11981
        }
11982
11983
        $courseBuilder->build_learnpaths(
11984
            api_get_session_id(),
11985
            $this->get_course_int_id(),
11986
            true,
11987
            [$this->get_id()],
11988
            false
11989
        );
11990
11991
        $courseBuilder->restoreDocumentsFromList();
11992
11993
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11994
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11995
        $result = DocumentManager::file_send_for_download(
11996
            $zipPath,
11997
            true,
11998
            $this->get_name().'.zip'
11999
        );
12000
12001
        if ($result) {
12002
            api_not_allowed();
12003
        }
12004
12005
        return true;
12006
    }
12007
12008
    /**
12009
     * Get whether this is a learning path with the accumulated work time or not.
12010
     *
12011
     * @return int
12012
     */
12013
    public function getAccumulateWorkTime()
12014
    {
12015
        return (int) $this->accumulateWorkTime;
12016
    }
12017
12018
    /**
12019
     * Get whether this is a learning path with the accumulated work time or not.
12020
     *
12021
     * @return int
12022
     */
12023
    public function getAccumulateWorkTimeTotalCourse()
12024
    {
12025
        $table = Database::get_course_table(TABLE_LP_MAIN);
12026
        $sql = "SELECT SUM(accumulate_work_time) AS total
12027
                FROM $table
12028
                WHERE c_id = ".$this->course_int_id;
12029
        $result = Database::query($sql);
12030
        $row = Database::fetch_array($result);
12031
12032
        return (int) $row['total'];
12033
    }
12034
12035
    /**
12036
     * Set whether this is a learning path with the accumulated work time or not.
12037
     *
12038
     * @param int $value (0 = false, 1 = true)
12039
     *
12040
     * @return bool
12041
     */
12042
    public function setAccumulateWorkTime($value)
12043
    {
12044
        if (!api_get_configuration_value('lp_minimum_time')) {
12045
            return false;
12046
        }
12047
12048
        $this->accumulateWorkTime = (int) $value;
12049
        $table = Database::get_course_table(TABLE_LP_MAIN);
12050
        $lp_id = $this->get_id();
12051
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
12052
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
12053
        Database::query($sql);
12054
12055
        return true;
12056
    }
12057
12058
    /**
12059
     * @param int $lpId
12060
     * @param int $courseId
12061
     *
12062
     * @return mixed
12063
     */
12064
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
12065
    {
12066
        $lpId = (int) $lpId;
12067
        $courseId = (int) $courseId;
12068
12069
        $table = Database::get_course_table(TABLE_LP_MAIN);
12070
        $sql = "SELECT accumulate_work_time
12071
                FROM $table
12072
                WHERE c_id = $courseId AND id = $lpId";
12073
        $result = Database::query($sql);
12074
        $row = Database::fetch_array($result);
12075
12076
        return $row['accumulate_work_time'];
12077
    }
12078
12079
    /**
12080
     * @param int $courseId
12081
     *
12082
     * @return int
12083
     */
12084
    public static function getAccumulateWorkTimeTotal($courseId)
12085
    {
12086
        $table = Database::get_course_table(TABLE_LP_MAIN);
12087
        $courseId = (int) $courseId;
12088
        $sql = "SELECT SUM(accumulate_work_time) AS total
12089
                FROM $table
12090
                WHERE c_id = $courseId";
12091
        $result = Database::query($sql);
12092
        $row = Database::fetch_array($result);
12093
12094
        return (int) $row['total'];
12095
    }
12096
12097
    /**
12098
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
12099
     * and put the images in.
12100
     *
12101
     * @return array
12102
     */
12103
    public static function getIconSelect()
12104
    {
12105
        $theme = api_get_visual_theme();
12106
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
12107
        $icons = ['' => get_lang('Please select an option')];
12108
12109
        if (is_dir($path)) {
12110
            $finder = new Finder();
12111
            $finder->files()->in($path);
12112
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
12113
            /** @var SplFileInfo $file */
12114
            foreach ($finder as $file) {
12115
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
12116
                    $icons[$file->getFilename()] = $file->getFilename();
12117
                }
12118
            }
12119
        }
12120
12121
        return $icons;
12122
    }
12123
12124
    /**
12125
     * @param int $lpId
12126
     *
12127
     * @return string
12128
     */
12129
    public static function getSelectedIcon($lpId)
12130
    {
12131
        $extraFieldValue = new ExtraFieldValue('lp');
12132
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
12133
        $icon = '';
12134
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
12135
            $icon = $lpIcon['value'];
12136
        }
12137
12138
        return $icon;
12139
    }
12140
12141
    /**
12142
     * @param int $lpId
12143
     *
12144
     * @return string
12145
     */
12146
    public static function getSelectedIconHtml($lpId)
12147
    {
12148
        $icon = self::getSelectedIcon($lpId);
12149
12150
        if (empty($icon)) {
12151
            return '';
12152
        }
12153
12154
        $theme = api_get_visual_theme();
12155
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
12156
12157
        return Display::img($path);
12158
    }
12159
12160
    /**
12161
     * @param string $value
12162
     *
12163
     * @return string
12164
     */
12165
    public function cleanItemTitle($value)
12166
    {
12167
        $value = Security::remove_XSS(strip_tags($value));
12168
12169
        return $value;
12170
    }
12171
12172
    public function setItemTitle(FormValidator $form)
12173
    {
12174
        if (api_get_configuration_value('save_titles_as_html')) {
12175
            $form->addHtmlEditor(
12176
                'title',
12177
                get_lang('Title'),
12178
                true,
12179
                false,
12180
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
12181
            );
12182
        } else {
12183
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
12184
            $form->applyFilter('title', 'trim');
12185
            $form->applyFilter('title', 'html_filter');
12186
        }
12187
    }
12188
12189
    /**
12190
     * @return array
12191
     */
12192
    public function getItemsForForm($addParentCondition = false)
12193
    {
12194
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12195
        $course_id = api_get_course_int_id();
12196
12197
        $sql = "SELECT * FROM $tbl_lp_item
12198
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12199
12200
        if ($addParentCondition) {
12201
            $sql .= ' AND parent_item_id = 0 ';
12202
        }
12203
        $sql .= ' ORDER BY display_order ASC';
12204
12205
        $result = Database::query($sql);
12206
        $arrLP = [];
12207
        while ($row = Database::fetch_array($result)) {
12208
            $arrLP[] = [
12209
                'iid' => $row['iid'],
12210
                'id' => $row['iid'],
12211
                'item_type' => $row['item_type'],
12212
                'title' => $this->cleanItemTitle($row['title']),
12213
                'title_raw' => $row['title'],
12214
                'path' => $row['path'],
12215
                'description' => Security::remove_XSS($row['description']),
12216
                'parent_item_id' => $row['parent_item_id'],
12217
                'previous_item_id' => $row['previous_item_id'],
12218
                'next_item_id' => $row['next_item_id'],
12219
                'display_order' => $row['display_order'],
12220
                'max_score' => $row['max_score'],
12221
                'min_score' => $row['min_score'],
12222
                'mastery_score' => $row['mastery_score'],
12223
                'prerequisite' => $row['prerequisite'],
12224
                'max_time_allowed' => $row['max_time_allowed'],
12225
                'prerequisite_min_score' => $row['prerequisite_min_score'],
12226
                'prerequisite_max_score' => $row['prerequisite_max_score'],
12227
            ];
12228
        }
12229
12230
        return $arrLP;
12231
    }
12232
12233
    /**
12234
     * Get the depth level of LP item.
12235
     *
12236
     * @param array $items
12237
     * @param int   $currentItemId
12238
     *
12239
     * @return int
12240
     */
12241
    private static function get_level_for_item($items, $currentItemId)
12242
    {
12243
        $parentItemId = 0;
12244
        if (isset($items[$currentItemId])) {
12245
            $parentItemId = $items[$currentItemId]->parent;
12246
        }
12247
12248
        if (0 == $parentItemId) {
12249
            return 0;
12250
        } else {
12251
            return self::get_level_for_item($items, $parentItemId) + 1;
12252
        }
12253
    }
12254
12255
    /**
12256
     * Generate the link for a learnpath category as course tool.
12257
     *
12258
     * @param int $categoryId
12259
     *
12260
     * @return string
12261
     */
12262
    private static function getCategoryLinkForTool($categoryId)
12263
    {
12264
        $categoryId = (int) $categoryId;
12265
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
12266
            .http_build_query(
12267
                [
12268
                    'action' => 'view_category',
12269
                    'id' => $categoryId,
12270
                ]
12271
            );
12272
12273
        return $link;
12274
    }
12275
12276
    /**
12277
     * Return the scorm item type object with spaces replaced with _
12278
     * The return result is use to build a css classname like scorm_type_$return.
12279
     *
12280
     * @param $in_type
12281
     *
12282
     * @return mixed
12283
     */
12284
    private static function format_scorm_type_item($in_type)
12285
    {
12286
        return str_replace(' ', '_', $in_type);
12287
    }
12288
12289
    /**
12290
     * Check and obtain the lp final item if exist.
12291
     *
12292
     * @return learnpathItem
12293
     */
12294
    private function getFinalItem()
12295
    {
12296
        if (empty($this->items)) {
12297
            return null;
12298
        }
12299
12300
        foreach ($this->items as $item) {
12301
            if ('final_item' !== $item->type) {
12302
                continue;
12303
            }
12304
12305
            return $item;
12306
        }
12307
    }
12308
12309
    /**
12310
     * Get the LP Final Item Template.
12311
     *
12312
     * @return string
12313
     */
12314
    private function getFinalItemTemplate()
12315
    {
12316
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
12317
    }
12318
12319
    /**
12320
     * Get the LP Final Item Url.
12321
     *
12322
     * @return string
12323
     */
12324
    private function getSavedFinalItem()
12325
    {
12326
        $finalItem = $this->getFinalItem();
12327
        $doc = DocumentManager::get_document_data_by_id(
12328
            $finalItem->path,
12329
            $this->cc
12330
        );
12331
        if ($doc && file_exists($doc['absolute_path'])) {
12332
            return file_get_contents($doc['absolute_path']);
12333
        }
12334
12335
        return '';
12336
    }
12337
}
12338