Passed
Push — master ( db3c2b...7f030f )
by Julito
20:00
created

learnpath::displayNewSectionForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 17
rs 9.9332
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
210
                if (Database::num_rows($res) > 0) {
211
                    $row = Database::fetch_array($res);
212
                    $this->attempt = $row['view_count'];
213
                    $this->lp_view_id = $row['id'];
214
                    $this->last_item_seen = $row['last_item'];
215
                    $this->progress_db = $row['progress'];
216
                    $this->lp_view_session_id = $row['session_id'];
217
                } elseif (!api_is_invitee()) {
218
                    $this->attempt = 1;
219
                    $params = [
220
                        'c_id' => $course_id,
221
                        'lp_id' => $lp_id,
222
                        'user_id' => $user_id,
223
                        'view_count' => 1,
224
                        'session_id' => $session_id,
225
                        'last_item' => 0,
226
                    ];
227
                    $this->last_item_seen = 0;
228
                    $this->lp_view_session_id = $session_id;
229
                    $this->lp_view_id = Database::insert($lp_table, $params);
230
                    if (!empty($this->lp_view_id)) {
231
                        $sql = "UPDATE $lp_table SET id = iid
232
                                WHERE iid = ".$this->lp_view_id;
233
                        Database::query($sql);
234
                    }
235
                }
236
237
                // Initialise items.
238
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
239
                $sql = "SELECT * FROM $lp_item_table
240
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
241
                        ORDER BY parent_item_id, display_order";
242
                $res = Database::query($sql);
243
244
                $lp_item_id_list = [];
245
                while ($row = Database::fetch_array($res)) {
246
                    $lp_item_id_list[] = $row['iid'];
247
                    switch ($this->type) {
248
                        case 3: //aicc
249
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
250
                            if (is_object($oItem)) {
251
                                $my_item_id = $oItem->get_id();
252
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
253
                                $oItem->set_prevent_reinit($this->prevent_reinit);
254
                                // Don't use reference here as the next loop will make the pointed object change.
255
                                $this->items[$my_item_id] = $oItem;
256
                                $this->refs_list[$oItem->ref] = $my_item_id;
257
                            }
258
                            break;
259
                        case 2:
260
                            $oItem = new scormItem('db', $row['iid'], $course_id);
261
                            if (is_object($oItem)) {
262
                                $my_item_id = $oItem->get_id();
263
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
264
                                $oItem->set_prevent_reinit($this->prevent_reinit);
265
                                // Don't use reference here as the next loop will make the pointed object change.
266
                                $this->items[$my_item_id] = $oItem;
267
                                $this->refs_list[$oItem->ref] = $my_item_id;
268
                            }
269
                            break;
270
                        case 1:
271
                        default:
272
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
273
                            if (is_object($oItem)) {
274
                                $my_item_id = $oItem->get_id();
275
                                // Moved down to when we are sure the item_view exists.
276
                                //$oItem->set_lp_view($this->lp_view_id);
277
                                $oItem->set_prevent_reinit($this->prevent_reinit);
278
                                // Don't use reference here as the next loop will make the pointed object change.
279
                                $this->items[$my_item_id] = $oItem;
280
                                $this->refs_list[$my_item_id] = $my_item_id;
281
                            }
282
                            break;
283
                    }
284
285
                    // Setting the object level with variable $this->items[$i][parent]
286
                    foreach ($this->items as $itemLPObject) {
287
                        $level = self::get_level_for_item(
288
                            $this->items,
289
                            $itemLPObject->db_id
290
                        );
291
                        $itemLPObject->level = $level;
292
                    }
293
294
                    // Setting the view in the item object.
295
                    if (is_object($this->items[$row['iid']])) {
296
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
297
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
298
                            $this->items[$row['iid']]->current_start_time = 0;
299
                            $this->items[$row['iid']]->current_stop_time = 0;
300
                        }
301
                    }
302
                }
303
304
                if (!empty($lp_item_id_list)) {
305
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
306
                    if (!empty($lp_item_id_list_to_string)) {
307
                        // Get last viewing vars.
308
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
309
                        // This query should only return one or zero result.
310
                        $sql = "SELECT lp_item_id, status
311
                                FROM $itemViewTable
312
                                WHERE
313
                                    c_id = $course_id AND
314
                                    lp_view_id = ".$this->get_view_id()." AND
315
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
316
                                ORDER BY view_count DESC ";
317
                        $status_list = [];
318
                        $res = Database::query($sql);
319
                        while ($row = Database:: fetch_array($res)) {
320
                            $status_list[$row['lp_item_id']] = $row['status'];
321
                        }
322
323
                        foreach ($lp_item_id_list as $item_id) {
324
                            if (isset($status_list[$item_id])) {
325
                                $status = $status_list[$item_id];
326
                                if (is_object($this->items[$item_id])) {
327
                                    $this->items[$item_id]->set_status($status);
328
                                    if (empty($status)) {
329
                                        $this->items[$item_id]->set_status(
330
                                            $this->default_status
331
                                        );
332
                                    }
333
                                }
334
                            } else {
335
                                if (!api_is_invitee()) {
336
                                    if (is_object($this->items[$item_id])) {
337
                                        $this->items[$item_id]->set_status(
338
                                            $this->default_status
339
                                        );
340
                                    }
341
342
                                    if (!empty($this->lp_view_id)) {
343
                                        // Add that row to the lp_item_view table so that
344
                                        // we have something to show in the stats page.
345
                                        $params = [
346
                                            'c_id' => $course_id,
347
                                            'lp_item_id' => $item_id,
348
                                            'lp_view_id' => $this->lp_view_id,
349
                                            'view_count' => 1,
350
                                            'status' => 'not attempted',
351
                                            'start_time' => time(),
352
                                            'total_time' => 0,
353
                                            'score' => 0,
354
                                        ];
355
                                        $insertId = Database::insert($itemViewTable, $params);
356
357
                                        if ($insertId) {
358
                                            $sql = "UPDATE $itemViewTable SET id = iid
359
                                                    WHERE iid = $insertId";
360
                                            Database::query($sql);
361
                                        }
362
363
                                        $this->items[$item_id]->set_lp_view(
364
                                            $this->lp_view_id,
365
                                            $course_id
366
                                        );
367
                                    }
368
                                }
369
                            }
370
                        }
371
                    }
372
                }
373
374
                $this->ordered_items = self::get_flat_ordered_items_list(
375
                    $this->get_id(),
376
                    0,
377
                    $course_id
378
                );
379
                $this->max_ordered_items = 0;
380
                foreach ($this->ordered_items as $index => $dummy) {
381
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
382
                        $this->max_ordered_items = $index;
383
                    }
384
                }
385
                // TODO: Define the current item better.
386
                $this->first();
387
                if ($debug) {
388
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
389
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
390
                }
391
            } else {
392
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
393
            }
394
        }
395
    }
396
397
    public function getEntity(): CLp
398
    {
399
        return $this->entity;
400
    }
401
402
    /**
403
     * @return string
404
     */
405
    public function getCourseCode()
406
    {
407
        return $this->cc;
408
    }
409
410
    /**
411
     * @return int
412
     */
413
    public function get_course_int_id()
414
    {
415
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
416
    }
417
418
    /**
419
     * @param $course_id
420
     *
421
     * @return int
422
     */
423
    public function set_course_int_id($course_id)
424
    {
425
        return $this->course_int_id = (int) $course_id;
426
    }
427
428
    /**
429
     * Function rewritten based on old_add_item() from Yannick Warnier.
430
     * Due the fact that users can decide where the item should come, I had to overlook this function and
431
     * I found it better to rewrite it. Old function is still available.
432
     * Added also the possibility to add a description.
433
     *
434
     * @param int    $parent
435
     * @param int    $previous
436
     * @param string $type
437
     * @param int    $id               resource ID (ref)
438
     * @param string $title
439
     * @param string $description
440
     * @param int    $prerequisites
441
     * @param int    $max_time_allowed
442
     * @param int    $userId
443
     *
444
     * @return int
445
     */
446
    public function add_item(
447
        $parent,
448
        $previous,
449
        $type = 'dir',
450
        $id,
451
        $title,
452
        $description,
453
        $prerequisites = 0,
454
        $max_time_allowed = 0,
455
        $userId = 0
456
    ) {
457
        $course_id = $this->course_info['real_id'];
458
        if (empty($course_id)) {
459
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
460
            $this->course_info = api_get_course_info($this->cc);
461
            $course_id = $this->course_info['real_id'];
462
        }
463
        $userId = empty($userId) ? api_get_user_id() : $userId;
464
        $sessionId = api_get_session_id();
465
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
466
        $_course = $this->course_info;
467
        $parent = (int) $parent;
468
        $previous = (int) $previous;
469
        $id = (int) $id;
470
        $max_time_allowed = htmlentities($max_time_allowed);
471
        if (empty($max_time_allowed)) {
472
            $max_time_allowed = 0;
473
        }
474
        $sql = "SELECT COUNT(iid) AS num
475
                FROM $tbl_lp_item
476
                WHERE
477
                    c_id = $course_id AND
478
                    lp_id = ".$this->get_id()." AND
479
                    parent_item_id = $parent ";
480
481
        $res_count = Database::query($sql);
482
        $row = Database::fetch_array($res_count);
483
        $num = $row['num'];
484
485
        $tmp_previous = 0;
486
        $display_order = 0;
487
        $next = 0;
488
        if ($num > 0) {
489
            if (empty($previous)) {
490
                $sql = "SELECT iid, next_item_id, display_order
491
                        FROM $tbl_lp_item
492
                        WHERE
493
                            c_id = $course_id AND
494
                            lp_id = ".$this->get_id()." AND
495
                            parent_item_id = $parent AND
496
                            previous_item_id = 0 OR
497
                            previous_item_id = $parent";
498
                $result = Database::query($sql);
499
                $row = Database::fetch_array($result);
500
                if ($row) {
501
                    $next = $row['iid'];
502
                }
503
            } else {
504
                $previous = (int) $previous;
505
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
506
						FROM $tbl_lp_item
507
                        WHERE
508
                            c_id = $course_id AND
509
                            lp_id = ".$this->get_id()." AND
510
                            id = $previous";
511
                $result = Database::query($sql);
512
                $row = Database::fetch_array($result);
513
                if ($row) {
514
                    $tmp_previous = $row['iid'];
515
                    $next = $row['next_item_id'];
516
                    $display_order = $row['display_order'];
517
                }
518
            }
519
        }
520
521
        $id = (int) $id;
522
        $typeCleaned = Database::escape_string($type);
523
        $max_score = 100;
524
        if ('quiz' === $type) {
525
            $sql = 'SELECT SUM(ponderation)
526
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
527
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
528
                    ON
529
                        quiz_question.id = quiz_rel_question.question_id AND
530
                        quiz_question.c_id = quiz_rel_question.c_id
531
                    WHERE
532
                        quiz_rel_question.exercice_id = '.$id." AND
533
                        quiz_question.c_id = $course_id AND
534
                        quiz_rel_question.c_id = $course_id ";
535
            $rsQuiz = Database::query($sql);
536
            $max_score = Database::result($rsQuiz, 0, 0);
537
538
            // Disabling the exercise if we add it inside a LP
539
            $exercise = new Exercise($course_id);
540
            $exercise->read($id);
541
            $exercise->disable();
542
            $exercise->save();
543
        }
544
545
        $params = [
546
            'c_id' => $course_id,
547
            'lp_id' => $this->get_id(),
548
            'item_type' => $typeCleaned,
549
            'ref' => '',
550
            'title' => $title,
551
            'description' => $description,
552
            'path' => $id,
553
            'max_score' => $max_score,
554
            'parent_item_id' => $parent,
555
            'previous_item_id' => $previous,
556
            'next_item_id' => (int) $next,
557
            'display_order' => $display_order + 1,
558
            'prerequisite' => $prerequisites,
559
            'max_time_allowed' => $max_time_allowed,
560
            'min_score' => 0,
561
            'launch_data' => '',
562
        ];
563
564
        if (0 != $prerequisites) {
565
            $params['prerequisite'] = $prerequisites;
566
        }
567
568
        $new_item_id = Database::insert($tbl_lp_item, $params);
569
        if ($new_item_id) {
570
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
571
            Database::query($sql);
572
573
            if (!empty($next)) {
574
                $sql = "UPDATE $tbl_lp_item
575
                        SET previous_item_id = $new_item_id
576
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
577
                Database::query($sql);
578
            }
579
580
            // Update the item that should be before the new item.
581
            if (!empty($tmp_previous)) {
582
                $sql = "UPDATE $tbl_lp_item
583
                        SET next_item_id = $new_item_id
584
                        WHERE c_id = $course_id AND id = $tmp_previous";
585
                Database::query($sql);
586
            }
587
588
            // Update all the items after the new item.
589
            $sql = "UPDATE $tbl_lp_item
590
                        SET display_order = display_order + 1
591
                    WHERE
592
                        c_id = $course_id AND
593
                        lp_id = ".$this->get_id()." AND
594
                        iid <> $new_item_id AND
595
                        parent_item_id = $parent AND
596
                        display_order > $display_order";
597
            Database::query($sql);
598
599
            // Update the item that should come after the new item.
600
            $sql = "UPDATE $tbl_lp_item
601
                    SET ref = $new_item_id
602
                    WHERE c_id = $course_id AND iid = $new_item_id";
603
            Database::query($sql);
604
605
            $sql = "UPDATE $tbl_lp_item
606
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
607
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
608
            Database::query($sql);
609
610
            // Upload audio.
611
            if (!empty($_FILES['mp3']['name'])) {
612
                // Create the audio folder if it does not exist yet.
613
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
614
                if (!is_dir($filepath.'audio')) {
615
                    mkdir(
616
                        $filepath.'audio',
617
                        api_get_permissions_for_new_directories()
618
                    );
619
                    $audio_id = DocumentManager::addDocument(
620
                        $_course,
621
                        '/audio',
622
                        'folder',
623
                        0,
624
                        'audio',
625
                        '',
626
                        0,
627
                        true,
628
                        null,
629
                        $sessionId,
630
                        $userId
631
                    );
632
                }
633
634
                $file_path = handle_uploaded_document(
635
                    $_course,
636
                    $_FILES['mp3'],
637
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
638
                    '/audio',
639
                    $userId,
640
                    '',
641
                    '',
642
                    '',
643
                    '',
644
                    false
645
                );
646
647
                // Getting the filename only.
648
                $file_components = explode('/', $file_path);
649
                $file = $file_components[count($file_components) - 1];
650
651
                // Store the mp3 file in the lp_item table.
652
                $sql = "UPDATE $tbl_lp_item SET
653
                          audio = '".Database::escape_string($file)."'
654
                        WHERE iid = '".intval($new_item_id)."'";
655
                Database::query($sql);
656
            }
657
        }
658
659
        return $new_item_id;
660
    }
661
662
    /**
663
     * Static admin function allowing addition of a learnpath to a course.
664
     *
665
     * @param string $courseCode
666
     * @param string $name
667
     * @param string $description
668
     * @param string $learnpath
669
     * @param string $origin
670
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
671
     * @param string $publicated_on
672
     * @param string $expired_on
673
     * @param int    $categoryId
674
     * @param int    $userId
675
     *
676
     * @return int The new learnpath ID on success, 0 on failure
677
     */
678
    public static function add_lp(
679
        $courseCode,
680
        $name,
681
        $description = '',
682
        $learnpath = 'guess',
683
        $origin = 'zip',
684
        $zipname = '',
685
        $publicated_on = '',
686
        $expired_on = '',
687
        $categoryId = 0,
688
        $userId = 0
689
    ) {
690
        global $charset;
691
692
        if (!empty($courseCode)) {
693
            $courseInfo = api_get_course_info($courseCode);
694
            $course_id = $courseInfo['real_id'];
695
        } else {
696
            $course_id = api_get_course_int_id();
697
            $courseInfo = api_get_course_info();
698
        }
699
700
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
701
        // Check course code exists.
702
        // Check lp_name doesn't exist, otherwise append something.
703
        $i = 0;
704
        $categoryId = (int) $categoryId;
705
        // Session id.
706
        $session_id = api_get_session_id();
707
        $userId = empty($userId) ? api_get_user_id() : $userId;
708
709
        if (empty($publicated_on)) {
710
            $publicated_on = null;
711
        } else {
712
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
713
        }
714
715
        if (empty($expired_on)) {
716
            $expired_on = null;
717
        } else {
718
            $expired_on = api_get_utc_datetime($expired_on, true, true);
719
        }
720
721
        $check_name = "SELECT * FROM $tbl_lp
722
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
723
        $res_name = Database::query($check_name);
724
725
        while (Database::num_rows($res_name)) {
726
            // There is already one such name, update the current one a bit.
727
            $i++;
728
            $name = $name.' - '.$i;
729
            $check_name = "SELECT * FROM $tbl_lp
730
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
731
            $res_name = Database::query($check_name);
732
        }
733
        // New name does not exist yet; keep it.
734
        // Escape description.
735
        // Kevin: added htmlentities().
736
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
737
        $type = 1;
738
        switch ($learnpath) {
739
            case 'guess':
740
            case 'aicc':
741
                break;
742
            case 'dokeos':
743
            case 'chamilo':
744
                $type = 1;
745
                break;
746
        }
747
748
        $id = null;
749
        switch ($origin) {
750
            case 'zip':
751
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
752
                break;
753
            case 'manual':
754
            default:
755
                $get_max = "SELECT MAX(display_order)
756
                            FROM $tbl_lp WHERE c_id = $course_id";
757
                $res_max = Database::query($get_max);
758
                if (Database::num_rows($res_max) < 1) {
759
                    $dsp = 1;
760
                } else {
761
                    $row = Database::fetch_array($res_max);
762
                    $dsp = $row[0] + 1;
763
                }
764
765
                $lp = new CLp();
766
                $lp
767
                    ->setCId($course_id)
768
                    ->setLpType($type)
769
                    ->setName($name)
770
                    ->setDescription($description)
771
                    ->setDisplayOrder($dsp)
772
                    ->setSessionId($session_id)
773
                    ->setCategoryId($categoryId)
774
                    ->setPublicatedOn($publicated_on)
775
                    ->setExpiredOn($expired_on)
776
                ;
777
778
                $repo = Container::getLpRepository();
779
                $em = $repo->getEntityManager();
780
                $em->persist($lp);
781
                $courseEntity = api_get_course_entity($courseInfo['real_id']);
782
783
                $repo->addResourceToCourse(
784
                    $lp,
785
                    ResourceLink::VISIBILITY_PUBLISHED,
786
                    api_get_user_entity(api_get_user_id()),
787
                    $courseEntity,
788
                    api_get_session_entity(),
789
                    api_get_group_entity()
790
                );
791
792
                $em->flush();
793
                if ($lp->getIid()) {
794
                    $id = $lp->getIid();
795
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
796
                    Database::query($sql);
797
                }
798
799
                // Insert into item_property.
800
                /*api_item_property_update(
801
                    $courseInfo,
802
                    TOOL_LEARNPATH,
803
                    $id,
804
                    'LearnpathAdded',
805
                    $userId
806
                );
807
                api_set_default_visibility(
808
                    $id,
809
                    TOOL_LEARNPATH,
810
                    0,
811
                    $courseInfo,
812
                    $session_id,
813
                    $userId
814
                );*/
815
816
                break;
817
        }
818
819
        return $id;
820
    }
821
822
    /**
823
     * Auto completes the parents of an item in case it's been completed or passed.
824
     *
825
     * @param int $item Optional ID of the item from which to look for parents
826
     */
827
    public function autocomplete_parents($item)
828
    {
829
        $debug = $this->debug;
830
831
        if (empty($item)) {
832
            $item = $this->current;
833
        }
834
835
        $currentItem = $this->getItem($item);
836
        if ($currentItem) {
837
            $parent_id = $currentItem->get_parent();
838
            $parent = $this->getItem($parent_id);
839
            if ($parent) {
840
                // if $item points to an object and there is a parent.
841
                if ($debug) {
842
                    error_log(
843
                        'Autocompleting parent of item '.$item.' '.
844
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
845
                        0
846
                    );
847
                }
848
849
                // New experiment including failed and browsed in completed status.
850
                //$current_status = $currentItem->get_status();
851
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
852
                // Fixes chapter auto complete
853
                if (true) {
854
                    // If the current item is completed or passes or succeeded.
855
                    $updateParentStatus = true;
856
                    if ($debug) {
857
                        error_log('Status of current item is alright');
858
                    }
859
860
                    foreach ($parent->get_children() as $childItemId) {
861
                        $childItem = $this->getItem($childItemId);
862
863
                        // If children was not set try to get the info
864
                        if (empty($childItem->db_item_view_id)) {
865
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
866
                        }
867
868
                        // Check all his brothers (parent's children) for completion status.
869
                        if ($childItemId != $item) {
870
                            if ($debug) {
871
                                error_log(
872
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
873
                                    0
874
                                );
875
                            }
876
                            // Trying completing parents of failed and browsed items as well.
877
                            if ($childItem->status_is(
878
                                [
879
                                    'completed',
880
                                    'passed',
881
                                    'succeeded',
882
                                    'browsed',
883
                                    'failed',
884
                                ]
885
                            )
886
                            ) {
887
                                // Keep completion status to true.
888
                                continue;
889
                            } else {
890
                                if ($debug > 2) {
891
                                    error_log(
892
                                        '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,
893
                                        0
894
                                    );
895
                                }
896
                                $updateParentStatus = false;
897
                                break;
898
                            }
899
                        }
900
                    }
901
902
                    if ($updateParentStatus) {
903
                        // If all the children were completed:
904
                        $parent->set_status('completed');
905
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
906
                        // Force the status to "completed"
907
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
908
                        $this->update_queue[$parent->get_id()] = 'completed';
909
                        if ($debug) {
910
                            error_log(
911
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
912
                                print_r($this->update_queue, 1),
913
                                0
914
                            );
915
                        }
916
                        // Recursive call.
917
                        $this->autocomplete_parents($parent->get_id());
918
                    }
919
                }
920
            } else {
921
                if ($debug) {
922
                    error_log("Parent #$parent_id does not exists");
923
                }
924
            }
925
        } else {
926
            if ($debug) {
927
                error_log("#$item is an item that doesn't have parents");
928
            }
929
        }
930
    }
931
932
    /**
933
     * Closes the current resource.
934
     *
935
     * Stops the timer
936
     * Saves into the database if required
937
     * Clears the current resource data from this object
938
     *
939
     * @return bool True on success, false on failure
940
     */
941
    public function close()
942
    {
943
        if (empty($this->lp_id)) {
944
            $this->error = 'Trying to close this learnpath but no ID is set';
945
946
            return false;
947
        }
948
        $this->current_time_stop = time();
949
        $this->ordered_items = [];
950
        $this->index = 0;
951
        unset($this->lp_id);
952
        //unset other stuff
953
        return true;
954
    }
955
956
    /**
957
     * Static admin function allowing removal of a learnpath.
958
     *
959
     * @param array  $courseInfo
960
     * @param int    $id         Learnpath ID
961
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
962
     *
963
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
964
     */
965
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
966
    {
967
        $course_id = api_get_course_int_id();
968
        if (!empty($courseInfo)) {
969
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
970
        }
971
972
        // TODO: Implement a way of getting this to work when the current object is not set.
973
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
974
        // If an ID is specifically given and the current LP is not the same, prevent delete.
975
        if (!empty($id) && ($id != $this->lp_id)) {
976
            return false;
977
        }
978
979
        $lp = Database::get_course_table(TABLE_LP_MAIN);
980
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
981
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
982
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
983
984
        // Delete lp item id.
985
        foreach ($this->items as $lpItemId => $dummy) {
986
            $sql = "DELETE FROM $lp_item_view
987
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
988
            Database::query($sql);
989
        }
990
991
        // Proposed by Christophe (nickname: clefevre)
992
        $sql = "DELETE FROM $lp_item
993
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
994
        Database::query($sql);
995
996
        $sql = "DELETE FROM $lp_view
997
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
998
        Database::query($sql);
999
1000
        //self::toggle_publish($this->lp_id, 'i');
1001
1002
        if (2 == $this->type || 3 == $this->type) {
1003
            // This is a scorm learning path, delete the files as well.
1004
            $sql = "SELECT path FROM $lp
1005
                    WHERE iid = ".$this->lp_id;
1006
            $res = Database::query($sql);
1007
            if (Database::num_rows($res) > 0) {
1008
                $row = Database::fetch_array($res);
1009
                $path = $row['path'];
1010
                $sql = "SELECT id FROM $lp
1011
                        WHERE
1012
                            c_id = $course_id AND
1013
                            path = '$path' AND
1014
                            iid != ".$this->lp_id;
1015
                $res = Database::query($sql);
1016
                if (Database::num_rows($res) > 0) {
1017
                    // Another learning path uses this directory, so don't delete it.
1018
                    if ($this->debug > 2) {
1019
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1020
                    }
1021
                } else {
1022
                    // No other LP uses that directory, delete it.
1023
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1024
                    // The absolute system path for this course.
1025
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1026
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1027
                        if ($this->debug > 2) {
1028
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1029
                        }
1030
                        // Proposed by Christophe (clefevre).
1031
                        if (0 == strcmp(substr($path, -2), "/.")) {
1032
                            $path = substr($path, 0, -1); // Remove "." at the end.
1033
                        }
1034
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1035
                        rmdirr($course_scorm_dir.$path);
1036
                    }
1037
                }
1038
            }
1039
        }
1040
1041
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1042
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1043
        // Delete tools
1044
        $sql = "DELETE FROM $tbl_tool
1045
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1046
        Database::query($sql);*/
1047
1048
        /*$sql = "DELETE FROM $lp
1049
                WHERE iid = ".$this->lp_id;
1050
        Database::query($sql);*/
1051
        $repo = Container::getLpRepository();
1052
        $lp = $repo->find($this->lp_id);
1053
        $repo->getEntityManager()->remove($lp);
1054
        $repo->getEntityManager()->flush();
1055
1056
        // Updates the display order of all lps.
1057
        $this->update_display_order();
1058
1059
        /*api_item_property_update(
1060
            api_get_course_info(),
1061
            TOOL_LEARNPATH,
1062
            $this->lp_id,
1063
            'delete',
1064
            api_get_user_id()
1065
        );*/
1066
1067
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1068
            api_get_course_id(),
1069
            4,
1070
            $id,
1071
            api_get_session_id()
1072
        );
1073
1074
        if (false !== $link_info) {
1075
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1076
        }
1077
1078
        if ('true' == api_get_setting('search_enabled')) {
1079
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1080
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1081
        }
1082
    }
1083
1084
    /**
1085
     * Removes all the children of one item - dangerous!
1086
     *
1087
     * @param int $id Element ID of which children have to be removed
1088
     *
1089
     * @return int Total number of children removed
1090
     */
1091
    public function delete_children_items($id)
1092
    {
1093
        $course_id = $this->course_info['real_id'];
1094
1095
        $num = 0;
1096
        $id = (int) $id;
1097
        if (empty($id) || empty($course_id)) {
1098
            return false;
1099
        }
1100
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1101
        $sql = "SELECT * FROM $lp_item
1102
                WHERE c_id = $course_id AND parent_item_id = $id";
1103
        $res = Database::query($sql);
1104
        while ($row = Database::fetch_array($res)) {
1105
            $num += $this->delete_children_items($row['iid']);
1106
            $sql = "DELETE FROM $lp_item
1107
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1108
            Database::query($sql);
1109
            $num++;
1110
        }
1111
1112
        return $num;
1113
    }
1114
1115
    /**
1116
     * Removes an item from the current learnpath.
1117
     *
1118
     * @param int $id Elem ID (0 if first)
1119
     *
1120
     * @return int Number of elements moved
1121
     *
1122
     * @todo implement resource removal
1123
     */
1124
    public function delete_item($id)
1125
    {
1126
        $course_id = api_get_course_int_id();
1127
        $id = (int) $id;
1128
        // TODO: Implement the resource removal.
1129
        if (empty($id) || empty($course_id)) {
1130
            return false;
1131
        }
1132
        // First select item to get previous, next, and display order.
1133
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1134
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1135
        $res_sel = Database::query($sql_sel);
1136
        if (Database::num_rows($res_sel) < 1) {
1137
            return false;
1138
        }
1139
        $row = Database::fetch_array($res_sel);
1140
        $previous = $row['previous_item_id'];
1141
        $next = $row['next_item_id'];
1142
        $display = $row['display_order'];
1143
        $parent = $row['parent_item_id'];
1144
        $lp = $row['lp_id'];
1145
        // Delete children items.
1146
        $this->delete_children_items($id);
1147
        // Now delete the item.
1148
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1149
        Database::query($sql_del);
1150
        // Now update surrounding items.
1151
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1152
                    WHERE iid = $previous";
1153
        Database::query($sql_upd);
1154
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1155
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1156
        Database::query($sql_upd);
1157
        // Now update all following items with new display order.
1158
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1159
                    WHERE
1160
                        c_id = $course_id AND
1161
                        lp_id = $lp AND
1162
                        parent_item_id = $parent AND
1163
                        display_order > $display";
1164
        Database::query($sql_all);
1165
1166
        //Removing prerequisites since the item will not longer exist
1167
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1168
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1169
        Database::query($sql_all);
1170
1171
        $sql = "UPDATE $lp_item
1172
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1173
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1174
        Database::query($sql);
1175
1176
        // Remove from search engine if enabled.
1177
        if ('true' === api_get_setting('search_enabled')) {
1178
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1179
            $sql = 'SELECT * FROM %s
1180
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1181
                    LIMIT 1';
1182
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1183
            $res = Database::query($sql);
1184
            if (Database::num_rows($res) > 0) {
1185
                $row2 = Database::fetch_array($res);
1186
                $di = new ChamiloIndexer();
1187
                $di->remove_document($row2['search_did']);
1188
            }
1189
            $sql = 'DELETE FROM %s
1190
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1191
                    LIMIT 1';
1192
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1193
            Database::query($sql);
1194
        }
1195
    }
1196
1197
    /**
1198
     * Updates an item's content in place.
1199
     *
1200
     * @param int    $id               Element ID
1201
     * @param int    $parent           Parent item ID
1202
     * @param int    $previous         Previous item ID
1203
     * @param string $title            Item title
1204
     * @param string $description      Item description
1205
     * @param string $prerequisites    Prerequisites (optional)
1206
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1207
     * @param int    $max_time_allowed
1208
     * @param string $url
1209
     *
1210
     * @return bool True on success, false on error
1211
     */
1212
    public function edit_item(
1213
        $id,
1214
        $parent,
1215
        $previous,
1216
        $title,
1217
        $description,
1218
        $prerequisites = '0',
1219
        $audio = [],
1220
        $max_time_allowed = 0,
1221
        $url = ''
1222
    ) {
1223
        $course_id = api_get_course_int_id();
1224
        $_course = api_get_course_info();
1225
        $id = (int) $id;
1226
1227
        if (empty($max_time_allowed)) {
1228
            $max_time_allowed = 0;
1229
        }
1230
1231
        if (empty($id) || empty($_course)) {
1232
            return false;
1233
        }
1234
1235
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1236
        $sql = "SELECT * FROM $tbl_lp_item
1237
                WHERE iid = $id";
1238
        $res_select = Database::query($sql);
1239
        $row_select = Database::fetch_array($res_select);
1240
        $audio_update_sql = '';
1241
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1242
            // Create the audio folder if it does not exist yet.
1243
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1244
            if (!is_dir($filepath.'audio')) {
1245
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1246
                $audio_id = DocumentManager::addDocument(
1247
                    $_course,
1248
                    '/audio',
1249
                    'folder',
1250
                    0,
1251
                    'audio'
1252
                );
1253
            }
1254
1255
            // Upload file in documents.
1256
            $pi = pathinfo($audio['name']);
1257
            if ('mp3' === $pi['extension']) {
1258
                $c_det = api_get_course_info($this->cc);
1259
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1260
                $path = handle_uploaded_document(
1261
                    $c_det,
1262
                    $audio,
1263
                    $bp,
1264
                    '/audio',
1265
                    api_get_user_id(),
1266
                    0,
1267
                    null,
1268
                    0,
1269
                    'rename',
1270
                    false,
1271
                    0
1272
                );
1273
                $path = substr($path, 7);
1274
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1275
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1276
            }
1277
        }
1278
1279
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1280
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1281
1282
        // TODO: htmlspecialchars to be checked for encoding related problems.
1283
        if ($same_parent && $same_previous) {
1284
            // Only update title and description.
1285
            $sql = "UPDATE $tbl_lp_item
1286
                    SET title = '".Database::escape_string($title)."',
1287
                        prerequisite = '".$prerequisites."',
1288
                        description = '".Database::escape_string($description)."'
1289
                        ".$audio_update_sql.",
1290
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1291
                    WHERE iid = $id";
1292
            Database::query($sql);
1293
        } else {
1294
            $old_parent = $row_select['parent_item_id'];
1295
            $old_previous = $row_select['previous_item_id'];
1296
            $old_next = $row_select['next_item_id'];
1297
            $old_order = $row_select['display_order'];
1298
            $old_prerequisite = $row_select['prerequisite'];
1299
            $old_max_time_allowed = $row_select['max_time_allowed'];
1300
1301
            /* BEGIN -- virtually remove the current item id */
1302
            /* for the next and previous item it is like the current item doesn't exist anymore */
1303
            if (0 != $old_previous) {
1304
                // Next
1305
                $sql = "UPDATE $tbl_lp_item
1306
                        SET next_item_id = $old_next
1307
                        WHERE iid = $old_previous";
1308
                Database::query($sql);
1309
            }
1310
1311
            if (!empty($old_next)) {
1312
                // Previous
1313
                $sql = "UPDATE $tbl_lp_item
1314
                        SET previous_item_id = $old_previous
1315
                        WHERE iid = $old_next";
1316
                Database::query($sql);
1317
            }
1318
1319
            // display_order - 1 for every item with a display_order
1320
            // bigger then the display_order of the current item.
1321
            $sql = "UPDATE $tbl_lp_item
1322
                    SET display_order = display_order - 1
1323
                    WHERE
1324
                        c_id = $course_id AND
1325
                        display_order > $old_order AND
1326
                        lp_id = ".$this->lp_id." AND
1327
                        parent_item_id = $old_parent";
1328
            Database::query($sql);
1329
            /* END -- virtually remove the current item id */
1330
1331
            /* BEGIN -- update the current item id to his new location */
1332
            if (0 == $previous) {
1333
                // Select the data of the item that should come after the current item.
1334
                $sql = "SELECT id, display_order
1335
                        FROM $tbl_lp_item
1336
                        WHERE
1337
                            c_id = $course_id AND
1338
                            lp_id = ".$this->lp_id." AND
1339
                            parent_item_id = $parent AND
1340
                            previous_item_id = $previous";
1341
                $res_select_old = Database::query($sql);
1342
                $row_select_old = Database::fetch_array($res_select_old);
1343
1344
                // If the new parent didn't have children before.
1345
                if (0 == Database::num_rows($res_select_old)) {
1346
                    $new_next = 0;
1347
                    $new_order = 1;
1348
                } else {
1349
                    $new_next = $row_select_old['id'];
1350
                    $new_order = $row_select_old['display_order'];
1351
                }
1352
            } else {
1353
                // Select the data of the item that should come before the current item.
1354
                $sql = "SELECT next_item_id, display_order
1355
                        FROM $tbl_lp_item
1356
                        WHERE iid = $previous";
1357
                $res_select_old = Database::query($sql);
1358
                $row_select_old = Database::fetch_array($res_select_old);
1359
                $new_next = $row_select_old['next_item_id'];
1360
                $new_order = $row_select_old['display_order'] + 1;
1361
            }
1362
1363
            // TODO: htmlspecialchars to be checked for encoding related problems.
1364
            // Update the current item with the new data.
1365
            $sql = "UPDATE $tbl_lp_item
1366
                    SET
1367
                        title = '".Database::escape_string($title)."',
1368
                        description = '".Database::escape_string($description)."',
1369
                        parent_item_id = $parent,
1370
                        previous_item_id = $previous,
1371
                        next_item_id = $new_next,
1372
                        display_order = $new_order
1373
                        $audio_update_sql
1374
                    WHERE iid = $id";
1375
            Database::query($sql);
1376
1377
            if (0 != $previous) {
1378
                // Update the previous item's next_item_id.
1379
                $sql = "UPDATE $tbl_lp_item
1380
                        SET next_item_id = $id
1381
                        WHERE iid = $previous";
1382
                Database::query($sql);
1383
            }
1384
1385
            if (!empty($new_next)) {
1386
                // Update the next item's previous_item_id.
1387
                $sql = "UPDATE $tbl_lp_item
1388
                        SET previous_item_id = $id
1389
                        WHERE iid = $new_next";
1390
                Database::query($sql);
1391
            }
1392
1393
            if ($old_prerequisite != $prerequisites) {
1394
                $sql = "UPDATE $tbl_lp_item
1395
                        SET prerequisite = '$prerequisites'
1396
                        WHERE iid = $id";
1397
                Database::query($sql);
1398
            }
1399
1400
            if ($old_max_time_allowed != $max_time_allowed) {
1401
                // update max time allowed
1402
                $sql = "UPDATE $tbl_lp_item
1403
                        SET max_time_allowed = $max_time_allowed
1404
                        WHERE iid = $id";
1405
                Database::query($sql);
1406
            }
1407
1408
            // Update all the items with the same or a bigger display_order than the current item.
1409
            $sql = "UPDATE $tbl_lp_item
1410
                    SET display_order = display_order + 1
1411
                    WHERE
1412
                       c_id = $course_id AND
1413
                       lp_id = ".$this->get_id()." AND
1414
                       iid <> $id AND
1415
                       parent_item_id = $parent AND
1416
                       display_order >= $new_order";
1417
            Database::query($sql);
1418
        }
1419
1420
        if ('link' == $row_select['item_type']) {
1421
            $link = new Link();
1422
            $linkId = $row_select['path'];
1423
            $link->updateLink($linkId, $url);
1424
        }
1425
    }
1426
1427
    /**
1428
     * Updates an item's prereq in place.
1429
     *
1430
     * @param int    $id              Element ID
1431
     * @param string $prerequisite_id Prerequisite Element ID
1432
     * @param int    $minScore        Prerequisite min score
1433
     * @param int    $maxScore        Prerequisite max score
1434
     *
1435
     * @return bool True on success, false on error
1436
     */
1437
    public function edit_item_prereq(
1438
        $id,
1439
        $prerequisite_id,
1440
        $minScore = 0,
1441
        $maxScore = 100
1442
    ) {
1443
        $id = (int) $id;
1444
        $prerequisite_id = (int) $prerequisite_id;
1445
1446
        if (empty($id)) {
1447
            return false;
1448
        }
1449
1450
        if (empty($minScore) || $minScore < 0) {
1451
            $minScore = 0;
1452
        }
1453
1454
        if (empty($maxScore) || $maxScore < 0) {
1455
            $maxScore = 100;
1456
        }
1457
1458
        $minScore = floatval($minScore);
1459
        $maxScore = floatval($maxScore);
1460
1461
        if (empty($prerequisite_id)) {
1462
            $prerequisite_id = 'NULL';
1463
            $minScore = 0;
1464
            $maxScore = 100;
1465
        }
1466
1467
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1468
        $sql = " UPDATE $tbl_lp_item
1469
                 SET
1470
                    prerequisite = $prerequisite_id ,
1471
                    prerequisite_min_score = $minScore ,
1472
                    prerequisite_max_score = $maxScore
1473
                 WHERE iid = $id";
1474
1475
        Database::query($sql);
1476
1477
        return true;
1478
    }
1479
1480
    /**
1481
     * Get the specific prefix index terms of this learning path.
1482
     *
1483
     * @param string $prefix
1484
     *
1485
     * @return array Array of terms
1486
     */
1487
    public function get_common_index_terms_by_prefix($prefix)
1488
    {
1489
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1490
        $terms = get_specific_field_values_list_by_prefix(
1491
            $prefix,
1492
            $this->cc,
1493
            TOOL_LEARNPATH,
1494
            $this->lp_id
1495
        );
1496
        $prefix_terms = [];
1497
        if (!empty($terms)) {
1498
            foreach ($terms as $term) {
1499
                $prefix_terms[] = $term['value'];
1500
            }
1501
        }
1502
1503
        return $prefix_terms;
1504
    }
1505
1506
    /**
1507
     * Gets the number of items currently completed.
1508
     *
1509
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1510
     *
1511
     * @return int The number of items currently completed
1512
     */
1513
    public function get_complete_items_count($failedStatusException = false)
1514
    {
1515
        $i = 0;
1516
        $completedStatusList = [
1517
            'completed',
1518
            'passed',
1519
            'succeeded',
1520
            'browsed',
1521
        ];
1522
1523
        if (!$failedStatusException) {
1524
            $completedStatusList[] = 'failed';
1525
        }
1526
1527
        foreach ($this->items as $id => $dummy) {
1528
            // Trying failed and browsed considered "progressed" as well.
1529
            if ($this->items[$id]->status_is($completedStatusList) &&
1530
                'dir' != $this->items[$id]->get_type()
1531
            ) {
1532
                $i++;
1533
            }
1534
        }
1535
1536
        return $i;
1537
    }
1538
1539
    /**
1540
     * Gets the current item ID.
1541
     *
1542
     * @return int The current learnpath item id
1543
     */
1544
    public function get_current_item_id()
1545
    {
1546
        $current = 0;
1547
        if (!empty($this->current)) {
1548
            $current = (int) $this->current;
1549
        }
1550
1551
        return $current;
1552
    }
1553
1554
    /**
1555
     * Force to get the first learnpath item id.
1556
     *
1557
     * @return int The current learnpath item id
1558
     */
1559
    public function get_first_item_id()
1560
    {
1561
        $current = 0;
1562
        if (is_array($this->ordered_items)) {
1563
            $current = $this->ordered_items[0];
1564
        }
1565
1566
        return $current;
1567
    }
1568
1569
    /**
1570
     * Gets the total number of items available for viewing in this SCORM.
1571
     *
1572
     * @return int The total number of items
1573
     */
1574
    public function get_total_items_count()
1575
    {
1576
        return count($this->items);
1577
    }
1578
1579
    /**
1580
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1581
     *
1582
     * @return int The total no-chapters number of items
1583
     */
1584
    public function getTotalItemsCountWithoutDirs()
1585
    {
1586
        $total = 0;
1587
        $typeListNotToCount = self::getChapterTypes();
1588
        foreach ($this->items as $temp2) {
1589
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1590
                $total++;
1591
            }
1592
        }
1593
1594
        return $total;
1595
    }
1596
1597
    /**
1598
     *  Sets the first element URL.
1599
     */
1600
    public function first()
1601
    {
1602
        if ($this->debug > 0) {
1603
            error_log('In learnpath::first()', 0);
1604
            error_log('$this->last_item_seen '.$this->last_item_seen);
1605
        }
1606
1607
        // Test if the last_item_seen exists and is not a dir.
1608
        if (0 == count($this->ordered_items)) {
1609
            $this->index = 0;
1610
        }
1611
1612
        if (!empty($this->last_item_seen) &&
1613
            !empty($this->items[$this->last_item_seen]) &&
1614
            'dir' != $this->items[$this->last_item_seen]->get_type()
1615
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1616
            //&& !$this->items[$this->last_item_seen]->is_done()
1617
        ) {
1618
            if ($this->debug > 2) {
1619
                error_log(
1620
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1621
                    $this->items[$this->last_item_seen]->get_type()
1622
                );
1623
            }
1624
            $index = -1;
1625
            foreach ($this->ordered_items as $myindex => $item_id) {
1626
                if ($item_id == $this->last_item_seen) {
1627
                    $index = $myindex;
1628
                    break;
1629
                }
1630
            }
1631
            if (-1 == $index) {
1632
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1633
                if ($this->debug > 2) {
1634
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1635
                }
1636
1637
                return false;
1638
            } else {
1639
                $this->last = $this->last_item_seen;
1640
                $this->current = $this->last_item_seen;
1641
                $this->index = $index;
1642
            }
1643
        } else {
1644
            if ($this->debug > 2) {
1645
                error_log('In learnpath::first() - No last item seen', 0);
1646
            }
1647
            $index = 0;
1648
            // Loop through all ordered items and stop at the first item that is
1649
            // not a directory *and* that has not been completed yet.
1650
            while (!empty($this->ordered_items[$index]) &&
1651
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1652
                (
1653
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1654
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1655
                ) && $index < $this->max_ordered_items) {
1656
                $index++;
1657
            }
1658
1659
            $this->last = $this->current;
1660
            // current is
1661
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1662
            $this->index = $index;
1663
            if ($this->debug > 2) {
1664
                error_log('$index '.$index);
1665
                error_log('In learnpath::first() - No last item seen');
1666
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1667
            }
1668
        }
1669
        if ($this->debug > 2) {
1670
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1671
        }
1672
    }
1673
1674
    /**
1675
     * Gets the js library from the database.
1676
     *
1677
     * @return string The name of the javascript library to be used
1678
     */
1679
    public function get_js_lib()
1680
    {
1681
        $lib = '';
1682
        if (!empty($this->js_lib)) {
1683
            $lib = $this->js_lib;
1684
        }
1685
1686
        return $lib;
1687
    }
1688
1689
    /**
1690
     * Gets the learnpath database ID.
1691
     *
1692
     * @return int Learnpath ID in the lp table
1693
     */
1694
    public function get_id()
1695
    {
1696
        if (!empty($this->lp_id)) {
1697
            return (int) $this->lp_id;
1698
        }
1699
1700
        return 0;
1701
    }
1702
1703
    /**
1704
     * Gets the last element URL.
1705
     *
1706
     * @return string URL to load into the viewer
1707
     */
1708
    public function get_last()
1709
    {
1710
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1711
        if (count($this->ordered_items) > 0) {
1712
            $this->index = count($this->ordered_items) - 1;
1713
1714
            return $this->ordered_items[$this->index];
1715
        }
1716
1717
        return false;
1718
    }
1719
1720
    /**
1721
     * Get the last element in the first level.
1722
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1723
     *
1724
     * @return mixed
1725
     */
1726
    public function getLastInFirstLevel()
1727
    {
1728
        try {
1729
            $lastId = Database::getManager()
1730
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1731
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1732
                ->setMaxResults(1)
1733
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1734
                ->getSingleScalarResult();
1735
1736
            return $lastId;
1737
        } catch (Exception $exception) {
1738
            return 0;
1739
        }
1740
    }
1741
1742
    /**
1743
     * Gets the navigation bar for the learnpath display screen.
1744
     *
1745
     * @param string $barId
1746
     *
1747
     * @return string The HTML string to use as a navigation bar
1748
     */
1749
    public function get_navigation_bar($barId = '')
1750
    {
1751
        if (empty($barId)) {
1752
            $barId = 'control-top';
1753
        }
1754
        $lpId = $this->lp_id;
1755
        $mycurrentitemid = $this->get_current_item_id();
1756
1757
        $reportingText = get_lang('Reporting');
1758
        $previousText = get_lang('Previous');
1759
        $nextText = get_lang('Next');
1760
        $fullScreenText = get_lang('Back to normal screen');
1761
1762
        $settings = api_get_configuration_value('lp_view_settings');
1763
        $display = isset($settings['display']) ? $settings['display'] : false;
1764
        $reportingIcon = '
1765
            <a class="icon-toolbar"
1766
                id="stats_link"
1767
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1768
                onclick="window.parent.API.save_asset(); return true;"
1769
                target="content_name" title="'.$reportingText.'">
1770
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1771
            </a>';
1772
1773
        if (!empty($display)) {
1774
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1775
            if (false === $showReporting) {
1776
                $reportingIcon = '';
1777
            }
1778
        }
1779
1780
        $hideArrows = false;
1781
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1782
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1783
        }
1784
1785
        $previousIcon = '';
1786
        $nextIcon = '';
1787
        if (false === $hideArrows) {
1788
            $previousIcon = '
1789
                <a class="icon-toolbar" id="scorm-previous" href="#"
1790
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1791
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1792
                </a>';
1793
1794
            $nextIcon = '
1795
                <a class="icon-toolbar" id="scorm-next" href="#"
1796
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1797
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1798
                </a>';
1799
        }
1800
1801
        if ('fullscreen' === $this->mode) {
1802
            $navbar = '
1803
                  <span id="'.$barId.'" class="buttons">
1804
                    '.$reportingIcon.'
1805
                    '.$previousIcon.'
1806
                    '.$nextIcon.'
1807
                    <a class="icon-toolbar" id="view-embedded"
1808
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1809
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1810
                    </a>
1811
                  </span>';
1812
        } else {
1813
            $navbar = '
1814
                 <span id="'.$barId.'" class="buttons text-right">
1815
                    '.$reportingIcon.'
1816
                    '.$previousIcon.'
1817
                    '.$nextIcon.'
1818
                </span>';
1819
        }
1820
1821
        return $navbar;
1822
    }
1823
1824
    /**
1825
     * Gets the next resource in queue (url).
1826
     *
1827
     * @return string URL to load into the viewer
1828
     */
1829
    public function get_next_index()
1830
    {
1831
        // TODO
1832
        $index = $this->index;
1833
        $index++;
1834
        while (
1835
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1836
            $index < $this->max_ordered_items
1837
        ) {
1838
            $index++;
1839
            if ($index == $this->max_ordered_items) {
1840
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1841
                    return $this->index;
1842
                }
1843
1844
                return $index;
1845
            }
1846
        }
1847
        if (empty($this->ordered_items[$index])) {
1848
            return $this->index;
1849
        }
1850
1851
        return $index;
1852
    }
1853
1854
    /**
1855
     * Gets item_id for the next element.
1856
     *
1857
     * @return int Next item (DB) ID
1858
     */
1859
    public function get_next_item_id()
1860
    {
1861
        $new_index = $this->get_next_index();
1862
        if (!empty($new_index)) {
1863
            if (isset($this->ordered_items[$new_index])) {
1864
                return $this->ordered_items[$new_index];
1865
            }
1866
        }
1867
1868
        return 0;
1869
    }
1870
1871
    /**
1872
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1873
     *
1874
     * Generally, the package provided is in the form of a zip file, so the function
1875
     * has been written to test a zip file. If not a zip, the function will return the
1876
     * default return value: ''
1877
     *
1878
     * @param string $file_path the path to the file
1879
     * @param string $file_name the original name of the file
1880
     *
1881
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1882
     */
1883
    public static function get_package_type($file_path, $file_name)
1884
    {
1885
        // Get name of the zip file without the extension.
1886
        $file_info = pathinfo($file_name);
1887
        $extension = $file_info['extension']; // Extension only.
1888
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1889
                'dll',
1890
                'exe',
1891
            ])) {
1892
            return 'oogie';
1893
        }
1894
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1895
                'dll',
1896
                'exe',
1897
            ])) {
1898
            return 'woogie';
1899
        }
1900
1901
        $zipFile = new PclZip($file_path);
1902
        // Check the zip content (real size and file extension).
1903
        $zipContentArray = $zipFile->listContent();
1904
        $package_type = '';
1905
        $manifest = '';
1906
        $aicc_match_crs = 0;
1907
        $aicc_match_au = 0;
1908
        $aicc_match_des = 0;
1909
        $aicc_match_cst = 0;
1910
1911
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1912
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
1913
            foreach ($zipContentArray as $thisContent) {
1914
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1915
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1916
                } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1917
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1918
                    $package_type = 'scorm';
1919
                    break; // Exit the foreach loop.
1920
                } elseif (
1921
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1922
                    in_array(
1923
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1924
                        ['crs', 'au', 'des', 'cst']
1925
                    )
1926
                ) {
1927
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1928
                    switch ($ext) {
1929
                        case 'crs':
1930
                            $aicc_match_crs = 1;
1931
                            break;
1932
                        case 'au':
1933
                            $aicc_match_au = 1;
1934
                            break;
1935
                        case 'des':
1936
                            $aicc_match_des = 1;
1937
                            break;
1938
                        case 'cst':
1939
                            $aicc_match_cst = 1;
1940
                            break;
1941
                        default:
1942
                            break;
1943
                    }
1944
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1945
                } else {
1946
                    $package_type = '';
1947
                }
1948
            }
1949
        }
1950
1951
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1952
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1953
            $package_type = 'aicc';
1954
        }
1955
1956
        // Try with chamilo course builder
1957
        if (empty($package_type)) {
1958
            $package_type = 'chamilo';
1959
        }
1960
1961
        return $package_type;
1962
    }
1963
1964
    /**
1965
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1966
     *
1967
     * @return string URL to load into the viewer
1968
     */
1969
    public function get_previous_index()
1970
    {
1971
        $index = $this->index;
1972
        if (isset($this->ordered_items[$index - 1])) {
1973
            $index--;
1974
            while (isset($this->ordered_items[$index]) &&
1975
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
1976
            ) {
1977
                $index--;
1978
                if ($index < 0) {
1979
                    return $this->index;
1980
                }
1981
            }
1982
        }
1983
1984
        return $index;
1985
    }
1986
1987
    /**
1988
     * Gets item_id for the next element.
1989
     *
1990
     * @return int Previous item (DB) ID
1991
     */
1992
    public function get_previous_item_id()
1993
    {
1994
        $index = $this->get_previous_index();
1995
1996
        return $this->ordered_items[$index];
1997
    }
1998
1999
    /**
2000
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2001
     *
2002
     * @param int    $lpItemId
2003
     * @param string $autostart
2004
     *
2005
     * @return string The mediaplayer HTML
2006
     */
2007
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2008
    {
2009
        $course_id = api_get_course_int_id();
2010
        $_course = api_get_course_info();
2011
        if (empty($_course)) {
2012
            return '';
2013
        }
2014
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2015
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2016
        $lpItemId = (int) $lpItemId;
2017
2018
        /** @var learnpathItem $item */
2019
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2020
        $itemViewId = 0;
2021
        if ($item) {
2022
            $itemViewId = (int) $item->db_item_view_id;
2023
        }
2024
2025
        // Getting all the information about the item.
2026
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2027
                FROM $tbl_lp_item as lpi
2028
                INNER JOIN $tbl_lp_item_view as lp_view
2029
                ON (lpi.iid = lp_view.lp_item_id)
2030
                WHERE
2031
                    lp_view.iid = $itemViewId AND
2032
                    lpi.iid = $lpItemId AND
2033
                    lp_view.c_id = $course_id";
2034
        $result = Database::query($sql);
2035
        $row = Database::fetch_assoc($result);
2036
        $output = '';
2037
2038
        if (!empty($row['audio'])) {
2039
            $list = $_SESSION['oLP']->get_toc();
2040
2041
            switch ($row['item_type']) {
2042
                case 'quiz':
2043
                    $type_quiz = false;
2044
                    foreach ($list as $toc) {
2045
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2046
                            $type_quiz = true;
2047
                        }
2048
                    }
2049
2050
                    if ($type_quiz) {
2051
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2052
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2053
                        } else {
2054
                            $autostart_audio = $autostart;
2055
                        }
2056
                    }
2057
                    break;
2058
                case TOOL_READOUT_TEXT:;
2059
                    $autostart_audio = 'false';
2060
                    break;
2061
                default:
2062
                    $autostart_audio = 'true';
2063
            }
2064
2065
            $courseInfo = api_get_course_info();
2066
            $audio = $row['audio'];
2067
2068
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2069
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2070
2071
            if (!file_exists($file)) {
2072
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2073
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2074
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2075
            }
2076
2077
            $player = Display::getMediaPlayer(
2078
                $file,
2079
                [
2080
                    'id' => 'lp_audio_media_player',
2081
                    'url' => $url,
2082
                    'autoplay' => $autostart_audio,
2083
                    'width' => '100%',
2084
                ]
2085
            );
2086
2087
            // The mp3 player.
2088
            $output = '<div id="container">';
2089
            $output .= $player;
2090
            $output .= '</div>';
2091
        }
2092
2093
        return $output;
2094
    }
2095
2096
    /**
2097
     * @param int   $studentId
2098
     * @param int   $prerequisite
2099
     * @param array $courseInfo
2100
     * @param int   $sessionId
2101
     *
2102
     * @return bool
2103
     */
2104
    public static function isBlockedByPrerequisite(
2105
        $studentId,
2106
        $prerequisite,
2107
        $courseInfo,
2108
        $sessionId
2109
    ) {
2110
        if (empty($courseInfo)) {
2111
            return false;
2112
        }
2113
2114
        $courseId = $courseInfo['real_id'];
2115
2116
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2117
        if ($allow) {
2118
            if (api_is_allowed_to_edit() ||
2119
                api_is_platform_admin(true) ||
2120
                api_is_drh() ||
2121
                api_is_coach($sessionId, $courseId, false)
2122
            ) {
2123
                return false;
2124
            }
2125
        }
2126
2127
        $isBlocked = false;
2128
        if (!empty($prerequisite)) {
2129
            $progress = self::getProgress(
2130
                $prerequisite,
2131
                $studentId,
2132
                $courseId,
2133
                $sessionId
2134
            );
2135
            if ($progress < 100) {
2136
                $isBlocked = true;
2137
            }
2138
2139
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2140
                // Block if it does not exceed minimum time
2141
                // Minimum time (in minutes) to pass the learning path
2142
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2143
2144
                if ($accumulateWorkTime > 0) {
2145
                    // Total time in course (sum of times in learning paths from course)
2146
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2147
2148
                    // Connect with the plugin_licences_course_session table
2149
                    // which indicates what percentage of the time applies
2150
                    // Minimum connection percentage
2151
                    $perc = 100;
2152
                    // Time from the course
2153
                    $tc = $accumulateWorkTimeTotal;
2154
2155
                    // Percentage of the learning paths
2156
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2157
                    // Minimum time for each learning path
2158
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2159
2160
                    // Spent time (in seconds) so far in the learning path
2161
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2162
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2163
2164
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2165
                        $isBlocked = true;
2166
                    }
2167
                }
2168
            }
2169
        }
2170
2171
        return $isBlocked;
2172
    }
2173
2174
    /**
2175
     * Checks if the learning path is visible for student after the progress
2176
     * of its prerequisite is completed, considering the time availability and
2177
     * the LP visibility.
2178
     *
2179
     * @param int   $lp_id
2180
     * @param int   $student_id
2181
     * @param array $courseInfo
2182
     * @param int   $sessionId
2183
     *
2184
     * @return bool
2185
     */
2186
    public static function is_lp_visible_for_student(
2187
        CLp $lp,
2188
        $student_id,
2189
        $courseInfo = [],
2190
        $sessionId = 0
2191
    ) {
2192
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2193
        $sessionId = (int) $sessionId;
2194
2195
        if (empty($courseInfo)) {
2196
            return false;
2197
        }
2198
2199
        if (empty($sessionId)) {
2200
            $sessionId = api_get_session_id();
2201
        }
2202
2203
        $courseId = $courseInfo['real_id'];
2204
2205
        /*$itemInfo = api_get_item_property_info(
2206
            $courseId,
2207
            TOOL_LEARNPATH,
2208
            $lp_id,
2209
            $sessionId
2210
        );*/
2211
2212
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2213
        // If the item was deleted.
2214
        if (false === $visibility) {
2215
            return false;
2216
        }
2217
2218
        $lp_id = $lp->getIid();
2219
        // @todo remove this query and load the row info as a parameter
2220
        $table = Database::get_course_table(TABLE_LP_MAIN);
2221
        // Get current prerequisite
2222
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2223
                FROM $table
2224
                WHERE iid = $lp_id";
2225
        $rs = Database::query($sql);
2226
        $now = time();
2227
        if (Database::num_rows($rs) > 0) {
2228
            $row = Database::fetch_array($rs, 'ASSOC');
2229
2230
            if (!empty($row['category_id'])) {
2231
                $em = Database::getManager();
2232
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2233
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2234
                    return false;
2235
                }
2236
            }
2237
2238
            $prerequisite = $row['prerequisite'];
2239
            $is_visible = true;
2240
2241
            $isBlocked = self::isBlockedByPrerequisite(
2242
                $student_id,
2243
                $prerequisite,
2244
                $courseInfo,
2245
                $sessionId
2246
            );
2247
2248
            if ($isBlocked) {
2249
                $is_visible = false;
2250
            }
2251
2252
            // Also check the time availability of the LP
2253
            if ($is_visible) {
2254
                // Adding visibility restrictions
2255
                if (!empty($row['publicated_on'])) {
2256
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2257
                        $is_visible = false;
2258
                    }
2259
                }
2260
                // Blocking empty start times see BT#2800
2261
                global $_custom;
2262
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2263
                    $_custom['lps_hidden_when_no_start_date']
2264
                ) {
2265
                    if (empty($row['publicated_on'])) {
2266
                        $is_visible = false;
2267
                    }
2268
                }
2269
2270
                if (!empty($row['expired_on'])) {
2271
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2272
                        $is_visible = false;
2273
                    }
2274
                }
2275
            }
2276
2277
            if ($is_visible) {
2278
                $subscriptionSettings = self::getSubscriptionSettings();
2279
2280
                // Check if the subscription users/group to a LP is ON
2281
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2282
                    true === $subscriptionSettings['allow_add_users_to_lp']
2283
                ) {
2284
                    // Try group
2285
                    $is_visible = false;
2286
                    // Checking only the user visibility
2287
                    $userVisibility = api_get_item_visibility(
2288
                        $courseInfo,
2289
                        'learnpath',
2290
                        $row['id'],
2291
                        $sessionId,
2292
                        $student_id,
2293
                        'LearnpathSubscription'
2294
                    );
2295
2296
                    if (1 == $userVisibility) {
2297
                        $is_visible = true;
2298
                    } else {
2299
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2300
                        if (!empty($userGroups)) {
2301
                            foreach ($userGroups as $groupInfo) {
2302
                                $groupId = $groupInfo['iid'];
2303
                                $userVisibility = api_get_item_visibility(
2304
                                    $courseInfo,
2305
                                    'learnpath',
2306
                                    $row['id'],
2307
                                    $sessionId,
2308
                                    null,
2309
                                    'LearnpathSubscription',
2310
                                    $groupId
2311
                                );
2312
2313
                                if (1 == $userVisibility) {
2314
                                    $is_visible = true;
2315
                                    break;
2316
                                }
2317
                            }
2318
                        }
2319
                    }
2320
                }
2321
            }
2322
2323
            return $is_visible;
2324
        }
2325
2326
        return false;
2327
    }
2328
2329
    /**
2330
     * @param int $lpId
2331
     * @param int $userId
2332
     * @param int $courseId
2333
     * @param int $sessionId
2334
     *
2335
     * @return int
2336
     */
2337
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2338
    {
2339
        $lpId = (int) $lpId;
2340
        $userId = (int) $userId;
2341
        $courseId = (int) $courseId;
2342
        $sessionId = (int) $sessionId;
2343
2344
        $sessionCondition = api_get_session_condition($sessionId);
2345
        $table = Database::get_course_table(TABLE_LP_VIEW);
2346
        $sql = "SELECT progress FROM $table
2347
                WHERE
2348
                    c_id = $courseId AND
2349
                    lp_id = $lpId AND
2350
                    user_id = $userId $sessionCondition ";
2351
        $res = Database::query($sql);
2352
2353
        $progress = 0;
2354
        if (Database::num_rows($res) > 0) {
2355
            $row = Database::fetch_array($res);
2356
            $progress = (int) $row['progress'];
2357
        }
2358
2359
        return $progress;
2360
    }
2361
2362
    /**
2363
     * @param array $lpList
2364
     * @param int   $userId
2365
     * @param int   $courseId
2366
     * @param int   $sessionId
2367
     *
2368
     * @return array
2369
     */
2370
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2371
    {
2372
        $lpList = array_map('intval', $lpList);
2373
        if (empty($lpList)) {
2374
            return [];
2375
        }
2376
2377
        $lpList = implode("','", $lpList);
2378
2379
        $userId = (int) $userId;
2380
        $courseId = (int) $courseId;
2381
        $sessionId = (int) $sessionId;
2382
2383
        $sessionCondition = api_get_session_condition($sessionId);
2384
        $table = Database::get_course_table(TABLE_LP_VIEW);
2385
        $sql = "SELECT lp_id, progress FROM $table
2386
                WHERE
2387
                    c_id = $courseId AND
2388
                    lp_id IN ('".$lpList."') AND
2389
                    user_id = $userId $sessionCondition ";
2390
        $res = Database::query($sql);
2391
2392
        if (Database::num_rows($res) > 0) {
2393
            $list = [];
2394
            while ($row = Database::fetch_array($res)) {
2395
                $list[$row['lp_id']] = $row['progress'];
2396
            }
2397
2398
            return $list;
2399
        }
2400
2401
        return [];
2402
    }
2403
2404
    /**
2405
     * Displays a progress bar
2406
     * completed so far.
2407
     *
2408
     * @param int    $percentage Progress value to display
2409
     * @param string $text_add   Text to display near the progress value
2410
     *
2411
     * @return string HTML string containing the progress bar
2412
     */
2413
    public static function get_progress_bar($percentage = -1, $text_add = '')
2414
    {
2415
        $text = $percentage.$text_add;
2416
        $output = '<div class="progress">
2417
            <div id="progress_bar_value"
2418
                class="progress-bar progress-bar-success" role="progressbar"
2419
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2420
            '.$text.'
2421
            </div>
2422
        </div>';
2423
2424
        return $output;
2425
    }
2426
2427
    /**
2428
     * @param string $mode can be '%' or 'abs'
2429
     *                     otherwise this value will be used $this->progress_bar_mode
2430
     *
2431
     * @return string
2432
     */
2433
    public function getProgressBar($mode = null)
2434
    {
2435
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2436
2437
        return self::get_progress_bar($percentage, $text_add);
2438
    }
2439
2440
    /**
2441
     * Gets the progress bar info to display inside the progress bar.
2442
     * Also used by scorm_api.php.
2443
     *
2444
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2445
     *                     we display a number of completed elements per total elements
2446
     * @param int    $add  Additional steps to fake as completed
2447
     *
2448
     * @return array Percentage or number and symbol (% or /xx)
2449
     */
2450
    public function get_progress_bar_text($mode = '', $add = 0)
2451
    {
2452
        if (empty($mode)) {
2453
            $mode = $this->progress_bar_mode;
2454
        }
2455
        $total_items = $this->getTotalItemsCountWithoutDirs();
2456
        $completeItems = $this->get_complete_items_count();
2457
        if (0 != $add) {
2458
            $completeItems += $add;
2459
        }
2460
        $text = '';
2461
        if ($completeItems > $total_items) {
2462
            $completeItems = $total_items;
2463
        }
2464
        $percentage = 0;
2465
        if ('%' == $mode) {
2466
            if ($total_items > 0) {
2467
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2468
            }
2469
            $percentage = number_format($percentage, 0);
2470
            $text = '%';
2471
        } elseif ('abs' === $mode) {
2472
            $percentage = $completeItems;
2473
            $text = '/'.$total_items;
2474
        }
2475
2476
        return [
2477
            $percentage,
2478
            $text,
2479
        ];
2480
    }
2481
2482
    /**
2483
     * Gets the progress bar mode.
2484
     *
2485
     * @return string The progress bar mode attribute
2486
     */
2487
    public function get_progress_bar_mode()
2488
    {
2489
        if (!empty($this->progress_bar_mode)) {
2490
            return $this->progress_bar_mode;
2491
        }
2492
2493
        return '%';
2494
    }
2495
2496
    /**
2497
     * Gets the learnpath theme (remote or local).
2498
     *
2499
     * @return string Learnpath theme
2500
     */
2501
    public function get_theme()
2502
    {
2503
        if (!empty($this->theme)) {
2504
            return $this->theme;
2505
        }
2506
2507
        return '';
2508
    }
2509
2510
    /**
2511
     * Gets the learnpath session id.
2512
     *
2513
     * @return int
2514
     */
2515
    public function get_lp_session_id()
2516
    {
2517
        if (!empty($this->lp_session_id)) {
2518
            return (int) $this->lp_session_id;
2519
        }
2520
2521
        return 0;
2522
    }
2523
2524
    /**
2525
     * Gets the learnpath image.
2526
     *
2527
     * @return string Web URL of the LP image
2528
     */
2529
    public function get_preview_image()
2530
    {
2531
        if (!empty($this->preview_image)) {
2532
            return $this->preview_image;
2533
        }
2534
2535
        return '';
2536
    }
2537
2538
    /**
2539
     * @param string $size
2540
     * @param string $path_type
2541
     *
2542
     * @return bool|string
2543
     */
2544
    public function get_preview_image_path($size = null, $path_type = 'web')
2545
    {
2546
        $preview_image = $this->get_preview_image();
2547
        if (isset($preview_image) && !empty($preview_image)) {
2548
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2549
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2550
2551
            if (isset($size)) {
2552
                $info = pathinfo($preview_image);
2553
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2554
2555
                if (file_exists($image_sys_path.$image_custom_size)) {
2556
                    if ('web' == $path_type) {
2557
                        return $image_path.$image_custom_size;
2558
                    } else {
2559
                        return $image_sys_path.$image_custom_size;
2560
                    }
2561
                }
2562
            } else {
2563
                if ('web' == $path_type) {
2564
                    return $image_path.$preview_image;
2565
                } else {
2566
                    return $image_sys_path.$preview_image;
2567
                }
2568
            }
2569
        }
2570
2571
        return false;
2572
    }
2573
2574
    /**
2575
     * Gets the learnpath author.
2576
     *
2577
     * @return string LP's author
2578
     */
2579
    public function get_author()
2580
    {
2581
        if (!empty($this->author)) {
2582
            return $this->author;
2583
        }
2584
2585
        return '';
2586
    }
2587
2588
    /**
2589
     * Gets hide table of contents.
2590
     *
2591
     * @return int
2592
     */
2593
    public function getHideTableOfContents()
2594
    {
2595
        return (int) $this->hide_toc_frame;
2596
    }
2597
2598
    /**
2599
     * Generate a new prerequisites string for a given item. If this item was a sco and
2600
     * its prerequisites were strings (instead of IDs), then transform those strings into
2601
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2602
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2603
     * same rule as the scormExport() method.
2604
     *
2605
     * @param int $item_id Item ID
2606
     *
2607
     * @return string Prerequisites string ready for the export as SCORM
2608
     */
2609
    public function get_scorm_prereq_string($item_id)
2610
    {
2611
        if ($this->debug > 0) {
2612
            error_log('In learnpath::get_scorm_prereq_string()');
2613
        }
2614
        if (!is_object($this->items[$item_id])) {
2615
            return false;
2616
        }
2617
        /** @var learnpathItem $oItem */
2618
        $oItem = $this->items[$item_id];
2619
        $prereq = $oItem->get_prereq_string();
2620
2621
        if (empty($prereq)) {
2622
            return '';
2623
        }
2624
        if (preg_match('/^\d+$/', $prereq) &&
2625
            isset($this->items[$prereq]) &&
2626
            is_object($this->items[$prereq])
2627
        ) {
2628
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2629
            // then simply return it (with the ITEM_ prefix).
2630
            //return 'ITEM_' . $prereq;
2631
            return $this->items[$prereq]->ref;
2632
        } else {
2633
            if (isset($this->refs_list[$prereq])) {
2634
                // It's a simple string item from which the ID can be found in the refs list,
2635
                // so we can transform it directly to an ID for export.
2636
                return $this->items[$this->refs_list[$prereq]]->ref;
2637
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2638
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2639
            } else {
2640
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2641
                // and replace them, one by one, by the internal IDs (chamilo db)
2642
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2643
                // by a space as well.
2644
                $find = [
2645
                    '&',
2646
                    '|',
2647
                    '~',
2648
                    '=',
2649
                    '<>',
2650
                    '{',
2651
                    '}',
2652
                    '*',
2653
                    '(',
2654
                    ')',
2655
                ];
2656
                $replace = [
2657
                    ' ',
2658
                    ' ',
2659
                    ' ',
2660
                    ' ',
2661
                    ' ',
2662
                    ' ',
2663
                    ' ',
2664
                    ' ',
2665
                    ' ',
2666
                    ' ',
2667
                ];
2668
                $prereq_mod = str_replace($find, $replace, $prereq);
2669
                $ids = explode(' ', $prereq_mod);
2670
                foreach ($ids as $id) {
2671
                    $id = trim($id);
2672
                    if (isset($this->refs_list[$id])) {
2673
                        $prereq = preg_replace(
2674
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2675
                            'ITEM_'.$this->refs_list[$id],
2676
                            $prereq
2677
                        );
2678
                    }
2679
                }
2680
2681
                return $prereq;
2682
            }
2683
        }
2684
    }
2685
2686
    /**
2687
     * Returns the XML DOM document's node.
2688
     *
2689
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2690
     * @param string   $id       The identifier to look for
2691
     *
2692
     * @return mixed The reference to the element found with that identifier. False if not found
2693
     */
2694
    public function get_scorm_xml_node(&$children, $id)
2695
    {
2696
        for ($i = 0; $i < $children->length; $i++) {
2697
            $item_temp = $children->item($i);
2698
            if ('item' == $item_temp->nodeName) {
2699
                if ($item_temp->getAttribute('identifier') == $id) {
2700
                    return $item_temp;
2701
                }
2702
            }
2703
            $subchildren = $item_temp->childNodes;
2704
            if ($subchildren && $subchildren->length > 0) {
2705
                $val = $this->get_scorm_xml_node($subchildren, $id);
2706
                if (is_object($val)) {
2707
                    return $val;
2708
                }
2709
            }
2710
        }
2711
2712
        return false;
2713
    }
2714
2715
    /**
2716
     * Gets the status list for all LP's items.
2717
     *
2718
     * @return array Array of [index] => [item ID => current status]
2719
     */
2720
    public function get_items_status_list()
2721
    {
2722
        $list = [];
2723
        foreach ($this->ordered_items as $item_id) {
2724
            $list[] = [
2725
                $item_id => $this->items[$item_id]->get_status(),
2726
            ];
2727
        }
2728
2729
        return $list;
2730
    }
2731
2732
    /**
2733
     * Return the number of interactions for the given learnpath Item View ID.
2734
     * This method can be used as static.
2735
     *
2736
     * @param int $lp_iv_id  Item View ID
2737
     * @param int $course_id course id
2738
     *
2739
     * @return int
2740
     */
2741
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2742
    {
2743
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2744
        $lp_iv_id = (int) $lp_iv_id;
2745
        $course_id = (int) $course_id;
2746
2747
        $sql = "SELECT count(*) FROM $table
2748
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2749
        $res = Database::query($sql);
2750
        $num = 0;
2751
        if (Database::num_rows($res)) {
2752
            $row = Database::fetch_array($res);
2753
            $num = $row[0];
2754
        }
2755
2756
        return $num;
2757
    }
2758
2759
    /**
2760
     * Return the interactions as an array for the given lp_iv_id.
2761
     * This method can be used as static.
2762
     *
2763
     * @param int $lp_iv_id Learnpath Item View ID
2764
     *
2765
     * @return array
2766
     *
2767
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2768
     */
2769
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2770
    {
2771
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2772
        $list = [];
2773
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2774
        $lp_iv_id = (int) $lp_iv_id;
2775
2776
        if (empty($lp_iv_id) || empty($course_id)) {
2777
            return [];
2778
        }
2779
2780
        $sql = "SELECT * FROM $table
2781
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2782
                ORDER BY order_id ASC";
2783
        $res = Database::query($sql);
2784
        $num = Database::num_rows($res);
2785
        if ($num > 0) {
2786
            $list[] = [
2787
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2788
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2789
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2790
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2791
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2792
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2793
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2794
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2795
                'student_response_formatted' => '',
2796
            ];
2797
            while ($row = Database::fetch_array($res)) {
2798
                $studentResponseFormatted = urldecode($row['student_response']);
2799
                $content_student_response = explode('__|', $studentResponseFormatted);
2800
                if (count($content_student_response) > 0) {
2801
                    if (count($content_student_response) >= 3) {
2802
                        // Pop the element off the end of array.
2803
                        array_pop($content_student_response);
2804
                    }
2805
                    $studentResponseFormatted = implode(',', $content_student_response);
2806
                }
2807
2808
                $list[] = [
2809
                    'order_id' => $row['order_id'] + 1,
2810
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2811
                    'type' => $row['interaction_type'],
2812
                    'time' => $row['completion_time'],
2813
                    'correct_responses' => '', // Hide correct responses from students.
2814
                    'student_response' => $row['student_response'],
2815
                    'result' => $row['result'],
2816
                    'latency' => $row['latency'],
2817
                    'student_response_formatted' => $studentResponseFormatted,
2818
                ];
2819
            }
2820
        }
2821
2822
        return $list;
2823
    }
2824
2825
    /**
2826
     * Return the number of objectives for the given learnpath Item View ID.
2827
     * This method can be used as static.
2828
     *
2829
     * @param int $lp_iv_id  Item View ID
2830
     * @param int $course_id Course ID
2831
     *
2832
     * @return int Number of objectives
2833
     */
2834
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2835
    {
2836
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2837
        $course_id = (int) $course_id;
2838
        $lp_iv_id = (int) $lp_iv_id;
2839
        $sql = "SELECT count(*) FROM $table
2840
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2841
        //@todo seems that this always returns 0
2842
        $res = Database::query($sql);
2843
        $num = 0;
2844
        if (Database::num_rows($res)) {
2845
            $row = Database::fetch_array($res);
2846
            $num = $row[0];
2847
        }
2848
2849
        return $num;
2850
    }
2851
2852
    /**
2853
     * Return the objectives as an array for the given lp_iv_id.
2854
     * This method can be used as static.
2855
     *
2856
     * @param int $lpItemViewId Learnpath Item View ID
2857
     * @param int $course_id
2858
     *
2859
     * @return array
2860
     *
2861
     * @todo    Translate labels
2862
     */
2863
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2864
    {
2865
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2866
        $lpItemViewId = (int) $lpItemViewId;
2867
2868
        if (empty($course_id) || empty($lpItemViewId)) {
2869
            return [];
2870
        }
2871
2872
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2873
        $sql = "SELECT * FROM $table
2874
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2875
                ORDER BY order_id ASC";
2876
        $res = Database::query($sql);
2877
        $num = Database::num_rows($res);
2878
        $list = [];
2879
        if ($num > 0) {
2880
            $list[] = [
2881
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2882
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2883
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2884
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2885
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2886
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2887
            ];
2888
            while ($row = Database::fetch_array($res)) {
2889
                $list[] = [
2890
                    'order_id' => $row['order_id'] + 1,
2891
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2892
                    'score_raw' => $row['score_raw'],
2893
                    'score_max' => $row['score_max'],
2894
                    'score_min' => $row['score_min'],
2895
                    'status' => $row['status'],
2896
                ];
2897
            }
2898
        }
2899
2900
        return $list;
2901
    }
2902
2903
    /**
2904
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2905
     * used by get_html_toc() to be ready to display.
2906
     *
2907
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2908
     */
2909
    public function get_toc()
2910
    {
2911
        $toc = [];
2912
        foreach ($this->ordered_items as $item_id) {
2913
            // TODO: Change this link generation and use new function instead.
2914
            $toc[] = [
2915
                'id' => $item_id,
2916
                'title' => $this->items[$item_id]->get_title(),
2917
                'status' => $this->items[$item_id]->get_status(),
2918
                'level' => $this->items[$item_id]->get_level(),
2919
                'type' => $this->items[$item_id]->get_type(),
2920
                'description' => $this->items[$item_id]->get_description(),
2921
                'path' => $this->items[$item_id]->get_path(),
2922
                'parent' => $this->items[$item_id]->get_parent(),
2923
            ];
2924
        }
2925
2926
        return $toc;
2927
    }
2928
2929
    /**
2930
     * Generate and return the table of contents for this learnpath. The JS
2931
     * table returned is used inside of scorm_api.php.
2932
     *
2933
     * @param string $varname
2934
     *
2935
     * @return string A JS array variable construction
2936
     */
2937
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2938
    {
2939
        $toc = $varname.' = new Array();';
2940
        foreach ($this->ordered_items as $item_id) {
2941
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2942
        }
2943
2944
        return $toc;
2945
    }
2946
2947
    /**
2948
     * Gets the learning path type.
2949
     *
2950
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2951
     *
2952
     * @return mixed Type ID or name, depending on the parameter
2953
     */
2954
    public function get_type($get_name = false)
2955
    {
2956
        $res = false;
2957
        if (!empty($this->type) && (!$get_name)) {
2958
            $res = $this->type;
2959
        }
2960
2961
        return $res;
2962
    }
2963
2964
    /**
2965
     * Gets the learning path type as static method.
2966
     *
2967
     * @param int $lp_id
2968
     *
2969
     * @return mixed Type ID or name, depending on the parameter
2970
     */
2971
    public static function get_type_static($lp_id = 0)
2972
    {
2973
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2974
        $lp_id = (int) $lp_id;
2975
        $sql = "SELECT lp_type FROM $tbl_lp
2976
                WHERE iid = $lp_id";
2977
        $res = Database::query($sql);
2978
        if (false === $res) {
2979
            return null;
2980
        }
2981
        if (Database::num_rows($res) <= 0) {
2982
            return null;
2983
        }
2984
        $row = Database::fetch_array($res);
2985
2986
        return $row['lp_type'];
2987
    }
2988
2989
    /**
2990
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2991
     * This method can be used as abstract and is recursive.
2992
     *
2993
     * @param int $lp        Learnpath ID
2994
     * @param int $parent    Parent ID of the items to look for
2995
     * @param int $course_id
2996
     *
2997
     * @return array Ordered list of item IDs (empty array on error)
2998
     */
2999
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3000
    {
3001
        if (empty($course_id)) {
3002
            $course_id = api_get_course_int_id();
3003
        } else {
3004
            $course_id = (int) $course_id;
3005
        }
3006
        $list = [];
3007
3008
        if (empty($lp)) {
3009
            return $list;
3010
        }
3011
3012
        $lp = (int) $lp;
3013
        $parent = (int) $parent;
3014
3015
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3016
        $sql = "SELECT iid FROM $tbl_lp_item
3017
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3018
                ORDER BY display_order";
3019
3020
        $res = Database::query($sql);
3021
        while ($row = Database::fetch_array($res)) {
3022
            $sublist = self::get_flat_ordered_items_list(
3023
                $lp,
3024
                $row['iid'],
3025
                $course_id
3026
            );
3027
            $list[] = $row['iid'];
3028
            foreach ($sublist as $item) {
3029
                $list[] = $item;
3030
            }
3031
        }
3032
3033
        return $list;
3034
    }
3035
3036
    /**
3037
     * @return array
3038
     */
3039
    public static function getChapterTypes()
3040
    {
3041
        return [
3042
            'dir',
3043
        ];
3044
    }
3045
3046
    /**
3047
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3048
     *
3049
     * @param $tree
3050
     *
3051
     * @return array HTML TOC ready to display
3052
     */
3053
    public function getParentToc($tree)
3054
    {
3055
        if (empty($tree)) {
3056
            $tree = $this->get_toc();
3057
        }
3058
        $dirTypes = self::getChapterTypes();
3059
        $myCurrentId = $this->get_current_item_id();
3060
        $listParent = [];
3061
        $listChildren = [];
3062
        $listNotParent = [];
3063
        $list = [];
3064
        foreach ($tree as $subtree) {
3065
            if (in_array($subtree['type'], $dirTypes)) {
3066
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3067
                $subtree['children'] = $listChildren;
3068
                if (!empty($subtree['children'])) {
3069
                    foreach ($subtree['children'] as $subItem) {
3070
                        if ($subItem['id'] == $this->current) {
3071
                            $subtree['parent_current'] = 'in';
3072
                            $subtree['current'] = 'on';
3073
                        }
3074
                    }
3075
                }
3076
                $listParent[] = $subtree;
3077
            }
3078
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3079
                $classStatus = [
3080
                    'not attempted' => 'scorm_not_attempted',
3081
                    'incomplete' => 'scorm_not_attempted',
3082
                    'failed' => 'scorm_failed',
3083
                    'completed' => 'scorm_completed',
3084
                    'passed' => 'scorm_completed',
3085
                    'succeeded' => 'scorm_completed',
3086
                    'browsed' => 'scorm_completed',
3087
                ];
3088
3089
                if (isset($classStatus[$subtree['status']])) {
3090
                    $cssStatus = $classStatus[$subtree['status']];
3091
                }
3092
3093
                $title = Security::remove_XSS($subtree['title']);
3094
                unset($subtree['title']);
3095
3096
                if (empty($title)) {
3097
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3098
                }
3099
                $classStyle = null;
3100
                if ($subtree['id'] == $this->current) {
3101
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3102
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3103
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3104
                }
3105
                $subtree['title'] = $title;
3106
                $subtree['class'] = $classStyle.' '.$cssStatus;
3107
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3108
                $subtree['current_id'] = $myCurrentId;
3109
                $listNotParent[] = $subtree;
3110
            }
3111
        }
3112
3113
        $list['are_parents'] = $listParent;
3114
        $list['not_parents'] = $listNotParent;
3115
3116
        return $list;
3117
    }
3118
3119
    /**
3120
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3121
     *
3122
     * @param array $tree
3123
     * @param int   $id
3124
     * @param bool  $parent
3125
     *
3126
     * @return array HTML TOC ready to display
3127
     */
3128
    public function getChildrenToc($tree, $id, $parent = true)
3129
    {
3130
        if (empty($tree)) {
3131
            $tree = $this->get_toc();
3132
        }
3133
3134
        $dirTypes = self::getChapterTypes();
3135
        $currentItemId = $this->get_current_item_id();
3136
        $list = [];
3137
        $classStatus = [
3138
            'not attempted' => 'scorm_not_attempted',
3139
            'incomplete' => 'scorm_not_attempted',
3140
            'failed' => 'scorm_failed',
3141
            'completed' => 'scorm_completed',
3142
            'passed' => 'scorm_completed',
3143
            'succeeded' => 'scorm_completed',
3144
            'browsed' => 'scorm_completed',
3145
        ];
3146
3147
        foreach ($tree as $subtree) {
3148
            $subtree['tree'] = null;
3149
3150
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3151
                if ($subtree['id'] == $this->current) {
3152
                    $subtree['current'] = 'active';
3153
                } else {
3154
                    $subtree['current'] = null;
3155
                }
3156
                if (isset($classStatus[$subtree['status']])) {
3157
                    $cssStatus = $classStatus[$subtree['status']];
3158
                }
3159
3160
                $title = Security::remove_XSS($subtree['title']);
3161
                unset($subtree['title']);
3162
                if (empty($title)) {
3163
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3164
                }
3165
3166
                $classStyle = null;
3167
                if ($subtree['id'] == $this->current) {
3168
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3169
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3170
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3171
                }
3172
3173
                if (in_array($subtree['type'], $dirTypes)) {
3174
                    $subtree['title'] = stripslashes($title);
3175
                } else {
3176
                    $subtree['title'] = $title;
3177
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3178
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3179
                    $subtree['current_id'] = $currentItemId;
3180
                }
3181
                $list[] = $subtree;
3182
            }
3183
        }
3184
3185
        return $list;
3186
    }
3187
3188
    /**
3189
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3190
     *
3191
     * @param array $toc_list
3192
     *
3193
     * @return array HTML TOC ready to display
3194
     */
3195
    public function getListArrayToc($toc_list = [])
3196
    {
3197
        if (empty($toc_list)) {
3198
            $toc_list = $this->get_toc();
3199
        }
3200
        // Temporary variables.
3201
        $currentItemId = $this->get_current_item_id();
3202
        $list = [];
3203
        $arrayList = [];
3204
        $classStatus = [
3205
            'not attempted' => 'scorm_not_attempted',
3206
            'incomplete' => 'scorm_not_attempted',
3207
            'failed' => 'scorm_failed',
3208
            'completed' => 'scorm_completed',
3209
            'passed' => 'scorm_completed',
3210
            'succeeded' => 'scorm_completed',
3211
            'browsed' => 'scorm_completed',
3212
        ];
3213
3214
        foreach ($toc_list as $item) {
3215
            $list['id'] = $item['id'];
3216
            $list['status'] = $item['status'];
3217
            $cssStatus = null;
3218
3219
            if (isset($classStatus[$item['status']])) {
3220
                $cssStatus = $classStatus[$item['status']];
3221
            }
3222
3223
            $classStyle = ' ';
3224
            $dirTypes = self::getChapterTypes();
3225
3226
            if (in_array($item['type'], $dirTypes)) {
3227
                $classStyle = 'scorm_item_section ';
3228
            }
3229
            if ($item['id'] == $this->current) {
3230
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3231
            } elseif (!in_array($item['type'], $dirTypes)) {
3232
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3233
            }
3234
            $title = $item['title'];
3235
            if (empty($title)) {
3236
                $title = self::rl_get_resource_name(
3237
                    api_get_course_id(),
3238
                    $this->get_id(),
3239
                    $item['id']
3240
                );
3241
            }
3242
            $title = Security::remove_XSS($item['title']);
3243
3244
            if (empty($item['description'])) {
3245
                $list['description'] = $title;
3246
            } else {
3247
                $list['description'] = $item['description'];
3248
            }
3249
3250
            $list['class'] = $classStyle.' '.$cssStatus;
3251
            $list['level'] = $item['level'];
3252
            $list['type'] = $item['type'];
3253
3254
            if (in_array($item['type'], $dirTypes)) {
3255
                $list['css_level'] = 'level_'.$item['level'];
3256
            } else {
3257
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3258
            }
3259
3260
            if (in_array($item['type'], $dirTypes)) {
3261
                $list['title'] = stripslashes($title);
3262
            } else {
3263
                $list['title'] = stripslashes($title);
3264
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3265
                $list['current_id'] = $currentItemId;
3266
            }
3267
            $arrayList[] = $list;
3268
        }
3269
3270
        return $arrayList;
3271
    }
3272
3273
    /**
3274
     * Returns an HTML-formatted string ready to display with teacher buttons
3275
     * in LP view menu.
3276
     *
3277
     * @return string HTML TOC ready to display
3278
     */
3279
    public function get_teacher_toc_buttons()
3280
    {
3281
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3282
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3283
        $html = '';
3284
        if ($isAllow && false == $hideIcons) {
3285
            if ($this->get_lp_session_id() == api_get_session_id()) {
3286
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3287
                $html .= '<div class="btn-group">';
3288
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3289
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3290
                $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'>".
3291
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3292
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3293
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3294
                $html .= '</div>';
3295
                $html .= '</div>';
3296
            }
3297
        }
3298
3299
        return $html;
3300
    }
3301
3302
    /**
3303
     * Gets the learnpath maker name - generally the editor's name.
3304
     *
3305
     * @return string Learnpath maker name
3306
     */
3307
    public function get_maker()
3308
    {
3309
        if (!empty($this->maker)) {
3310
            return $this->maker;
3311
        }
3312
3313
        return '';
3314
    }
3315
3316
    /**
3317
     * Gets the learnpath name/title.
3318
     *
3319
     * @return string Learnpath name/title
3320
     */
3321
    public function get_name()
3322
    {
3323
        if (!empty($this->name)) {
3324
            return $this->name;
3325
        }
3326
3327
        return 'N/A';
3328
    }
3329
3330
    /**
3331
     * @return string
3332
     */
3333
    public function getNameNoTags()
3334
    {
3335
        return strip_tags($this->get_name());
3336
    }
3337
3338
    /**
3339
     * Gets a link to the resource from the present location, depending on item ID.
3340
     *
3341
     * @param string $type         Type of link expected
3342
     * @param int    $item_id      Learnpath item ID
3343
     * @param bool   $provided_toc
3344
     *
3345
     * @return string $provided_toc Link to the lp_item resource
3346
     */
3347
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3348
    {
3349
        $course_id = $this->get_course_int_id();
3350
        $item_id = (int) $item_id;
3351
3352
        if (empty($item_id)) {
3353
            $item_id = $this->get_current_item_id();
3354
3355
            if (empty($item_id)) {
3356
                //still empty, this means there was no item_id given and we are not in an object context or
3357
                //the object property is empty, return empty link
3358
                $this->first();
3359
3360
                return '';
3361
            }
3362
        }
3363
3364
        $file = '';
3365
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3366
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3367
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3368
3369
        $sql = "SELECT
3370
                    l.lp_type as ltype,
3371
                    l.path as lpath,
3372
                    li.item_type as litype,
3373
                    li.path as lipath,
3374
                    li.parameters as liparams
3375
        		FROM $lp_table l
3376
                INNER JOIN $lp_item_table li
3377
                ON (li.lp_id = l.iid)
3378
        		WHERE
3379
        		    li.iid = $item_id
3380
        		";
3381
        $res = Database::query($sql);
3382
        if (Database::num_rows($res) > 0) {
3383
            $row = Database::fetch_array($res);
3384
            $lp_type = $row['ltype'];
3385
            $lp_path = $row['lpath'];
3386
            $lp_item_type = $row['litype'];
3387
            $lp_item_path = $row['lipath'];
3388
            $lp_item_params = $row['liparams'];
3389
3390
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3391
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3392
            }
3393
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3394
            if ('http' === $type) {
3395
                //web path
3396
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3397
            } else {
3398
                //$course_path = $sys_course_path; //system path
3399
            }
3400
3401
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3402
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3403
            if (in_array(
3404
                $lp_item_type,
3405
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3406
            )
3407
            ) {
3408
                $lp_type = 1;
3409
            }
3410
3411
            // Now go through the specific cases to get the end of the path
3412
            // @todo Use constants instead of int values.
3413
3414
            switch ($lp_type) {
3415
                case 1:
3416
                    $file = self::rl_get_resource_link_for_learnpath(
3417
                        $course_id,
3418
                        $this->get_id(),
3419
                        $item_id,
3420
                        $this->get_view_id()
3421
                    );
3422
                    switch ($lp_item_type) {
3423
                        case 'document':
3424
                            // Shows a button to download the file instead of just downloading the file directly.
3425
                            $documentPathInfo = pathinfo($file);
3426
                            if (isset($documentPathInfo['extension'])) {
3427
                                $parsed = parse_url($documentPathInfo['extension']);
3428
                                if (isset($parsed['path'])) {
3429
                                    $extension = $parsed['path'];
3430
                                    $extensionsToDownload = [
3431
                                        'zip',
3432
                                        'ppt',
3433
                                        'pptx',
3434
                                        'ods',
3435
                                        'xlsx',
3436
                                        'xls',
3437
                                        'csv',
3438
                                        'doc',
3439
                                        'docx',
3440
                                        'dot',
3441
                                    ];
3442
3443
                                    if (in_array($extension, $extensionsToDownload)) {
3444
                                        $file = api_get_path(WEB_CODE_PATH).
3445
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3446
                                    }
3447
                                }
3448
                            }
3449
                            break;
3450
                        case 'dir':
3451
                            $file = 'lp_content.php?type=dir';
3452
                            break;
3453
                        case 'link':
3454
                            if (Link::is_youtube_link($file)) {
3455
                                $src = Link::get_youtube_video_id($file);
3456
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3457
                            } elseif (Link::isVimeoLink($file)) {
3458
                                $src = Link::getVimeoLinkId($file);
3459
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3460
                            } else {
3461
                                // If the current site is HTTPS and the link is
3462
                                // HTTP, browsers will refuse opening the link
3463
                                $urlId = api_get_current_access_url_id();
3464
                                $url = api_get_access_url($urlId, false);
3465
                                $protocol = substr($url['url'], 0, 5);
3466
                                if ('https' === $protocol) {
3467
                                    $linkProtocol = substr($file, 0, 5);
3468
                                    if ('http:' === $linkProtocol) {
3469
                                        //this is the special intervention case
3470
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3471
                                    }
3472
                                }
3473
                            }
3474
                            break;
3475
                        case 'quiz':
3476
                            // Check how much attempts of a exercise exits in lp
3477
                            $lp_item_id = $this->get_current_item_id();
3478
                            $lp_view_id = $this->get_view_id();
3479
3480
                            $prevent_reinit = null;
3481
                            if (isset($this->items[$this->current])) {
3482
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3483
                            }
3484
3485
                            if (empty($provided_toc)) {
3486
                                if ($this->debug > 0) {
3487
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3488
                                }
3489
                                $list = $this->get_toc();
3490
                            } else {
3491
                                if ($this->debug > 0) {
3492
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3493
                                }
3494
                                $list = $provided_toc;
3495
                            }
3496
3497
                            $type_quiz = false;
3498
                            foreach ($list as $toc) {
3499
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3500
                                    $type_quiz = true;
3501
                                }
3502
                            }
3503
3504
                            if ($type_quiz) {
3505
                                $lp_item_id = (int) $lp_item_id;
3506
                                $lp_view_id = (int) $lp_view_id;
3507
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3508
                                        WHERE
3509
                                            c_id = $course_id AND
3510
                                            lp_item_id='".$lp_item_id."' AND
3511
                                            lp_view_id ='".$lp_view_id."' AND
3512
                                            status='completed'";
3513
                                $result = Database::query($sql);
3514
                                $row_count = Database:: fetch_row($result);
3515
                                $count_item_view = (int) $row_count[0];
3516
                                $not_multiple_attempt = 0;
3517
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3518
                                    $not_multiple_attempt = 1;
3519
                                }
3520
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3521
                            }
3522
                            break;
3523
                    }
3524
3525
                    $tmp_array = explode('/', $file);
3526
                    $document_name = $tmp_array[count($tmp_array) - 1];
3527
                    if (strpos($document_name, '_DELETED_')) {
3528
                        $file = 'blank.php?error=document_deleted';
3529
                    }
3530
                    break;
3531
                case 2:
3532
                    if ($this->debug > 2) {
3533
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3534
                    }
3535
3536
                    if ('dir' != $lp_item_type) {
3537
                        // Quite complex here:
3538
                        // We want to make sure 'http://' (and similar) links can
3539
                        // be loaded as is (withouth the Chamilo path in front) but
3540
                        // some contents use this form: resource.htm?resource=http://blablabla
3541
                        // which means we have to find a protocol at the path's start, otherwise
3542
                        // it should not be considered as an external URL.
3543
                        // if ($this->prerequisites_match($item_id)) {
3544
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3545
                            if ($this->debug > 2) {
3546
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3547
                            }
3548
                            // Distant url, return as is.
3549
                            $file = $lp_item_path;
3550
                        } else {
3551
                            if ($this->debug > 2) {
3552
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3553
                            }
3554
                            // Prevent getting untranslatable urls.
3555
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3556
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3557
                            // Prepare the path.
3558
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3559
                            // TODO: Fix this for urls with protocol header.
3560
                            $file = str_replace('//', '/', $file);
3561
                            $file = str_replace(':/', '://', $file);
3562
                            if ('/' == substr($lp_path, -1)) {
3563
                                $lp_path = substr($lp_path, 0, -1);
3564
                            }
3565
3566
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3567
                                // if file not found.
3568
                                $decoded = html_entity_decode($lp_item_path);
3569
                                list($decoded) = explode('?', $decoded);
3570
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3571
                                    $file = self::rl_get_resource_link_for_learnpath(
3572
                                        $course_id,
3573
                                        $this->get_id(),
3574
                                        $item_id,
3575
                                        $this->get_view_id()
3576
                                    );
3577
                                    if (empty($file)) {
3578
                                        $file = 'blank.php?error=document_not_found';
3579
                                    } else {
3580
                                        $tmp_array = explode('/', $file);
3581
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3582
                                        if (strpos($document_name, '_DELETED_')) {
3583
                                            $file = 'blank.php?error=document_deleted';
3584
                                        } else {
3585
                                            $file = 'blank.php?error=document_not_found';
3586
                                        }
3587
                                    }
3588
                                } else {
3589
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3590
                                }
3591
                            }
3592
                        }
3593
3594
                        // We want to use parameters if they were defined in the imsmanifest
3595
                        if (false === strpos($file, 'blank.php')) {
3596
                            $lp_item_params = ltrim($lp_item_params, '?');
3597
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3598
                        }
3599
                    } else {
3600
                        $file = 'lp_content.php?type=dir';
3601
                    }
3602
                    break;
3603
                case 3:
3604
                    if ($this->debug > 2) {
3605
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3606
                    }
3607
                    // Formatting AICC HACP append URL.
3608
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3609
                    if (!empty($lp_item_params)) {
3610
                        $aicc_append .= $lp_item_params.'&';
3611
                    }
3612
                    if ('dir' != $lp_item_type) {
3613
                        // Quite complex here:
3614
                        // We want to make sure 'http://' (and similar) links can
3615
                        // be loaded as is (withouth the Chamilo path in front) but
3616
                        // some contents use this form: resource.htm?resource=http://blablabla
3617
                        // which means we have to find a protocol at the path's start, otherwise
3618
                        // it should not be considered as an external URL.
3619
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3620
                            if ($this->debug > 2) {
3621
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3622
                            }
3623
                            // Distant url, return as is.
3624
                            $file = $lp_item_path;
3625
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3626
                            /*
3627
                            if (stristr($file,'<servername>') !== false) {
3628
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3629
                            }
3630
                            */
3631
                            if (false !== stripos($file, '<servername>')) {
3632
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3633
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3634
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3635
                            }
3636
3637
                            $file .= $aicc_append;
3638
                        } else {
3639
                            if ($this->debug > 2) {
3640
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3641
                            }
3642
                            // Prevent getting untranslatable urls.
3643
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3644
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3645
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3646
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3647
                            // TODO: Fix this for urls with protocol header.
3648
                            $file = str_replace('//', '/', $file);
3649
                            $file = str_replace(':/', '://', $file);
3650
                            $file .= $aicc_append;
3651
                        }
3652
                    } else {
3653
                        $file = 'lp_content.php?type=dir';
3654
                    }
3655
                    break;
3656
                case 4:
3657
                    break;
3658
                default:
3659
                    break;
3660
            }
3661
            // Replace &amp; by & because &amp; will break URL with params
3662
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3663
        }
3664
        if ($this->debug > 2) {
3665
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3666
        }
3667
3668
        return $file;
3669
    }
3670
3671
    /**
3672
     * Gets the latest usable view or generate a new one.
3673
     *
3674
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3675
     *
3676
     * @return int DB lp_view id
3677
     */
3678
    public function get_view($attempt_num = 0)
3679
    {
3680
        $search = '';
3681
        // Use $attempt_num to enable multi-views management (disabled so far).
3682
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3683
            $search = 'AND view_count = '.$attempt_num;
3684
        }
3685
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3686
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3687
3688
        $course_id = api_get_course_int_id();
3689
        $sessionId = api_get_session_id();
3690
3691
        $sql = "SELECT iid, view_count FROM $lp_view_table
3692
        		WHERE
3693
        		    c_id = $course_id AND
3694
        		    lp_id = ".$this->get_id()." AND
3695
        		    user_id = ".$this->get_user_id()." AND
3696
        		    session_id = $sessionId
3697
        		    $search
3698
                ORDER BY view_count DESC";
3699
        $res = Database::query($sql);
3700
        if (Database::num_rows($res) > 0) {
3701
            $row = Database::fetch_array($res);
3702
            $this->lp_view_id = $row['iid'];
3703
        } elseif (!api_is_invitee()) {
3704
            // There is no database record, create one.
3705
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3706
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3707
            Database::query($sql);
3708
            $id = Database::insert_id();
3709
            $this->lp_view_id = $id;
3710
3711
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3712
            Database::query($sql);
3713
        }
3714
3715
        return $this->lp_view_id;
3716
    }
3717
3718
    /**
3719
     * Gets the current view id.
3720
     *
3721
     * @return int View ID (from lp_view)
3722
     */
3723
    public function get_view_id()
3724
    {
3725
        if (!empty($this->lp_view_id)) {
3726
            return (int) $this->lp_view_id;
3727
        }
3728
3729
        return 0;
3730
    }
3731
3732
    /**
3733
     * Gets the update queue.
3734
     *
3735
     * @return array Array containing IDs of items to be updated by JavaScript
3736
     */
3737
    public function get_update_queue()
3738
    {
3739
        return $this->update_queue;
3740
    }
3741
3742
    /**
3743
     * Gets the user ID.
3744
     *
3745
     * @return int User ID
3746
     */
3747
    public function get_user_id()
3748
    {
3749
        if (!empty($this->user_id)) {
3750
            return (int) $this->user_id;
3751
        }
3752
3753
        return false;
3754
    }
3755
3756
    /**
3757
     * Checks if any of the items has an audio element attached.
3758
     *
3759
     * @return bool True or false
3760
     */
3761
    public function has_audio()
3762
    {
3763
        $has = false;
3764
        foreach ($this->items as $i => $item) {
3765
            if (!empty($this->items[$i]->audio)) {
3766
                $has = true;
3767
                break;
3768
            }
3769
        }
3770
3771
        return $has;
3772
    }
3773
3774
    /**
3775
     * Moves an item up and down at its level.
3776
     *
3777
     * @param int    $id        Item to move up and down
3778
     * @param string $direction Direction 'up' or 'down'
3779
     *
3780
     * @return bool|int
3781
     */
3782
    public function move_item($id, $direction)
3783
    {
3784
        $course_id = api_get_course_int_id();
3785
        if (empty($id) || empty($direction)) {
3786
            return false;
3787
        }
3788
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3789
        $sql_sel = "SELECT *
3790
                    FROM $tbl_lp_item
3791
                    WHERE
3792
                        iid = $id
3793
                    ";
3794
        $res_sel = Database::query($sql_sel);
3795
        // Check if elem exists.
3796
        if (Database::num_rows($res_sel) < 1) {
3797
            return false;
3798
        }
3799
        // Gather data.
3800
        $row = Database::fetch_array($res_sel);
3801
        $previous = $row['previous_item_id'];
3802
        $next = $row['next_item_id'];
3803
        $display = $row['display_order'];
3804
        $parent = $row['parent_item_id'];
3805
        $lp = $row['lp_id'];
3806
        // Update the item (switch with previous/next one).
3807
        switch ($direction) {
3808
            case 'up':
3809
                if ($display > 1) {
3810
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3811
                                 WHERE iid = $previous";
3812
                    $res_sel2 = Database::query($sql_sel2);
3813
                    if (Database::num_rows($res_sel2) < 1) {
3814
                        $previous_previous = 0;
3815
                    }
3816
                    // Gather data.
3817
                    $row2 = Database::fetch_array($res_sel2);
3818
                    $previous_previous = $row2['previous_item_id'];
3819
                    // Update previous_previous item (switch "next" with current).
3820
                    if (0 != $previous_previous) {
3821
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3822
                                        next_item_id = $id
3823
                                    WHERE iid = $previous_previous";
3824
                        Database::query($sql_upd2);
3825
                    }
3826
                    // Update previous item (switch with current).
3827
                    if (0 != $previous) {
3828
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3829
                                    next_item_id = $next,
3830
                                    previous_item_id = $id,
3831
                                    display_order = display_order +1
3832
                                    WHERE iid = $previous";
3833
                        Database::query($sql_upd2);
3834
                    }
3835
3836
                    // Update current item (switch with previous).
3837
                    if (0 != $id) {
3838
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3839
                                        next_item_id = $previous,
3840
                                        previous_item_id = $previous_previous,
3841
                                        display_order = display_order-1
3842
                                    WHERE c_id = ".$course_id." AND id = $id";
3843
                        Database::query($sql_upd2);
3844
                    }
3845
                    // Update next item (new previous item).
3846
                    if (!empty($next)) {
3847
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3848
                                     WHERE iid = $next";
3849
                        Database::query($sql_upd2);
3850
                    }
3851
                    $display = $display - 1;
3852
                }
3853
                break;
3854
            case 'down':
3855
                if (0 != $next) {
3856
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3857
                                 WHERE iid = $next";
3858
                    $res_sel2 = Database::query($sql_sel2);
3859
                    if (Database::num_rows($res_sel2) < 1) {
3860
                        $next_next = 0;
3861
                    }
3862
                    // Gather data.
3863
                    $row2 = Database::fetch_array($res_sel2);
3864
                    $next_next = $row2['next_item_id'];
3865
                    // Update previous item (switch with current).
3866
                    if (0 != $previous) {
3867
                        $sql_upd2 = "UPDATE $tbl_lp_item
3868
                                     SET next_item_id = $next
3869
                                     WHERE iid = $previous";
3870
                        Database::query($sql_upd2);
3871
                    }
3872
                    // Update current item (switch with previous).
3873
                    if (0 != $id) {
3874
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3875
                                     previous_item_id = $next,
3876
                                     next_item_id = $next_next,
3877
                                     display_order = display_order + 1
3878
                                     WHERE iid = $id";
3879
                        Database::query($sql_upd2);
3880
                    }
3881
3882
                    // Update next item (new previous item).
3883
                    if (0 != $next) {
3884
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3885
                                     previous_item_id = $previous,
3886
                                     next_item_id = $id,
3887
                                     display_order = display_order-1
3888
                                     WHERE iid = $next";
3889
                        Database::query($sql_upd2);
3890
                    }
3891
3892
                    // Update next_next item (switch "previous" with current).
3893
                    if (0 != $next_next) {
3894
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3895
                                     previous_item_id = $id
3896
                                     WHERE iid = $next_next";
3897
                        Database::query($sql_upd2);
3898
                    }
3899
                    $display = $display + 1;
3900
                }
3901
                break;
3902
            default:
3903
                return false;
3904
        }
3905
3906
        return $display;
3907
    }
3908
3909
    /**
3910
     * Move a LP up (display_order).
3911
     *
3912
     * @param int $lp_id      Learnpath ID
3913
     * @param int $categoryId Category ID
3914
     *
3915
     * @return bool
3916
     */
3917
    public static function move_up($lp_id, $categoryId = 0)
3918
    {
3919
        $courseId = api_get_course_int_id();
3920
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3921
3922
        $categoryCondition = '';
3923
        if (!empty($categoryId)) {
3924
            $categoryId = (int) $categoryId;
3925
            $categoryCondition = " AND category_id = $categoryId";
3926
        }
3927
        $sql = "SELECT * FROM $lp_table
3928
                WHERE c_id = $courseId
3929
                $categoryCondition
3930
                ORDER BY display_order";
3931
        $res = Database::query($sql);
3932
        if (false === $res) {
3933
            return false;
3934
        }
3935
3936
        $lps = [];
3937
        $lp_order = [];
3938
        $num = Database::num_rows($res);
3939
        // First check the order is correct, globally (might be wrong because
3940
        // of versions < 1.8.4)
3941
        if ($num > 0) {
3942
            $i = 1;
3943
            while ($row = Database::fetch_array($res)) {
3944
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3945
                    $sql = "UPDATE $lp_table SET display_order = $i
3946
                            WHERE iid = ".$row['iid'];
3947
                    Database::query($sql);
3948
                }
3949
                $row['display_order'] = $i;
3950
                $lps[$row['iid']] = $row;
3951
                $lp_order[$i] = $row['iid'];
3952
                $i++;
3953
            }
3954
        }
3955
        if ($num > 1) { // If there's only one element, no need to sort.
3956
            $order = $lps[$lp_id]['display_order'];
3957
            if ($order > 1) { // If it's the first element, no need to move up.
3958
                $sql = "UPDATE $lp_table SET display_order = $order
3959
                        WHERE iid = ".$lp_order[$order - 1];
3960
                Database::query($sql);
3961
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3962
                        WHERE iid = $lp_id";
3963
                Database::query($sql);
3964
            }
3965
        }
3966
3967
        return true;
3968
    }
3969
3970
    /**
3971
     * Move a learnpath down (display_order).
3972
     *
3973
     * @param int $lp_id      Learnpath ID
3974
     * @param int $categoryId Category ID
3975
     *
3976
     * @return bool
3977
     */
3978
    public static function move_down($lp_id, $categoryId = 0)
3979
    {
3980
        $courseId = api_get_course_int_id();
3981
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3982
3983
        $categoryCondition = '';
3984
        if (!empty($categoryId)) {
3985
            $categoryId = (int) $categoryId;
3986
            $categoryCondition = " AND category_id = $categoryId";
3987
        }
3988
3989
        $sql = "SELECT * FROM $lp_table
3990
                WHERE c_id = $courseId
3991
                $categoryCondition
3992
                ORDER BY display_order";
3993
        $res = Database::query($sql);
3994
        if (false === $res) {
3995
            return false;
3996
        }
3997
        $lps = [];
3998
        $lp_order = [];
3999
        $num = Database::num_rows($res);
4000
        $max = 0;
4001
        // First check the order is correct, globally (might be wrong because
4002
        // of versions < 1.8.4).
4003
        if ($num > 0) {
4004
            $i = 1;
4005
            while ($row = Database::fetch_array($res)) {
4006
                $max = $i;
4007
                if ($row['display_order'] != $i) {
4008
                    // If we find a gap in the order, we need to fix it.
4009
                    $sql = "UPDATE $lp_table SET display_order = $i
4010
                              WHERE iid = ".$row['iid'];
4011
                    Database::query($sql);
4012
                }
4013
                $row['display_order'] = $i;
4014
                $lps[$row['iid']] = $row;
4015
                $lp_order[$i] = $row['iid'];
4016
                $i++;
4017
            }
4018
        }
4019
        if ($num > 1) { // If there's only one element, no need to sort.
4020
            $order = $lps[$lp_id]['display_order'];
4021
            if ($order < $max) { // If it's the first element, no need to move up.
4022
                $sql = "UPDATE $lp_table SET display_order = $order
4023
                        WHERE iid = ".$lp_order[$order + 1];
4024
                Database::query($sql);
4025
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4026
                        WHERE iid = $lp_id";
4027
                Database::query($sql);
4028
            }
4029
        }
4030
4031
        return true;
4032
    }
4033
4034
    /**
4035
     * Updates learnpath attributes to point to the next element
4036
     * The last part is similar to set_current_item but processing the other way around.
4037
     */
4038
    public function next()
4039
    {
4040
        if ($this->debug > 0) {
4041
            error_log('In learnpath::next()', 0);
4042
        }
4043
        $this->last = $this->get_current_item_id();
4044
        $this->items[$this->last]->save(
4045
            false,
4046
            $this->prerequisites_match($this->last)
4047
        );
4048
        $this->autocomplete_parents($this->last);
4049
        $new_index = $this->get_next_index();
4050
        if ($this->debug > 2) {
4051
            error_log('New index: '.$new_index, 0);
4052
        }
4053
        $this->index = $new_index;
4054
        if ($this->debug > 2) {
4055
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4056
        }
4057
        $this->current = $this->ordered_items[$new_index];
4058
        if ($this->debug > 2) {
4059
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4060
        }
4061
    }
4062
4063
    /**
4064
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4065
     * class, this might be redefined to allow several behaviours depending on the document type.
4066
     *
4067
     * @param int $id Resource ID
4068
     */
4069
    public function open($id)
4070
    {
4071
        // TODO:
4072
        // set the current resource attribute to this resource
4073
        // switch on element type (redefine in child class?)
4074
        // set status for this item to "opened"
4075
        // start timer
4076
        // initialise score
4077
        $this->index = 0; //or = the last item seen (see $this->last)
4078
    }
4079
4080
    /**
4081
     * Check that all prerequisites are fulfilled. Returns true and an
4082
     * empty string on success, returns false
4083
     * and the prerequisite string on error.
4084
     * This function is based on the rules for aicc_script language as
4085
     * described in the SCORM 1.2 CAM documentation page 108.
4086
     *
4087
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4088
     *
4089
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4090
     *              string otherwise
4091
     */
4092
    public function prerequisites_match($itemId = null)
4093
    {
4094
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4095
        if ($allow) {
4096
            if (api_is_allowed_to_edit() ||
4097
                api_is_platform_admin(true) ||
4098
                api_is_drh() ||
4099
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4100
            ) {
4101
                return true;
4102
            }
4103
        }
4104
4105
        $debug = $this->debug;
4106
        if ($debug > 0) {
4107
            error_log('In learnpath::prerequisites_match()');
4108
        }
4109
4110
        if (empty($itemId)) {
4111
            $itemId = $this->current;
4112
        }
4113
4114
        $currentItem = $this->getItem($itemId);
4115
4116
        if ($currentItem) {
4117
            if (2 == $this->type) {
4118
                // Getting prereq from scorm
4119
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4120
            } else {
4121
                $prereq_string = $currentItem->get_prereq_string();
4122
            }
4123
4124
            if (empty($prereq_string)) {
4125
                if ($debug > 0) {
4126
                    error_log('Found prereq_string is empty return true');
4127
                }
4128
4129
                return true;
4130
            }
4131
4132
            // Clean spaces.
4133
            $prereq_string = str_replace(' ', '', $prereq_string);
4134
            if ($debug > 0) {
4135
                error_log('Found prereq_string: '.$prereq_string, 0);
4136
            }
4137
4138
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4139
            $result = $currentItem->parse_prereq(
4140
                $prereq_string,
4141
                $this->items,
4142
                $this->refs_list,
4143
                $this->get_user_id()
4144
            );
4145
4146
            if (false === $result) {
4147
                $this->set_error_msg($currentItem->prereq_alert);
4148
            }
4149
        } else {
4150
            $result = true;
4151
            if ($debug > 1) {
4152
                error_log('$this->items['.$itemId.'] was not an object', 0);
4153
            }
4154
        }
4155
4156
        if ($debug > 1) {
4157
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4158
        }
4159
4160
        return $result;
4161
    }
4162
4163
    /**
4164
     * Updates learnpath attributes to point to the previous element
4165
     * The last part is similar to set_current_item but processing the other way around.
4166
     */
4167
    public function previous()
4168
    {
4169
        $this->last = $this->get_current_item_id();
4170
        $this->items[$this->last]->save(
4171
            false,
4172
            $this->prerequisites_match($this->last)
4173
        );
4174
        $this->autocomplete_parents($this->last);
4175
        $new_index = $this->get_previous_index();
4176
        $this->index = $new_index;
4177
        $this->current = $this->ordered_items[$new_index];
4178
    }
4179
4180
    /**
4181
     * Publishes a learnpath. This basically means show or hide the learnpath
4182
     * to normal users.
4183
     * Can be used as abstract.
4184
     *
4185
     * @param int $lp_id          Learnpath ID
4186
     * @param int $set_visibility New visibility
4187
     *
4188
     * @return bool
4189
     */
4190
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4191
    {
4192
        $action = 'visible';
4193
        if (1 != $set_visibility) {
4194
            $action = 'invisible';
4195
            self::toggle_publish($lp_id, 'i');
4196
        }
4197
4198
        return api_item_property_update(
4199
            api_get_course_info(),
4200
            TOOL_LEARNPATH,
4201
            $lp_id,
4202
            $action,
4203
            api_get_user_id()
4204
        );
4205
    }
4206
4207
    /**
4208
     * Publishes a learnpath category.
4209
     * This basically means show or hide the learnpath category to normal users.
4210
     *
4211
     * @param int $id
4212
     * @param int $visibility
4213
     *
4214
     * @throws \Doctrine\ORM\NonUniqueResultException
4215
     * @throws \Doctrine\ORM\ORMException
4216
     * @throws \Doctrine\ORM\OptimisticLockException
4217
     * @throws \Doctrine\ORM\TransactionRequiredException
4218
     *
4219
     * @return bool
4220
     */
4221
    public static function toggleCategoryVisibility($id, $visibility = 1)
4222
    {
4223
        $action = 'visible';
4224
        if (1 != $visibility) {
4225
            self::toggleCategoryPublish($id, 0);
4226
            $action = 'invisible';
4227
        }
4228
4229
        return api_item_property_update(
4230
            api_get_course_info(),
4231
            TOOL_LEARNPATH_CATEGORY,
4232
            $id,
4233
            $action,
4234
            api_get_user_id()
4235
        );
4236
    }
4237
4238
    /**
4239
     * Publishes a learnpath. This basically means show or hide the learnpath
4240
     * on the course homepage
4241
     * Can be used as abstract.
4242
     *
4243
     * @param int    $lp_id          Learnpath id
4244
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4245
     *
4246
     * @return bool
4247
     */
4248
    public static function toggle_publish($id, $setVisibility = 'v')
4249
    {
4250
        $addShortcut = false;
4251
        if ('v' === $setVisibility) {
4252
            $addShortcut = true;
4253
        }
4254
        $repo = Container::getLpRepository();
4255
        /** @var CLp $lp */
4256
        $lp = $repo->find($id);
4257
        $repoShortcut = Container::getShortcutRepository();
4258
        if ($addShortcut) {
4259
            $shortcut = new CShortcut();
4260
            $shortcut->setName($lp->getName());
4261
            $shortcut->setShortCutNode($lp->getResourceNode());
4262
4263
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4264
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4265
            $repoShortcut->getEntityManager()->flush();
4266
        } else {
4267
            $shortcut = $repoShortcut->getShortcutFromResource($lp);
4268
            if (null !== $shortcut) {
4269
                $repoShortcut->getEntityManager()->remove($shortcut);
4270
                $repoShortcut->getEntityManager()->flush();
4271
            }
4272
        }
4273
4274
        return true;
4275
        /*
4276
        $course_id = api_get_course_int_id();
4277
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4278
        $lp_id = (int) $lp_id;
4279
        $sql = "SELECT * FROM $tbl_lp
4280
                WHERE iid = $lp_id";
4281
        $result = Database::query($sql);
4282
4283
        if (Database::num_rows($result)) {
4284
            $row = Database::fetch_array($result);
4285
            $name = Database::escape_string($row['name']);
4286
            if ($set_visibility == 'i') {
4287
                $v = 0;
4288
            }
4289
            if ($set_visibility == 'v') {
4290
                $v = 1;
4291
            }
4292
4293
            $session_id = api_get_session_id();
4294
            $session_condition = api_get_session_condition($session_id);
4295
4296
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4297
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4298
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4299
4300
            $sql = "SELECT * FROM $tbl_tool
4301
                    WHERE
4302
                        c_id = $course_id AND
4303
                        (link = '$link' OR link = '$oldLink') AND
4304
                        image = 'scormbuilder.gif' AND
4305
                        (
4306
                            link LIKE '$link%' OR
4307
                            link LIKE '$oldLink%'
4308
                        )
4309
                        $session_condition
4310
                    ";
4311
4312
            $result = Database::query($sql);
4313
            $num = Database::num_rows($result);
4314
            if ($set_visibility == 'i' && $num > 0) {
4315
                $sql = "DELETE FROM $tbl_tool
4316
                        WHERE
4317
                            c_id = $course_id AND
4318
                            (link = '$link' OR link = '$oldLink') AND
4319
                            image='scormbuilder.gif'
4320
                            $session_condition";
4321
                Database::query($sql);
4322
            } elseif ($set_visibility == 'v' && $num == 0) {
4323
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4324
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4325
                Database::query($sql);
4326
                $insertId = Database::insert_id();
4327
                if ($insertId) {
4328
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4329
                    Database::query($sql);
4330
                }
4331
            } elseif ($set_visibility == 'v' && $num > 0) {
4332
                $sql = "UPDATE $tbl_tool SET
4333
                            c_id = $course_id,
4334
                            name = '$name',
4335
                            link = '$link',
4336
                            image = 'scormbuilder.gif',
4337
                            visibility = '$v',
4338
                            admin = '0',
4339
                            address = 'pastillegris.gif',
4340
                            added_tool = 0,
4341
                            session_id = $session_id
4342
                        WHERE
4343
                            c_id = ".$course_id." AND
4344
                            (link = '$link' OR link = '$oldLink') AND
4345
                            image='scormbuilder.gif'
4346
                            $session_condition
4347
                        ";
4348
                Database::query($sql);
4349
            } else {
4350
                // Parameter and database incompatible, do nothing, exit.
4351
                return false;
4352
            }
4353
        } else {
4354
            return false;
4355
        }*/
4356
    }
4357
4358
    /**
4359
     * Publishes a learnpath.
4360
     * Show or hide the learnpath category on the course homepage.
4361
     *
4362
     * @param int $id
4363
     * @param int $setVisibility
4364
     *
4365
     * @throws \Doctrine\ORM\NonUniqueResultException
4366
     * @throws \Doctrine\ORM\ORMException
4367
     * @throws \Doctrine\ORM\OptimisticLockException
4368
     * @throws \Doctrine\ORM\TransactionRequiredException
4369
     *
4370
     * @return bool
4371
     */
4372
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4373
    {
4374
        $courseId = api_get_course_int_id();
4375
        $sessionId = api_get_session_id();
4376
        $sessionCondition = api_get_session_condition(
4377
            $sessionId,
4378
            true,
4379
            false,
4380
            't.sessionId'
4381
        );
4382
4383
        $em = Database::getManager();
4384
4385
        /** @var CLpCategory $category */
4386
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4387
4388
        if (!$category) {
4389
            return false;
4390
        }
4391
4392
        if (empty($courseId)) {
4393
            return false;
4394
        }
4395
4396
        $link = self::getCategoryLinkForTool($id);
4397
4398
        /** @var CTool $tool */
4399
        $tool = $em->createQuery("
4400
                SELECT t FROM ChamiloCourseBundle:CTool t
4401
                WHERE
4402
                    t.course = :course AND
4403
                    t.link = :link1 AND
4404
                    t.image LIKE 'lp_category.%' AND
4405
                    t.link LIKE :link2
4406
                    $sessionCondition
4407
            ")
4408
            ->setParameters([
4409
                'course' => $courseId,
4410
                'link1' => $link,
4411
                'link2' => "$link%",
4412
            ])
4413
            ->getOneOrNullResult();
4414
4415
        if (0 == $setVisibility && $tool) {
4416
            $em->remove($tool);
4417
            $em->flush();
4418
4419
            return true;
4420
        }
4421
4422
        if (1 == $setVisibility && !$tool) {
4423
            $tool = new CTool();
4424
            $tool
4425
                ->setCategory('authoring')
4426
                ->setCourse(api_get_course_entity($courseId))
4427
                ->setName(strip_tags($category->getName()))
4428
                ->setLink($link)
4429
                ->setImage('lp_category.png')
4430
                ->setVisibility(1)
4431
                ->setAdmin(0)
4432
                ->setAddress('pastillegris.gif')
4433
                ->setAddedTool(0)
4434
                ->setSessionId($sessionId)
4435
                ->setTarget('_self');
4436
4437
            $em->persist($tool);
4438
            $em->flush();
4439
4440
            $tool->setId($tool->getIid());
4441
4442
            $em->persist($tool);
4443
            $em->flush();
4444
4445
            return true;
4446
        }
4447
4448
        if (1 == $setVisibility && $tool) {
4449
            $tool
4450
                ->setName(strip_tags($category->getName()))
4451
                ->setVisibility(1);
4452
4453
            $em->persist($tool);
4454
            $em->flush();
4455
4456
            return true;
4457
        }
4458
4459
        return false;
4460
    }
4461
4462
    /**
4463
     * Check if the learnpath category is visible for a user.
4464
     *
4465
     * @param int
4466
     * @param int
4467
     *
4468
     * @return bool
4469
     */
4470
    public static function categoryIsVisibleForStudent(
4471
        CLpCategory $category,
4472
        User $user,
4473
        $courseId = 0,
4474
        $sessionId = 0
4475
    ) {
4476
        if (empty($category)) {
4477
            return false;
4478
        }
4479
4480
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4481
4482
        if ($isAllowedToEdit) {
4483
            return true;
4484
        }
4485
4486
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4487
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4488
4489
        $courseInfo = api_get_course_info_by_id($courseId);
4490
4491
        $categoryVisibility = api_get_item_visibility(
4492
            $courseInfo,
4493
            TOOL_LEARNPATH_CATEGORY,
4494
            $category->getId(),
4495
            $sessionId
4496
        );
4497
4498
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4499
            return false;
4500
        }
4501
4502
        $subscriptionSettings = self::getSubscriptionSettings();
4503
4504
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4505
            return true;
4506
        }
4507
4508
        $users = $category->getUsers();
4509
4510
        if (empty($users) || !$users->count()) {
4511
            return true;
4512
        }
4513
4514
        if ($category->hasUserAdded($user)) {
4515
            return true;
4516
        }
4517
4518
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4519
        if (!empty($groups)) {
4520
            $em = Database::getManager();
4521
4522
            /** @var ItemPropertyRepository $itemRepo */
4523
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4524
4525
            /** @var CourseRepository $courseRepo */
4526
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4527
            $session = null;
4528
            if (!empty($sessionId)) {
4529
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4530
            }
4531
4532
            if (0 != $courseId) {
4533
                $course = $courseRepo->find($courseId);
4534
4535
                // Subscribed groups to a LP
4536
                $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4537
                    TOOL_LEARNPATH_CATEGORY,
4538
                    $category->getId(),
4539
                    $course,
4540
                    $session
4541
                );
4542
            }
4543
4544
            if (!empty($subscribedGroupsInLp)) {
4545
                $groups = array_column($groups, 'iid');
4546
                /** @var CItemProperty $item */
4547
                foreach ($subscribedGroupsInLp as $item) {
4548
                    if ($item->getGroup() &&
4549
                        in_array($item->getGroup()->getId(), $groups)
4550
                    ) {
4551
                        return true;
4552
                    }
4553
                }
4554
            }
4555
        }
4556
4557
        return false;
4558
    }
4559
4560
    /**
4561
     * Check if a learnpath category is published as course tool.
4562
     *
4563
     * @param int $courseId
4564
     *
4565
     * @return bool
4566
     */
4567
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4568
    {
4569
        return false;
4570
        $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...
4571
        $em = Database::getManager();
4572
4573
        $tools = $em
4574
            ->createQuery("
4575
                SELECT t FROM ChamiloCourseBundle:CTool t
4576
                WHERE t.course = :course AND
4577
                    t.name = :name AND
4578
                    t.image LIKE 'lp_category.%' AND
4579
                    t.link LIKE :link
4580
            ")
4581
            ->setParameters([
4582
                'course' => $courseId,
4583
                'name' => strip_tags($category->getName()),
4584
                'link' => "$link%",
4585
            ])
4586
            ->getResult();
4587
4588
        /** @var CTool $tool */
4589
        $tool = current($tools);
4590
4591
        return $tool ? $tool->getVisibility() : false;
4592
    }
4593
4594
    /**
4595
     * Restart the whole learnpath. Return the URL of the first element.
4596
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4597
     * To use a similar method  statically, use the create_new_attempt() method.
4598
     *
4599
     * @return bool
4600
     */
4601
    public function restart()
4602
    {
4603
        if ($this->debug > 0) {
4604
            error_log('In learnpath::restart()', 0);
4605
        }
4606
        // TODO
4607
        // Call autosave method to save the current progress.
4608
        //$this->index = 0;
4609
        if (api_is_invitee()) {
4610
            return false;
4611
        }
4612
        $session_id = api_get_session_id();
4613
        $course_id = api_get_course_int_id();
4614
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4615
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4616
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4617
        if ($this->debug > 2) {
4618
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4619
        }
4620
        Database::query($sql);
4621
        $view_id = Database::insert_id();
4622
4623
        if ($view_id) {
4624
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4625
            Database::query($sql);
4626
            $this->lp_view_id = $view_id;
4627
            $this->attempt = $this->attempt + 1;
4628
        } else {
4629
            $this->error = 'Could not insert into item_view table...';
4630
4631
            return false;
4632
        }
4633
        $this->autocomplete_parents($this->current);
4634
        foreach ($this->items as $index => $dummy) {
4635
            $this->items[$index]->restart();
4636
            $this->items[$index]->set_lp_view($this->lp_view_id);
4637
        }
4638
        $this->first();
4639
4640
        return true;
4641
    }
4642
4643
    /**
4644
     * Saves the current item.
4645
     *
4646
     * @return bool
4647
     */
4648
    public function save_current()
4649
    {
4650
        $debug = $this->debug;
4651
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4652
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4653
        if ($debug) {
4654
            error_log('save_current() saving item '.$this->current, 0);
4655
            error_log(''.print_r($this->items, true), 0);
4656
        }
4657
        if (isset($this->items[$this->current]) &&
4658
            is_object($this->items[$this->current])
4659
        ) {
4660
            if ($debug) {
4661
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4662
            }
4663
4664
            $res = $this->items[$this->current]->save(
4665
                false,
4666
                $this->prerequisites_match($this->current)
4667
            );
4668
            $this->autocomplete_parents($this->current);
4669
            $status = $this->items[$this->current]->get_status();
4670
            $this->update_queue[$this->current] = $status;
4671
4672
            if ($debug) {
4673
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4674
            }
4675
4676
            return $res;
4677
        }
4678
4679
        return false;
4680
    }
4681
4682
    /**
4683
     * Saves the given item.
4684
     *
4685
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4686
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4687
     *
4688
     * @return bool
4689
     */
4690
    public function save_item($item_id = null, $from_outside = true)
4691
    {
4692
        $debug = $this->debug;
4693
        if ($debug) {
4694
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4695
        }
4696
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4697
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4698
        if (empty($item_id)) {
4699
            $item_id = (int) $_REQUEST['id'];
4700
        }
4701
4702
        if (empty($item_id)) {
4703
            $item_id = $this->get_current_item_id();
4704
        }
4705
        if (isset($this->items[$item_id]) &&
4706
            is_object($this->items[$item_id])
4707
        ) {
4708
            if ($debug) {
4709
                error_log('Object exists');
4710
            }
4711
4712
            // Saving the item.
4713
            $res = $this->items[$item_id]->save(
4714
                $from_outside,
4715
                $this->prerequisites_match($item_id)
4716
            );
4717
4718
            if ($debug) {
4719
                error_log('update_queue before:');
4720
                error_log(print_r($this->update_queue, 1));
4721
            }
4722
            $this->autocomplete_parents($item_id);
4723
4724
            $status = $this->items[$item_id]->get_status();
4725
            $this->update_queue[$item_id] = $status;
4726
4727
            if ($debug) {
4728
                error_log('get_status(): '.$status);
4729
                error_log('update_queue after:');
4730
                error_log(print_r($this->update_queue, 1));
4731
            }
4732
4733
            return $res;
4734
        }
4735
4736
        return false;
4737
    }
4738
4739
    /**
4740
     * Saves the last item seen's ID only in case.
4741
     */
4742
    public function save_last()
4743
    {
4744
        $course_id = api_get_course_int_id();
4745
        $debug = $this->debug;
4746
        if ($debug) {
4747
            error_log('In learnpath::save_last()', 0);
4748
        }
4749
        $session_condition = api_get_session_condition(
4750
            api_get_session_id(),
4751
            true,
4752
            false
4753
        );
4754
        $table = Database::get_course_table(TABLE_LP_VIEW);
4755
4756
        if (isset($this->current) && !api_is_invitee()) {
4757
            if ($debug) {
4758
                error_log('Saving current item ('.$this->current.') for later review', 0);
4759
            }
4760
            $sql = "UPDATE $table SET
4761
                        last_item = ".$this->get_current_item_id()."
4762
                    WHERE
4763
                        c_id = $course_id AND
4764
                        lp_id = ".$this->get_id()." AND
4765
                        user_id = ".$this->get_user_id()." ".$session_condition;
4766
4767
            if ($debug) {
4768
                error_log('Saving last item seen : '.$sql, 0);
4769
            }
4770
            Database::query($sql);
4771
        }
4772
4773
        if (!api_is_invitee()) {
4774
            // Save progress.
4775
            list($progress) = $this->get_progress_bar_text('%');
4776
            if ($progress >= 0 && $progress <= 100) {
4777
                $progress = (int) $progress;
4778
                $sql = "UPDATE $table SET
4779
                            progress = $progress
4780
                        WHERE
4781
                            c_id = $course_id AND
4782
                            lp_id = ".$this->get_id()." AND
4783
                            user_id = ".$this->get_user_id()." ".$session_condition;
4784
                // Ignore errors as some tables might not have the progress field just yet.
4785
                Database::query($sql);
4786
                $this->progress_db = $progress;
4787
            }
4788
        }
4789
    }
4790
4791
    /**
4792
     * Sets the current item ID (checks if valid and authorized first).
4793
     *
4794
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4795
     */
4796
    public function set_current_item($item_id = null)
4797
    {
4798
        $debug = $this->debug;
4799
        if ($debug) {
4800
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4801
        }
4802
        if (empty($item_id)) {
4803
            if ($debug) {
4804
                error_log('No new current item given, ignore...', 0);
4805
            }
4806
            // Do nothing.
4807
        } else {
4808
            if ($debug) {
4809
                error_log('New current item given is '.$item_id.'...', 0);
4810
            }
4811
            if (is_numeric($item_id)) {
4812
                $item_id = (int) $item_id;
4813
                // TODO: Check in database here.
4814
                $this->last = $this->current;
4815
                $this->current = $item_id;
4816
                // TODO: Update $this->index as well.
4817
                foreach ($this->ordered_items as $index => $item) {
4818
                    if ($item == $this->current) {
4819
                        $this->index = $index;
4820
                        break;
4821
                    }
4822
                }
4823
                if ($debug) {
4824
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4825
                }
4826
            } else {
4827
                if ($debug) {
4828
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4829
                }
4830
            }
4831
        }
4832
    }
4833
4834
    /**
4835
     * Sets the encoding.
4836
     *
4837
     * @param string $enc New encoding
4838
     *
4839
     * @return bool
4840
     *
4841
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4842
     */
4843
    public function set_encoding($enc = 'UTF-8')
4844
    {
4845
        $enc = api_refine_encoding_id($enc);
4846
        if (empty($enc)) {
4847
            $enc = api_get_system_encoding();
4848
        }
4849
        if (api_is_encoding_supported($enc)) {
4850
            $lp = $this->get_id();
4851
            if (0 != $lp) {
4852
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4853
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4854
                        WHERE iid = ".$lp;
4855
                $res = Database::query($sql);
4856
4857
                return $res;
4858
            }
4859
        }
4860
4861
        return false;
4862
    }
4863
4864
    /**
4865
     * Sets the JS lib setting in the database directly.
4866
     * This is the JavaScript library file this lp needs to load on startup.
4867
     *
4868
     * @param string $lib Proximity setting
4869
     *
4870
     * @return bool True on update success. False otherwise.
4871
     */
4872
    public function set_jslib($lib = '')
4873
    {
4874
        $lp = $this->get_id();
4875
4876
        if (0 != $lp) {
4877
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4878
            $lib = Database::escape_string($lib);
4879
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4880
                    WHERE iid = $lp";
4881
            $res = Database::query($sql);
4882
4883
            return $res;
4884
        }
4885
4886
        return false;
4887
    }
4888
4889
    /**
4890
     * Sets the name of the LP maker (publisher) (and save).
4891
     *
4892
     * @param string $name Optional string giving the new content_maker of this learnpath
4893
     *
4894
     * @return bool True
4895
     */
4896
    public function set_maker($name = '')
4897
    {
4898
        if (empty($name)) {
4899
            return false;
4900
        }
4901
        $this->maker = $name;
4902
        $table = Database::get_course_table(TABLE_LP_MAIN);
4903
        $lp_id = $this->get_id();
4904
        $sql = "UPDATE $table SET
4905
                content_maker = '".Database::escape_string($this->maker)."'
4906
                WHERE iid = $lp_id";
4907
        Database::query($sql);
4908
4909
        return true;
4910
    }
4911
4912
    /**
4913
     * Sets the name of the current learnpath (and save).
4914
     *
4915
     * @param string $name Optional string giving the new name of this learnpath
4916
     *
4917
     * @return bool True/False
4918
     */
4919
    public function set_name($name = null)
4920
    {
4921
        if (empty($name)) {
4922
            return false;
4923
        }
4924
        $this->name = $name;
4925
4926
        $lp_id = $this->get_id();
4927
4928
        $repo = Container::getLpRepository();
4929
        /** @var CLp $lp */
4930
        $lp = $repo->find($lp_id);
4931
        $lp->setName($name);
4932
        $repo->updateNodeForResource($lp);
4933
4934
        /*
4935
        $course_id = $this->course_info['real_id'];
4936
        $sql = "UPDATE $lp_table SET
4937
            name = '$name'
4938
            WHERE iid = $lp_id";
4939
        $result = Database::query($sql);
4940
        // If the lp is visible on the homepage, change his name there.
4941
        if (Database::affected_rows($result)) {
4942
        $session_id = api_get_session_id();
4943
        $session_condition = api_get_session_condition($session_id);
4944
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4945
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4946
        $sql = "UPDATE $tbl_tool SET name = '$name'
4947
        	    WHERE
4948
        	        c_id = $course_id AND
4949
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4950
        Database::query($sql);*/
4951
4952
        //return true;
4953
        //}
4954
4955
        return false;
4956
    }
4957
4958
    /**
4959
     * Set index specified prefix terms for all items in this path.
4960
     *
4961
     * @param string $terms_string Comma-separated list of terms
4962
     * @param string $prefix       Xapian term prefix
4963
     *
4964
     * @return bool False on error, true otherwise
4965
     */
4966
    public function set_terms_by_prefix($terms_string, $prefix)
4967
    {
4968
        $course_id = api_get_course_int_id();
4969
        if ('true' !== api_get_setting('search_enabled')) {
4970
            return false;
4971
        }
4972
4973
        if (!extension_loaded('xapian')) {
4974
            return false;
4975
        }
4976
4977
        $terms_string = trim($terms_string);
4978
        $terms = explode(',', $terms_string);
4979
        array_walk($terms, 'trim_value');
4980
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4981
4982
        // Don't do anything if no change, verify only at DB, not the search engine.
4983
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
4984
            return false;
4985
        }
4986
4987
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4988
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4989
4990
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4991
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4992
        $lp_id = (int) $_POST['lp_id'];
4993
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4994
        $result = Database::query($sql);
4995
        $di = new ChamiloIndexer();
4996
4997
        while ($lp_item = Database::fetch_array($result)) {
4998
            // Get search_did.
4999
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5000
            $sql = 'SELECT * FROM %s
5001
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5002
                    LIMIT 1';
5003
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5004
5005
            //echo $sql; echo '<br>';
5006
            $res = Database::query($sql);
5007
            if (Database::num_rows($res) > 0) {
5008
                $se_ref = Database::fetch_array($res);
5009
                // Compare terms.
5010
                $doc = $di->get_document($se_ref['search_did']);
5011
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5012
                $xterms = [];
5013
                foreach ($xapian_terms as $xapian_term) {
5014
                    $xterms[] = substr($xapian_term['name'], 1);
5015
                }
5016
5017
                $dterms = $terms;
5018
                $missing_terms = array_diff($dterms, $xterms);
5019
                $deprecated_terms = array_diff($xterms, $dterms);
5020
5021
                // Save it to search engine.
5022
                foreach ($missing_terms as $term) {
5023
                    $doc->add_term($prefix.$term, 1);
5024
                }
5025
                foreach ($deprecated_terms as $term) {
5026
                    $doc->remove_term($prefix.$term);
5027
                }
5028
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5029
                $di->getDb()->flush();
5030
            }
5031
        }
5032
5033
        return true;
5034
    }
5035
5036
    /**
5037
     * Sets the theme of the LP (local/remote) (and save).
5038
     *
5039
     * @param string $name Optional string giving the new theme of this learnpath
5040
     *
5041
     * @return bool Returns true if theme name is not empty
5042
     */
5043
    public function set_theme($name = '')
5044
    {
5045
        $this->theme = $name;
5046
        $table = Database::get_course_table(TABLE_LP_MAIN);
5047
        $lp_id = $this->get_id();
5048
        $sql = "UPDATE $table
5049
                SET theme = '".Database::escape_string($this->theme)."'
5050
                WHERE iid = $lp_id";
5051
        Database::query($sql);
5052
5053
        return true;
5054
    }
5055
5056
    /**
5057
     * Sets the image of an LP (and save).
5058
     *
5059
     * @param string $name Optional string giving the new image of this learnpath
5060
     *
5061
     * @return bool Returns true if theme name is not empty
5062
     */
5063
    public function set_preview_image($name = '')
5064
    {
5065
        $this->preview_image = $name;
5066
        $table = Database::get_course_table(TABLE_LP_MAIN);
5067
        $lp_id = $this->get_id();
5068
        $sql = "UPDATE $table SET
5069
                preview_image = '".Database::escape_string($this->preview_image)."'
5070
                WHERE iid = $lp_id";
5071
        Database::query($sql);
5072
5073
        return true;
5074
    }
5075
5076
    /**
5077
     * Sets the author of a LP (and save).
5078
     *
5079
     * @param string $name Optional string giving the new author of this learnpath
5080
     *
5081
     * @return bool Returns true if author's name is not empty
5082
     */
5083
    public function set_author($name = '')
5084
    {
5085
        $this->author = $name;
5086
        $table = Database::get_course_table(TABLE_LP_MAIN);
5087
        $lp_id = $this->get_id();
5088
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5089
                WHERE iid = $lp_id";
5090
        Database::query($sql);
5091
5092
        return true;
5093
    }
5094
5095
    /**
5096
     * Sets the hide_toc_frame parameter of a LP (and save).
5097
     *
5098
     * @param int $hide 1 if frame is hidden 0 then else
5099
     *
5100
     * @return bool Returns true if author's name is not empty
5101
     */
5102
    public function set_hide_toc_frame($hide)
5103
    {
5104
        if (intval($hide) == $hide) {
5105
            $this->hide_toc_frame = $hide;
5106
            $table = Database::get_course_table(TABLE_LP_MAIN);
5107
            $lp_id = $this->get_id();
5108
            $sql = "UPDATE $table SET
5109
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5110
                    WHERE iid = $lp_id";
5111
            Database::query($sql);
5112
5113
            return true;
5114
        }
5115
5116
        return false;
5117
    }
5118
5119
    /**
5120
     * Sets the prerequisite of a LP (and save).
5121
     *
5122
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5123
     *
5124
     * @return bool returns true if prerequisite is not empty
5125
     */
5126
    public function set_prerequisite($prerequisite)
5127
    {
5128
        $this->prerequisite = (int) $prerequisite;
5129
        $table = Database::get_course_table(TABLE_LP_MAIN);
5130
        $lp_id = $this->get_id();
5131
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5132
                WHERE iid = $lp_id";
5133
        Database::query($sql);
5134
5135
        return true;
5136
    }
5137
5138
    /**
5139
     * Sets the location/proximity of the LP (local/remote) (and save).
5140
     *
5141
     * @param string $name Optional string giving the new location of this learnpath
5142
     *
5143
     * @return bool True on success / False on error
5144
     */
5145
    public function set_proximity($name = '')
5146
    {
5147
        if (empty($name)) {
5148
            return false;
5149
        }
5150
5151
        $this->proximity = $name;
5152
        $table = Database::get_course_table(TABLE_LP_MAIN);
5153
        $lp_id = $this->get_id();
5154
        $sql = "UPDATE $table SET
5155
                    content_local = '".Database::escape_string($name)."'
5156
                WHERE iid = $lp_id";
5157
        Database::query($sql);
5158
5159
        return true;
5160
    }
5161
5162
    /**
5163
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5164
     *
5165
     * @param int $id DB ID of the item
5166
     */
5167
    public function set_previous_item($id)
5168
    {
5169
        if ($this->debug > 0) {
5170
            error_log('In learnpath::set_previous_item()', 0);
5171
        }
5172
        $this->last = $id;
5173
    }
5174
5175
    /**
5176
     * Sets use_max_score.
5177
     *
5178
     * @param int $use_max_score Optional string giving the new location of this learnpath
5179
     *
5180
     * @return bool True on success / False on error
5181
     */
5182
    public function set_use_max_score($use_max_score = 1)
5183
    {
5184
        $use_max_score = (int) $use_max_score;
5185
        $this->use_max_score = $use_max_score;
5186
        $table = Database::get_course_table(TABLE_LP_MAIN);
5187
        $lp_id = $this->get_id();
5188
        $sql = "UPDATE $table SET
5189
                    use_max_score = '".$this->use_max_score."'
5190
                WHERE iid = $lp_id";
5191
        Database::query($sql);
5192
5193
        return true;
5194
    }
5195
5196
    /**
5197
     * Sets and saves the expired_on date.
5198
     *
5199
     * @param string $expired_on Optional string giving the new author of this learnpath
5200
     *
5201
     * @throws \Doctrine\ORM\OptimisticLockException
5202
     *
5203
     * @return bool Returns true if author's name is not empty
5204
     */
5205
    public function set_expired_on($expired_on)
5206
    {
5207
        $em = Database::getManager();
5208
        /** @var CLp $lp */
5209
        $lp = $em
5210
            ->getRepository('ChamiloCourseBundle:CLp')
5211
            ->findOneBy(
5212
                [
5213
                    'iid' => $this->get_id(),
5214
                ]
5215
            );
5216
5217
        if (!$lp) {
5218
            return false;
5219
        }
5220
5221
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5222
5223
        $lp->setExpiredOn($this->expired_on);
5224
        $em->persist($lp);
5225
        $em->flush();
5226
5227
        return true;
5228
    }
5229
5230
    /**
5231
     * Sets and saves the publicated_on date.
5232
     *
5233
     * @param string $publicated_on Optional string giving the new author of this learnpath
5234
     *
5235
     * @throws \Doctrine\ORM\OptimisticLockException
5236
     *
5237
     * @return bool Returns true if author's name is not empty
5238
     */
5239
    public function set_publicated_on($publicated_on)
5240
    {
5241
        $em = Database::getManager();
5242
        /** @var CLp $lp */
5243
        $lp = $em
5244
            ->getRepository('ChamiloCourseBundle:CLp')
5245
            ->findOneBy(
5246
                [
5247
                    'iid' => $this->get_id(),
5248
                ]
5249
            );
5250
5251
        if (!$lp) {
5252
            return false;
5253
        }
5254
5255
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5256
        $lp->setPublicatedOn($this->publicated_on);
5257
        $em->persist($lp);
5258
        $em->flush();
5259
5260
        return true;
5261
    }
5262
5263
    /**
5264
     * Sets and saves the expired_on date.
5265
     *
5266
     * @return bool Returns true if author's name is not empty
5267
     */
5268
    public function set_modified_on()
5269
    {
5270
        $this->modified_on = api_get_utc_datetime();
5271
        $table = Database::get_course_table(TABLE_LP_MAIN);
5272
        $lp_id = $this->get_id();
5273
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5274
                WHERE iid = $lp_id";
5275
        Database::query($sql);
5276
5277
        return true;
5278
    }
5279
5280
    /**
5281
     * Sets the object's error message.
5282
     *
5283
     * @param string $error Error message. If empty, reinits the error string
5284
     */
5285
    public function set_error_msg($error = '')
5286
    {
5287
        if ($this->debug > 0) {
5288
            error_log('In learnpath::set_error_msg()', 0);
5289
        }
5290
        if (empty($error)) {
5291
            $this->error = '';
5292
        } else {
5293
            $this->error .= $error;
5294
        }
5295
    }
5296
5297
    /**
5298
     * Launches the current item if not 'sco'
5299
     * (starts timer and make sure there is a record ready in the DB).
5300
     *
5301
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5302
     *
5303
     * @return bool
5304
     */
5305
    public function start_current_item($allow_new_attempt = false)
5306
    {
5307
        $debug = $this->debug;
5308
        if ($debug) {
5309
            error_log('In learnpath::start_current_item()');
5310
            error_log('current: '.$this->current);
5311
        }
5312
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5313
            $type = $this->get_type();
5314
            $item_type = $this->items[$this->current]->get_type();
5315
            if ((2 == $type && 'sco' != $item_type) ||
5316
                (3 == $type && 'au' != $item_type) ||
5317
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5318
            ) {
5319
                if ($debug) {
5320
                    error_log('item type: '.$item_type);
5321
                    error_log('lp type: '.$type);
5322
                }
5323
                $this->items[$this->current]->open($allow_new_attempt);
5324
                $this->autocomplete_parents($this->current);
5325
                $prereq_check = $this->prerequisites_match($this->current);
5326
                if ($debug) {
5327
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5328
                }
5329
                $this->items[$this->current]->save(false, $prereq_check);
5330
            }
5331
            // If sco, then it is supposed to have been updated by some other call.
5332
            if ('sco' == $item_type) {
5333
                $this->items[$this->current]->restart();
5334
            }
5335
        }
5336
        if ($debug) {
5337
            error_log('lp_view_session_id');
5338
            error_log($this->lp_view_session_id);
5339
            error_log('api session id');
5340
            error_log(api_get_session_id());
5341
            error_log('End of learnpath::start_current_item()');
5342
        }
5343
5344
        return true;
5345
    }
5346
5347
    /**
5348
     * Stops the processing and counters for the old item (as held in $this->last).
5349
     *
5350
     * @return bool True/False
5351
     */
5352
    public function stop_previous_item()
5353
    {
5354
        $debug = $this->debug;
5355
        if ($debug) {
5356
            error_log('In learnpath::stop_previous_item()', 0);
5357
        }
5358
5359
        if (0 != $this->last && $this->last != $this->current &&
5360
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5361
        ) {
5362
            if ($debug) {
5363
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5364
            }
5365
            switch ($this->get_type()) {
5366
                case '3':
5367
                    if ('au' != $this->items[$this->last]->get_type()) {
5368
                        if ($debug) {
5369
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5370
                        }
5371
                        $this->items[$this->last]->close();
5372
                    } else {
5373
                        if ($debug) {
5374
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5375
                        }
5376
                    }
5377
                    break;
5378
                case '2':
5379
                    if ('sco' != $this->items[$this->last]->get_type()) {
5380
                        if ($debug) {
5381
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5382
                        }
5383
                        $this->items[$this->last]->close();
5384
                    } else {
5385
                        if ($debug) {
5386
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5387
                        }
5388
                    }
5389
                    break;
5390
                case '1':
5391
                default:
5392
                    if ($debug) {
5393
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5394
                    }
5395
                    $this->items[$this->last]->close();
5396
                    break;
5397
            }
5398
        } else {
5399
            if ($debug) {
5400
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5401
            }
5402
5403
            return false;
5404
        }
5405
5406
        return true;
5407
    }
5408
5409
    /**
5410
     * Updates the default view mode from fullscreen to embedded and inversely.
5411
     *
5412
     * @return string The current default view mode ('fullscreen' or 'embedded')
5413
     */
5414
    public function update_default_view_mode()
5415
    {
5416
        $table = Database::get_course_table(TABLE_LP_MAIN);
5417
        $sql = "SELECT * FROM $table
5418
                WHERE iid = ".$this->get_id();
5419
        $res = Database::query($sql);
5420
        if (Database::num_rows($res) > 0) {
5421
            $row = Database::fetch_array($res);
5422
            $default_view_mode = $row['default_view_mod'];
5423
            $view_mode = $default_view_mode;
5424
            switch ($default_view_mode) {
5425
                case 'fullscreen': // default with popup
5426
                    $view_mode = 'embedded';
5427
                    break;
5428
                case 'embedded': // default view with left menu
5429
                    $view_mode = 'embedframe';
5430
                    break;
5431
                case 'embedframe': //folded menu
5432
                    $view_mode = 'impress';
5433
                    break;
5434
                case 'impress':
5435
                    $view_mode = 'fullscreen';
5436
                    break;
5437
            }
5438
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5439
                    WHERE iid = ".$this->get_id();
5440
            Database::query($sql);
5441
            $this->mode = $view_mode;
5442
5443
            return $view_mode;
5444
        }
5445
5446
        return -1;
5447
    }
5448
5449
    /**
5450
     * Updates the default behaviour about auto-commiting SCORM updates.
5451
     *
5452
     * @return bool True if auto-commit has been set to 'on', false otherwise
5453
     */
5454
    public function update_default_scorm_commit()
5455
    {
5456
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5457
        $sql = "SELECT * FROM $lp_table
5458
                WHERE iid = ".$this->get_id();
5459
        $res = Database::query($sql);
5460
        if (Database::num_rows($res) > 0) {
5461
            $row = Database::fetch_array($res);
5462
            $force = $row['force_commit'];
5463
            if (1 == $force) {
5464
                $force = 0;
5465
                $force_return = false;
5466
            } elseif (0 == $force) {
5467
                $force = 1;
5468
                $force_return = true;
5469
            }
5470
            $sql = "UPDATE $lp_table SET force_commit = $force
5471
                    WHERE iid = ".$this->get_id();
5472
            Database::query($sql);
5473
            $this->force_commit = $force_return;
5474
5475
            return $force_return;
5476
        }
5477
5478
        return -1;
5479
    }
5480
5481
    /**
5482
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5483
     *
5484
     * @return bool True on success, false on failure
5485
     */
5486
    public function update_display_order()
5487
    {
5488
        $course_id = api_get_course_int_id();
5489
        $table = Database::get_course_table(TABLE_LP_MAIN);
5490
        $sql = "SELECT * FROM $table
5491
                WHERE c_id = $course_id
5492
                ORDER BY display_order";
5493
        $res = Database::query($sql);
5494
        if (false === $res) {
5495
            return false;
5496
        }
5497
5498
        $num = Database::num_rows($res);
5499
        // First check the order is correct, globally (might be wrong because
5500
        // of versions < 1.8.4).
5501
        if ($num > 0) {
5502
            $i = 1;
5503
            while ($row = Database::fetch_array($res)) {
5504
                if ($row['display_order'] != $i) {
5505
                    // If we find a gap in the order, we need to fix it.
5506
                    $sql = "UPDATE $table SET display_order = $i
5507
                            WHERE iid = ".$row['iid'];
5508
                    Database::query($sql);
5509
                }
5510
                $i++;
5511
            }
5512
        }
5513
5514
        return true;
5515
    }
5516
5517
    /**
5518
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5519
     *
5520
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5521
     */
5522
    public function update_reinit()
5523
    {
5524
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5525
        $sql = "SELECT * FROM $lp_table
5526
                WHERE iid = ".$this->get_id();
5527
        $res = Database::query($sql);
5528
        if (Database::num_rows($res) > 0) {
5529
            $row = Database::fetch_array($res);
5530
            $force = $row['prevent_reinit'];
5531
            if (1 == $force) {
5532
                $force = 0;
5533
            } elseif (0 == $force) {
5534
                $force = 1;
5535
            }
5536
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5537
                    WHERE iid = ".$this->get_id();
5538
            Database::query($sql);
5539
            $this->prevent_reinit = $force;
5540
5541
            return $force;
5542
        }
5543
5544
        return -1;
5545
    }
5546
5547
    /**
5548
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5549
     *
5550
     * @return string 'single', 'multi' or 'seriousgame'
5551
     *
5552
     * @author ndiechburg <[email protected]>
5553
     */
5554
    public function get_attempt_mode()
5555
    {
5556
        //Set default value for seriousgame_mode
5557
        if (!isset($this->seriousgame_mode)) {
5558
            $this->seriousgame_mode = 0;
5559
        }
5560
        // Set default value for prevent_reinit
5561
        if (!isset($this->prevent_reinit)) {
5562
            $this->prevent_reinit = 1;
5563
        }
5564
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5565
            return 'seriousgame';
5566
        }
5567
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5568
            return 'single';
5569
        }
5570
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5571
            return 'multiple';
5572
        }
5573
5574
        return 'single';
5575
    }
5576
5577
    /**
5578
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5579
     *
5580
     * @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...
5581
     *
5582
     * @return bool
5583
     *
5584
     * @author ndiechburg <[email protected]>
5585
     */
5586
    public function set_attempt_mode($mode)
5587
    {
5588
        switch ($mode) {
5589
            case 'seriousgame':
5590
                $sg_mode = 1;
5591
                $prevent_reinit = 1;
5592
                break;
5593
            case 'single':
5594
                $sg_mode = 0;
5595
                $prevent_reinit = 1;
5596
                break;
5597
            case 'multiple':
5598
                $sg_mode = 0;
5599
                $prevent_reinit = 0;
5600
                break;
5601
            default:
5602
                $sg_mode = 0;
5603
                $prevent_reinit = 0;
5604
                break;
5605
        }
5606
        $this->prevent_reinit = $prevent_reinit;
5607
        $this->seriousgame_mode = $sg_mode;
5608
        $table = Database::get_course_table(TABLE_LP_MAIN);
5609
        $sql = "UPDATE $table SET
5610
                prevent_reinit = $prevent_reinit ,
5611
                seriousgame_mode = $sg_mode
5612
                WHERE iid = ".$this->get_id();
5613
        $res = Database::query($sql);
5614
        if ($res) {
5615
            return true;
5616
        } else {
5617
            return false;
5618
        }
5619
    }
5620
5621
    /**
5622
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5623
     *
5624
     * @author ndiechburg <[email protected]>
5625
     */
5626
    public function switch_attempt_mode()
5627
    {
5628
        $mode = $this->get_attempt_mode();
5629
        switch ($mode) {
5630
            case 'single':
5631
                $next_mode = 'multiple';
5632
                break;
5633
            case 'multiple':
5634
                $next_mode = 'seriousgame';
5635
                break;
5636
            case 'seriousgame':
5637
            default:
5638
                $next_mode = 'single';
5639
                break;
5640
        }
5641
        $this->set_attempt_mode($next_mode);
5642
    }
5643
5644
    /**
5645
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5646
     * but possibility to do again a completed item.
5647
     *
5648
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5649
     *
5650
     * @author ndiechburg <[email protected]>
5651
     */
5652
    public function set_seriousgame_mode()
5653
    {
5654
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5655
        $sql = "SELECT * FROM $lp_table
5656
                WHERE iid = ".$this->get_id();
5657
        $res = Database::query($sql);
5658
        if (Database::num_rows($res) > 0) {
5659
            $row = Database::fetch_array($res);
5660
            $force = $row['seriousgame_mode'];
5661
            if (1 == $force) {
5662
                $force = 0;
5663
            } elseif (0 == $force) {
5664
                $force = 1;
5665
            }
5666
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5667
			        WHERE iid = ".$this->get_id();
5668
            Database::query($sql);
5669
            $this->seriousgame_mode = $force;
5670
5671
            return $force;
5672
        }
5673
5674
        return -1;
5675
    }
5676
5677
    /**
5678
     * Updates the "scorm_debug" value that shows or hide the debug window.
5679
     *
5680
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5681
     */
5682
    public function update_scorm_debug()
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['debug'];
5691
            if (1 == $force) {
5692
                $force = 0;
5693
            } elseif (0 == $force) {
5694
                $force = 1;
5695
            }
5696
            $sql = "UPDATE $lp_table SET debug = $force
5697
                    WHERE iid = ".$this->get_id();
5698
            Database::query($sql);
5699
            $this->scorm_debug = $force;
5700
5701
            return $force;
5702
        }
5703
5704
        return -1;
5705
    }
5706
5707
    /**
5708
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5709
     *
5710
     * @author Kevin Van Den Haute
5711
     *
5712
     * @param  array
5713
     */
5714
    public function tree_array($array)
5715
    {
5716
        $array = $this->sort_tree_array($array);
5717
        $this->create_tree_array($array);
5718
    }
5719
5720
    /**
5721
     * Creates an array with the elements of the learning path tree in it.
5722
     *
5723
     * @author Kevin Van Den Haute
5724
     *
5725
     * @param array $array
5726
     * @param int   $parent
5727
     * @param int   $depth
5728
     * @param array $tmp
5729
     */
5730
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5731
    {
5732
        if (is_array($array)) {
5733
            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...
5734
                if ($array[$i]['parent_item_id'] == $parent) {
5735
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5736
                        $tmp[] = $array[$i]['parent_item_id'];
5737
                        $depth++;
5738
                    }
5739
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5740
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5741
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5742
5743
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5744
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5745
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5746
                    $this->arrMenu[] = [
5747
                        'id' => $array[$i]['id'],
5748
                        'ref' => $ref,
5749
                        'item_type' => $array[$i]['item_type'],
5750
                        'title' => $array[$i]['title'],
5751
                        'title_raw' => $array[$i]['title_raw'],
5752
                        'path' => $path,
5753
                        'description' => $array[$i]['description'],
5754
                        'parent_item_id' => $array[$i]['parent_item_id'],
5755
                        'previous_item_id' => $array[$i]['previous_item_id'],
5756
                        'next_item_id' => $array[$i]['next_item_id'],
5757
                        'min_score' => $array[$i]['min_score'],
5758
                        'max_score' => $array[$i]['max_score'],
5759
                        'mastery_score' => $array[$i]['mastery_score'],
5760
                        'display_order' => $array[$i]['display_order'],
5761
                        'prerequisite' => $preq,
5762
                        'depth' => $depth,
5763
                        'audio' => $audio,
5764
                        'prerequisite_min_score' => $prerequisiteMinScore,
5765
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5766
                    ];
5767
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5768
                }
5769
            }
5770
        }
5771
    }
5772
5773
    /**
5774
     * Sorts a multi dimensional array by parent id and display order.
5775
     *
5776
     * @author Kevin Van Den Haute
5777
     *
5778
     * @param array $array (array with al the learning path items in it)
5779
     *
5780
     * @return array
5781
     */
5782
    public function sort_tree_array($array)
5783
    {
5784
        foreach ($array as $key => $row) {
5785
            $parent[$key] = $row['parent_item_id'];
5786
            $position[$key] = $row['display_order'];
5787
        }
5788
5789
        if (count($array) > 0) {
5790
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5791
        }
5792
5793
        return $array;
5794
    }
5795
5796
    /**
5797
     * Function that creates a html list of learning path items so that we can add audio files to them.
5798
     *
5799
     * @author Kevin Van Den Haute
5800
     *
5801
     * @return string
5802
     */
5803
    public function overview()
5804
    {
5805
        $return = '';
5806
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5807
5808
        // we need to start a form when we want to update all the mp3 files
5809
        if ('true' == $update_audio) {
5810
            $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">';
5811
        }
5812
        $return .= '<div id="message"></div>';
5813
        if (0 == count($this->items)) {
5814
            $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');
5815
        } else {
5816
            $return_audio = '<table class="data_table">';
5817
            $return_audio .= '<tr>';
5818
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5819
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5820
            $return_audio .= '</tr>';
5821
5822
            if ('true' != $update_audio) {
5823
                $return .= '<div class="col-md-12">';
5824
                $return .= self::return_new_tree($update_audio);
5825
                $return .= '</div>';
5826
                $return .= Display::div(
5827
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5828
                    ['style' => 'float:left; margin-top:15px;width:100%']
5829
                );
5830
            } else {
5831
                $return_audio .= self::return_new_tree($update_audio);
5832
                $return .= $return_audio.'</table>';
5833
            }
5834
5835
            // We need to close the form when we are updating the mp3 files.
5836
            if ('true' == $update_audio) {
5837
                $return .= '<div class="footer-audio">';
5838
                $return .= Display::button(
5839
                    'save_audio',
5840
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5841
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5842
                );
5843
                $return .= '</div>';
5844
            }
5845
        }
5846
5847
        // We need to close the form when we are updating the mp3 files.
5848
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5849
            $return .= '</form>';
5850
        }
5851
5852
        return $return;
5853
    }
5854
5855
    /**
5856
     * @param string $update_audio
5857
     *
5858
     * @return array
5859
     */
5860
    public function processBuildMenuElements($update_audio = 'false')
5861
    {
5862
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5863
        $arrLP = $this->getItemsForForm();
5864
5865
        $this->tree_array($arrLP);
5866
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5867
        unset($this->arrMenu);
5868
        $default_data = null;
5869
        $default_content = null;
5870
        $elements = [];
5871
        $return_audio = null;
5872
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5873
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5874
        $countItems = count($arrLP);
5875
5876
        $upIcon = Display::return_icon(
5877
            'up.png',
5878
            get_lang('Up'),
5879
            [],
5880
            ICON_SIZE_TINY
5881
        );
5882
5883
        $disableUpIcon = Display::return_icon(
5884
            'up_na.png',
5885
            get_lang('Up'),
5886
            [],
5887
            ICON_SIZE_TINY
5888
        );
5889
5890
        $downIcon = Display::return_icon(
5891
            'down.png',
5892
            get_lang('Down'),
5893
            [],
5894
            ICON_SIZE_TINY
5895
        );
5896
5897
        $disableDownIcon = Display::return_icon(
5898
            'down_na.png',
5899
            get_lang('Down'),
5900
            [],
5901
            ICON_SIZE_TINY
5902
        );
5903
5904
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5905
5906
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5907
        $plugin = null;
5908
        if ($pluginCalendar) {
5909
            $plugin = LearningCalendarPlugin::create();
5910
        }
5911
5912
        for ($i = 0; $i < $countItems; $i++) {
5913
            $parent_id = $arrLP[$i]['parent_item_id'];
5914
            $title = $arrLP[$i]['title'];
5915
            $title_cut = $arrLP[$i]['title_raw'];
5916
            if (false === $show) {
5917
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5918
            }
5919
            // Link for the documents
5920
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5921
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5922
                $title_cut = Display::url(
5923
                    $title_cut,
5924
                    $url,
5925
                    [
5926
                        'class' => 'ajax moved',
5927
                        'data-title' => $title,
5928
                        'title' => $title,
5929
                    ]
5930
                );
5931
            }
5932
5933
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5934
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5935
                Session::write('pathItem', $arrLP[$i]['path']);
5936
            }
5937
5938
            $oddClass = 'row_even';
5939
            if (0 == ($i % 2)) {
5940
                $oddClass = 'row_odd';
5941
            }
5942
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5943
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5944
5945
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5946
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5947
            } else {
5948
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5949
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5950
                } else {
5951
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5952
                        $icon = Display::return_icon('certificate.png');
5953
                    } else {
5954
                        $icon = Display::return_icon('folder_document.png');
5955
                    }
5956
                }
5957
            }
5958
5959
            // The audio column.
5960
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5961
            $audio = '';
5962
            if (!$update_audio || 'true' != $update_audio) {
5963
                if (empty($arrLP[$i]['audio'])) {
5964
                    $audio .= '';
5965
                }
5966
            } else {
5967
                $types = self::getChapterTypes();
5968
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5969
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5970
                    if (!empty($arrLP[$i]['audio'])) {
5971
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5972
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5973
                    }
5974
                }
5975
            }
5976
5977
            $return_audio .= Display::span($icon.' '.$title).
5978
                Display::tag(
5979
                    'td',
5980
                    $audio,
5981
                    ['style' => '']
5982
                );
5983
            $return_audio .= '</td>';
5984
            $move_icon = '';
5985
            $move_item_icon = '';
5986
            $edit_icon = '';
5987
            $delete_icon = '';
5988
            $audio_icon = '';
5989
            $prerequisities_icon = '';
5990
            $forumIcon = '';
5991
            $previewIcon = '';
5992
            $pluginCalendarIcon = '';
5993
            $orderIcons = '';
5994
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5995
5996
            if ($is_allowed_to_edit) {
5997
                if (!$update_audio || 'true' != $update_audio) {
5998
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5999
                        $move_icon .= '<a class="moved" href="#">';
6000
                        $move_icon .= Display::return_icon(
6001
                            'move_everywhere.png',
6002
                            get_lang('Move'),
6003
                            [],
6004
                            ICON_SIZE_TINY
6005
                        );
6006
                        $move_icon .= '</a>';
6007
                    }
6008
                }
6009
6010
                // No edit for this item types
6011
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6012
                    if ('dir' != $arrLP[$i]['item_type']) {
6013
                        $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">';
6014
                        $edit_icon .= Display::return_icon(
6015
                            'edit.png',
6016
                            get_lang('Edit section description/name'),
6017
                            [],
6018
                            ICON_SIZE_TINY
6019
                        );
6020
                        $edit_icon .= '</a>';
6021
6022
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6023
                            $forumThread = null;
6024
                            if (isset($this->items[$arrLP[$i]['id']])) {
6025
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6026
                                    $this->course_int_id,
6027
                                    $this->lp_session_id
6028
                                );
6029
                            }
6030
                            if ($forumThread) {
6031
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6032
                                        'action' => 'dissociate_forum',
6033
                                        'id' => $arrLP[$i]['id'],
6034
                                        'lp_id' => $this->lp_id,
6035
                                    ]);
6036
                                $forumIcon = Display::url(
6037
                                    Display::return_icon(
6038
                                        'forum.png',
6039
                                        get_lang('Dissociate the forum of this learning path item'),
6040
                                        [],
6041
                                        ICON_SIZE_TINY
6042
                                    ),
6043
                                    $forumIconUrl,
6044
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6045
                                );
6046
                            } else {
6047
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6048
                                        'action' => 'create_forum',
6049
                                        'id' => $arrLP[$i]['id'],
6050
                                        'lp_id' => $this->lp_id,
6051
                                    ]);
6052
                                $forumIcon = Display::url(
6053
                                    Display::return_icon(
6054
                                        'forum.png',
6055
                                        get_lang('Associate a forum to this learning path item'),
6056
                                        [],
6057
                                        ICON_SIZE_TINY
6058
                                    ),
6059
                                    $forumIconUrl,
6060
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6061
                                );
6062
                            }
6063
                        }
6064
                    } else {
6065
                        $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">';
6066
                        $edit_icon .= Display::return_icon(
6067
                            'edit.png',
6068
                            get_lang('Edit section description/name'),
6069
                            [],
6070
                            ICON_SIZE_TINY
6071
                        );
6072
                        $edit_icon .= '</a>';
6073
                    }
6074
                } else {
6075
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6076
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6077
                        $edit_icon .= Display::return_icon(
6078
                            'edit.png',
6079
                            get_lang('Edit'),
6080
                            [],
6081
                            ICON_SIZE_TINY
6082
                        );
6083
                        $edit_icon .= '</a>';
6084
                    }
6085
                }
6086
6087
                if ($pluginCalendar) {
6088
                    $pluginLink = $pluginUrl.
6089
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6090
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6091
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6092
                    if ($itemInfo && 1 == $itemInfo['value']) {
6093
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6094
                    }
6095
                    $pluginCalendarIcon = Display::url(
6096
                        $iconCalendar,
6097
                        $pluginLink,
6098
                        ['class' => 'btn btn-default']
6099
                    );
6100
                }
6101
6102
                if ('final_item' != $arrLP[$i]['item_type']) {
6103
                    $orderIcons = Display::url(
6104
                        $upIcon,
6105
                        'javascript:void(0)',
6106
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6107
                    );
6108
                    $orderIcons .= Display::url(
6109
                        $downIcon,
6110
                        'javascript:void(0)',
6111
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6112
                    );
6113
                }
6114
6115
                $delete_icon .= ' <a
6116
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6117
                    onclick="return confirmation(\''.addslashes($title).'\');"
6118
                    class="btn btn-default">';
6119
                $delete_icon .= Display::return_icon(
6120
                    'delete.png',
6121
                    get_lang('Delete section'),
6122
                    [],
6123
                    ICON_SIZE_TINY
6124
                );
6125
                $delete_icon .= '</a>';
6126
6127
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6128
                $previewImage = Display::return_icon(
6129
                    'preview_view.png',
6130
                    get_lang('Preview'),
6131
                    [],
6132
                    ICON_SIZE_TINY
6133
                );
6134
6135
                switch ($arrLP[$i]['item_type']) {
6136
                    case TOOL_DOCUMENT:
6137
                    case TOOL_LP_FINAL_ITEM:
6138
                    case TOOL_READOUT_TEXT:
6139
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6140
                        $previewIcon = Display::url(
6141
                            $previewImage,
6142
                            $urlPreviewLink,
6143
                            [
6144
                                'target' => '_blank',
6145
                                'class' => 'btn btn-default',
6146
                                'data-title' => $arrLP[$i]['title'],
6147
                                'title' => $arrLP[$i]['title'],
6148
                            ]
6149
                        );
6150
                        break;
6151
                    case TOOL_THREAD:
6152
                    case TOOL_FORUM:
6153
                    case TOOL_QUIZ:
6154
                    case TOOL_STUDENTPUBLICATION:
6155
                    case TOOL_LP_FINAL_ITEM:
6156
                    case TOOL_LINK:
6157
                        $class = 'btn btn-default';
6158
                        $target = '_blank';
6159
                        $link = self::rl_get_resource_link_for_learnpath(
6160
                            $this->course_int_id,
6161
                            $this->lp_id,
6162
                            $arrLP[$i]['id'],
6163
                            0
6164
                        );
6165
                        $previewIcon = Display::url(
6166
                            $previewImage,
6167
                            $link,
6168
                            [
6169
                                'class' => $class,
6170
                                'data-title' => $arrLP[$i]['title'],
6171
                                'title' => $arrLP[$i]['title'],
6172
                                'target' => $target,
6173
                            ]
6174
                        );
6175
                        break;
6176
                    default:
6177
                        $previewIcon = Display::url(
6178
                            $previewImage,
6179
                            $url.'&action=view_item',
6180
                            ['class' => 'btn btn-default', 'target' => '_blank']
6181
                        );
6182
                        break;
6183
                }
6184
6185
                if ('dir' != $arrLP[$i]['item_type']) {
6186
                    $prerequisities_icon = Display::url(
6187
                        Display::return_icon(
6188
                            'accept.png',
6189
                            get_lang('Prerequisites'),
6190
                            [],
6191
                            ICON_SIZE_TINY
6192
                        ),
6193
                        $url.'&action=edit_item_prereq',
6194
                        ['class' => 'btn btn-default']
6195
                    );
6196
                    if ('final_item' != $arrLP[$i]['item_type']) {
6197
                        /*$move_item_icon = Display::url(
6198
                            Display::return_icon(
6199
                                'move.png',
6200
                                get_lang('Move'),
6201
                                [],
6202
                                ICON_SIZE_TINY
6203
                            ),
6204
                            $url.'&action=move_item',
6205
                            ['class' => 'btn btn-default']
6206
                        );*/
6207
                    }
6208
                    $audio_icon = Display::url(
6209
                        Display::return_icon(
6210
                            'audio.png',
6211
                            get_lang('Upload'),
6212
                            [],
6213
                            ICON_SIZE_TINY
6214
                        ),
6215
                        $url.'&action=add_audio',
6216
                        ['class' => 'btn btn-default']
6217
                    );
6218
                }
6219
            }
6220
            if ('true' != $update_audio) {
6221
                $row = $move_icon.' '.$icon.
6222
                    Display::span($title_cut).
6223
                    Display::tag(
6224
                        'div',
6225
                        "<div class=\"btn-group btn-group-xs\">
6226
                                    $previewIcon
6227
                                    $audio
6228
                                    $edit_icon
6229
                                    $pluginCalendarIcon
6230
                                    $forumIcon
6231
                                    $prerequisities_icon
6232
                                    $move_item_icon
6233
                                    $audio_icon
6234
                                    $orderIcons
6235
                                    $delete_icon
6236
                                </div>",
6237
                        ['class' => 'btn-toolbar button_actions']
6238
                    );
6239
            } else {
6240
                $row =
6241
                    Display::span($title.$icon).
6242
                    Display::span($audio, ['class' => 'button_actions']);
6243
            }
6244
6245
            $default_data[$arrLP[$i]['id']] = $row;
6246
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6247
6248
            if (empty($parent_id)) {
6249
                $elements[$arrLP[$i]['id']]['data'] = $row;
6250
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6251
            } else {
6252
                $parent_arrays = [];
6253
                if ($arrLP[$i]['depth'] > 1) {
6254
                    // Getting list of parents
6255
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6256
                        foreach ($arrLP as $item) {
6257
                            if ($item['id'] == $parent_id) {
6258
                                if (0 == $item['parent_item_id']) {
6259
                                    $parent_id = $item['id'];
6260
                                    break;
6261
                                } else {
6262
                                    $parent_id = $item['parent_item_id'];
6263
                                    if (empty($parent_arrays)) {
6264
                                        $parent_arrays[] = intval($item['id']);
6265
                                    }
6266
                                    $parent_arrays[] = $parent_id;
6267
                                    break;
6268
                                }
6269
                            }
6270
                        }
6271
                    }
6272
                }
6273
6274
                if (!empty($parent_arrays)) {
6275
                    $parent_arrays = array_reverse($parent_arrays);
6276
                    $val = '$elements';
6277
                    $x = 0;
6278
                    foreach ($parent_arrays as $item) {
6279
                        if ($x != count($parent_arrays) - 1) {
6280
                            $val .= '["'.$item.'"]["children"]';
6281
                        } else {
6282
                            $val .= '["'.$item.'"]["children"]';
6283
                        }
6284
                        $x++;
6285
                    }
6286
                    $val .= "";
6287
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6288
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6289
                } else {
6290
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6291
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6292
                }
6293
            }
6294
        }
6295
6296
        return [
6297
            'elements' => $elements,
6298
            'default_data' => $default_data,
6299
            'default_content' => $default_content,
6300
            'return_audio' => $return_audio,
6301
        ];
6302
    }
6303
6304
    /**
6305
     * @param string $updateAudio true/false strings
6306
     *
6307
     * @return string
6308
     */
6309
    public function returnLpItemList($updateAudio)
6310
    {
6311
        $result = $this->processBuildMenuElements($updateAudio);
6312
6313
        $html = self::print_recursive(
6314
            $result['elements'],
6315
            $result['default_data'],
6316
            $result['default_content']
6317
        );
6318
6319
        if (!empty($html)) {
6320
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6321
        }
6322
6323
        return $html;
6324
    }
6325
6326
    /**
6327
     * @param string $update_audio
6328
     * @param bool   $drop_element_here
6329
     *
6330
     * @return string
6331
     */
6332
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6333
    {
6334
        $result = $this->processBuildMenuElements($update_audio);
6335
6336
        $list = '<ul id="lp_item_list">';
6337
        $tree = $this->print_recursive(
6338
            $result['elements'],
6339
            $result['default_data'],
6340
            $result['default_content']
6341
        );
6342
6343
        if (!empty($tree)) {
6344
            $list .= $tree;
6345
        } else {
6346
            if ($drop_element_here) {
6347
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6348
            }
6349
        }
6350
        $list .= '</ul>';
6351
6352
        $return = Display::panelCollapse(
6353
            $this->name,
6354
            $list,
6355
            'scorm-list',
6356
            null,
6357
            'scorm-list-accordion',
6358
            'scorm-list-collapse'
6359
        );
6360
6361
        if ('true' === $update_audio) {
6362
            $return = $result['return_audio'];
6363
        }
6364
6365
        return $return;
6366
    }
6367
6368
    /**
6369
     * @param array $elements
6370
     * @param array $default_data
6371
     * @param array $default_content
6372
     *
6373
     * @return string
6374
     */
6375
    public function print_recursive($elements, $default_data, $default_content)
6376
    {
6377
        $return = '';
6378
        foreach ($elements as $key => $item) {
6379
            if (isset($item['load_data']) || empty($item['data'])) {
6380
                $item['data'] = $default_data[$item['load_data']];
6381
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6382
            }
6383
            $sub_list = '';
6384
            if (isset($item['type']) && 'dir' === $item['type']) {
6385
                // empty value
6386
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6387
            }
6388
            if (empty($item['children'])) {
6389
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6390
                $active = null;
6391
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6392
                    $active = 'active';
6393
                }
6394
                $return .= Display::tag(
6395
                    'li',
6396
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6397
                    ['id' => $key, 'class' => 'record li_container']
6398
                );
6399
            } else {
6400
                // Sections
6401
                $data = '';
6402
                if (isset($item['children'])) {
6403
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6404
                }
6405
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6406
                $return .= Display::tag(
6407
                    'li',
6408
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6409
                    ['id' => $key, 'class' => 'record li_container']
6410
                );
6411
            }
6412
        }
6413
6414
        return $return;
6415
    }
6416
6417
    /**
6418
     * This function builds the action menu.
6419
     *
6420
     * @param bool $returnContent          Optional
6421
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6422
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6423
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6424
     *
6425
     * @return string
6426
     */
6427
    public function build_action_menu(
6428
        $returnContent = false,
6429
        $showRequirementButtons = true,
6430
        $isConfigPage = false,
6431
        $allowExpand = true
6432
    ) {
6433
        $actionsRight = '';
6434
        $actionsLeft = Display::url(
6435
            Display::return_icon(
6436
                'back.png',
6437
                get_lang('Back to learning paths'),
6438
                '',
6439
                ICON_SIZE_MEDIUM
6440
            ),
6441
            'lp_controller.php?'.api_get_cidreq()
6442
        );
6443
        $actionsLeft .= Display::url(
6444
            Display::return_icon(
6445
                'preview_view.png',
6446
                get_lang('Preview'),
6447
                '',
6448
                ICON_SIZE_MEDIUM
6449
            ),
6450
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6451
                'action' => 'view',
6452
                'lp_id' => $this->lp_id,
6453
                'isStudentView' => 'true',
6454
            ])
6455
        );
6456
6457
        $actionsLeft .= Display::url(
6458
            Display::return_icon(
6459
                'upload_audio.png',
6460
                get_lang('Add audio'),
6461
                '',
6462
                ICON_SIZE_MEDIUM
6463
            ),
6464
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6465
                'action' => 'admin_view',
6466
                'lp_id' => $this->lp_id,
6467
                'updateaudio' => 'true',
6468
            ])
6469
        );
6470
6471
        $subscriptionSettings = self::getSubscriptionSettings();
6472
6473
        $request = api_request_uri();
6474
        if (false === strpos($request, 'edit')) {
6475
            $actionsLeft .= Display::url(
6476
                Display::return_icon(
6477
                    'settings.png',
6478
                    get_lang('Course settings'),
6479
                    '',
6480
                    ICON_SIZE_MEDIUM
6481
                ),
6482
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6483
                    'action' => 'edit',
6484
                    'lp_id' => $this->lp_id,
6485
                ])
6486
            );
6487
        }
6488
6489
        if (false === strpos($request, 'build') && false === strpos($request, 'add_item')) {
6490
            $actionsLeft .= Display::url(
6491
                Display::return_icon(
6492
                    'edit.png',
6493
                    get_lang('Edit'),
6494
                    '',
6495
                    ICON_SIZE_MEDIUM
6496
                ),
6497
                'lp_controller.php?'.http_build_query([
6498
                    'action' => 'build',
6499
                    'lp_id' => $this->lp_id,
6500
                ]).'&'.api_get_cidreq()
6501
            );
6502
        }
6503
6504
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6505
            if (1 == $this->subscribeUsers &&
6506
                $subscriptionSettings['allow_add_users_to_lp']) {
6507
                $actionsLeft .= Display::url(
6508
                    Display::return_icon(
6509
                        'user.png',
6510
                        get_lang('Subscribe users to learning path'),
6511
                        '',
6512
                        ICON_SIZE_MEDIUM
6513
                    ),
6514
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6515
                );
6516
            }
6517
        }
6518
6519
        if ($allowExpand) {
6520
            /*$actionsLeft .= Display::url(
6521
                Display::return_icon(
6522
                    'expand.png',
6523
                    get_lang('Expand'),
6524
                    ['id' => 'expand'],
6525
                    ICON_SIZE_MEDIUM
6526
                ).
6527
                Display::return_icon(
6528
                    'contract.png',
6529
                    get_lang('Collapse'),
6530
                    ['id' => 'contract', 'class' => 'hide'],
6531
                    ICON_SIZE_MEDIUM
6532
                ),
6533
                '#',
6534
                ['role' => 'button', 'id' => 'hide_bar_template']
6535
            );*/
6536
        }
6537
6538
        if ($showRequirementButtons) {
6539
            $buttons = [
6540
                [
6541
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6542
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6543
                        'action' => 'set_previous_step_as_prerequisite',
6544
                        'lp_id' => $this->lp_id,
6545
                    ]),
6546
                ],
6547
                [
6548
                    'title' => get_lang('Clear all prerequisites'),
6549
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6550
                        'action' => 'clear_prerequisites',
6551
                        'lp_id' => $this->lp_id,
6552
                    ]),
6553
                ],
6554
            ];
6555
            $actionsRight = Display::groupButtonWithDropDown(
6556
                get_lang('Prerequisites options'),
6557
                $buttons,
6558
                true
6559
            );
6560
        }
6561
6562
        $toolbar = Display::toolbarAction(
6563
            'actions-lp-controller',
6564
            [$actionsLeft, $actionsRight]
6565
        );
6566
6567
        if ($returnContent) {
6568
            return $toolbar;
6569
        }
6570
6571
        echo $toolbar;
6572
    }
6573
6574
    /**
6575
     * Creates the default learning path folder.
6576
     *
6577
     * @param array $course
6578
     * @param int   $creatorId
6579
     *
6580
     * @return bool
6581
     */
6582
    public static function generate_learning_path_folder($course, $creatorId = 0)
6583
    {
6584
        // Creating learning_path folder
6585
        $dir = 'learning_path';
6586
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6587
        $folder = false;
6588
        $folderData = create_unexisting_directory(
6589
            $course,
6590
            $creatorId,
6591
            0,
6592
            null,
6593
            0,
6594
            '',
6595
            $dir,
6596
            get_lang('Learning paths'),
6597
            0
6598
        );
6599
6600
        if (!empty($folderData)) {
6601
            $folder = true;
6602
        }
6603
6604
        return $folder;
6605
    }
6606
6607
    /**
6608
     * @param array  $course
6609
     * @param string $lp_name
6610
     * @param int    $creatorId
6611
     *
6612
     * @return array
6613
     */
6614
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6615
    {
6616
        $filepath = '';
6617
        $dir = '/learning_path/';
6618
6619
        if (empty($lp_name)) {
6620
            $lp_name = $this->name;
6621
        }
6622
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6623
        $folder = self::generate_learning_path_folder($course, $creatorId);
6624
6625
        // Limits title size
6626
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6627
        $dir = $dir.$title;
6628
6629
        // Creating LP folder
6630
        $documentId = null;
6631
        if ($folder) {
6632
            $folderData = create_unexisting_directory(
6633
                $course,
6634
                $creatorId,
6635
                0,
6636
                0,
6637
                0,
6638
                $filepath,
6639
                $dir,
6640
                $lp_name
6641
            );
6642
            if (!empty($folderData)) {
6643
                $folder = true;
6644
            }
6645
6646
            $documentId = $folderData->getIid();
6647
            $dir = $dir.'/';
6648
            if ($folder) {
6649
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6650
            }
6651
        }
6652
6653
        if (empty($documentId)) {
6654
            $dir = api_remove_trailing_slash($dir);
6655
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6656
        }
6657
6658
        $array = [
6659
            'dir' => $dir,
6660
            'filepath' => $filepath,
6661
            'folder' => $folder,
6662
            'id' => $documentId,
6663
        ];
6664
6665
        return $array;
6666
    }
6667
6668
    /**
6669
     * Create a new document //still needs some finetuning.
6670
     *
6671
     * @param array  $courseInfo
6672
     * @param string $content
6673
     * @param string $title
6674
     * @param string $extension
6675
     * @param int    $parentId
6676
     * @param int    $creatorId  creator id
6677
     *
6678
     * @return int
6679
     */
6680
    public function create_document(
6681
        $courseInfo,
6682
        $content = '',
6683
        $title = '',
6684
        $extension = 'html',
6685
        $parentId = 0,
6686
        $creatorId = 0
6687
    ) {
6688
        if (!empty($courseInfo)) {
6689
            $course_id = $courseInfo['real_id'];
6690
        } else {
6691
            $course_id = api_get_course_int_id();
6692
        }
6693
6694
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6695
        $sessionId = api_get_session_id();
6696
6697
        // Generates folder
6698
        $result = $this->generate_lp_folder($courseInfo);
6699
        $dir = $result['dir'];
6700
6701
        if (empty($parentId) || '/' == $parentId) {
6702
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6703
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6704
6705
            if ('/' === $parentId) {
6706
                $dir = '/';
6707
            }
6708
6709
            // Please, do not modify this dirname formatting.
6710
            if (strstr($dir, '..')) {
6711
                $dir = '/';
6712
            }
6713
6714
            if (!empty($dir[0]) && '.' == $dir[0]) {
6715
                $dir = substr($dir, 1);
6716
            }
6717
            if (!empty($dir[0]) && '/' != $dir[0]) {
6718
                $dir = '/'.$dir;
6719
            }
6720
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6721
                $dir .= '/';
6722
            }
6723
        } else {
6724
            $parentInfo = DocumentManager::get_document_data_by_id(
6725
                $parentId,
6726
                $courseInfo['code']
6727
            );
6728
            if (!empty($parentInfo)) {
6729
                $dir = $parentInfo['path'].'/';
6730
            }
6731
        }
6732
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6733
        // is already escaped twice when it gets here.
6734
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6735
        if (!empty($title)) {
6736
            $title = api_replace_dangerous_char(stripslashes($title));
6737
        } else {
6738
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6739
        }
6740
6741
        $title = disable_dangerous_file($title);
6742
        $filename = $title;
6743
        $content = !empty($content) ? $content : $_POST['content_lp'];
6744
        $tmp_filename = $filename;
6745
        $filename = $tmp_filename.'.'.$extension;
6746
6747
        if ('html' === $extension) {
6748
            $content = stripslashes($content);
6749
            $content = str_replace(
6750
                api_get_path(WEB_COURSE_PATH),
6751
                api_get_path(REL_PATH).'courses/',
6752
                $content
6753
            );
6754
6755
            // Change the path of mp3 to absolute.
6756
            // The first regexp deals with :// urls.
6757
            $content = preg_replace(
6758
                "|(flashvars=\"file=)([^:/]+)/|",
6759
                "$1".api_get_path(
6760
                    REL_COURSE_PATH
6761
                ).$courseInfo['path'].'/document/',
6762
                $content
6763
            );
6764
            // The second regexp deals with audio/ urls.
6765
            $content = preg_replace(
6766
                "|(flashvars=\"file=)([^/]+)/|",
6767
                "$1".api_get_path(
6768
                    REL_COURSE_PATH
6769
                ).$courseInfo['path'].'/document/$2/',
6770
                $content
6771
            );
6772
            // For flv player: To prevent edition problem with firefox,
6773
            // we have to use a strange tip (don't blame me please).
6774
            $content = str_replace(
6775
                '</body>',
6776
                '<style type="text/css">body{}</style></body>',
6777
                $content
6778
            );
6779
        }
6780
6781
        $save_file_path = $dir.$filename;
6782
6783
        $document = DocumentManager::addDocument(
6784
            $courseInfo,
6785
            $save_file_path,
6786
            'file',
6787
            '',
6788
            $tmp_filename,
6789
            '',
6790
            0, //readonly
6791
            true,
6792
            null,
6793
            $sessionId,
6794
            $creatorId,
6795
            false,
6796
            $content,
6797
            $parentId
6798
        );
6799
6800
        $document_id = $document->getIid();
6801
        if ($document_id) {
6802
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6803
            $new_title = $originalTitle;
6804
6805
            if ($new_comment || $new_title) {
6806
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6807
                $ct = '';
6808
                if ($new_comment) {
6809
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6810
                }
6811
                if ($new_title) {
6812
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6813
                }
6814
6815
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6816
                        WHERE c_id = $course_id AND id = $document_id ";
6817
                Database::query($sql);
6818
            }
6819
        }
6820
6821
        return $document_id;
6822
    }
6823
6824
    /**
6825
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6826
     *
6827
     */
6828
    public function edit_document()
6829
    {
6830
        $repo = Container::getDocumentRepository();
6831
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6832
            $id = (int) $_REQUEST['document_id'];
6833
            /** @var CDocument $document */
6834
            $document = $repo->find($id);
6835
6836
            if ($document->getResourceNode()->hasEditableContent()) {
6837
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6838
            }
6839
6840
            $document->setTitle($_REQUEST['title']);
6841
            $repo->getEntityManager()->persist($document);
6842
            $repo->getEntityManager()->flush();
6843
        }
6844
    }
6845
6846
    /**
6847
     * Displays the selected item, with a panel for manipulating the item.
6848
     *
6849
     * @param CLpItem $lpItem
6850
     * @param string  $msg
6851
     * @param bool    $show_actions
6852
     *
6853
     * @return string
6854
     */
6855
    public function display_item($lpItem, $msg = null, $show_actions = true)
6856
    {
6857
        $course_id = api_get_course_int_id();
6858
        $return = '';
6859
6860
        if (empty($lpItem)) {
6861
            return '';
6862
        }
6863
        $item_id = $lpItem->getIid();
6864
        $itemType = $lpItem->getItemType();
6865
        $lpId = $lpItem->getLpId();
6866
        $path = $lpItem->getPath();
6867
6868
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6869
6870
        // Prevents wrong parent selection for document, see Bug#1251.
6871
        if ('dir' !== $itemType) {
6872
            Session::write('parent_item_id', $lpItem->getParentItemId());
6873
        }
6874
6875
        if ($show_actions) {
6876
            $return .= $this->displayItemMenu($lpItem);
6877
        }
6878
        $return .= '<div style="padding:10px;">';
6879
6880
        if ('' != $msg) {
6881
            $return .= $msg;
6882
        }
6883
6884
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6885
6886
        switch ($itemType) {
6887
            case TOOL_THREAD:
6888
                $link = $this->rl_get_resource_link_for_learnpath(
6889
                    $course_id,
6890
                    $lpId,
6891
                    $item_id,
6892
                    0
6893
                );
6894
                $return .= Display::url(
6895
                    get_lang('Go to thread'),
6896
                    $link,
6897
                    ['class' => 'btn btn-primary']
6898
                );
6899
                break;
6900
            case TOOL_FORUM:
6901
                $return .= Display::url(
6902
                    get_lang('Go to the forum'),
6903
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6904
                    ['class' => 'btn btn-primary']
6905
                );
6906
                break;
6907
            case TOOL_QUIZ:
6908
                if (!empty($path)) {
6909
                    $exercise = new Exercise();
6910
                    $exercise->read($path);
6911
                    $return .= $exercise->description.'<br />';
6912
                    $return .= Display::url(
6913
                        get_lang('Go to exercise'),
6914
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6915
                        ['class' => 'btn btn-primary']
6916
                    );
6917
                }
6918
                break;
6919
            case TOOL_LP_FINAL_ITEM:
6920
                $return .= $this->getSavedFinalItem();
6921
                break;
6922
            case TOOL_DOCUMENT:
6923
            case TOOL_READOUT_TEXT:
6924
                $repo = Container::getDocumentRepository();
6925
                /** @var CDocument $document */
6926
                $document = $repo->find($lpItem->getPath());
6927
                $return .= $this->display_document($document, true, true);
6928
                break;
6929
            case TOOL_HOTPOTATOES:
6930
                $return .= $this->display_document($document, false, true);
6931
                break;
6932
        }
6933
        $return .= '</div>';
6934
6935
        return $return;
6936
    }
6937
6938
    /**
6939
     * Shows the needed forms for editing a specific item.
6940
     *
6941
     * @param CLpItem $lpItem
6942
     *
6943
     * @throws Exception
6944
     * @throws HTML_QuickForm_Error
6945
     *
6946
     * @return string
6947
     */
6948
    public function display_edit_item($lpItem)
6949
    {
6950
        $course_id = api_get_course_int_id();
6951
        $return = '';
6952
6953
        if (empty($lpItem)) {
6954
            return '';
6955
        }
6956
        $item_id  = $lpItem->getIid();
6957
        $itemType = $lpItem->getItemType();
6958
        $path = $lpItem->getPath();
6959
6960
        switch ($itemType) {
6961
            case 'dir':
6962
            case 'asset':
6963
            case 'sco':
6964
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6965
                    $return .= $this->displayItemMenu($lpItem);
6966
                    $return .= $this->display_item_form(
6967
                        $lpItem,
6968
                        'edit'
6969
                    );
6970
                } else {
6971
                    $return .= $this->display_item_form(
6972
                        $lpItem,
6973
                        'edit_item'
6974
                    );
6975
                }
6976
                break;
6977
            case TOOL_LP_FINAL_ITEM:
6978
            case TOOL_DOCUMENT:
6979
            case TOOL_READOUT_TEXT:
6980
                $return .= $this->displayItemMenu($lpItem);
6981
                $return .= $this->display_document_form('edit', $lpItem);
6982
                break;
6983
            case TOOL_LINK:
6984
                $link = null;
6985
                if (!empty($path)) {
6986
                    $repo = Container::getLinkRepository();
6987
                    $link = $repo->find($path);
6988
                }
6989
                $return .= $this->displayItemMenu($lpItem);
6990
                $return .= $this->display_link_form('edit', $lpItem, $link);
6991
6992
                break;
6993
            case TOOL_QUIZ:
6994
                if (!empty($path)) {
6995
                    $repo = Container::getExerciseRepository();
6996
                    $resource = $repo->find($path);
6997
                }
6998
                $return .= $this->displayItemMenu($lpItem);
6999
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7000
                break;
7001
            /*case TOOL_HOTPOTATOES:
7002
                $return .= $this->displayItemMenu($lpItem);
7003
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7004
                break;*/
7005
            case TOOL_STUDENTPUBLICATION:
7006
                if (!empty($path)) {
7007
                    $repo = Container::getStudentPublicationRepository();
7008
                    $resource = $repo->find($path);
7009
                }
7010
                $return .= $this->displayItemMenu($lpItem);
7011
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7012
                break;
7013
            case TOOL_FORUM:
7014
                if (!empty($path)) {
7015
                    $repo = Container::getForumRepository();
7016
                    $resource = $repo->find($path);
7017
                }
7018
                $return .= $this->displayItemMenu($lpItem);
7019
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7020
                break;
7021
            case TOOL_THREAD:
7022
                if (!empty($path)) {
7023
                    $repo = Container::getForumPostRepository();
7024
                    $resource = $repo->find($path);
7025
                }
7026
                $return .= $this->displayItemMenu($lpItem);
7027
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7028
                break;
7029
        }
7030
7031
        return $return;
7032
    }
7033
7034
    /**
7035
     * Function that displays a list with al the resources that
7036
     * could be added to the learning path.
7037
     *
7038
     * @throws Exception
7039
     * @throws HTML_QuickForm_Error
7040
     *
7041
     * @return bool
7042
     */
7043
    public function display_resources()
7044
    {
7045
        $course_code = api_get_course_id();
7046
7047
        // Get all the docs.
7048
        $documents = $this->get_documents(true);
7049
7050
        // Get all the exercises.
7051
        $exercises = $this->get_exercises();
7052
7053
        // Get all the links.
7054
        $links = $this->get_links();
7055
7056
        // Get all the student publications.
7057
        $works = $this->get_student_publications();
7058
7059
        // Get all the forums.
7060
        $forums = $this->get_forums(null, $course_code);
7061
7062
        // Get the final item form (see BT#11048) .
7063
        $finish = $this->getFinalItemForm();
7064
7065
        $headers = [
7066
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7067
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7068
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7069
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7070
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7071
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7072
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7073
        ];
7074
7075
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7076
        $dir = $this->displayNewSectionForm();
7077
7078
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7079
7080
        echo Display::tabs(
7081
            $headers,
7082
            [
7083
                $documents,
7084
                $exercises,
7085
                $links,
7086
                $works,
7087
                $forums,
7088
                $dir,
7089
                $finish,
7090
            ],
7091
            'resource_tab',
7092
            [],
7093
            [],
7094
            $selected
7095
        );
7096
7097
        return true;
7098
    }
7099
7100
    /**
7101
     * Returns the extension of a document.
7102
     *
7103
     * @param string $filename
7104
     *
7105
     * @return string Extension (part after the last dot)
7106
     */
7107
    public function get_extension($filename)
7108
    {
7109
        $explode = explode('.', $filename);
7110
7111
        return $explode[count($explode) - 1];
7112
    }
7113
7114
    /**
7115
     * @return string
7116
     */
7117
    public function getCurrentBuildingModeURL()
7118
    {
7119
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7120
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7121
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7122
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7123
7124
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7125
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7126
7127
        return $currentUrl;
7128
    }
7129
7130
    /**
7131
     * Displays a document by id.
7132
     *
7133
     * @param CDocument  $document
7134
     * @param bool $show_title
7135
     * @param bool $iframe
7136
     * @param bool $edit_link
7137
     *
7138
     * @return string
7139
     */
7140
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7141
    {
7142
        $return = '';
7143
        if (!$document) {
7144
            return '';
7145
        }
7146
7147
        $repo = Container::getDocumentRepository();
7148
7149
        // TODO: Add a path filter.
7150
        if ($iframe) {
7151
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7152
            $url = $repo->getResourceFileUrl($document);
7153
7154
            $return .= '<iframe
7155
                id="learnpath_preview_frame"
7156
                frameborder="0"
7157
                height="400"
7158
                width="100%"
7159
                scrolling="auto"
7160
                src="'.$url.'"></iframe>';
7161
        } else {
7162
            $return = $repo->getResourceFileContent($document);
7163
        }
7164
7165
        return $return;
7166
    }
7167
7168
    /**
7169
     * Return HTML form to add/edit a link item.
7170
     *
7171
     * @param string $action     (add/edit)
7172
     * @param CLpItem    $lpItem
7173
     * @param CLink  $link
7174
     *
7175
     * @throws Exception
7176
     * @throws HTML_QuickForm_Error
7177
     *
7178
     * @return string HTML form
7179
     */
7180
    public function display_link_form($action = 'add', $lpItem, $link)
7181
    {
7182
        $item_url = '';
7183
        if ($link) {
7184
            $item_url = stripslashes($link->getUrl());
7185
        }
7186
        $form = new FormValidator(
7187
            'edit_link',
7188
            'POST',
7189
            $this->getCurrentBuildingModeURL()
7190
        );
7191
7192
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7193
7194
        $urlAttributes = ['class' => 'learnpath_item_form'];
7195
        $urlAttributes['disabled'] = 'disabled';
7196
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7197
        $form->setDefault('url', $item_url);
7198
7199
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7200
7201
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7202
    }
7203
7204
    /**
7205
     * Return HTML form to add/edit a quiz.
7206
     *
7207
     * @param string $action     Action (add/edit)
7208
     * @param CLpItem    $lpItem         Item ID if already exists
7209
     * @param CQuiz  $exercise Extra information (quiz ID if integer)
7210
     *
7211
     * @throws Exception
7212
     *
7213
     * @return string HTML form
7214
     */
7215
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7216
    {
7217
        $form = new FormValidator(
7218
            'quiz_form',
7219
            'POST',
7220
            $this->getCurrentBuildingModeURL()
7221
        );
7222
7223
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7224
7225
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7226
7227
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7228
    }
7229
7230
    /**
7231
     * Return the form to display the forum edit/add option.
7232
     *
7233
     * @param CLpItem $lpItem
7234
     *
7235
     * @throws Exception
7236
     *
7237
     * @return string HTML form
7238
     */
7239
    public function display_forum_form($action = 'add', $lpItem, $resource)
7240
    {
7241
        $form = new FormValidator(
7242
            'forum_form',
7243
            'POST',
7244
            $this->getCurrentBuildingModeURL()
7245
        );
7246
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7247
7248
        if ('add' === $action) {
7249
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7250
        } else {
7251
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7252
        }
7253
7254
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7255
    }
7256
7257
    /**
7258
     * Return HTML form to add/edit forum threads.
7259
     *
7260
     * @param string $action
7261
     * @param CLpItem    $lpItem
7262
     * @param string $resource
7263
     *
7264
     * @throws Exception
7265
     *
7266
     * @return string HTML form
7267
     */
7268
    public function display_thread_form($action = 'add', $lpItem, $resource)
7269
    {
7270
        $form = new FormValidator(
7271
            'thread_form',
7272
            'POST',
7273
            $this->getCurrentBuildingModeURL()
7274
        );
7275
7276
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7277
7278
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7279
7280
        return $form->returnForm();
7281
    }
7282
7283
    /**
7284
     * Return the HTML form to display an item (generally a dir item).
7285
     *
7286
     * @param CLpItem $lpItem
7287
     * @param string $title
7288
     * @param string $action
7289
     * @param string $extra_info
7290
     *
7291
     * @throws Exception
7292
     * @throws HTML_QuickForm_Error
7293
     *
7294
     * @return string HTML form
7295
     */
7296
    public function display_item_form(
7297
        $lpItem,
7298
        $action = 'add_item'
7299
    ) {
7300
        $item_type = $lpItem->getItemType();
7301
7302
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7303
7304
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7305
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7306
7307
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7308
7309
        return $form->returnForm();
7310
    }
7311
7312
7313
    /**
7314
     * Return HTML form to add/edit a student publication (work).
7315
     *
7316
     * @param string $action
7317
     * @param CLpItem    $lpItem
7318
     * @param CStudentPublication $resource
7319
     *
7320
     * @throws Exception
7321
     *
7322
     * @return string HTML form
7323
     */
7324
    public function display_student_publication_form(
7325
        $action = 'add',
7326
        CLpItem $lpItem,
7327
        $resource
7328
    ) {
7329
        $form = new FormValidator('frm_student_publication', 'post', '#');
7330
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7331
7332
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7333
7334
        $return = '<div class="sectioncomment">';
7335
        $return .= $form->returnForm();
7336
        $return .= '</div>';
7337
7338
        return $return;
7339
    }
7340
7341
    public function displayNewSectionForm()
7342
    {
7343
        $action = 'add_item';
7344
        $item_type = 'dir';
7345
7346
        $lpItem = new CLpItem();
7347
        $lpItem->setItemType('dir');
7348
7349
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7350
7351
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7352
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7353
7354
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7355
        $form->addElement('hidden', 'type', 'dir');
7356
7357
        return $form->returnForm();
7358
    }
7359
7360
7361
    /**
7362
     * Returns the form to update or create a document.
7363
     *
7364
     * @param string  $action (add/edit)
7365
     * @param CLpItem $lpItem
7366
     *
7367
     * @return string HTML form
7368
     * @throws HTML_QuickForm_Error
7369
     *
7370
     * @throws Exception
7371
     */
7372
    public function display_document_form($action = 'add', $lpItem = null)
7373
    {
7374
        if (empty($lpItem)) {
7375
            return '';
7376
        }
7377
7378
        $_course = api_get_course_info();
7379
7380
        $form = new FormValidator(
7381
            'form',
7382
            'POST',
7383
            $this->getCurrentBuildingModeURL(),
7384
            '',
7385
            ['enctype' => 'multipart/form-data']
7386
        );
7387
7388
        $data = $this->generate_lp_folder($_course);
7389
7390
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7391
7392
        switch ($action) {
7393
            case 'add':
7394
                $folders = DocumentManager::get_all_document_folders(
7395
                    $_course,
7396
                    0,
7397
                    true
7398
                );
7399
                DocumentManager::build_directory_selector(
7400
                    $folders,
7401
                    '',
7402
                    [],
7403
                    true,
7404
                    $form,
7405
                    'directory_parent_id'
7406
                );
7407
7408
                if (isset($data['id'])) {
7409
                    $defaults['directory_parent_id'] = $data['id'];
7410
                }
7411
7412
                break;
7413
        }
7414
7415
        $showButton = false;
7416
7417
        $form->addButtonSave(get_lang('Save document'), 'submit_button');
7418
7419
        if ($showButton) {
7420
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
7421
            //$form->addElement('hidden', 'path', $extra_info);
7422
        }
7423
        /* elseif (is_array($extra_info)) {
7424
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
7425
            $form->addElement('hidden', 'path', $extra_info['path']);
7426
        }*/
7427
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
7428
7429
        return $form->returnForm();
7430
    }
7431
7432
    /**
7433
     * @param array  $courseInfo
7434
     * @param string $content
7435
     * @param string $title
7436
     * @param int    $parentId
7437
     *
7438
     * @throws \Doctrine\ORM\ORMException
7439
     * @throws \Doctrine\ORM\OptimisticLockException
7440
     * @throws \Doctrine\ORM\TransactionRequiredException
7441
     *
7442
     * @return int
7443
     */
7444
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7445
    {
7446
        $creatorId = api_get_user_id();
7447
        $sessionId = api_get_session_id();
7448
7449
        // Generates folder
7450
        $result = $this->generate_lp_folder($courseInfo);
7451
        $dir = $result['dir'];
7452
7453
        if (empty($parentId) || '/' == $parentId) {
7454
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7455
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7456
7457
            if ('/' === $parentId) {
7458
                $dir = '/';
7459
            }
7460
7461
            // Please, do not modify this dirname formatting.
7462
            if (strstr($dir, '..')) {
7463
                $dir = '/';
7464
            }
7465
7466
            if (!empty($dir[0]) && '.' == $dir[0]) {
7467
                $dir = substr($dir, 1);
7468
            }
7469
            if (!empty($dir[0]) && '/' != $dir[0]) {
7470
                $dir = '/'.$dir;
7471
            }
7472
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7473
                $dir .= '/';
7474
            }
7475
        } else {
7476
            $parentInfo = DocumentManager::get_document_data_by_id(
7477
                $parentId,
7478
                $courseInfo['code']
7479
            );
7480
            if (!empty($parentInfo)) {
7481
                $dir = $parentInfo['path'].'/';
7482
            }
7483
        }
7484
7485
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7486
7487
        if (!is_dir($filepath)) {
7488
            $dir = '/';
7489
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7490
        }
7491
7492
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7493
7494
        if (!empty($title)) {
7495
            $title = api_replace_dangerous_char(stripslashes($title));
7496
        } else {
7497
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7498
        }
7499
7500
        $title = disable_dangerous_file($title);
7501
        $filename = $title;
7502
        $content = !empty($content) ? $content : $_POST['content_lp'];
7503
        $tmpFileName = $filename;
7504
7505
        $i = 0;
7506
        while (file_exists($filepath.$tmpFileName.'.html')) {
7507
            $tmpFileName = $filename.'_'.++$i;
7508
        }
7509
7510
        $filename = $tmpFileName.'.html';
7511
        $content = stripslashes($content);
7512
7513
        if (file_exists($filepath.$filename)) {
7514
            return 0;
7515
        }
7516
7517
        $putContent = file_put_contents($filepath.$filename, $content);
7518
7519
        if (false === $putContent) {
7520
            return 0;
7521
        }
7522
7523
        $fileSize = filesize($filepath.$filename);
7524
        $saveFilePath = $dir.$filename;
7525
7526
        $document = DocumentManager::addDocument(
7527
            $courseInfo,
7528
            $saveFilePath,
7529
            'file',
7530
            $fileSize,
7531
            $tmpFileName,
7532
            '',
7533
            0, //readonly
7534
            true,
7535
            null,
7536
            $sessionId,
7537
            $creatorId
7538
        );
7539
7540
        $documentId = $document->getId();
7541
7542
        if (!$document) {
7543
            return 0;
7544
        }
7545
7546
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7547
        $newTitle = $originalTitle;
7548
7549
        if ($newComment || $newTitle) {
7550
            $em = Database::getManager();
7551
7552
            if ($newComment) {
7553
                $document->setComment($newComment);
7554
            }
7555
7556
            if ($newTitle) {
7557
                $document->setTitle($newTitle);
7558
            }
7559
7560
            $em->persist($document);
7561
            $em->flush();
7562
        }
7563
7564
        return $documentId;
7565
    }
7566
7567
    /**
7568
     * Displays the menu for manipulating a step.
7569
     *
7570
     * @return string
7571
     */
7572
    public function displayItemMenu(CLpItem $lpItem)
7573
    {
7574
        $item_id = $lpItem->getIid();
7575
        $audio = $lpItem->getAudio();
7576
        $itemType = $lpItem->getItemType();
7577
        $path = $lpItem->getPath();
7578
7579
        $return = '<div class="actions">';
7580
        $audio_player = null;
7581
        // We display an audio player if needed.
7582
        if (!empty($audio)) {
7583
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7584
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7585
                .'<audio src="'.$webAudioPath.'" controls>'
7586
                .'</div><br>';*/
7587
        }
7588
7589
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7590
7591
        if (TOOL_LP_FINAL_ITEM != $itemType) {
7592
            $return .= Display::url(
7593
                Display::return_icon(
7594
                    'edit.png',
7595
                    get_lang('Edit'),
7596
                    [],
7597
                    ICON_SIZE_SMALL
7598
                ),
7599
                $url.'&action=edit_item&path_item='.$path
7600
            );
7601
7602
            $return .= Display::url(
7603
                Display::return_icon(
7604
                    'move.png',
7605
                    get_lang('Move'),
7606
                    [],
7607
                    ICON_SIZE_SMALL
7608
                ),
7609
                $url.'&action=move_item'
7610
            );
7611
        }
7612
7613
        // Commented for now as prerequisites cannot be added to chapters.
7614
        if ('dir' != $itemType) {
7615
            $return .= Display::url(
7616
                Display::return_icon(
7617
                    'accept.png',
7618
                    get_lang('Prerequisites'),
7619
                    [],
7620
                    ICON_SIZE_SMALL
7621
                ),
7622
                $url.'&action=edit_item_prereq'
7623
            );
7624
        }
7625
        $return .= Display::url(
7626
            Display::return_icon(
7627
                'delete.png',
7628
                get_lang('Delete'),
7629
                [],
7630
                ICON_SIZE_SMALL
7631
            ),
7632
            $url.'&action=delete_item'
7633
        );
7634
7635
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7636
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7637
            if (empty($documentData)) {
7638
                // Try with iid
7639
                $table = Database::get_course_table(TABLE_DOCUMENT);
7640
                $sql = "SELECT path FROM $table
7641
                        WHERE
7642
                              c_id = ".api_get_course_int_id()." AND
7643
                              iid = ".$path." AND
7644
                              path NOT LIKE '%_DELETED_%'";
7645
                $result = Database::query($sql);
7646
                $documentData = Database::fetch_array($result);
7647
                if ($documentData) {
7648
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7649
                }
7650
            }
7651
            if (isset($documentData['absolute_path_from_document'])) {
7652
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7653
            }
7654
        }*/
7655
7656
        $return .= '</div>';
7657
7658
        if (!empty($audio_player)) {
7659
            $return .= $audio_player;
7660
        }
7661
7662
        return $return;
7663
    }
7664
7665
    /**
7666
     * Creates the javascript needed for filling up the checkboxes without page reload.
7667
     *
7668
     * @return string
7669
     */
7670
    public function get_js_dropdown_array()
7671
    {
7672
        $course_id = api_get_course_int_id();
7673
        $return = 'var child_name = new Array();'."\n";
7674
        $return .= 'var child_value = new Array();'."\n\n";
7675
        $return .= 'child_name[0] = new Array();'."\n";
7676
        $return .= 'child_value[0] = new Array();'."\n\n";
7677
7678
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7679
        $sql = "SELECT * FROM ".$tbl_lp_item."
7680
                WHERE
7681
                    c_id = $course_id AND
7682
                    lp_id = ".$this->lp_id." AND
7683
                    parent_item_id = 0
7684
                ORDER BY display_order ASC";
7685
        Database::query($sql);
7686
        $i = 0;
7687
7688
        $list = $this->getItemsForForm(true);
7689
7690
        foreach ($list as $row_zero) {
7691
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7692
                if (TOOL_QUIZ == $row_zero['item_type']) {
7693
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7694
                }
7695
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7696
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7697
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7698
            }
7699
        }
7700
7701
        $return .= "\n";
7702
        $sql = "SELECT * FROM $tbl_lp_item
7703
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7704
        $res = Database::query($sql);
7705
        while ($row = Database::fetch_array($res)) {
7706
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7707
                           WHERE
7708
                                c_id = ".$course_id." AND
7709
                                parent_item_id = ".$row['iid']."
7710
                           ORDER BY display_order ASC";
7711
            $res_parent = Database::query($sql_parent);
7712
            $i = 0;
7713
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7714
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7715
7716
            while ($row_parent = Database::fetch_array($res_parent)) {
7717
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7718
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7719
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7720
            }
7721
            $return .= "\n";
7722
        }
7723
7724
        $return .= "
7725
            function load_cbo(id) {
7726
                if (!id) {
7727
                    return false;
7728
                }
7729
7730
                var cbo = document.getElementById('previous');
7731
                for(var i = cbo.length - 1; i > 0; i--) {
7732
                    cbo.options[i] = null;
7733
                }
7734
7735
                var k=0;
7736
                for(var i = 1; i <= child_name[id].length; i++){
7737
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7738
                    option.style.paddingLeft = '40px';
7739
                    cbo.options[i] = option;
7740
                    k = i;
7741
                }
7742
7743
                cbo.options[k].selected = true;
7744
                $('#previous').selectpicker('refresh');
7745
            }";
7746
7747
        return $return;
7748
    }
7749
7750
    /**
7751
     * Display the form to allow moving an item.
7752
     *
7753
     * @param CLpItem $lpItem
7754
     *
7755
     * @throws Exception
7756
     * @throws HTML_QuickForm_Error
7757
     *
7758
     * @return string HTML form
7759
     */
7760
    public function display_move_item($lpItem)
7761
    {
7762
        $return = '';
7763
        $path = $lpItem->getPath();
7764
7765
        if ($lpItem) {
7766
            $itemType = $lpItem->getItemType();
7767
            switch ($itemType) {
7768
                case 'dir':
7769
                case 'asset':
7770
                    $return .= $this->displayItemMenu($lpItem);
7771
                    $return .= $this->display_item_form(
7772
                        $lpItem,
7773
                        get_lang('Move the current section'),
7774
                        'move',
7775
                        $row
7776
                    );
7777
                    break;
7778
                case TOOL_DOCUMENT:
7779
                    $return .= $this->displayItemMenu($lpItem);
7780
                    $return .= $this->display_document_form('move', $lpItem);
7781
                    break;
7782
                case TOOL_LINK:
7783
                    $link = null;
7784
                    if (!empty($path)) {
7785
                        $repo = Container::getLinkRepository();
7786
                        $link = $repo->find($path);
7787
                    }
7788
                    $return .= $this->displayItemMenu($lpItem);
7789
                    $return .= $this->display_link_form('move', $lpItem, $link);
7790
                    break;
7791
                case TOOL_HOTPOTATOES:
7792
                    $return .= $this->displayItemMenu($lpItem);
7793
                    $return .= $this->display_link_form('move', $lpItem, $row);
7794
                    break;
7795
                case TOOL_QUIZ:
7796
                    $return .= $this->displayItemMenu($lpItem);
7797
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7798
                    break;
7799
                case TOOL_STUDENTPUBLICATION:
7800
                    $return .= $this->displayItemMenu($lpItem);
7801
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7802
                    break;
7803
                case TOOL_FORUM:
7804
                    $return .= $this->displayItemMenu($lpItem);
7805
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7806
                    break;
7807
                case TOOL_THREAD:
7808
                    $return .= $this->displayItemMenu($lpItem);
7809
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7810
                    break;
7811
            }
7812
        }
7813
7814
        return $return;
7815
    }
7816
7817
    /**
7818
     * Return HTML form to allow prerequisites selection.
7819
     *
7820
     * @todo use FormValidator
7821
     *
7822
     * @param CLpItem $lpItem
7823
     *
7824
     * @return string HTML form
7825
     */
7826
    public function display_item_prerequisites_form(CLpItem $lpItem)
7827
    {
7828
        $course_id = api_get_course_int_id();
7829
        $prerequisiteId = $lpItem->getPrerequisite();
7830
        $itemId = $lpItem->getIid();
7831
7832
        $return = '<legend>';
7833
        $return .= get_lang('Add/edit prerequisites');
7834
        $return .= '</legend>';
7835
        $return .= '<form method="POST">';
7836
        $return .= '<div class="table-responsive">';
7837
        $return .= '<table class="table table-hover">';
7838
        $return .= '<thead>';
7839
        $return .= '<tr>';
7840
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7841
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7842
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7843
        $return .= '</tr>';
7844
        $return .= '</thead>';
7845
7846
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7847
        $return .= '<tbody>';
7848
        $return .= '<tr>';
7849
        $return .= '<td colspan="3">';
7850
        $return .= '<div class="radio learnpath"><label for="idnone">';
7851
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7852
        $return .= get_lang('none').'</label>';
7853
        $return .= '</div>';
7854
        $return .= '</tr>';
7855
7856
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7857
        $sql = "SELECT * FROM $tbl_lp_item
7858
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7859
        $result = Database::query($sql);
7860
7861
        $selectedMinScore = [];
7862
        $selectedMaxScore = [];
7863
        $masteryScore = [];
7864
        while ($row = Database::fetch_array($result)) {
7865
            if ($row['iid'] == $itemId) {
7866
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7867
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7868
            }
7869
            $masteryScore[$row['iid']] = $row['mastery_score'];
7870
        }
7871
7872
        $arrLP = $this->getItemsForForm();
7873
        $this->tree_array($arrLP);
7874
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7875
        unset($this->arrMenu);
7876
7877
        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...
7878
            $item = $arrLP[$i];
7879
7880
            if ($item['id'] == $itemId) {
7881
                break;
7882
            }
7883
7884
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7885
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7886
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7887
7888
            $return .= '<tr>';
7889
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7890
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7891
            $return .= '<label for="id'.$item['id'].'">';
7892
            $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'].'" />';
7893
7894
            $icon_name = str_replace(' ', '', $item['item_type']);
7895
7896
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7897
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7898
            } else {
7899
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7900
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7901
                } else {
7902
                    $return .= Display::return_icon('folder_document.png');
7903
                }
7904
            }
7905
7906
            $return .= $item['title'].'</label>';
7907
            $return .= '</div>';
7908
            $return .= '</td>';
7909
7910
            if (TOOL_QUIZ == $item['item_type']) {
7911
                // lets update max_score Tests information depending of the Tests Advanced properties
7912
                $lpItemObj = new LpItem($course_id, $item['id']);
7913
                $exercise = new Exercise($course_id);
7914
                $exercise->read($lpItemObj->path);
7915
                $lpItemObj->max_score = $exercise->get_max_score();
7916
                $lpItemObj->update();
7917
                $item['max_score'] = $lpItemObj->max_score;
7918
7919
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7920
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7921
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7922
                }
7923
7924
                $return .= '<td>';
7925
                $return .= '<input
7926
                    class="form-control"
7927
                    size="4" maxlength="3"
7928
                    name="min_'.$item['id'].'"
7929
                    type="number"
7930
                    min="0"
7931
                    step="1"
7932
                    max="'.$item['max_score'].'"
7933
                    value="'.$selectedMinScoreValue.'"
7934
                />';
7935
                $return .= '</td>';
7936
                $return .= '<td>';
7937
                $return .= '<input
7938
                    class="form-control"
7939
                    size="4"
7940
                    maxlength="3"
7941
                    name="max_'.$item['id'].'"
7942
                    type="number"
7943
                    min="0"
7944
                    step="1"
7945
                    max="'.$item['max_score'].'"
7946
                    value="'.$selectedMaxScoreValue.'"
7947
                />';
7948
                $return .= '</td>';
7949
            }
7950
7951
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7952
                $return .= '<td>';
7953
                $return .= '<input
7954
                    size="4"
7955
                    maxlength="3"
7956
                    name="min_'.$item['id'].'"
7957
                    type="number"
7958
                    min="0"
7959
                    step="1"
7960
                    max="'.$item['max_score'].'"
7961
                    value="'.$selectedMinScoreValue.'"
7962
                />';
7963
                $return .= '</td>';
7964
                $return .= '<td>';
7965
                $return .= '<input
7966
                    size="4"
7967
                    maxlength="3"
7968
                    name="max_'.$item['id'].'"
7969
                    type="number"
7970
                    min="0"
7971
                    step="1"
7972
                    max="'.$item['max_score'].'"
7973
                    value="'.$selectedMaxScoreValue.'"
7974
                />';
7975
                $return .= '</td>';
7976
            }
7977
            $return .= '</tr>';
7978
        }
7979
        $return .= '<tr>';
7980
        $return .= '</tr>';
7981
        $return .= '</tbody>';
7982
        $return .= '</table>';
7983
        $return .= '</div>';
7984
        $return .= '<div class="form-group">';
7985
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7986
            get_lang('Save prerequisites settings').'</button>';
7987
        $return .= '</form>';
7988
7989
        return $return;
7990
    }
7991
7992
    /**
7993
     * Return HTML list to allow prerequisites selection for lp.
7994
     *
7995
     * @return string HTML form
7996
     */
7997
    public function display_lp_prerequisites_list()
7998
    {
7999
        $course_id = api_get_course_int_id();
8000
        $lp_id = $this->lp_id;
8001
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8002
8003
        // get current prerequisite
8004
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8005
        $result = Database::query($sql);
8006
        $row = Database::fetch_array($result);
8007
        $prerequisiteId = $row['prerequisite'];
8008
        $session_id = api_get_session_id();
8009
        $session_condition = api_get_session_condition($session_id, true, true);
8010
        $sql = "SELECT * FROM $tbl_lp
8011
                WHERE c_id = $course_id $session_condition
8012
                ORDER BY display_order ";
8013
        $rs = Database::query($sql);
8014
        $return = '';
8015
        $return .= '<select name="prerequisites" class="form-control">';
8016
        $return .= '<option value="0">'.get_lang('none').'</option>';
8017
        if (Database::num_rows($rs) > 0) {
8018
            while ($row = Database::fetch_array($rs)) {
8019
                if ($row['id'] == $lp_id) {
8020
                    continue;
8021
                }
8022
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
8023
            }
8024
        }
8025
        $return .= '</select>';
8026
8027
        return $return;
8028
    }
8029
8030
    /**
8031
     * Creates a list with all the documents in it.
8032
     *
8033
     * @param bool $showInvisibleFiles
8034
     *
8035
     * @throws Exception
8036
     * @throws HTML_QuickForm_Error
8037
     *
8038
     * @return string
8039
     */
8040
    public function get_documents($showInvisibleFiles = false)
8041
    {
8042
        $course_info = api_get_course_info();
8043
        $sessionId = api_get_session_id();
8044
        $documentTree = DocumentManager::get_document_preview(
8045
            $course_info,
8046
            $this->lp_id,
8047
            null,
8048
            $sessionId,
8049
            true,
8050
            null,
8051
            null,
8052
            $showInvisibleFiles,
8053
            true
8054
        );
8055
8056
        $headers = [
8057
            get_lang('Files'),
8058
            get_lang('Create a new document'),
8059
            get_lang('Create read-out text'),
8060
            get_lang('Upload'),
8061
        ];
8062
8063
        $form = new FormValidator(
8064
            'form_upload',
8065
            'POST',
8066
            $this->getCurrentBuildingModeURL(),
8067
            '',
8068
            ['enctype' => 'multipart/form-data']
8069
        );
8070
8071
        $folders = DocumentManager::get_all_document_folders(
8072
            api_get_course_info(),
8073
            0,
8074
            true
8075
        );
8076
8077
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8078
8079
        DocumentManager::build_directory_selector(
8080
            $folders,
8081
            $lpPathInfo['id'],
8082
            [],
8083
            true,
8084
            $form,
8085
            'directory_parent_id'
8086
        );
8087
8088
        $group = [
8089
            $form->createElement(
8090
                'radio',
8091
                'if_exists',
8092
                get_lang('If file exists:'),
8093
                get_lang('Do nothing'),
8094
                'nothing'
8095
            ),
8096
            $form->createElement(
8097
                'radio',
8098
                'if_exists',
8099
                null,
8100
                get_lang('Overwrite the existing file'),
8101
                'overwrite'
8102
            ),
8103
            $form->createElement(
8104
                'radio',
8105
                'if_exists',
8106
                null,
8107
                get_lang('Rename the uploaded file if it exists'),
8108
                'rename'
8109
            ),
8110
        ];
8111
        $form->addGroup($group, null, get_lang('If file exists:'));
8112
8113
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8114
        $defaultFileExistsOption = 'rename';
8115
        if (!empty($fileExistsOption)) {
8116
            $defaultFileExistsOption = $fileExistsOption;
8117
        }
8118
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8119
8120
        // Check box options
8121
        $form->addElement(
8122
            'checkbox',
8123
            'unzip',
8124
            get_lang('Options'),
8125
            get_lang('Uncompress zip')
8126
        );
8127
8128
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8129
        $form->addMultipleUpload($url);
8130
        $new = $this->display_document_form('add');
8131
        $frmReadOutText = $this->display_document_form('add');
8132
        $tabs = Display::tabs(
8133
            $headers,
8134
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
8135
            'subtab'
8136
        );
8137
8138
        return $tabs;
8139
    }
8140
8141
    /**
8142
     * Creates a list with all the exercises (quiz) in it.
8143
     *
8144
     * @return string
8145
     */
8146
    public function get_exercises()
8147
    {
8148
        $course_id = api_get_course_int_id();
8149
        $session_id = api_get_session_id();
8150
        $userInfo = api_get_user_info();
8151
8152
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8153
        $condition_session = api_get_session_condition($session_id, true, true);
8154
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8155
8156
        $activeCondition = ' active <> -1 ';
8157
        if ($setting) {
8158
            $activeCondition = ' active = 1 ';
8159
        }
8160
8161
        $categoryCondition = '';
8162
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8163
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8164
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8165
        }
8166
8167
        $keywordCondition = '';
8168
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8169
8170
        if (!empty($keyword)) {
8171
            $keyword = Database::escape_string($keyword);
8172
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8173
        }
8174
8175
        $sql_quiz = "SELECT * FROM $tbl_quiz
8176
                     WHERE
8177
                            c_id = $course_id AND
8178
                            $activeCondition
8179
                            $condition_session
8180
                            $categoryCondition
8181
                            $keywordCondition
8182
                     ORDER BY title ASC";
8183
        $res_quiz = Database::query($sql_quiz);
8184
8185
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8186
8187
        // Create a search-box
8188
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8189
        $form->addHidden('action', 'add_item');
8190
        $form->addHidden('type', 'step');
8191
        $form->addHidden('lp_id', $this->lp_id);
8192
        $form->addHidden('lp_build_selected', '2');
8193
8194
        $form->addCourseHiddenParams();
8195
        $form->addText(
8196
            'keyword',
8197
            get_lang('Search'),
8198
            false,
8199
            [
8200
                'aria-label' => get_lang('Search'),
8201
            ]
8202
        );
8203
8204
        if (api_get_configuration_value('allow_exercise_categories')) {
8205
            $manager = new ExerciseCategoryManager();
8206
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8207
            if (!empty($options)) {
8208
                $form->addSelect(
8209
                    'category_id',
8210
                    get_lang('Category'),
8211
                    $options,
8212
                    ['placeholder' => get_lang('Please select an option')]
8213
                );
8214
            }
8215
        }
8216
8217
        $form->addButtonSearch(get_lang('Search'));
8218
        $return = $form->returnForm();
8219
8220
        $return .= '<ul class="lp_resource">';
8221
        $return .= '<li class="lp_resource_element">';
8222
        $return .= Display::return_icon('new_exercice.png');
8223
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8224
            get_lang('New test').'</a>';
8225
        $return .= '</li>';
8226
8227
        $previewIcon = Display::return_icon(
8228
            'preview_view.png',
8229
            get_lang('Preview')
8230
        );
8231
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8232
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8233
8234
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8235
        $repo = Container::getExerciseRepository();
8236
        $courseEntity = api_get_course_entity();
8237
        $sessionEntity = api_get_session_entity();
8238
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8239
            /** @var CQuiz $exercise */
8240
            $exercise = $repo->find($row_quiz['id']);
8241
            $title = strip_tags(
8242
                api_html_entity_decode($row_quiz['title'])
8243
            );
8244
8245
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8246
            /*$visibility = api_get_item_visibility(
8247
                ['real_id' => $course_id],
8248
                TOOL_QUIZ,
8249
                $row_quiz['iid'],
8250
                $session_id
8251
            );*/
8252
8253
            $link = Display::url(
8254
                $previewIcon,
8255
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
8256
                ['target' => '_blank']
8257
            );
8258
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
8259
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8260
            $return .= $quizIcon;
8261
            $sessionStar = api_get_session_image(
8262
                $row_quiz['session_id'],
8263
                $userInfo['status']
8264
            );
8265
            $return .= Display::url(
8266
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8267
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
8268
                [
8269
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8270
                ]
8271
            );
8272
            $return .= '</li>';
8273
        }
8274
8275
        $return .= '</ul>';
8276
8277
        return $return;
8278
    }
8279
8280
    /**
8281
     * Creates a list with all the links in it.
8282
     *
8283
     * @return string
8284
     */
8285
    public function get_links()
8286
    {
8287
        $sessionId = api_get_session_id();
8288
        $repo = Container::getLinkRepository();
8289
8290
        $course = api_get_course_entity();
8291
        $session = api_get_session_entity($sessionId);
8292
        $qb = $repo->getResourcesByCourse($course, $session);
8293
        /** @var CLink[] $links */
8294
        $links = $qb->getQuery()->getResult();
8295
8296
        $selfUrl = api_get_self();
8297
        $courseIdReq = api_get_cidreq();
8298
        $userInfo = api_get_user_info();
8299
8300
        $moveEverywhereIcon = Display::return_icon(
8301
            'move_everywhere.png',
8302
            get_lang('Move'),
8303
            [],
8304
            ICON_SIZE_TINY
8305
        );
8306
8307
        /*$condition_session = api_get_session_condition(
8308
            $session_id,
8309
            true,
8310
            true,
8311
            'link.session_id'
8312
        );
8313
8314
        $sql = "SELECT
8315
                    link.id as link_id,
8316
                    link.title as link_title,
8317
                    link.session_id as link_session_id,
8318
                    link.category_id as category_id,
8319
                    link_category.category_title as category_title
8320
                FROM $tbl_link as link
8321
                LEFT JOIN $linkCategoryTable as link_category
8322
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8323
                WHERE link.c_id = $course_id $condition_session
8324
                ORDER BY link_category.category_title ASC, link.title ASC";
8325
        $result = Database::query($sql);*/
8326
        $categorizedLinks = [];
8327
        $categories = [];
8328
8329
        foreach ($links as $link) {
8330
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8331
8332
            if (empty($categoryId)) {
8333
                $categories[0] = get_lang('Uncategorized');
8334
            } else {
8335
                $category = $link->getCategory();
8336
                $categories[$categoryId] = $category->getCategoryTitle();
8337
            }
8338
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8339
        }
8340
8341
        $linksHtmlCode =
8342
            '<script>
8343
            function toggle_tool(tool, id) {
8344
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8345
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8346
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8347
                } else {
8348
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8349
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8350
                }
8351
            }
8352
        </script>
8353
8354
        <ul class="lp_resource">
8355
            <li class="lp_resource_element">
8356
                '.Display::return_icon('linksnew.gif').'
8357
                <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').'">'.
8358
                get_lang('Add a link').'
8359
                </a>
8360
            </li>';
8361
8362
        foreach ($categorizedLinks as $categoryId => $links) {
8363
            $linkNodes = null;
8364
            /** @var CLink $link */
8365
            foreach ($links as $key => $link) {
8366
                $title = $link->getTitle();
8367
                $linkSessionId = $link->getSessionId();
8368
8369
                $linkUrl = Display::url(
8370
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8371
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8372
                    ['target' => '_blank']
8373
                );
8374
8375
                if ($link->isVisible($course, $session)) {
8376
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8377
                    $linkNodes .=
8378
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8379
                        <a class="moved" href="#">'.
8380
                            $moveEverywhereIcon.
8381
                        '</a>
8382
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8383
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8384
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8385
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8386
                        '</a>
8387
                    </li>';
8388
                }
8389
            }
8390
            $linksHtmlCode .=
8391
                '<li>
8392
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8393
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8394
                    align="absbottom" />
8395
                </a>
8396
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8397
            </li>
8398
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8399
        }
8400
        $linksHtmlCode .= '</ul>';
8401
8402
        return $linksHtmlCode;
8403
    }
8404
8405
    /**
8406
     * Creates a list with all the student publications in it.
8407
     *
8408
     * @return string
8409
     */
8410
    public function get_student_publications()
8411
    {
8412
        $return = '<ul class="lp_resource">';
8413
        $return .= '<li class="lp_resource_element">';
8414
        /*
8415
        $return .= Display::return_icon('works_new.gif');
8416
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8417
            get_lang('Add the Assignments tool to the course').'</a>';
8418
        $return .= '</li>';*/
8419
8420
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8421
        $works = getWorkListTeacher(0, 100, null, null, null);
8422
        if (!empty($works)) {
8423
            foreach ($works as $work) {
8424
                $link = Display::url(
8425
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8426
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8427
                    ['target' => '_blank']
8428
                );
8429
8430
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8431
                $return .= '<a class="moved" href="#">';
8432
                $return .= Display::return_icon(
8433
                    'move_everywhere.png',
8434
                    get_lang('Move'),
8435
                    [],
8436
                    ICON_SIZE_TINY
8437
                );
8438
                $return .= '</a> ';
8439
8440
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8441
                $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.'">'.
8442
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8443
                </a>';
8444
8445
                $return .= '</li>';
8446
            }
8447
        }
8448
8449
        $return .= '</ul>';
8450
8451
        return $return;
8452
    }
8453
8454
    /**
8455
     * Creates a list with all the forums in it.
8456
     *
8457
     * @return string
8458
     */
8459
    public function get_forums()
8460
    {
8461
        require_once '../forum/forumfunction.inc.php';
8462
8463
        $forumCategories = get_forum_categories();
8464
        $forumsInNoCategory = get_forums_in_category(0);
8465
        if (!empty($forumsInNoCategory)) {
8466
            $forumCategories = array_merge(
8467
                $forumCategories,
8468
                [
8469
                    [
8470
                        'cat_id' => 0,
8471
                        'session_id' => 0,
8472
                        'visibility' => 1,
8473
                        'cat_comment' => null,
8474
                    ],
8475
                ]
8476
            );
8477
        }
8478
8479
        $forumList = get_forums();
8480
        $a_forums = [];
8481
        foreach ($forumCategories as $forumCategory) {
8482
            // The forums in this category.
8483
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8484
            if (!empty($forumsInCategory)) {
8485
                foreach ($forumList as $forum) {
8486
                    $a_forums[] = $forum;
8487
                }
8488
            }
8489
        }
8490
8491
        $return = '<ul class="lp_resource">';
8492
8493
        // First add link
8494
        $return .= '<li class="lp_resource_element">';
8495
        $return .= Display::return_icon('new_forum.png');
8496
        $return .= Display::url(
8497
            get_lang('Create a new forum'),
8498
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8499
                'action' => 'add',
8500
                'content' => 'forum',
8501
                'lp_id' => $this->lp_id,
8502
            ]),
8503
            ['title' => get_lang('Create a new forum')]
8504
        );
8505
        $return .= '</li>';
8506
8507
        $return .= '<script>
8508
            function toggle_forum(forum_id) {
8509
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8510
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8511
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8512
                } else {
8513
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8514
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8515
                }
8516
            }
8517
        </script>';
8518
8519
        foreach ($a_forums as $forum) {
8520
            $forumId = $forum->getIid();
8521
            $title = Security::remove_XSS($forum->getForumTitle());
8522
            $link = Display::url(
8523
                Display::return_icon('preview_view.png', get_lang('Preview')),
8524
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8525
                ['target' => '_blank']
8526
            );
8527
8528
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8529
            $return .= '<a class="moved" href="#">';
8530
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8531
            $return .= ' </a>';
8532
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8533
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8534
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8535
                        </a>
8536
                        <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">'.
8537
                $title.' '.$link.'</a>';
8538
8539
            $return .= '</li>';
8540
8541
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8542
            $a_threads = get_threads($forumId);
8543
            if (is_array($a_threads)) {
8544
                foreach ($a_threads as $thread) {
8545
                    $threadId = $thread->getIid();
8546
                    $link = Display::url(
8547
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8548
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8549
                        ['target' => '_blank']
8550
                    );
8551
8552
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8553
                    $return .= '&nbsp;<a class="moved" href="#">';
8554
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8555
                    $return .= ' </a>';
8556
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8557
                    $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.'">'.
8558
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8559
                    $return .= '</li>';
8560
                }
8561
            }
8562
            $return .= '</div>';
8563
        }
8564
        $return .= '</ul>';
8565
8566
        return $return;
8567
    }
8568
8569
    /**
8570
     * // TODO: The output encoding should be equal to the system encoding.
8571
     *
8572
     * Exports the learning path as a SCORM package. This is the main function that
8573
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8574
     * whole thing and returns the zip.
8575
     *
8576
     * This method needs to be called in PHP5, as it will fail with non-adequate
8577
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8578
     * you need to call it on a learnpath object.
8579
     *
8580
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8581
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8582
     * path has been modified, it should use the generic method here below.
8583
     *
8584
     * @return string Returns the zip package string, or null if error
8585
     */
8586
    public function scormExport()
8587
    {
8588
        api_set_more_memory_and_time_limits();
8589
8590
        $_course = api_get_course_info();
8591
        $course_id = $_course['real_id'];
8592
        // Create the zip handler (this will remain available throughout the method).
8593
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8594
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8595
        $temp_dir_short = uniqid('scorm_export', true);
8596
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8597
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8598
        $zip_folder = new PclZip($temp_zip_file);
8599
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8600
        $root_path = $main_path = api_get_path(SYS_PATH);
8601
        $files_cleanup = [];
8602
8603
        // Place to temporarily stash the zip file.
8604
        // create the temp dir if it doesn't exist
8605
        // or do a cleanup before creating the zip file.
8606
        if (!is_dir($temp_zip_dir)) {
8607
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8608
        } else {
8609
            // Cleanup: Check the temp dir for old files and delete them.
8610
            $handle = opendir($temp_zip_dir);
8611
            while (false !== ($file = readdir($handle))) {
8612
                if ('.' != $file && '..' != $file) {
8613
                    unlink("$temp_zip_dir/$file");
8614
                }
8615
            }
8616
            closedir($handle);
8617
        }
8618
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8619
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8620
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8621
        ) {
8622
            // Remove the possible . at the end of the path.
8623
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8624
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8625
            mkdir(
8626
                $dest_path_to_scorm_folder,
8627
                api_get_permissions_for_new_directories(),
8628
                true
8629
            );
8630
            copyr(
8631
                $current_course_path.'/scorm/'.$this->path,
8632
                $dest_path_to_scorm_folder,
8633
                ['imsmanifest'],
8634
                $zip_files
8635
            );
8636
        }
8637
8638
        // Build a dummy imsmanifest structure.
8639
        // Do not add to the zip yet (we still need it).
8640
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8641
        // Aggregation Model official document, section "2.3 Content Packaging".
8642
        // We are going to build a UTF-8 encoded manifest.
8643
        // Later we will recode it to the desired (and supported) encoding.
8644
        $xmldoc = new DOMDocument('1.0');
8645
        $root = $xmldoc->createElement('manifest');
8646
        $root->setAttribute('identifier', 'SingleCourseManifest');
8647
        $root->setAttribute('version', '1.1');
8648
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8649
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8650
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8651
        $root->setAttribute(
8652
            'xsi:schemaLocation',
8653
            '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'
8654
        );
8655
        // Build mandatory sub-root container elements.
8656
        $metadata = $xmldoc->createElement('metadata');
8657
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8658
        $metadata->appendChild($md_schema);
8659
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8660
        $metadata->appendChild($md_schemaversion);
8661
        $root->appendChild($metadata);
8662
8663
        $organizations = $xmldoc->createElement('organizations');
8664
        $resources = $xmldoc->createElement('resources');
8665
8666
        // Build the only organization we will use in building our learnpaths.
8667
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8668
        $organization = $xmldoc->createElement('organization');
8669
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8670
        // To set the title of the SCORM entity (=organization), we take the name given
8671
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8672
        // learning path charset) as it is the encoding that defines how it is stored
8673
        // in the database. Then we convert it to HTML entities again as the "&" character
8674
        // alone is not authorized in XML (must be &amp;).
8675
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8676
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8677
        $organization->appendChild($org_title);
8678
        $folder_name = 'document';
8679
8680
        // Removes the learning_path/scorm_folder path when exporting see #4841
8681
        $path_to_remove = '';
8682
        $path_to_replace = '';
8683
        $result = $this->generate_lp_folder($_course);
8684
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8685
            $path_to_remove = 'document'.$result['dir'];
8686
            $path_to_replace = $folder_name.'/';
8687
        }
8688
8689
        // Fixes chamilo scorm exports
8690
        if ('chamilo_scorm_export' === $this->ref) {
8691
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8692
        }
8693
8694
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8695
        $link_updates = [];
8696
        $links_to_create = [];
8697
        foreach ($this->ordered_items as $index => $itemId) {
8698
            /** @var learnpathItem $item */
8699
            $item = $this->items[$itemId];
8700
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8701
                // Get included documents from this item.
8702
                if ('sco' === $item->type) {
8703
                    $inc_docs = $item->get_resources_from_source(
8704
                        null,
8705
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8706
                    );
8707
                } else {
8708
                    $inc_docs = $item->get_resources_from_source();
8709
                }
8710
8711
                // Give a child element <item> to the <organization> element.
8712
                $my_item_id = $item->get_id();
8713
                $my_item = $xmldoc->createElement('item');
8714
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8715
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8716
                $my_item->setAttribute('isvisible', 'true');
8717
                // Give a child element <title> to the <item> element.
8718
                $my_title = $xmldoc->createElement(
8719
                    'title',
8720
                    htmlspecialchars(
8721
                        api_utf8_encode($item->get_title()),
8722
                        ENT_QUOTES,
8723
                        'UTF-8'
8724
                    )
8725
                );
8726
                $my_item->appendChild($my_title);
8727
                // Give a child element <adlcp:prerequisites> to the <item> element.
8728
                $my_prereqs = $xmldoc->createElement(
8729
                    'adlcp:prerequisites',
8730
                    $this->get_scorm_prereq_string($my_item_id)
8731
                );
8732
                $my_prereqs->setAttribute('type', 'aicc_script');
8733
                $my_item->appendChild($my_prereqs);
8734
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8735
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8736
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8737
                //$xmldoc->createElement('adlcp:timelimitaction','');
8738
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8739
                //$xmldoc->createElement('adlcp:datafromlms','');
8740
                // Give a child element <adlcp:masteryscore> to the <item> element.
8741
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8742
                $my_item->appendChild($my_masteryscore);
8743
8744
                // Attach this item to the organization element or hits parent if there is one.
8745
                if (!empty($item->parent) && 0 != $item->parent) {
8746
                    $children = $organization->childNodes;
8747
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8748
                    if (is_object($possible_parent)) {
8749
                        $possible_parent->appendChild($my_item);
8750
                    } else {
8751
                        if ($this->debug > 0) {
8752
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8753
                        }
8754
                    }
8755
                } else {
8756
                    if ($this->debug > 0) {
8757
                        error_log('No parent');
8758
                    }
8759
                    $organization->appendChild($my_item);
8760
                }
8761
8762
                // Get the path of the file(s) from the course directory root.
8763
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8764
                $my_xml_file_path = $my_file_path;
8765
                if (!empty($path_to_remove)) {
8766
                    // From docs
8767
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8768
8769
                    // From quiz
8770
                    if ('chamilo_scorm_export' === $this->ref) {
8771
                        $path_to_remove = 'scorm/'.$this->path.'/';
8772
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8773
                    }
8774
                }
8775
8776
                $my_sub_dir = dirname($my_file_path);
8777
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8778
                $my_xml_sub_dir = $my_sub_dir;
8779
                // Give a <resource> child to the <resources> element
8780
                $my_resource = $xmldoc->createElement('resource');
8781
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8782
                $my_resource->setAttribute('type', 'webcontent');
8783
                $my_resource->setAttribute('href', $my_xml_file_path);
8784
                // adlcp:scormtype can be either 'sco' or 'asset'.
8785
                if ('sco' === $item->type) {
8786
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8787
                } else {
8788
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8789
                }
8790
                // xml:base is the base directory to find the files declared in this resource.
8791
                $my_resource->setAttribute('xml:base', '');
8792
                // Give a <file> child to the <resource> element.
8793
                $my_file = $xmldoc->createElement('file');
8794
                $my_file->setAttribute('href', $my_xml_file_path);
8795
                $my_resource->appendChild($my_file);
8796
8797
                // Dependency to other files - not yet supported.
8798
                $i = 1;
8799
                if ($inc_docs) {
8800
                    foreach ($inc_docs as $doc_info) {
8801
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8802
                            continue;
8803
                        }
8804
                        $my_dep = $xmldoc->createElement('resource');
8805
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8806
                        $my_dep->setAttribute('identifier', $res_id);
8807
                        $my_dep->setAttribute('type', 'webcontent');
8808
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8809
                        $my_dep_file = $xmldoc->createElement('file');
8810
                        // Check type of URL.
8811
                        if ('remote' == $doc_info[1]) {
8812
                            // Remote file. Save url as is.
8813
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8814
                            $my_dep->setAttribute('xml:base', '');
8815
                        } elseif ('local' === $doc_info[1]) {
8816
                            switch ($doc_info[2]) {
8817
                                case 'url':
8818
                                    // Local URL - save path as url for now, don't zip file.
8819
                                    $abs_path = api_get_path(SYS_PATH).
8820
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8821
                                    $current_dir = dirname($abs_path);
8822
                                    $current_dir = str_replace('\\', '/', $current_dir);
8823
                                    $file_path = realpath($abs_path);
8824
                                    $file_path = str_replace('\\', '/', $file_path);
8825
                                    $my_dep_file->setAttribute('href', $file_path);
8826
                                    $my_dep->setAttribute('xml:base', '');
8827
                                    if (false !== strstr($file_path, $main_path)) {
8828
                                        // The calculated real path is really inside Chamilo's root path.
8829
                                        // Reduce file path to what's under the DocumentRoot.
8830
                                        $replace = $file_path;
8831
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8832
                                        $destinationFile = $file_path;
8833
8834
                                        if (false !== strstr($file_path, 'upload/users')) {
8835
                                            $pos = strpos($file_path, 'my_files/');
8836
                                            if (false !== $pos) {
8837
                                                $onlyDirectory = str_replace(
8838
                                                    'upload/users/',
8839
                                                    '',
8840
                                                    substr($file_path, $pos, strlen($file_path))
8841
                                                );
8842
                                            }
8843
                                            $replace = $onlyDirectory;
8844
                                            $destinationFile = $replace;
8845
                                        }
8846
                                        $zip_files_abs[] = $file_path;
8847
                                        $link_updates[$my_file_path][] = [
8848
                                            'orig' => $doc_info[0],
8849
                                            'dest' => $destinationFile,
8850
                                            'replace' => $replace,
8851
                                        ];
8852
                                        $my_dep_file->setAttribute('href', $file_path);
8853
                                        $my_dep->setAttribute('xml:base', '');
8854
                                    } elseif (empty($file_path)) {
8855
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8856
                                        $file_path = str_replace('//', '/', $file_path);
8857
                                        if (file_exists($file_path)) {
8858
                                            // We get the relative path.
8859
                                            $file_path = substr($file_path, strlen($current_dir));
8860
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8861
                                            $link_updates[$my_file_path][] = [
8862
                                                'orig' => $doc_info[0],
8863
                                                'dest' => $file_path,
8864
                                            ];
8865
                                            $my_dep_file->setAttribute('href', $file_path);
8866
                                            $my_dep->setAttribute('xml:base', '');
8867
                                        }
8868
                                    }
8869
                                    break;
8870
                                case 'abs':
8871
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8872
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8873
                                    $my_dep->setAttribute('xml:base', '');
8874
8875
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8876
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8877
                                    $abs_img_path_without_subdir = $doc_info[0];
8878
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8879
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8880
                                    if (0 === $pos) {
8881
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8882
                                    }
8883
8884
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8885
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8886
8887
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8888
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8889
                                    // Check if the current document is in that path.
8890
                                    if (false !== strstr($file_path, $cur_path)) {
8891
                                        $destinationFile = substr($file_path, strlen($cur_path));
8892
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8893
8894
                                        $fileToTest = $cur_path.$my_file_path;
8895
                                        if (!empty($path_to_remove)) {
8896
                                            $fileToTest = str_replace(
8897
                                                $path_to_remove.'/',
8898
                                                $path_to_replace,
8899
                                                $cur_path.$my_file_path
8900
                                            );
8901
                                        }
8902
8903
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8904
8905
                                        // Put the current document in the zip (this array is the array
8906
                                        // that will manage documents already in the course folder - relative).
8907
                                        $zip_files[] = $filePathNoCoursePart;
8908
                                        // Update the links to the current document in the
8909
                                        // containing document (make them relative).
8910
                                        $link_updates[$my_file_path][] = [
8911
                                            'orig' => $doc_info[0],
8912
                                            'dest' => $destinationFile,
8913
                                            'replace' => $relative_path,
8914
                                        ];
8915
8916
                                        $my_dep_file->setAttribute('href', $file_path);
8917
                                        $my_dep->setAttribute('xml:base', '');
8918
                                    } elseif (false !== strstr($file_path, $main_path)) {
8919
                                        // The calculated real path is really inside Chamilo's root path.
8920
                                        // Reduce file path to what's under the DocumentRoot.
8921
                                        $file_path = substr($file_path, strlen($root_path));
8922
                                        $zip_files_abs[] = $file_path;
8923
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8924
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8925
                                        $my_dep->setAttribute('xml:base', '');
8926
                                    } elseif (empty($file_path)) {
8927
                                        // Probably this is an image inside "/main" directory
8928
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8929
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8930
8931
                                        if (file_exists($file_path)) {
8932
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8933
                                                // We get the relative path.
8934
                                                $pos = strpos($file_path, 'main/default_course_document/');
8935
                                                if (false !== $pos) {
8936
                                                    $onlyDirectory = str_replace(
8937
                                                        'main/default_course_document/',
8938
                                                        '',
8939
                                                        substr($file_path, $pos, strlen($file_path))
8940
                                                    );
8941
                                                }
8942
8943
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8944
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8945
                                                $zip_files_abs[] = $fileAbs;
8946
                                                $link_updates[$my_file_path][] = [
8947
                                                    'orig' => $doc_info[0],
8948
                                                    'dest' => $destinationFile,
8949
                                                ];
8950
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8951
                                                $my_dep->setAttribute('xml:base', '');
8952
                                            }
8953
                                        }
8954
                                    }
8955
                                    break;
8956
                                case 'rel':
8957
                                    // Path relative to the current document.
8958
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8959
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8960
                                        // Relative path going up.
8961
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8962
                                        $current_dir = str_replace('\\', '/', $current_dir);
8963
                                        $file_path = realpath($current_dir.$doc_info[0]);
8964
                                        $file_path = str_replace('\\', '/', $file_path);
8965
                                        if (false !== strstr($file_path, $main_path)) {
8966
                                            // The calculated real path is really inside Chamilo's root path.
8967
                                            // Reduce file path to what's under the DocumentRoot.
8968
                                            $file_path = substr($file_path, strlen($root_path));
8969
                                            $zip_files_abs[] = $file_path;
8970
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8971
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8972
                                            $my_dep->setAttribute('xml:base', '');
8973
                                        }
8974
                                    } else {
8975
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8976
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8977
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8978
                                    }
8979
                                    break;
8980
                                default:
8981
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8982
                                    $my_dep->setAttribute('xml:base', '');
8983
                                    break;
8984
                            }
8985
                        }
8986
                        $my_dep->appendChild($my_dep_file);
8987
                        $resources->appendChild($my_dep);
8988
                        $dependency = $xmldoc->createElement('dependency');
8989
                        $dependency->setAttribute('identifierref', $res_id);
8990
                        $my_resource->appendChild($dependency);
8991
                        $i++;
8992
                    }
8993
                }
8994
                $resources->appendChild($my_resource);
8995
                $zip_files[] = $my_file_path;
8996
            } else {
8997
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8998
                switch ($item->type) {
8999
                    case TOOL_LINK:
9000
                        $my_item = $xmldoc->createElement('item');
9001
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9002
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9003
                        $my_item->setAttribute('isvisible', 'true');
9004
                        // Give a child element <title> to the <item> element.
9005
                        $my_title = $xmldoc->createElement(
9006
                            'title',
9007
                            htmlspecialchars(
9008
                                api_utf8_encode($item->get_title()),
9009
                                ENT_QUOTES,
9010
                                'UTF-8'
9011
                            )
9012
                        );
9013
                        $my_item->appendChild($my_title);
9014
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9015
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9016
                        $my_prereqs->setAttribute('type', 'aicc_script');
9017
                        $my_item->appendChild($my_prereqs);
9018
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9019
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9020
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9021
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9022
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9023
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9024
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9025
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9026
                        $my_item->appendChild($my_masteryscore);
9027
9028
                        // Attach this item to the organization element or its parent if there is one.
9029
                        if (!empty($item->parent) && 0 != $item->parent) {
9030
                            $children = $organization->childNodes;
9031
                            for ($i = 0; $i < $children->length; $i++) {
9032
                                $item_temp = $children->item($i);
9033
                                if ('item' == $item_temp->nodeName) {
9034
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9035
                                        $item_temp->appendChild($my_item);
9036
                                    }
9037
                                }
9038
                            }
9039
                        } else {
9040
                            $organization->appendChild($my_item);
9041
                        }
9042
9043
                        $my_file_path = 'link_'.$item->get_id().'.html';
9044
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9045
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9046
                        $rs = Database::query($sql);
9047
                        if ($link = Database::fetch_array($rs)) {
9048
                            $url = $link['url'];
9049
                            $title = stripslashes($link['title']);
9050
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9051
                            $my_xml_file_path = $my_file_path;
9052
                            $my_sub_dir = dirname($my_file_path);
9053
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9054
                            $my_xml_sub_dir = $my_sub_dir;
9055
                            // Give a <resource> child to the <resources> element.
9056
                            $my_resource = $xmldoc->createElement('resource');
9057
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9058
                            $my_resource->setAttribute('type', 'webcontent');
9059
                            $my_resource->setAttribute('href', $my_xml_file_path);
9060
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9061
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9062
                            // xml:base is the base directory to find the files declared in this resource.
9063
                            $my_resource->setAttribute('xml:base', '');
9064
                            // give a <file> child to the <resource> element.
9065
                            $my_file = $xmldoc->createElement('file');
9066
                            $my_file->setAttribute('href', $my_xml_file_path);
9067
                            $my_resource->appendChild($my_file);
9068
                            $resources->appendChild($my_resource);
9069
                        }
9070
                        break;
9071
                    case TOOL_QUIZ:
9072
                        $exe_id = $item->path;
9073
                        // Should be using ref when everything will be cleaned up in this regard.
9074
                        $exe = new Exercise();
9075
                        $exe->read($exe_id);
9076
                        $my_item = $xmldoc->createElement('item');
9077
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9078
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9079
                        $my_item->setAttribute('isvisible', 'true');
9080
                        // Give a child element <title> to the <item> element.
9081
                        $my_title = $xmldoc->createElement(
9082
                            'title',
9083
                            htmlspecialchars(
9084
                                api_utf8_encode($item->get_title()),
9085
                                ENT_QUOTES,
9086
                                'UTF-8'
9087
                            )
9088
                        );
9089
                        $my_item->appendChild($my_title);
9090
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9091
                        $my_item->appendChild($my_max_score);
9092
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9093
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9094
                        $my_prereqs->setAttribute('type', 'aicc_script');
9095
                        $my_item->appendChild($my_prereqs);
9096
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9097
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9098
                        $my_item->appendChild($my_masteryscore);
9099
9100
                        // Attach this item to the organization element or hits parent if there is one.
9101
                        if (!empty($item->parent) && 0 != $item->parent) {
9102
                            $children = $organization->childNodes;
9103
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9104
                            if ($possible_parent) {
9105
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9106
                                    $possible_parent->appendChild($my_item);
9107
                                }
9108
                            }
9109
                        } else {
9110
                            $organization->appendChild($my_item);
9111
                        }
9112
9113
                        // Get the path of the file(s) from the course directory root
9114
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9115
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9116
                        // Write the contents of the exported exercise into a (big) html file
9117
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9118
                        $scormExercise = new ScormExercise($exe, true);
9119
                        $contents = $scormExercise->export();
9120
9121
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9122
                        $res = file_put_contents($tmp_file_path, $contents);
9123
                        if (false === $res) {
9124
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9125
                        }
9126
                        $files_cleanup[] = $tmp_file_path;
9127
                        $my_xml_file_path = $my_file_path;
9128
                        $my_sub_dir = dirname($my_file_path);
9129
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9130
                        $my_xml_sub_dir = $my_sub_dir;
9131
                        // Give a <resource> child to the <resources> element.
9132
                        $my_resource = $xmldoc->createElement('resource');
9133
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9134
                        $my_resource->setAttribute('type', 'webcontent');
9135
                        $my_resource->setAttribute('href', $my_xml_file_path);
9136
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9137
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9138
                        // xml:base is the base directory to find the files declared in this resource.
9139
                        $my_resource->setAttribute('xml:base', '');
9140
                        // Give a <file> child to the <resource> element.
9141
                        $my_file = $xmldoc->createElement('file');
9142
                        $my_file->setAttribute('href', $my_xml_file_path);
9143
                        $my_resource->appendChild($my_file);
9144
9145
                        // Get included docs.
9146
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9147
9148
                        // Dependency to other files - not yet supported.
9149
                        $i = 1;
9150
                        foreach ($inc_docs as $doc_info) {
9151
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9152
                                continue;
9153
                            }
9154
                            $my_dep = $xmldoc->createElement('resource');
9155
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9156
                            $my_dep->setAttribute('identifier', $res_id);
9157
                            $my_dep->setAttribute('type', 'webcontent');
9158
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9159
                            $my_dep_file = $xmldoc->createElement('file');
9160
                            // Check type of URL.
9161
                            if ('remote' == $doc_info[1]) {
9162
                                // Remote file. Save url as is.
9163
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9164
                                $my_dep->setAttribute('xml:base', '');
9165
                            } elseif ('local' == $doc_info[1]) {
9166
                                switch ($doc_info[2]) {
9167
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9168
                                        // Save file but as local file (retrieve from URL).
9169
                                        $abs_path = api_get_path(SYS_PATH).
9170
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9171
                                        $current_dir = dirname($abs_path);
9172
                                        $current_dir = str_replace('\\', '/', $current_dir);
9173
                                        $file_path = realpath($abs_path);
9174
                                        $file_path = str_replace('\\', '/', $file_path);
9175
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9176
                                        $my_dep->setAttribute('xml:base', '');
9177
                                        if (false !== strstr($file_path, $main_path)) {
9178
                                            // The calculated real path is really inside the chamilo root path.
9179
                                            // Reduce file path to what's under the DocumentRoot.
9180
                                            $file_path = substr($file_path, strlen($root_path));
9181
                                            $zip_files_abs[] = $file_path;
9182
                                            $link_updates[$my_file_path][] = [
9183
                                                'orig' => $doc_info[0],
9184
                                                'dest' => 'document/'.$file_path,
9185
                                            ];
9186
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9187
                                            $my_dep->setAttribute('xml:base', '');
9188
                                        } elseif (empty($file_path)) {
9189
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9190
                                            $file_path = str_replace('//', '/', $file_path);
9191
                                            if (file_exists($file_path)) {
9192
                                                $file_path = substr($file_path, strlen($current_dir));
9193
                                                // We get the relative path.
9194
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9195
                                                $link_updates[$my_file_path][] = [
9196
                                                    'orig' => $doc_info[0],
9197
                                                    'dest' => 'document/'.$file_path,
9198
                                                ];
9199
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9200
                                                $my_dep->setAttribute('xml:base', '');
9201
                                            }
9202
                                        }
9203
                                        break;
9204
                                    case 'abs':
9205
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9206
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9207
                                        $current_dir = str_replace('\\', '/', $current_dir);
9208
                                        $file_path = realpath($doc_info[0]);
9209
                                        $file_path = str_replace('\\', '/', $file_path);
9210
                                        $my_dep_file->setAttribute('href', $file_path);
9211
                                        $my_dep->setAttribute('xml:base', '');
9212
9213
                                        if (false !== strstr($file_path, $main_path)) {
9214
                                            // The calculated real path is really inside the chamilo root path.
9215
                                            // Reduce file path to what's under the DocumentRoot.
9216
                                            $file_path = substr($file_path, strlen($root_path));
9217
                                            $zip_files_abs[] = $file_path;
9218
                                            $link_updates[$my_file_path][] = [
9219
                                                'orig' => $doc_info[0],
9220
                                                'dest' => $file_path,
9221
                                            ];
9222
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9223
                                            $my_dep->setAttribute('xml:base', '');
9224
                                        } elseif (empty($file_path)) {
9225
                                            $docSysPartPath = str_replace(
9226
                                                api_get_path(REL_COURSE_PATH),
9227
                                                '',
9228
                                                $doc_info[0]
9229
                                            );
9230
9231
                                            $docSysPartPathNoCourseCode = str_replace(
9232
                                                $_course['directory'].'/',
9233
                                                '',
9234
                                                $docSysPartPath
9235
                                            );
9236
9237
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9238
                                            if (file_exists($docSysPath)) {
9239
                                                $file_path = $docSysPartPathNoCourseCode;
9240
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9241
                                                $link_updates[$my_file_path][] = [
9242
                                                    'orig' => $doc_info[0],
9243
                                                    'dest' => $file_path,
9244
                                                ];
9245
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9246
                                                $my_dep->setAttribute('xml:base', '');
9247
                                            }
9248
                                        }
9249
                                        break;
9250
                                    case 'rel':
9251
                                        // Path relative to the current document. Save xml:base as current document's
9252
                                        // directory and save file in zip as subdir.file_path
9253
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9254
                                            // Relative path going up.
9255
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9256
                                            $current_dir = str_replace('\\', '/', $current_dir);
9257
                                            $file_path = realpath($current_dir.$doc_info[0]);
9258
                                            $file_path = str_replace('\\', '/', $file_path);
9259
                                            if (false !== strstr($file_path, $main_path)) {
9260
                                                // The calculated real path is really inside Chamilo's root path.
9261
                                                // Reduce file path to what's under the DocumentRoot.
9262
9263
                                                $file_path = substr($file_path, strlen($root_path));
9264
                                                $file_path_dest = $file_path;
9265
9266
                                                // File path is courses/CHAMILO/document/....
9267
                                                $info_file_path = explode('/', $file_path);
9268
                                                if ('courses' == $info_file_path[0]) {
9269
                                                    // Add character "/" in file path.
9270
                                                    $file_path_dest = 'document/'.$file_path;
9271
                                                }
9272
                                                $zip_files_abs[] = $file_path;
9273
9274
                                                $link_updates[$my_file_path][] = [
9275
                                                    'orig' => $doc_info[0],
9276
                                                    'dest' => $file_path_dest,
9277
                                                ];
9278
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9279
                                                $my_dep->setAttribute('xml:base', '');
9280
                                            }
9281
                                        } else {
9282
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9283
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9284
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9285
                                        }
9286
                                        break;
9287
                                    default:
9288
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9289
                                        $my_dep->setAttribute('xml:base', '');
9290
                                        break;
9291
                                }
9292
                            }
9293
                            $my_dep->appendChild($my_dep_file);
9294
                            $resources->appendChild($my_dep);
9295
                            $dependency = $xmldoc->createElement('dependency');
9296
                            $dependency->setAttribute('identifierref', $res_id);
9297
                            $my_resource->appendChild($dependency);
9298
                            $i++;
9299
                        }
9300
                        $resources->appendChild($my_resource);
9301
                        $zip_files[] = $my_file_path;
9302
                        break;
9303
                    default:
9304
                        // Get the path of the file(s) from the course directory root
9305
                        $my_file_path = 'non_exportable.html';
9306
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9307
                        $my_xml_file_path = $my_file_path;
9308
                        $my_sub_dir = dirname($my_file_path);
9309
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9310
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9311
                        $my_xml_sub_dir = $my_sub_dir;
9312
                        // Give a <resource> child to the <resources> element.
9313
                        $my_resource = $xmldoc->createElement('resource');
9314
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9315
                        $my_resource->setAttribute('type', 'webcontent');
9316
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9317
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9318
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9319
                        // xml:base is the base directory to find the files declared in this resource.
9320
                        $my_resource->setAttribute('xml:base', '');
9321
                        // Give a <file> child to the <resource> element.
9322
                        $my_file = $xmldoc->createElement('file');
9323
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9324
                        $my_resource->appendChild($my_file);
9325
                        $resources->appendChild($my_resource);
9326
                        break;
9327
                }
9328
            }
9329
        }
9330
        $organizations->appendChild($organization);
9331
        $root->appendChild($organizations);
9332
        $root->appendChild($resources);
9333
        $xmldoc->appendChild($root);
9334
9335
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9336
9337
        // then add the file to the zip, then destroy the file (this is done automatically).
9338
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9339
        foreach ($zip_files as $file_path) {
9340
            if (empty($file_path)) {
9341
                continue;
9342
            }
9343
9344
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9345
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9346
9347
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9348
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9349
            }
9350
9351
            $this->create_path($dest_file);
9352
            @copy($filePath, $dest_file);
9353
9354
            // Check if the file needs a link update.
9355
            if (in_array($file_path, array_keys($link_updates))) {
9356
                $string = file_get_contents($dest_file);
9357
                unlink($dest_file);
9358
                foreach ($link_updates[$file_path] as $old_new) {
9359
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9360
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9361
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9362
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9363
                    if ('flv' === substr($old_new['dest'], -3) &&
9364
                        'main/' === substr($old_new['dest'], 0, 5)
9365
                    ) {
9366
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9367
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9368
                        'video/' === substr($old_new['dest'], 0, 6)
9369
                    ) {
9370
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9371
                    }
9372
9373
                    // Fix to avoid problems with default_course_document
9374
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9375
                        $newDestination = $old_new['dest'];
9376
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9377
                            $newDestination = $old_new['replace'];
9378
                        }
9379
                    } else {
9380
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9381
                    }
9382
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9383
9384
                    // Add files inside the HTMLs
9385
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9386
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9387
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9388
                        copy(
9389
                            $sys_course_path.$new_path,
9390
                            $destinationFile
9391
                        );
9392
                    }
9393
                }
9394
                file_put_contents($dest_file, $string);
9395
            }
9396
9397
            if (file_exists($filePath) && $copyAll) {
9398
                $extension = $this->get_extension($filePath);
9399
                if (in_array($extension, ['html', 'html'])) {
9400
                    $containerOrigin = dirname($filePath);
9401
                    $containerDestination = dirname($dest_file);
9402
9403
                    $finder = new Finder();
9404
                    $finder->files()->in($containerOrigin)
9405
                        ->notName('*_DELETED_*')
9406
                        ->exclude('share_folder')
9407
                        ->exclude('chat_files')
9408
                        ->exclude('certificates')
9409
                    ;
9410
9411
                    if (is_dir($containerOrigin) &&
9412
                        is_dir($containerDestination)
9413
                    ) {
9414
                        $fs = new Filesystem();
9415
                        $fs->mirror(
9416
                            $containerOrigin,
9417
                            $containerDestination,
9418
                            $finder
9419
                        );
9420
                    }
9421
                }
9422
            }
9423
        }
9424
9425
        foreach ($zip_files_abs as $file_path) {
9426
            if (empty($file_path)) {
9427
                continue;
9428
            }
9429
9430
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9431
                continue;
9432
            }
9433
9434
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9435
            if (false !== strstr($file_path, 'upload/users')) {
9436
                $pos = strpos($file_path, 'my_files/');
9437
                if (false !== $pos) {
9438
                    $onlyDirectory = str_replace(
9439
                        'upload/users/',
9440
                        '',
9441
                        substr($file_path, $pos, strlen($file_path))
9442
                    );
9443
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9444
                }
9445
            }
9446
9447
            if (false !== strstr($file_path, 'default_course_document/')) {
9448
                $replace = str_replace('/main', '', $file_path);
9449
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9450
            }
9451
9452
            if (empty($dest_file)) {
9453
                continue;
9454
            }
9455
9456
            $this->create_path($dest_file);
9457
            copy($main_path.$file_path, $dest_file);
9458
            // Check if the file needs a link update.
9459
            if (in_array($file_path, array_keys($link_updates))) {
9460
                $string = file_get_contents($dest_file);
9461
                unlink($dest_file);
9462
                foreach ($link_updates[$file_path] as $old_new) {
9463
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9464
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9465
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9466
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9467
                    if ('flv' == substr($old_new['dest'], -3) &&
9468
                        'main/' == substr($old_new['dest'], 0, 5)
9469
                    ) {
9470
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9471
                    }
9472
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9473
                }
9474
                file_put_contents($dest_file, $string);
9475
            }
9476
        }
9477
9478
        if (is_array($links_to_create)) {
9479
            foreach ($links_to_create as $file => $link) {
9480
                $content = '<!DOCTYPE html><head>
9481
                            <meta charset="'.api_get_language_isocode().'" />
9482
                            <title>'.$link['title'].'</title>
9483
                            </head>
9484
                            <body dir="'.api_get_text_direction().'">
9485
                            <div style="text-align:center">
9486
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9487
                            </body>
9488
                            </html>';
9489
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9490
            }
9491
        }
9492
9493
        // Add non exportable message explanation.
9494
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9495
        $file_content = '<!DOCTYPE html><head>
9496
                        <meta charset="'.api_get_language_isocode().'" />
9497
                        <title>'.$lang_not_exportable.'</title>
9498
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9499
                        </head>
9500
                        <body dir="'.api_get_text_direction().'">';
9501
        $file_content .=
9502
            <<<EOD
9503
                    <style>
9504
            .error-message {
9505
                font-family: arial, verdana, helvetica, sans-serif;
9506
                border-width: 1px;
9507
                border-style: solid;
9508
                left: 50%;
9509
                margin: 10px auto;
9510
                min-height: 30px;
9511
                padding: 5px;
9512
                right: 50%;
9513
                width: 500px;
9514
                background-color: #FFD1D1;
9515
                border-color: #FF0000;
9516
                color: #000;
9517
            }
9518
        </style>
9519
    <body>
9520
        <div class="error-message">
9521
            $lang_not_exportable
9522
        </div>
9523
    </body>
9524
</html>
9525
EOD;
9526
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9527
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9528
        }
9529
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9530
9531
        // Add the extra files that go along with a SCORM package.
9532
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9533
9534
        $fs = new Filesystem();
9535
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9536
9537
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9538
        $manifest = @$xmldoc->saveXML();
9539
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9540
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9541
        $zip_folder->add(
9542
            $archivePath.'/'.$temp_dir_short,
9543
            PCLZIP_OPT_REMOVE_PATH,
9544
            $archivePath.'/'.$temp_dir_short.'/'
9545
        );
9546
9547
        // Clean possible temporary files.
9548
        foreach ($files_cleanup as $file) {
9549
            $res = unlink($file);
9550
            if (false === $res) {
9551
                error_log(
9552
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9553
                    0
9554
                );
9555
            }
9556
        }
9557
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9558
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9559
    }
9560
9561
    /**
9562
     * @param int $lp_id
9563
     *
9564
     * @return bool
9565
     */
9566
    public function scorm_export_to_pdf($lp_id)
9567
    {
9568
        $lp_id = (int) $lp_id;
9569
        $files_to_export = [];
9570
9571
        $sessionId = api_get_session_id();
9572
        $course_data = api_get_course_info($this->cc);
9573
9574
        if (!empty($course_data)) {
9575
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9576
            $list = self::get_flat_ordered_items_list($lp_id);
9577
            if (!empty($list)) {
9578
                foreach ($list as $item_id) {
9579
                    $item = $this->items[$item_id];
9580
                    switch ($item->type) {
9581
                        case 'document':
9582
                            // Getting documents from a LP with chamilo documents
9583
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9584
                            // Try loading document from the base course.
9585
                            if (empty($file_data) && !empty($sessionId)) {
9586
                                $file_data = DocumentManager::get_document_data_by_id(
9587
                                    $item->path,
9588
                                    $this->cc,
9589
                                    false,
9590
                                    0
9591
                                );
9592
                            }
9593
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9594
                            if (file_exists($file_path)) {
9595
                                $files_to_export[] = [
9596
                                    'title' => $item->get_title(),
9597
                                    'path' => $file_path,
9598
                                ];
9599
                            }
9600
                            break;
9601
                        case 'asset': //commes from a scorm package generated by chamilo
9602
                        case 'sco':
9603
                            $file_path = $scorm_path.'/'.$item->path;
9604
                            if (file_exists($file_path)) {
9605
                                $files_to_export[] = [
9606
                                    'title' => $item->get_title(),
9607
                                    'path' => $file_path,
9608
                                ];
9609
                            }
9610
                            break;
9611
                        case 'dir':
9612
                            $files_to_export[] = [
9613
                                'title' => $item->get_title(),
9614
                                'path' => null,
9615
                            ];
9616
                            break;
9617
                    }
9618
                }
9619
            }
9620
9621
            $pdf = new PDF();
9622
            $result = $pdf->html_to_pdf(
9623
                $files_to_export,
9624
                $this->name,
9625
                $this->cc,
9626
                true,
9627
                true,
9628
                true,
9629
                $this->get_name()
9630
            );
9631
9632
            return $result;
9633
        }
9634
9635
        return false;
9636
    }
9637
9638
    /**
9639
     * Temp function to be moved in main_api or the best place around for this.
9640
     * Creates a file path if it doesn't exist.
9641
     *
9642
     * @param string $path
9643
     */
9644
    public function create_path($path)
9645
    {
9646
        $path_bits = explode('/', dirname($path));
9647
9648
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9649
        $path_built = IS_WINDOWS_OS ? '' : '/';
9650
        foreach ($path_bits as $bit) {
9651
            if (!empty($bit)) {
9652
                $new_path = $path_built.$bit;
9653
                if (is_dir($new_path)) {
9654
                    $path_built = $new_path.'/';
9655
                } else {
9656
                    mkdir($new_path, api_get_permissions_for_new_directories());
9657
                    $path_built = $new_path.'/';
9658
                }
9659
            }
9660
        }
9661
    }
9662
9663
    /**
9664
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
9665
     *
9666
     * @return bool The results of the unlink function, or false if there was no image to start with
9667
     */
9668
    public function delete_lp_image()
9669
    {
9670
        $img = $this->get_preview_image();
9671
        if ('' != $img) {
9672
            $del_file = $this->get_preview_image_path(null, 'sys');
9673
            if (isset($del_file) && file_exists($del_file)) {
9674
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
9675
                if (file_exists($del_file_2)) {
9676
                    unlink($del_file_2);
9677
                }
9678
                $this->set_preview_image('');
9679
9680
                return @unlink($del_file);
9681
            }
9682
        }
9683
9684
        return false;
9685
    }
9686
9687
    /**
9688
     * Uploads an author image to the upload/learning_path/images directory.
9689
     *
9690
     * @param array    The image array, coming from the $_FILES superglobal
9691
     *
9692
     * @return bool True on success, false on error
9693
     */
9694
    public function upload_image($image_array)
9695
    {
9696
        if (!empty($image_array['name'])) {
9697
            $upload_ok = process_uploaded_file($image_array);
9698
            $has_attachment = true;
9699
        }
9700
9701
        if ($upload_ok && $has_attachment) {
9702
            $courseDir = api_get_course_path().'/upload/learning_path/images';
9703
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
9704
            $updir = $sys_course_path.$courseDir;
9705
            // Try to add an extension to the file if it hasn't one.
9706
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
9707
9708
            if (filter_extension($new_file_name)) {
9709
                $file_extension = explode('.', $image_array['name']);
9710
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
9711
                $filename = uniqid('');
9712
                $new_file_name = $filename.'.'.$file_extension;
9713
                $new_path = $updir.'/'.$new_file_name;
9714
9715
                // Resize the image.
9716
                $temp = new Image($image_array['tmp_name']);
9717
                $temp->resize(104);
9718
                $result = $temp->send_image($new_path);
9719
9720
                // Storing the image filename.
9721
                if ($result) {
9722
                    $this->set_preview_image($new_file_name);
9723
9724
                    //Resize to 64px to use on course homepage
9725
                    $temp->resize(64);
9726
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
9727
9728
                    return true;
9729
                }
9730
            }
9731
        }
9732
9733
        return false;
9734
    }
9735
9736
    /**
9737
     * @param int    $lp_id
9738
     * @param string $status
9739
     */
9740
    public function set_autolaunch($lp_id, $status)
9741
    {
9742
        $course_id = api_get_course_int_id();
9743
        $lp_id = (int) $lp_id;
9744
        $status = (int) $status;
9745
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9746
9747
        // Setting everything to autolaunch = 0
9748
        $attributes['autolaunch'] = 0;
9749
        $where = [
9750
            'session_id = ? AND c_id = ? ' => [
9751
                api_get_session_id(),
9752
                $course_id,
9753
            ],
9754
        ];
9755
        Database::update($lp_table, $attributes, $where);
9756
        if (1 == $status) {
9757
            //Setting my lp_id to autolaunch = 1
9758
            $attributes['autolaunch'] = 1;
9759
            $where = [
9760
                'iid = ? AND session_id = ? AND c_id = ?' => [
9761
                    $lp_id,
9762
                    api_get_session_id(),
9763
                    $course_id,
9764
                ],
9765
            ];
9766
            Database::update($lp_table, $attributes, $where);
9767
        }
9768
    }
9769
9770
    /**
9771
     * Gets previous_item_id for the next element of the lp_item table.
9772
     *
9773
     * @author Isaac flores paz
9774
     *
9775
     * @return int Previous item ID
9776
     */
9777
    public function select_previous_item_id()
9778
    {
9779
        $course_id = api_get_course_int_id();
9780
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9781
9782
        // Get the max order of the items
9783
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9784
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9785
        $rs_max_order = Database::query($sql);
9786
        $row_max_order = Database::fetch_object($rs_max_order);
9787
        $max_order = $row_max_order->display_order;
9788
        // Get the previous item ID
9789
        $sql = "SELECT iid as previous FROM $table_lp_item
9790
                WHERE
9791
                    c_id = $course_id AND
9792
                    lp_id = ".$this->lp_id." AND
9793
                    display_order = '$max_order' ";
9794
        $rs_max = Database::query($sql);
9795
        $row_max = Database::fetch_object($rs_max);
9796
9797
        // Return the previous item ID
9798
        return $row_max->previous;
9799
    }
9800
9801
    /**
9802
     * Copies an LP.
9803
     */
9804
    public function copy()
9805
    {
9806
        // Course builder
9807
        $cb = new CourseBuilder();
9808
9809
        //Setting tools that will be copied
9810
        $cb->set_tools_to_build(['learnpaths']);
9811
9812
        //Setting elements that will be copied
9813
        $cb->set_tools_specific_id_list(
9814
            ['learnpaths' => [$this->lp_id]]
9815
        );
9816
9817
        $course = $cb->build();
9818
9819
        //Course restorer
9820
        $course_restorer = new CourseRestorer($course);
9821
        $course_restorer->set_add_text_in_items(true);
9822
        $course_restorer->set_tool_copy_settings(
9823
            ['learnpaths' => ['reset_dates' => true]]
9824
        );
9825
        $course_restorer->restore(
9826
            api_get_course_id(),
9827
            api_get_session_id(),
9828
            false,
9829
            false
9830
        );
9831
    }
9832
9833
    /**
9834
     * Verify document size.
9835
     *
9836
     * @param string $s
9837
     *
9838
     * @return bool
9839
     */
9840
    public static function verify_document_size($s)
9841
    {
9842
        $post_max = ini_get('post_max_size');
9843
        if ('M' == substr($post_max, -1, 1)) {
9844
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9845
        } elseif ('G' == substr($post_max, -1, 1)) {
9846
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9847
        }
9848
        $upl_max = ini_get('upload_max_filesize');
9849
        if ('M' == substr($upl_max, -1, 1)) {
9850
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9851
        } elseif ('G' == substr($upl_max, -1, 1)) {
9852
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9853
        }
9854
9855
        $repo = Container::getDocumentRepository();
9856
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9857
9858
        $course_max_space = DocumentManager::get_course_quota();
9859
        $total_size = filesize($s) + $documents_total_space;
9860
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9861
            return true;
9862
        }
9863
9864
        return false;
9865
    }
9866
9867
    /**
9868
     * Clear LP prerequisites.
9869
     */
9870
    public function clear_prerequisites()
9871
    {
9872
        $course_id = $this->get_course_int_id();
9873
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9874
        $lp_id = $this->get_id();
9875
        // Cleaning prerequisites
9876
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9877
                WHERE c_id = $course_id AND lp_id = $lp_id";
9878
        Database::query($sql);
9879
9880
        // Cleaning mastery score for exercises
9881
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9882
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
9883
        Database::query($sql);
9884
    }
9885
9886
    public function set_previous_step_as_prerequisite_for_all_items()
9887
    {
9888
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9889
        $course_id = $this->get_course_int_id();
9890
        $lp_id = $this->get_id();
9891
9892
        if (!empty($this->items)) {
9893
            $previous_item_id = null;
9894
            $previous_item_max = 0;
9895
            $previous_item_type = null;
9896
            $last_item_not_dir = null;
9897
            $last_item_not_dir_type = null;
9898
            $last_item_not_dir_max = null;
9899
9900
            foreach ($this->ordered_items as $itemId) {
9901
                $item = $this->getItem($itemId);
9902
                // if there was a previous item... (otherwise jump to set it)
9903
                if (!empty($previous_item_id)) {
9904
                    $current_item_id = $item->get_id(); //save current id
9905
                    if ('dir' != $item->get_type()) {
9906
                        // Current item is not a folder, so it qualifies to get a prerequisites
9907
                        if ('quiz' == $last_item_not_dir_type) {
9908
                            // if previous is quiz, mark its max score as default score to be achieved
9909
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9910
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9911
                            Database::query($sql);
9912
                        }
9913
                        // now simply update the prerequisite to set it to the last non-chapter item
9914
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9915
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
9916
                        Database::query($sql);
9917
                        // record item as 'non-chapter' reference
9918
                        $last_item_not_dir = $item->get_id();
9919
                        $last_item_not_dir_type = $item->get_type();
9920
                        $last_item_not_dir_max = $item->get_max();
9921
                    }
9922
                } else {
9923
                    if ('dir' != $item->get_type()) {
9924
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9925
                        $last_item_not_dir = $item->get_id();
9926
                        $last_item_not_dir_type = $item->get_type();
9927
                        $last_item_not_dir_max = $item->get_max();
9928
                    }
9929
                }
9930
                // Saving the item as "previous item" for the next loop
9931
                $previous_item_id = $item->get_id();
9932
                $previous_item_max = $item->get_max();
9933
                $previous_item_type = $item->get_type();
9934
            }
9935
        }
9936
    }
9937
9938
    /**
9939
     * @param array $params
9940
     *
9941
     * @throws \Doctrine\ORM\OptimisticLockException
9942
     *
9943
     * @return int
9944
     */
9945
    public static function createCategory($params)
9946
    {
9947
        $item = new CLpCategory();
9948
        $item->setName($params['name']);
9949
        $item->setCId($params['c_id']);
9950
9951
        $repo = Container::getLpCategoryRepository();
9952
        $em = $repo->getEntityManager();
9953
        $em->persist($item);
9954
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9955
9956
        $repo->addResourceToCourse(
9957
            $item,
9958
            ResourceLink::VISIBILITY_PUBLISHED,
9959
            api_get_user_entity(api_get_user_id()),
9960
            $courseEntity,
9961
            api_get_session_entity(),
9962
            api_get_group_entity()
9963
        );
9964
9965
        $em->flush();
9966
9967
        /*api_item_property_update(
9968
            api_get_course_info(),
9969
            TOOL_LEARNPATH_CATEGORY,
9970
            $item->getId(),
9971
            'visible',
9972
            api_get_user_id()
9973
        );*/
9974
9975
        return $item->getId();
9976
    }
9977
9978
    /**
9979
     * @param array $params
9980
     *
9981
     * @throws \Doctrine\ORM\ORMException
9982
     * @throws \Doctrine\ORM\OptimisticLockException
9983
     * @throws \Doctrine\ORM\TransactionRequiredException
9984
     */
9985
    public static function updateCategory($params)
9986
    {
9987
        $em = Database::getManager();
9988
        /** @var CLpCategory $item */
9989
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
9990
        if ($item) {
9991
            $item->setName($params['name']);
9992
            $em->merge($item);
9993
            $em->flush();
9994
        }
9995
    }
9996
9997
    /**
9998
     * @param int $id
9999
     *
10000
     * @throws \Doctrine\ORM\ORMException
10001
     * @throws \Doctrine\ORM\OptimisticLockException
10002
     * @throws \Doctrine\ORM\TransactionRequiredException
10003
     */
10004
    public static function moveUpCategory($id)
10005
    {
10006
        $id = (int) $id;
10007
        $em = Database::getManager();
10008
        /** @var CLpCategory $item */
10009
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10010
        if ($item) {
10011
            $position = $item->getPosition() - 1;
10012
            $item->setPosition($position);
10013
            $em->persist($item);
10014
            $em->flush();
10015
        }
10016
    }
10017
10018
    /**
10019
     * @param int $id
10020
     *
10021
     * @throws \Doctrine\ORM\ORMException
10022
     * @throws \Doctrine\ORM\OptimisticLockException
10023
     * @throws \Doctrine\ORM\TransactionRequiredException
10024
     */
10025
    public static function moveDownCategory($id)
10026
    {
10027
        $id = (int) $id;
10028
        $em = Database::getManager();
10029
        /** @var CLpCategory $item */
10030
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10031
        if ($item) {
10032
            $position = $item->getPosition() + 1;
10033
            $item->setPosition($position);
10034
            $em->persist($item);
10035
            $em->flush();
10036
        }
10037
    }
10038
10039
    /**
10040
     * @param int $courseId
10041
     *
10042
     * @throws \Doctrine\ORM\Query\QueryException
10043
     *
10044
     * @return int|mixed
10045
     */
10046
    public static function getCountCategories($courseId)
10047
    {
10048
        if (empty($courseId)) {
10049
            return 0;
10050
        }
10051
        $em = Database::getManager();
10052
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10053
        $query->setParameter('id', $courseId);
10054
10055
        return $query->getSingleScalarResult();
10056
    }
10057
10058
    /**
10059
     * @param int $courseId
10060
     *
10061
     * @return mixed
10062
     */
10063
    public static function getCategories($courseId)
10064
    {
10065
        $em = Database::getManager();
10066
10067
        // Using doctrine extensions
10068
        /** @var SortableRepository $repo */
10069
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10070
        $items = $repo
10071
            ->getBySortableGroupsQuery(['cId' => $courseId])
10072
            ->getResult();
10073
10074
        return $items;
10075
    }
10076
10077
    /**
10078
     * @param int $id
10079
     *
10080
     * @throws \Doctrine\ORM\ORMException
10081
     * @throws \Doctrine\ORM\OptimisticLockException
10082
     * @throws \Doctrine\ORM\TransactionRequiredException
10083
     *
10084
     * @return CLpCategory
10085
     */
10086
    public static function getCategory($id)
10087
    {
10088
        $id = (int) $id;
10089
        $em = Database::getManager();
10090
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10091
10092
        return $item;
10093
    }
10094
10095
    /**
10096
     * @param int $courseId
10097
     *
10098
     * @return array
10099
     */
10100
    public static function getCategoryByCourse($courseId)
10101
    {
10102
        $em = Database::getManager();
10103
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10104
            ['cId' => $courseId]
10105
        );
10106
10107
        return $items;
10108
    }
10109
10110
    /**
10111
     * @param int $id
10112
     *
10113
     * @throws \Doctrine\ORM\ORMException
10114
     * @throws \Doctrine\ORM\OptimisticLockException
10115
     * @throws \Doctrine\ORM\TransactionRequiredException
10116
     *
10117
     * @return mixed
10118
     */
10119
    public static function deleteCategory($id)
10120
    {
10121
        $em = Database::getManager();
10122
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10123
        if ($item) {
10124
            $courseId = $item->getCId();
10125
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
10126
            $query->setParameter('id', $courseId);
10127
            $query->setParameter('catId', $item->getId());
10128
            $lps = $query->getResult();
10129
10130
            // Setting category = 0.
10131
            if ($lps) {
10132
                foreach ($lps as $lpItem) {
10133
                    $lpItem->setCategoryId(0);
10134
                }
10135
            }
10136
10137
            // Removing category.
10138
            $em->remove($item);
10139
            $em->flush();
10140
10141
            $courseInfo = api_get_course_info_by_id($courseId);
10142
            $sessionId = api_get_session_id();
10143
10144
            // Delete link tool
10145
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
10146
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
10147
            // Delete tools
10148
            $sql = "DELETE FROM $tbl_tool
10149
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
10150
            Database::query($sql);*/
10151
10152
            return true;
10153
        }
10154
10155
        return false;
10156
    }
10157
10158
    /**
10159
     * @param int  $courseId
10160
     * @param bool $addSelectOption
10161
     *
10162
     * @return mixed
10163
     */
10164
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10165
    {
10166
        $items = self::getCategoryByCourse($courseId);
10167
        $cats = [];
10168
        if ($addSelectOption) {
10169
            $cats = [get_lang('Select a category')];
10170
        }
10171
10172
        if (!empty($items)) {
10173
            foreach ($items as $cat) {
10174
                $cats[$cat->getId()] = $cat->getName();
10175
            }
10176
        }
10177
10178
        return $cats;
10179
    }
10180
10181
    /**
10182
     * @param string $courseCode
10183
     * @param int    $lpId
10184
     * @param int    $user_id
10185
     *
10186
     * @return learnpath
10187
     */
10188
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10189
    {
10190
        $debug = 0;
10191
        $learnPath = null;
10192
        $lpObject = Session::read('lpobject');
10193
        if (null !== $lpObject) {
10194
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10195
            if ($debug) {
10196
                error_log('getLpFromSession: unserialize');
10197
                error_log('------getLpFromSession------');
10198
                error_log('------unserialize------');
10199
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10200
                error_log("api_get_sessionid: ".api_get_session_id());
10201
            }
10202
        }
10203
10204
        if (!is_object($learnPath)) {
10205
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10206
            if ($debug) {
10207
                error_log('------getLpFromSession------');
10208
                error_log('getLpFromSession: create new learnpath');
10209
                error_log("create new LP with $courseCode - $lpId - $user_id");
10210
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10211
                error_log("api_get_sessionid: ".api_get_session_id());
10212
            }
10213
        }
10214
10215
        return $learnPath;
10216
    }
10217
10218
    /**
10219
     * @param int $itemId
10220
     *
10221
     * @return learnpathItem|false
10222
     */
10223
    public function getItem($itemId)
10224
    {
10225
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10226
            return $this->items[$itemId];
10227
        }
10228
10229
        return false;
10230
    }
10231
10232
    /**
10233
     * @return int
10234
     */
10235
    public function getCurrentAttempt()
10236
    {
10237
        $attempt = $this->getItem($this->get_current_item_id());
10238
        if ($attempt) {
10239
            $attemptId = $attempt->get_attempt_id();
10240
10241
            return $attemptId;
10242
        }
10243
10244
        return 0;
10245
    }
10246
10247
    /**
10248
     * @return int
10249
     */
10250
    public function getCategoryId()
10251
    {
10252
        return (int) $this->categoryId;
10253
    }
10254
10255
    /**
10256
     * @param int $categoryId
10257
     *
10258
     * @return bool
10259
     */
10260
    public function setCategoryId($categoryId)
10261
    {
10262
        $this->categoryId = (int) $categoryId;
10263
        $table = Database::get_course_table(TABLE_LP_MAIN);
10264
        $lp_id = $this->get_id();
10265
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10266
                WHERE iid = $lp_id";
10267
        Database::query($sql);
10268
10269
        return true;
10270
    }
10271
10272
    /**
10273
     * Get whether this is a learning path with the possibility to subscribe
10274
     * users or not.
10275
     *
10276
     * @return int
10277
     */
10278
    public function getSubscribeUsers()
10279
    {
10280
        return $this->subscribeUsers;
10281
    }
10282
10283
    /**
10284
     * Set whether this is a learning path with the possibility to subscribe
10285
     * users or not.
10286
     *
10287
     * @param int $value (0 = false, 1 = true)
10288
     *
10289
     * @return bool
10290
     */
10291
    public function setSubscribeUsers($value)
10292
    {
10293
        $this->subscribeUsers = (int) $value;
10294
        $table = Database::get_course_table(TABLE_LP_MAIN);
10295
        $lp_id = $this->get_id();
10296
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10297
                WHERE iid = $lp_id";
10298
        Database::query($sql);
10299
10300
        return true;
10301
    }
10302
10303
    /**
10304
     * Calculate the count of stars for a user in this LP
10305
     * This calculation is based on the following rules:
10306
     * - the student gets one star when he gets to 50% of the learning path
10307
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10308
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10309
     * - the student gets the final star when the score for the *last* test is >= 80%.
10310
     *
10311
     * @param int $sessionId Optional. The session ID
10312
     *
10313
     * @return int The count of stars
10314
     */
10315
    public function getCalculateStars($sessionId = 0)
10316
    {
10317
        $stars = 0;
10318
        $progress = self::getProgress(
10319
            $this->lp_id,
10320
            $this->user_id,
10321
            $this->course_int_id,
10322
            $sessionId
10323
        );
10324
10325
        if ($progress >= 50) {
10326
            $stars++;
10327
        }
10328
10329
        // Calculate stars chapters evaluation
10330
        $exercisesItems = $this->getExercisesItems();
10331
10332
        if (!empty($exercisesItems)) {
10333
            $totalResult = 0;
10334
10335
            foreach ($exercisesItems as $exerciseItem) {
10336
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10337
                    $this->user_id,
10338
                    $exerciseItem->path,
10339
                    $this->course_int_id,
10340
                    $sessionId,
10341
                    $this->lp_id,
10342
                    $exerciseItem->db_id
10343
                );
10344
10345
                $exerciseResultInfo = end($exerciseResultInfo);
10346
10347
                if (!$exerciseResultInfo) {
10348
                    continue;
10349
                }
10350
10351
                if (!empty($exerciseResultInfo['max_score'])) {
10352
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10353
                } else {
10354
                    $exerciseResult = 0;
10355
                }
10356
                $totalResult += $exerciseResult;
10357
            }
10358
10359
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10360
10361
            if ($totalExerciseAverage >= 50) {
10362
                $stars++;
10363
            }
10364
10365
            if ($totalExerciseAverage >= 80) {
10366
                $stars++;
10367
            }
10368
        }
10369
10370
        // Calculate star for final evaluation
10371
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10372
10373
        if (!empty($finalEvaluationItem)) {
10374
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10375
                $this->user_id,
10376
                $finalEvaluationItem->path,
10377
                $this->course_int_id,
10378
                $sessionId,
10379
                $this->lp_id,
10380
                $finalEvaluationItem->db_id
10381
            );
10382
10383
            $evaluationResultInfo = end($evaluationResultInfo);
10384
10385
            if ($evaluationResultInfo) {
10386
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10387
10388
                if ($evaluationResult >= 80) {
10389
                    $stars++;
10390
                }
10391
            }
10392
        }
10393
10394
        return $stars;
10395
    }
10396
10397
    /**
10398
     * Get the items of exercise type.
10399
     *
10400
     * @return array The items. Otherwise return false
10401
     */
10402
    public function getExercisesItems()
10403
    {
10404
        $exercises = [];
10405
        foreach ($this->items as $item) {
10406
            if ('quiz' != $item->type) {
10407
                continue;
10408
            }
10409
            $exercises[] = $item;
10410
        }
10411
10412
        array_pop($exercises);
10413
10414
        return $exercises;
10415
    }
10416
10417
    /**
10418
     * Get the item of exercise type (evaluation type).
10419
     *
10420
     * @return array The final evaluation. Otherwise return false
10421
     */
10422
    public function getFinalEvaluationItem()
10423
    {
10424
        $exercises = [];
10425
        foreach ($this->items as $item) {
10426
            if ('quiz' != $item->type) {
10427
                continue;
10428
            }
10429
10430
            $exercises[] = $item;
10431
        }
10432
10433
        return array_pop($exercises);
10434
    }
10435
10436
    /**
10437
     * Calculate the total points achieved for the current user in this learning path.
10438
     *
10439
     * @param int $sessionId Optional. The session Id
10440
     *
10441
     * @return int
10442
     */
10443
    public function getCalculateScore($sessionId = 0)
10444
    {
10445
        // Calculate stars chapters evaluation
10446
        $exercisesItems = $this->getExercisesItems();
10447
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10448
        $totalExercisesResult = 0;
10449
        $totalEvaluationResult = 0;
10450
10451
        if (false !== $exercisesItems) {
10452
            foreach ($exercisesItems as $exerciseItem) {
10453
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10454
                    $this->user_id,
10455
                    $exerciseItem->path,
10456
                    $this->course_int_id,
10457
                    $sessionId,
10458
                    $this->lp_id,
10459
                    $exerciseItem->db_id
10460
                );
10461
10462
                $exerciseResultInfo = end($exerciseResultInfo);
10463
10464
                if (!$exerciseResultInfo) {
10465
                    continue;
10466
                }
10467
10468
                $totalExercisesResult += $exerciseResultInfo['score'];
10469
            }
10470
        }
10471
10472
        if (!empty($finalEvaluationItem)) {
10473
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10474
                $this->user_id,
10475
                $finalEvaluationItem->path,
10476
                $this->course_int_id,
10477
                $sessionId,
10478
                $this->lp_id,
10479
                $finalEvaluationItem->db_id
10480
            );
10481
10482
            $evaluationResultInfo = end($evaluationResultInfo);
10483
10484
            if ($evaluationResultInfo) {
10485
                $totalEvaluationResult += $evaluationResultInfo['score'];
10486
            }
10487
        }
10488
10489
        return $totalExercisesResult + $totalEvaluationResult;
10490
    }
10491
10492
    /**
10493
     * Check if URL is not allowed to be show in a iframe.
10494
     *
10495
     * @param string $src
10496
     *
10497
     * @return string
10498
     */
10499
    public function fixBlockedLinks($src)
10500
    {
10501
        $urlInfo = parse_url($src);
10502
10503
        $platformProtocol = 'https';
10504
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10505
            $platformProtocol = 'http';
10506
        }
10507
10508
        $protocolFixApplied = false;
10509
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10510
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10511
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10512
10513
        if ($platformProtocol != $scheme) {
10514
            Session::write('x_frame_source', $src);
10515
            $src = 'blank.php?error=x_frames_options';
10516
            $protocolFixApplied = true;
10517
        }
10518
10519
        if (false == $protocolFixApplied) {
10520
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10521
                // Check X-Frame-Options
10522
                $ch = curl_init();
10523
                $options = [
10524
                    CURLOPT_URL => $src,
10525
                    CURLOPT_RETURNTRANSFER => true,
10526
                    CURLOPT_HEADER => true,
10527
                    CURLOPT_FOLLOWLOCATION => true,
10528
                    CURLOPT_ENCODING => "",
10529
                    CURLOPT_AUTOREFERER => true,
10530
                    CURLOPT_CONNECTTIMEOUT => 120,
10531
                    CURLOPT_TIMEOUT => 120,
10532
                    CURLOPT_MAXREDIRS => 10,
10533
                ];
10534
10535
                $proxySettings = api_get_configuration_value('proxy_settings');
10536
                if (!empty($proxySettings) &&
10537
                    isset($proxySettings['curl_setopt_array'])
10538
                ) {
10539
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10540
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10541
                }
10542
10543
                curl_setopt_array($ch, $options);
10544
                $response = curl_exec($ch);
10545
                $httpCode = curl_getinfo($ch);
10546
                $headers = substr($response, 0, $httpCode['header_size']);
10547
10548
                $error = false;
10549
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10550
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10551
                ) {
10552
                    $error = true;
10553
                }
10554
10555
                if ($error) {
10556
                    Session::write('x_frame_source', $src);
10557
                    $src = 'blank.php?error=x_frames_options';
10558
                }
10559
            }
10560
        }
10561
10562
        return $src;
10563
    }
10564
10565
    /**
10566
     * Check if this LP has a created forum in the basis course.
10567
     *
10568
     * @return bool
10569
     */
10570
    public function lpHasForum()
10571
    {
10572
        $forumTable = Database::get_course_table(TABLE_FORUM);
10573
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10574
10575
        $fakeFrom = "
10576
            $forumTable f
10577
            INNER JOIN $itemProperty ip
10578
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10579
        ";
10580
10581
        $resultData = Database::select(
10582
            'COUNT(f.iid) AS qty',
10583
            $fakeFrom,
10584
            [
10585
                'where' => [
10586
                    'ip.visibility != ? AND ' => 2,
10587
                    'ip.tool = ? AND ' => TOOL_FORUM,
10588
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10589
                    'f.lp_id = ?' => intval($this->lp_id),
10590
                ],
10591
            ],
10592
            'first'
10593
        );
10594
10595
        return $resultData['qty'] > 0;
10596
    }
10597
10598
    /**
10599
     * Get the forum for this learning path.
10600
     *
10601
     * @param int $sessionId
10602
     *
10603
     * @return array
10604
     */
10605
    public function getForum($sessionId = 0)
10606
    {
10607
        $repo = Container::getForumRepository();
10608
10609
        $course = api_get_course_entity();
10610
        $session = api_get_session_entity($sessionId);
10611
        $qb = $repo->getResourcesByCourse($course, $session);
10612
10613
        return $qb->getQuery()->getResult();
10614
    }
10615
10616
    /**
10617
     * Create a forum for this learning path.
10618
     *
10619
     * @param int $forumCategoryId
10620
     *
10621
     * @return int The forum ID if was created. Otherwise return false
10622
     */
10623
    public function createForum($forumCategoryId)
10624
    {
10625
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10626
10627
        $forumId = store_forum(
10628
            [
10629
                'lp_id' => $this->lp_id,
10630
                'forum_title' => $this->name,
10631
                'forum_comment' => null,
10632
                'forum_category' => (int) $forumCategoryId,
10633
                'students_can_edit_group' => ['students_can_edit' => 0],
10634
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10635
                'default_view_type_group' => ['default_view_type' => 'flat'],
10636
                'group_forum' => 0,
10637
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10638
            ],
10639
            [],
10640
            true
10641
        );
10642
10643
        return $forumId;
10644
    }
10645
10646
    /**
10647
     * Get the LP Final Item form.
10648
     *
10649
     * @throws Exception
10650
     * @throws HTML_QuickForm_Error
10651
     *
10652
     * @return string
10653
     */
10654
    public function getFinalItemForm()
10655
    {
10656
        $finalItem = $this->getFinalItem();
10657
        $title = '';
10658
10659
        if ($finalItem) {
10660
            $title = $finalItem->get_title();
10661
            $buttonText = get_lang('Save');
10662
            $content = $this->getSavedFinalItem();
10663
        } else {
10664
            $buttonText = get_lang('Add this document to the course');
10665
            $content = $this->getFinalItemTemplate();
10666
        }
10667
10668
        $editorConfig = [
10669
            'ToolbarSet' => 'LearningPathDocuments',
10670
            'Width' => '100%',
10671
            'Height' => '500',
10672
            'FullPage' => true,
10673
//            'CreateDocumentDir' => $relative_prefix,
10674
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10675
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10676
        ];
10677
10678
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10679
            'type' => 'document',
10680
            'lp_id' => $this->lp_id,
10681
        ]);
10682
10683
        $form = new FormValidator('final_item', 'POST', $url);
10684
        $form->addText('title', get_lang('Title'));
10685
        $form->addButtonSave($buttonText);
10686
        $form->addHtml(
10687
            Display::return_message(
10688
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10689
                'normal',
10690
                false
10691
            )
10692
        );
10693
10694
        $renderer = $form->defaultRenderer();
10695
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10696
10697
        $form->addHtmlEditor(
10698
            'content_lp_certificate',
10699
            null,
10700
            true,
10701
            false,
10702
            $editorConfig,
10703
            true
10704
        );
10705
        $form->addHidden('action', 'add_final_item');
10706
        $form->addHidden('path', Session::read('pathItem'));
10707
        $form->addHidden('previous', $this->get_last());
10708
        $form->setDefaults(
10709
            ['title' => $title, 'content_lp_certificate' => $content]
10710
        );
10711
10712
        if ($form->validate()) {
10713
            $values = $form->exportValues();
10714
            $lastItemId = $this->getLastInFirstLevel();
10715
10716
            if (!$finalItem) {
10717
                $documentId = $this->create_document(
10718
                    $this->course_info,
10719
                    $values['content_lp_certificate'],
10720
                    $values['title']
10721
                );
10722
                $this->add_item(
10723
                    0,
10724
                    $lastItemId,
10725
                    'final_item',
10726
                    $documentId,
10727
                    $values['title'],
10728
                    ''
10729
                );
10730
10731
                Display::addFlash(
10732
                    Display::return_message(get_lang('Added'))
10733
                );
10734
            } else {
10735
                $this->edit_document($this->course_info);
10736
            }
10737
        }
10738
10739
        return $form->returnForm();
10740
    }
10741
10742
    /**
10743
     * Check if the current lp item is first, both, last or none from lp list.
10744
     *
10745
     * @param int $currentItemId
10746
     *
10747
     * @return string
10748
     */
10749
    public function isFirstOrLastItem($currentItemId)
10750
    {
10751
        $lpItemId = [];
10752
        $typeListNotToVerify = self::getChapterTypes();
10753
10754
        // Using get_toc() function instead $this->items because returns the correct order of the items
10755
        foreach ($this->get_toc() as $item) {
10756
            if (!in_array($item['type'], $typeListNotToVerify)) {
10757
                $lpItemId[] = $item['id'];
10758
            }
10759
        }
10760
10761
        $lastLpItemIndex = count($lpItemId) - 1;
10762
        $position = array_search($currentItemId, $lpItemId);
10763
10764
        switch ($position) {
10765
            case 0:
10766
                if (!$lastLpItemIndex) {
10767
                    $answer = 'both';
10768
                    break;
10769
                }
10770
10771
                $answer = 'first';
10772
                break;
10773
            case $lastLpItemIndex:
10774
                $answer = 'last';
10775
                break;
10776
            default:
10777
                $answer = 'none';
10778
        }
10779
10780
        return $answer;
10781
    }
10782
10783
    /**
10784
     * Get whether this is a learning path with the accumulated SCORM time or not.
10785
     *
10786
     * @return int
10787
     */
10788
    public function getAccumulateScormTime()
10789
    {
10790
        return $this->accumulateScormTime;
10791
    }
10792
10793
    /**
10794
     * Set whether this is a learning path with the accumulated SCORM time or not.
10795
     *
10796
     * @param int $value (0 = false, 1 = true)
10797
     *
10798
     * @return bool Always returns true
10799
     */
10800
    public function setAccumulateScormTime($value)
10801
    {
10802
        $this->accumulateScormTime = (int) $value;
10803
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
10804
        $lp_id = $this->get_id();
10805
        $sql = "UPDATE $lp_table
10806
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
10807
                WHERE iid = $lp_id";
10808
        Database::query($sql);
10809
10810
        return true;
10811
    }
10812
10813
    /**
10814
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10815
     * the new learning path tool.
10816
     *
10817
     * The function is a big switch on tool type.
10818
     * In each case, we query the corresponding table for information and build the link
10819
     * with that information.
10820
     *
10821
     * @author Yannick Warnier <[email protected]> - rebranding based on
10822
     * previous work (display_addedresource_link_in_learnpath())
10823
     *
10824
     * @param int $course_id      Course code
10825
     * @param int $learningPathId The learning path ID (in lp table)
10826
     * @param int $id_in_path     the unique index in the items table
10827
     * @param int $lpViewId
10828
     *
10829
     * @return string
10830
     */
10831
    public static function rl_get_resource_link_for_learnpath(
10832
        $course_id,
10833
        $learningPathId,
10834
        $id_in_path,
10835
        $lpViewId
10836
    ) {
10837
        $session_id = api_get_session_id();
10838
        $course_info = api_get_course_info_by_id($course_id);
10839
10840
        $learningPathId = (int) $learningPathId;
10841
        $id_in_path = (int) $id_in_path;
10842
        $lpViewId = (int) $lpViewId;
10843
10844
        $em = Database::getManager();
10845
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
10846
10847
        /** @var CLpItem $rowItem */
10848
        $rowItem = $lpItemRepo->findOneBy([
10849
            'cId' => $course_id,
10850
            'lpId' => $learningPathId,
10851
            'iid' => $id_in_path,
10852
        ]);
10853
10854
        if (!$rowItem) {
10855
            // Try one more time with "id"
10856
            /** @var CLpItem $rowItem */
10857
            $rowItem = $lpItemRepo->findOneBy([
10858
                'cId' => $course_id,
10859
                'lpId' => $learningPathId,
10860
                'id' => $id_in_path,
10861
            ]);
10862
10863
            if (!$rowItem) {
10864
                return -1;
10865
            }
10866
        }
10867
10868
        $type = $rowItem->getItemType();
10869
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10870
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10871
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
10872
        $link = '';
10873
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
10874
10875
        switch ($type) {
10876
            case 'dir':
10877
                return $main_dir_path.'lp/blank.php';
10878
            case TOOL_CALENDAR_EVENT:
10879
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10880
            case TOOL_ANNOUNCEMENT:
10881
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10882
            case TOOL_LINK:
10883
                $linkInfo = Link::getLinkInfo($id);
10884
                if (isset($linkInfo['url'])) {
10885
                    return $linkInfo['url'];
10886
                }
10887
10888
                return '';
10889
            case TOOL_QUIZ:
10890
                if (empty($id)) {
10891
                    return '';
10892
                }
10893
10894
                // Get the lp_item_view with the highest view_count.
10895
                $learnpathItemViewResult = $em
10896
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10897
                    ->findBy(
10898
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
10899
                        ['viewCount' => 'DESC'],
10900
                        1
10901
                    );
10902
                /** @var CLpItemView $learnpathItemViewData */
10903
                $learnpathItemViewData = current($learnpathItemViewResult);
10904
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
10905
10906
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10907
                    .http_build_query([
10908
                        'lp_init' => 1,
10909
                        'learnpath_item_view_id' => $learnpathItemViewId,
10910
                        'learnpath_id' => $learningPathId,
10911
                        'learnpath_item_id' => $id_in_path,
10912
                        'exerciseId' => $id,
10913
                    ]);
10914
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10915
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10916
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10917
                $myrow = Database::fetch_array($result);
10918
                $path = $myrow['path'];
10919
10920
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10921
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10922
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10923
            case TOOL_FORUM:
10924
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10925
            case TOOL_THREAD:
10926
                // forum post
10927
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10928
                if (empty($id)) {
10929
                    return '';
10930
                }
10931
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
10932
                $result = Database::query($sql);
10933
                $myrow = Database::fetch_array($result);
10934
10935
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10936
                    .$extraParams;
10937
            case TOOL_POST:
10938
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10939
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10940
                $myrow = Database::fetch_array($result);
10941
10942
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10943
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10944
            case TOOL_READOUT_TEXT:
10945
                return api_get_path(WEB_CODE_PATH).
10946
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10947
            case TOOL_DOCUMENT:
10948
                $repo = Container::getDocumentRepository();
10949
                $document = $repo->find($rowItem->getPath());
10950
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
10951
10952
                return $file;
10953
10954
                $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...
10955
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10956
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10957
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10958
10959
                $openmethod = 2;
10960
                $officedoc = false;
10961
                Session::write('openmethod', $openmethod);
10962
                Session::write('officedoc', $officedoc);
10963
10964
                if ($showDirectUrl) {
10965
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10966
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10967
                        if (Link::isPdfLink($file)) {
10968
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10969
10970
                            return $pdfUrl;
10971
                        }
10972
                    }
10973
10974
                    return $file;
10975
                }
10976
10977
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10978
            case TOOL_LP_FINAL_ITEM:
10979
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10980
                    .$extraParams;
10981
            case 'assignments':
10982
                return $main_dir_path.'work/work.php?'.$extraParams;
10983
            case TOOL_DROPBOX:
10984
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10985
            case 'introduction_text': //DEPRECATED
10986
                return '';
10987
            case TOOL_COURSE_DESCRIPTION:
10988
                return $main_dir_path.'course_description?'.$extraParams;
10989
            case TOOL_GROUP:
10990
                return $main_dir_path.'group/group.php?'.$extraParams;
10991
            case TOOL_USER:
10992
                return $main_dir_path.'user/user.php?'.$extraParams;
10993
            case TOOL_STUDENTPUBLICATION:
10994
                if (!empty($rowItem->getPath())) {
10995
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10996
                }
10997
10998
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10999
        }
11000
11001
        return $link;
11002
    }
11003
11004
    /**
11005
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11006
     *
11007
     * @author Yannick Warnier <[email protected]>
11008
     *
11009
     * @param string $course_code    Course code
11010
     * @param int    $learningPathId
11011
     * @param int    $id_in_path     The resource ID
11012
     *
11013
     * @return string
11014
     */
11015
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11016
    {
11017
        $_course = api_get_course_info($course_code);
11018
        if (empty($_course)) {
11019
            return '';
11020
        }
11021
        $course_id = $_course['real_id'];
11022
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11023
        $learningPathId = (int) $learningPathId;
11024
        $id_in_path = (int) $id_in_path;
11025
11026
        $sql = "SELECT item_type, title, ref
11027
                FROM $tbl_lp_item
11028
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11029
        $res_item = Database::query($sql);
11030
11031
        if (Database::num_rows($res_item) < 1) {
11032
            return '';
11033
        }
11034
        $row_item = Database::fetch_array($res_item);
11035
        $type = strtolower($row_item['item_type']);
11036
        $id = $row_item['ref'];
11037
        $output = '';
11038
11039
        switch ($type) {
11040
            case TOOL_CALENDAR_EVENT:
11041
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11042
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11043
                $myrow = Database::fetch_array($result);
11044
                $output = $myrow['title'];
11045
                break;
11046
            case TOOL_ANNOUNCEMENT:
11047
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11048
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11049
                $myrow = Database::fetch_array($result);
11050
                $output = $myrow['title'];
11051
                break;
11052
            case TOOL_LINK:
11053
                // Doesn't take $target into account.
11054
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11055
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11056
                $myrow = Database::fetch_array($result);
11057
                $output = $myrow['title'];
11058
                break;
11059
            case TOOL_QUIZ:
11060
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11061
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11062
                $myrow = Database::fetch_array($result);
11063
                $output = $myrow['title'];
11064
                break;
11065
            case TOOL_FORUM:
11066
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11067
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11068
                $myrow = Database::fetch_array($result);
11069
                $output = $myrow['forum_name'];
11070
                break;
11071
            case TOOL_THREAD:
11072
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11073
                // Grabbing the title of the post.
11074
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11075
                $result_title = Database::query($sql_title);
11076
                $myrow_title = Database::fetch_array($result_title);
11077
                $output = $myrow_title['post_title'];
11078
                break;
11079
            case TOOL_POST:
11080
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11081
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11082
                $result = Database::query($sql);
11083
                $post = Database::fetch_array($result);
11084
                $output = $post['post_title'];
11085
                break;
11086
            case 'dir':
11087
            case TOOL_DOCUMENT:
11088
                $title = $row_item['title'];
11089
                $output = '-';
11090
                if (!empty($title)) {
11091
                    $output = $title;
11092
                }
11093
                break;
11094
            case 'hotpotatoes':
11095
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11096
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11097
                $myrow = Database::fetch_array($result);
11098
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11099
                $last = count($pathname) - 1; // Making a correct name for the link.
11100
                $filename = $pathname[$last]; // Making a correct name for the link.
11101
                $myrow['path'] = rawurlencode($myrow['path']);
11102
                $output = $filename;
11103
                break;
11104
        }
11105
11106
        return stripslashes($output);
11107
    }
11108
11109
    /**
11110
     * Get the parent names for the current item.
11111
     *
11112
     * @param int $newItemId Optional. The item ID
11113
     *
11114
     * @return array
11115
     */
11116
    public function getCurrentItemParentNames($newItemId = 0)
11117
    {
11118
        $newItemId = $newItemId ?: $this->get_current_item_id();
11119
        $return = [];
11120
        $item = $this->getItem($newItemId);
11121
        $parent = $this->getItem($item->get_parent());
11122
11123
        while ($parent) {
11124
            $return[] = $parent->get_title();
11125
            $parent = $this->getItem($parent->get_parent());
11126
        }
11127
11128
        return array_reverse($return);
11129
    }
11130
11131
    /**
11132
     * Reads and process "lp_subscription_settings" setting.
11133
     *
11134
     * @return array
11135
     */
11136
    public static function getSubscriptionSettings()
11137
    {
11138
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11139
        if (empty($subscriptionSettings)) {
11140
            // By default allow both settings
11141
            $subscriptionSettings = [
11142
                'allow_add_users_to_lp' => true,
11143
                'allow_add_users_to_lp_category' => true,
11144
            ];
11145
        } else {
11146
            $subscriptionSettings = $subscriptionSettings['options'];
11147
        }
11148
11149
        return $subscriptionSettings;
11150
    }
11151
11152
    /**
11153
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11154
     */
11155
    public function exportToCourseBuildFormat()
11156
    {
11157
        if (!api_is_allowed_to_edit()) {
11158
            return false;
11159
        }
11160
11161
        $courseBuilder = new CourseBuilder();
11162
        $itemList = [];
11163
        /** @var learnpathItem $item */
11164
        foreach ($this->items as $item) {
11165
            $itemList[$item->get_type()][] = $item->get_path();
11166
        }
11167
11168
        if (empty($itemList)) {
11169
            return false;
11170
        }
11171
11172
        if (isset($itemList['document'])) {
11173
            // Get parents
11174
            foreach ($itemList['document'] as $documentId) {
11175
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11176
                if (!empty($documentInfo['parents'])) {
11177
                    foreach ($documentInfo['parents'] as $parentInfo) {
11178
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11179
                            continue;
11180
                        }
11181
                        $itemList['document'][] = $parentInfo['iid'];
11182
                    }
11183
                }
11184
            }
11185
11186
            $courseInfo = api_get_course_info();
11187
            foreach ($itemList['document'] as $documentId) {
11188
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11189
                $items = DocumentManager::get_resources_from_source_html(
11190
                    $documentInfo['absolute_path'],
11191
                    true,
11192
                    TOOL_DOCUMENT
11193
                );
11194
11195
                if (!empty($items)) {
11196
                    foreach ($items as $item) {
11197
                        // Get information about source url
11198
                        $url = $item[0]; // url
11199
                        $scope = $item[1]; // scope (local, remote)
11200
                        $type = $item[2]; // type (rel, abs, url)
11201
11202
                        $origParseUrl = parse_url($url);
11203
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11204
11205
                        if ('local' == $scope) {
11206
                            if ('abs' == $type || 'rel' == $type) {
11207
                                $documentFile = strstr($realOrigPath, 'document');
11208
                                if (false !== strpos($realOrigPath, $documentFile)) {
11209
                                    $documentFile = str_replace('document', '', $documentFile);
11210
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11211
                                    // Document found! Add it to the list
11212
                                    if ($itemDocumentId) {
11213
                                        $itemList['document'][] = $itemDocumentId;
11214
                                    }
11215
                                }
11216
                            }
11217
                        }
11218
                    }
11219
                }
11220
            }
11221
11222
            $courseBuilder->build_documents(
11223
                api_get_session_id(),
11224
                $this->get_course_int_id(),
11225
                true,
11226
                $itemList['document']
11227
            );
11228
        }
11229
11230
        if (isset($itemList['quiz'])) {
11231
            $courseBuilder->build_quizzes(
11232
                api_get_session_id(),
11233
                $this->get_course_int_id(),
11234
                true,
11235
                $itemList['quiz']
11236
            );
11237
        }
11238
11239
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11240
11241
        /*if (!empty($itemList['thread'])) {
11242
            $postList = [];
11243
            foreach ($itemList['thread'] as $postId) {
11244
                $post = get_post_information($postId);
11245
                if ($post) {
11246
                    if (!isset($itemList['forum'])) {
11247
                        $itemList['forum'] = [];
11248
                    }
11249
                    $itemList['forum'][] = $post['forum_id'];
11250
                    $postList[] = $postId;
11251
                }
11252
            }
11253
11254
            if (!empty($postList)) {
11255
                $courseBuilder->build_forum_posts(
11256
                    $this->get_course_int_id(),
11257
                    null,
11258
                    null,
11259
                    $postList
11260
                );
11261
            }
11262
        }*/
11263
11264
        if (!empty($itemList['thread'])) {
11265
            $threadList = [];
11266
            $em = Database::getManager();
11267
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11268
            foreach ($itemList['thread'] as $threadId) {
11269
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11270
                $thread = $repo->find($threadId);
11271
                if ($thread) {
11272
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11273
                    $threadList[] = $thread->getIid();
11274
                }
11275
            }
11276
11277
            if (!empty($threadList)) {
11278
                $courseBuilder->build_forum_topics(
11279
                    api_get_session_id(),
11280
                    $this->get_course_int_id(),
11281
                    null,
11282
                    $threadList
11283
                );
11284
            }
11285
        }
11286
11287
        $forumCategoryList = [];
11288
        if (isset($itemList['forum'])) {
11289
            foreach ($itemList['forum'] as $forumId) {
11290
                $forumInfo = get_forums($forumId);
11291
                $forumCategoryList[] = $forumInfo['forum_category'];
11292
            }
11293
        }
11294
11295
        if (!empty($forumCategoryList)) {
11296
            $courseBuilder->build_forum_category(
11297
                api_get_session_id(),
11298
                $this->get_course_int_id(),
11299
                true,
11300
                $forumCategoryList
11301
            );
11302
        }
11303
11304
        if (!empty($itemList['forum'])) {
11305
            $courseBuilder->build_forums(
11306
                api_get_session_id(),
11307
                $this->get_course_int_id(),
11308
                true,
11309
                $itemList['forum']
11310
            );
11311
        }
11312
11313
        if (isset($itemList['link'])) {
11314
            $courseBuilder->build_links(
11315
                api_get_session_id(),
11316
                $this->get_course_int_id(),
11317
                true,
11318
                $itemList['link']
11319
            );
11320
        }
11321
11322
        if (!empty($itemList['student_publication'])) {
11323
            $courseBuilder->build_works(
11324
                api_get_session_id(),
11325
                $this->get_course_int_id(),
11326
                true,
11327
                $itemList['student_publication']
11328
            );
11329
        }
11330
11331
        $courseBuilder->build_learnpaths(
11332
            api_get_session_id(),
11333
            $this->get_course_int_id(),
11334
            true,
11335
            [$this->get_id()],
11336
            false
11337
        );
11338
11339
        $courseBuilder->restoreDocumentsFromList();
11340
11341
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11342
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11343
        $result = DocumentManager::file_send_for_download(
11344
            $zipPath,
11345
            true,
11346
            $this->get_name().'.zip'
11347
        );
11348
11349
        if ($result) {
11350
            api_not_allowed();
11351
        }
11352
11353
        return true;
11354
    }
11355
11356
    /**
11357
     * Get whether this is a learning path with the accumulated work time or not.
11358
     *
11359
     * @return int
11360
     */
11361
    public function getAccumulateWorkTime()
11362
    {
11363
        return (int) $this->accumulateWorkTime;
11364
    }
11365
11366
    /**
11367
     * Get whether this is a learning path with the accumulated work time or not.
11368
     *
11369
     * @return int
11370
     */
11371
    public function getAccumulateWorkTimeTotalCourse()
11372
    {
11373
        $table = Database::get_course_table(TABLE_LP_MAIN);
11374
        $sql = "SELECT SUM(accumulate_work_time) AS total
11375
                FROM $table
11376
                WHERE c_id = ".$this->course_int_id;
11377
        $result = Database::query($sql);
11378
        $row = Database::fetch_array($result);
11379
11380
        return (int) $row['total'];
11381
    }
11382
11383
    /**
11384
     * Set whether this is a learning path with the accumulated work time or not.
11385
     *
11386
     * @param int $value (0 = false, 1 = true)
11387
     *
11388
     * @return bool
11389
     */
11390
    public function setAccumulateWorkTime($value)
11391
    {
11392
        if (!api_get_configuration_value('lp_minimum_time')) {
11393
            return false;
11394
        }
11395
11396
        $this->accumulateWorkTime = (int) $value;
11397
        $table = Database::get_course_table(TABLE_LP_MAIN);
11398
        $lp_id = $this->get_id();
11399
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
11400
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
11401
        Database::query($sql);
11402
11403
        return true;
11404
    }
11405
11406
    /**
11407
     * @param int $lpId
11408
     * @param int $courseId
11409
     *
11410
     * @return mixed
11411
     */
11412
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11413
    {
11414
        $lpId = (int) $lpId;
11415
        $courseId = (int) $courseId;
11416
11417
        $table = Database::get_course_table(TABLE_LP_MAIN);
11418
        $sql = "SELECT accumulate_work_time
11419
                FROM $table
11420
                WHERE c_id = $courseId AND id = $lpId";
11421
        $result = Database::query($sql);
11422
        $row = Database::fetch_array($result);
11423
11424
        return $row['accumulate_work_time'];
11425
    }
11426
11427
    /**
11428
     * @param int $courseId
11429
     *
11430
     * @return int
11431
     */
11432
    public static function getAccumulateWorkTimeTotal($courseId)
11433
    {
11434
        $table = Database::get_course_table(TABLE_LP_MAIN);
11435
        $courseId = (int) $courseId;
11436
        $sql = "SELECT SUM(accumulate_work_time) AS total
11437
                FROM $table
11438
                WHERE c_id = $courseId";
11439
        $result = Database::query($sql);
11440
        $row = Database::fetch_array($result);
11441
11442
        return (int) $row['total'];
11443
    }
11444
11445
    /**
11446
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11447
     * and put the images in.
11448
     *
11449
     * @return array
11450
     */
11451
    public static function getIconSelect()
11452
    {
11453
        $theme = api_get_visual_theme();
11454
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11455
        $icons = ['' => get_lang('Please select an option')];
11456
11457
        if (is_dir($path)) {
11458
            $finder = new Finder();
11459
            $finder->files()->in($path);
11460
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11461
            /** @var SplFileInfo $file */
11462
            foreach ($finder as $file) {
11463
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11464
                    $icons[$file->getFilename()] = $file->getFilename();
11465
                }
11466
            }
11467
        }
11468
11469
        return $icons;
11470
    }
11471
11472
    /**
11473
     * @param int $lpId
11474
     *
11475
     * @return string
11476
     */
11477
    public static function getSelectedIcon($lpId)
11478
    {
11479
        $extraFieldValue = new ExtraFieldValue('lp');
11480
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11481
        $icon = '';
11482
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11483
            $icon = $lpIcon['value'];
11484
        }
11485
11486
        return $icon;
11487
    }
11488
11489
    /**
11490
     * @param int $lpId
11491
     *
11492
     * @return string
11493
     */
11494
    public static function getSelectedIconHtml($lpId)
11495
    {
11496
        $icon = self::getSelectedIcon($lpId);
11497
11498
        if (empty($icon)) {
11499
            return '';
11500
        }
11501
11502
        $theme = api_get_visual_theme();
11503
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11504
11505
        return Display::img($path);
11506
    }
11507
11508
    /**
11509
     * @param string $value
11510
     *
11511
     * @return string
11512
     */
11513
    public function cleanItemTitle($value)
11514
    {
11515
        $value = Security::remove_XSS(strip_tags($value));
11516
11517
        return $value;
11518
    }
11519
11520
    public function setItemTitle(FormValidator $form)
11521
    {
11522
        if (api_get_configuration_value('save_titles_as_html')) {
11523
            $form->addHtmlEditor(
11524
                'title',
11525
                get_lang('Title'),
11526
                true,
11527
                false,
11528
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11529
            );
11530
        } else {
11531
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11532
            $form->applyFilter('title', 'trim');
11533
            $form->applyFilter('title', 'html_filter');
11534
        }
11535
    }
11536
11537
    /**
11538
     * @return array
11539
     */
11540
    public function getItemsForForm($addParentCondition = false)
11541
    {
11542
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11543
        $course_id = api_get_course_int_id();
11544
11545
        $sql = "SELECT * FROM $tbl_lp_item
11546
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11547
11548
        if ($addParentCondition) {
11549
            $sql .= ' AND parent_item_id = 0 ';
11550
        }
11551
        $sql .= ' ORDER BY display_order ASC';
11552
11553
        $result = Database::query($sql);
11554
        $arrLP = [];
11555
        while ($row = Database::fetch_array($result)) {
11556
            $arrLP[] = [
11557
                'iid' => $row['iid'],
11558
                'id' => $row['iid'],
11559
                'item_type' => $row['item_type'],
11560
                'title' => $this->cleanItemTitle($row['title']),
11561
                'title_raw' => $row['title'],
11562
                'path' => $row['path'],
11563
                'description' => Security::remove_XSS($row['description']),
11564
                'parent_item_id' => $row['parent_item_id'],
11565
                'previous_item_id' => $row['previous_item_id'],
11566
                'next_item_id' => $row['next_item_id'],
11567
                'display_order' => $row['display_order'],
11568
                'max_score' => $row['max_score'],
11569
                'min_score' => $row['min_score'],
11570
                'mastery_score' => $row['mastery_score'],
11571
                'prerequisite' => $row['prerequisite'],
11572
                'max_time_allowed' => $row['max_time_allowed'],
11573
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11574
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11575
            ];
11576
        }
11577
11578
        return $arrLP;
11579
    }
11580
11581
    /**
11582
     * Get the depth level of LP item.
11583
     *
11584
     * @param array $items
11585
     * @param int   $currentItemId
11586
     *
11587
     * @return int
11588
     */
11589
    private static function get_level_for_item($items, $currentItemId)
11590
    {
11591
        $parentItemId = 0;
11592
        if (isset($items[$currentItemId])) {
11593
            $parentItemId = $items[$currentItemId]->parent;
11594
        }
11595
11596
        if (0 == $parentItemId) {
11597
            return 0;
11598
        } else {
11599
            return self::get_level_for_item($items, $parentItemId) + 1;
11600
        }
11601
    }
11602
11603
    /**
11604
     * Generate the link for a learnpath category as course tool.
11605
     *
11606
     * @param int $categoryId
11607
     *
11608
     * @return string
11609
     */
11610
    private static function getCategoryLinkForTool($categoryId)
11611
    {
11612
        $categoryId = (int) $categoryId;
11613
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11614
            .http_build_query(
11615
                [
11616
                    'action' => 'view_category',
11617
                    'id' => $categoryId,
11618
                ]
11619
            );
11620
11621
        return $link;
11622
    }
11623
11624
    /**
11625
     * Return the scorm item type object with spaces replaced with _
11626
     * The return result is use to build a css classname like scorm_type_$return.
11627
     *
11628
     * @param $in_type
11629
     *
11630
     * @return mixed
11631
     */
11632
    private static function format_scorm_type_item($in_type)
11633
    {
11634
        return str_replace(' ', '_', $in_type);
11635
    }
11636
11637
    /**
11638
     * Check and obtain the lp final item if exist.
11639
     *
11640
     * @return learnpathItem
11641
     */
11642
    private function getFinalItem()
11643
    {
11644
        if (empty($this->items)) {
11645
            return null;
11646
        }
11647
11648
        foreach ($this->items as $item) {
11649
            if ('final_item' !== $item->type) {
11650
                continue;
11651
            }
11652
11653
            return $item;
11654
        }
11655
    }
11656
11657
    /**
11658
     * Get the LP Final Item Template.
11659
     *
11660
     * @return string
11661
     */
11662
    private function getFinalItemTemplate()
11663
    {
11664
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11665
    }
11666
11667
    /**
11668
     * Get the LP Final Item Url.
11669
     *
11670
     * @return string
11671
     */
11672
    private function getSavedFinalItem()
11673
    {
11674
        $finalItem = $this->getFinalItem();
11675
11676
        $repo = Container::getDocumentRepository();
11677
        /** @var CDocument $document */
11678
        $document = $repo->find($finalItem->path);
11679
11680
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11681
            return  $repo->getResourceFileContent($document);
11682
        }
11683
11684
        return '';
11685
    }
11686
}
11687