Passed
Push — master ( fe97dd...55e34c )
by Julito
07:41
created

learnpath::get_theme()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\User;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\Node\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\CForumCategory;
13
use Chamilo\CourseBundle\Entity\CLink;
14
use Chamilo\CourseBundle\Entity\CLp;
15
use Chamilo\CourseBundle\Entity\CLpCategory;
16
use Chamilo\CourseBundle\Entity\CLpItem;
17
use Chamilo\CourseBundle\Entity\CLpItemView;
18
use Chamilo\CourseBundle\Entity\CQuiz;
19
use Chamilo\CourseBundle\Entity\CStudentPublication;
20
use Chamilo\CourseBundle\Entity\CTool;
21
use ChamiloSession as Session;
22
use Gedmo\Sortable\Entity\Repository\SortableRepository;
23
use Symfony\Component\Filesystem\Filesystem;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
26
27
/**
28
 * Class learnpath
29
 * This class defines the parent attributes and methods for Chamilo learnpaths
30
 * and SCORM learnpaths. It is used by the scorm class.
31
 *
32
 * @todo decouple class
33
 *
34
 * @author  Yannick Warnier <[email protected]>
35
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
36
 */
37
class learnpath
38
{
39
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
40
    public const STATUS_CSS_CLASS_NAME = [
41
        'not attempted' => 'scorm_not_attempted',
42
        'incomplete' => 'scorm_not_attempted',
43
        'failed' => 'scorm_failed',
44
        'completed' => 'scorm_completed',
45
        'passed' => 'scorm_completed',
46
        'succeeded' => 'scorm_completed',
47
        'browsed' => 'scorm_completed',
48
    ];
49
50
    public $attempt = 0; // The number for the current ID view.
51
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
52
    public $current; // Id of the current item the user is viewing.
53
    public $current_score; // The score of the current item.
54
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
55
    public $current_time_stop; // The time the user closed this resource.
56
    public $default_status = 'not attempted';
57
    public $encoding = 'UTF-8';
58
    public $error = '';
59
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
60
    public $index; // The index of the active learnpath_item in $ordered_items array.
61
    /** @var learnpathItem[] */
62
    public $items = [];
63
    public $last; // item_id of last item viewed in the learning path.
64
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
65
    public $license; // Which license this course has been given - not used yet on 20060522.
66
    public $lp_id; // DB iid for this learnpath.
67
    public $lp_view_id; // DB ID for lp_view
68
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
69
    public $message = '';
70
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
71
    public $name; // Learnpath name (they generally have one).
72
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
73
    public $path = ''; // Path inside the scorm directory (if scorm).
74
    public $theme; // The current theme of the learning path.
75
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
76
    public $accumulateWorkTime; // The min time of learnpath
77
78
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
79
    public $prevent_reinit = 1;
80
81
    // Describes the mode of progress bar display.
82
    public $seriousgame_mode = 0;
83
    public $progress_bar_mode = '%';
84
85
    // Percentage progress as saved in the db.
86
    public $progress_db = 0;
87
    public $proximity; // Wether the content is distant or local or unknown.
88
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
89
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
90
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
91
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
92
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
93
    public $user_id; //ID of the user that is viewing/using the course
94
    public $update_queue = [];
95
    public $scorm_debug = 0;
96
    public $arrMenu = []; // Array for the menu items.
97
    public $debug = 0; // Logging level.
98
    public $lp_session_id = 0;
99
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
100
    public $prerequisite = 0;
101
    public $use_max_score = 1; // 1 or 0
102
    public $subscribeUsers = 0; // Subscribe users or not
103
    public $created_on = '';
104
    public $modified_on = '';
105
    public $publicated_on = '';
106
    public $expired_on = '';
107
    public $ref;
108
    public $course_int_id;
109
    public $course_info = [];
110
    public $categoryId;
111
    public $scormUrl;
112
113
    /**
114
     * Constructor.
115
     * Needs a database handler, a course code and a learnpath id from the database.
116
     * Also builds the list of items into $this->items.
117
     *
118
     */
119
    public function __construct(CLp $entity, $course_info, $user_id)
120
    {
121
        $debug = $this->debug;
122
        $this->encoding = api_get_system_encoding();
123
        $lp_id = (int) $entity->getIid();
124
        $course_id = (int) $course_info['real_id'];
125
        $this->set_course_int_id($course_id);
126
        if (empty($lp_id) || empty($course_id)) {
127
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
128
        } else {
129
            $this->lp_id = $lp_id;
130
            $this->type = $entity->getLpType();
131
            $this->name = stripslashes($entity->getName());
132
            $this->proximity = $entity->getContentLocal();
133
            $this->theme = $entity->getTheme();
134
            $this->maker = $entity->getContentLocal();
135
            $this->prevent_reinit = $entity->getPreventReinit();
136
            $this->seriousgame_mode = $entity->getSeriousgameMode();
137
            $this->license = $entity->getContentLicense();
138
            $this->scorm_debug = $entity->getDebug();
139
            $this->js_lib = $entity->getJsLib();
140
            $this->path = $entity->getPath();
141
            $this->author = $entity->getAuthor();
142
            $this->hide_toc_frame = $entity->getHideTocFrame();
143
            $this->lp_session_id = $entity->getSessionId();
144
            $this->use_max_score = $entity->getUseMaxScore();
145
            $this->subscribeUsers = $entity->getSubscribeUsers();
146
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
147
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
148
            $this->ref = $entity->getRef();
149
            $this->categoryId = 0;
150
            if ($entity->getCategory()) {
151
                $this->categoryId = $entity->getCategory()->getIid();
152
            }
153
154
            if ($entity->hasAsset()) {
155
                $asset = $entity->getAsset();
156
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/';
157
            }
158
159
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
160
161
            if (!empty($entity->getPublicatedOn())) {
162
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
163
            }
164
165
            if (!empty($entity->getExpiredOn())) {
166
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
167
            }
168
            if (2 == $this->type) {
169
                if (1 == $entity->getForceCommit()) {
170
                    $this->force_commit = true;
171
                }
172
            }
173
            $this->mode = $entity->getDefaultViewMod();
174
175
            // Check user ID.
176
            if (empty($user_id)) {
177
                $this->error = 'User ID is empty';
178
            } else {
179
                $userInfo = api_get_user_info($user_id);
180
                if (!empty($userInfo)) {
181
                    $this->user_id = $userInfo['user_id'];
182
                } else {
183
                    $this->error = 'User ID does not exist in database #'.$user_id;
184
                }
185
            }
186
187
            // End of variables checking.
188
            $session_id = api_get_session_id();
189
            //  Get the session condition for learning paths of the base + session.
190
            $session = api_get_session_condition($session_id);
191
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
192
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
193
194
            // Selecting by view_count descending allows to get the highest view_count first.
195
            $sql = "SELECT * FROM $lp_table
196
                    WHERE
197
                        c_id = $course_id AND
198
                        lp_id = $lp_id AND
199
                        user_id = $user_id
200
                        $session
201
                    ORDER BY view_count DESC";
202
            $res = Database::query($sql);
203
204
            if (Database::num_rows($res) > 0) {
205
                $row = Database::fetch_array($res);
206
                $this->attempt = $row['view_count'];
207
                $this->lp_view_id = $row['iid'];
208
                $this->last_item_seen = $row['last_item'];
209
                $this->progress_db = $row['progress'];
210
                $this->lp_view_session_id = $row['session_id'];
211
            } elseif (!api_is_invitee()) {
212
                $this->attempt = 1;
213
                $params = [
214
                    'c_id' => $course_id,
215
                    'lp_id' => $lp_id,
216
                    'user_id' => $user_id,
217
                    'view_count' => 1,
218
                    'session_id' => $session_id,
219
                    'last_item' => 0,
220
                ];
221
                $this->last_item_seen = 0;
222
                $this->lp_view_session_id = $session_id;
223
                $this->lp_view_id = Database::insert($lp_table, $params);
224
            }
225
226
            // Initialise items.
227
            $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
228
            $sql = "SELECT * FROM $lp_item_table
229
                    WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
230
                    ORDER BY parent_item_id, display_order";
231
            $res = Database::query($sql);
232
233
            $lp_item_id_list = [];
234
            while ($row = Database::fetch_array($res)) {
235
                $lp_item_id_list[] = $row['iid'];
236
                switch ($this->type) {
237
                    case 3: //aicc
238
                        $oItem = new aiccItem('db', $row['iid'], $course_id);
239
                        if (is_object($oItem)) {
240
                            $my_item_id = $oItem->get_id();
241
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
242
                            $oItem->set_prevent_reinit($this->prevent_reinit);
243
                            // Don't use reference here as the next loop will make the pointed object change.
244
                            $this->items[$my_item_id] = $oItem;
245
                            $this->refs_list[$oItem->ref] = $my_item_id;
246
                        }
247
                        break;
248
                    case 2:
249
                        $oItem = new scormItem('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 1:
260
                    default:
261
                        $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
262
                        if (is_object($oItem)) {
263
                            $my_item_id = $oItem->get_id();
264
                            // Moved down to when we are sure the item_view exists.
265
                            //$oItem->set_lp_view($this->lp_view_id);
266
                            $oItem->set_prevent_reinit($this->prevent_reinit);
267
                            // Don't use reference here as the next loop will make the pointed object change.
268
                            $this->items[$my_item_id] = $oItem;
269
                            $this->refs_list[$my_item_id] = $my_item_id;
270
                        }
271
                        break;
272
                }
273
274
                // Setting the object level with variable $this->items[$i][parent]
275
                foreach ($this->items as $itemLPObject) {
276
                    $level = self::get_level_for_item(
277
                        $this->items,
278
                        $itemLPObject->db_id
279
                    );
280
                    $itemLPObject->level = $level;
281
                }
282
283
                // Setting the view in the item object.
284
                if (is_object($this->items[$row['iid']])) {
285
                    $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
286
                    if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
287
                        $this->items[$row['iid']]->current_start_time = 0;
288
                        $this->items[$row['iid']]->current_stop_time = 0;
289
                    }
290
                }
291
            }
292
293
            if (!empty($lp_item_id_list)) {
294
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
295
                if (!empty($lp_item_id_list_to_string)) {
296
                    // Get last viewing vars.
297
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
298
                    // This query should only return one or zero result.
299
                    $sql = "SELECT lp_item_id, status
300
                            FROM $itemViewTable
301
                            WHERE
302
                                c_id = $course_id AND
303
                                lp_view_id = ".$this->get_view_id()." AND
304
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
305
                            ORDER BY view_count DESC ";
306
                    $status_list = [];
307
                    $res = Database::query($sql);
308
                    while ($row = Database:: fetch_array($res)) {
309
                        $status_list[$row['lp_item_id']] = $row['status'];
310
                    }
311
312
                    foreach ($lp_item_id_list as $item_id) {
313
                        if (isset($status_list[$item_id])) {
314
                            $status = $status_list[$item_id];
315
                            if (is_object($this->items[$item_id])) {
316
                                $this->items[$item_id]->set_status($status);
317
                                if (empty($status)) {
318
                                    $this->items[$item_id]->set_status(
319
                                        $this->default_status
320
                                    );
321
                                }
322
                            }
323
                        } else {
324
                            if (!api_is_invitee()) {
325
                                if (is_object($this->items[$item_id])) {
326
                                    $this->items[$item_id]->set_status(
327
                                        $this->default_status
328
                                    );
329
                                }
330
331
                                if (!empty($this->lp_view_id)) {
332
                                    // Add that row to the lp_item_view table so that
333
                                    // we have something to show in the stats page.
334
                                    $params = [
335
                                        'c_id' => $course_id,
336
                                        'lp_item_id' => $item_id,
337
                                        'lp_view_id' => $this->lp_view_id,
338
                                        'view_count' => 1,
339
                                        'status' => 'not attempted',
340
                                        'start_time' => time(),
341
                                        'total_time' => 0,
342
                                        'score' => 0,
343
                                    ];
344
                                    Database::insert($itemViewTable, $params);
345
346
                                    $this->items[$item_id]->set_lp_view(
347
                                        $this->lp_view_id,
348
                                        $course_id
349
                                    );
350
                                }
351
                            }
352
                        }
353
                    }
354
                }
355
            }
356
357
            $this->ordered_items = self::get_flat_ordered_items_list(
358
                $this->get_id(),
359
                0,
360
                $course_id
361
            );
362
            $this->max_ordered_items = 0;
363
            foreach ($this->ordered_items as $index => $dummy) {
364
                if ($index > $this->max_ordered_items && !empty($dummy)) {
365
                    $this->max_ordered_items = $index;
366
                }
367
            }
368
            // TODO: Define the current item better.
369
            $this->first();
370
            if ($debug) {
371
                error_log('lp_view_session_id '.$this->lp_view_session_id);
372
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
373
            }
374
        }
375
376
    }
377
378
    /**
379
     * @return string
380
     */
381
    public function getCourseCode()
382
    {
383
        return $this->cc;
384
    }
385
386
    /**
387
     * @return int
388
     */
389
    public function get_course_int_id()
390
    {
391
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
392
    }
393
394
    /**
395
     * @param $course_id
396
     *
397
     * @return int
398
     */
399
    public function set_course_int_id($course_id)
400
    {
401
        return $this->course_int_id = (int) $course_id;
402
    }
403
404
    /**
405
     * Function rewritten based on old_add_item() from Yannick Warnier.
406
     * Due the fact that users can decide where the item should come, I had to overlook this function and
407
     * I found it better to rewrite it. Old function is still available.
408
     * Added also the possibility to add a description.
409
     *
410
     * @param int    $parent
411
     * @param int    $previous
412
     * @param string $type
413
     * @param int    $id               resource ID (ref)
414
     * @param string $title
415
     * @param string $description
416
     * @param int    $prerequisites
417
     * @param int    $max_time_allowed
418
     * @param int    $userId
419
     *
420
     * @return int
421
     */
422
    public function add_item(
423
        $parent,
424
        $previous,
425
        $type = 'dir',
426
        $id,
427
        $title,
428
        $description,
429
        $prerequisites = 0,
430
        $max_time_allowed = 0,
431
        $userId = 0
432
    ) {
433
        $course_id = $this->course_info['real_id'];
434
        if (empty($course_id)) {
435
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
436
            $this->course_info = api_get_course_info($this->cc);
437
            $course_id = $this->course_info['real_id'];
438
        }
439
        $userId = empty($userId) ? api_get_user_id() : $userId;
440
        $sessionId = api_get_session_id();
441
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
442
        $_course = $this->course_info;
443
        $parent = (int) $parent;
444
        $previous = (int) $previous;
445
        $id = (int) $id;
446
        $max_time_allowed = (int) $max_time_allowed;
447
        if (empty($max_time_allowed)) {
448
            $max_time_allowed = 0;
449
        }
450
        $sql = "SELECT COUNT(iid) AS num
451
                FROM $tbl_lp_item
452
                WHERE
453
                    c_id = $course_id AND
454
                    lp_id = ".$this->get_id()." AND
455
                    parent_item_id = $parent ";
456
457
        $res_count = Database::query($sql);
458
        $row = Database::fetch_array($res_count);
459
        $num = $row['num'];
460
461
        $tmp_previous = 0;
462
        $display_order = 0;
463
        $next = 0;
464
        if ($num > 0) {
465
            if (empty($previous)) {
466
                $sql = "SELECT iid, next_item_id, display_order
467
                        FROM $tbl_lp_item
468
                        WHERE
469
                            c_id = $course_id AND
470
                            lp_id = ".$this->get_id()." AND
471
                            parent_item_id = $parent AND
472
                            previous_item_id = 0 OR
473
                            previous_item_id = $parent ";
474
                $result = Database::query($sql);
475
                $row = Database::fetch_array($result);
476
                if ($row) {
477
                    $next = $row['iid'];
478
                }
479
            } else {
480
                $previous = (int) $previous;
481
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
482
						FROM $tbl_lp_item
483
                        WHERE
484
                            c_id = $course_id AND
485
                            lp_id = ".$this->get_id()." AND
486
                            iid = $previous";
487
                $result = Database::query($sql);
488
                $row = Database::fetch_array($result);
489
                if ($row) {
490
                    $tmp_previous = $row['iid'];
491
                    $next = $row['next_item_id'];
492
                    $display_order = $row['display_order'];
493
                }
494
            }
495
        }
496
497
        $id = (int) $id;
498
        $typeCleaned = Database::escape_string($type);
499
        $max_score = 100;
500
        if ('quiz' === $type && $id) {
501
            $sql = 'SELECT SUM(ponderation)
502
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
503
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
504
                    ON
505
                        quiz_question.id = quiz_rel_question.question_id AND
506
                        quiz_question.c_id = quiz_rel_question.c_id
507
                    WHERE
508
                        quiz_rel_question.exercice_id = '.$id." AND
509
                        quiz_question.c_id = $course_id AND
510
                        quiz_rel_question.c_id = $course_id ";
511
            $rsQuiz = Database::query($sql);
512
            $max_score = Database::result($rsQuiz, 0, 0);
513
514
            // Disabling the exercise if we add it inside a LP
515
            $exercise = new Exercise($course_id);
516
            $exercise->read($id);
517
            $exercise->disable();
518
            $exercise->save();
519
        }
520
521
        $params = [
522
            'c_id' => $course_id,
523
            'lp_id' => $this->get_id(),
524
            'item_type' => $typeCleaned,
525
            'ref' => '',
526
            'title' => $title,
527
            'description' => $description,
528
            'path' => $id,
529
            'max_score' => $max_score,
530
            'parent_item_id' => $parent,
531
            'previous_item_id' => $previous,
532
            'next_item_id' => (int) $next,
533
            'display_order' => $display_order + 1,
534
            'prerequisite' => $prerequisites,
535
            'max_time_allowed' => $max_time_allowed,
536
            'min_score' => 0,
537
            'launch_data' => '',
538
        ];
539
540
        if (0 != $prerequisites) {
541
            $params['prerequisite'] = $prerequisites;
542
        }
543
544
        $new_item_id = Database::insert($tbl_lp_item, $params);
545
        if ($new_item_id) {
546
            if (!empty($next)) {
547
                $sql = "UPDATE $tbl_lp_item
548
                        SET previous_item_id = $new_item_id
549
                        WHERE c_id = $course_id AND iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
550
                Database::query($sql);
551
            }
552
553
            // Update the item that should be before the new item.
554
            if (!empty($tmp_previous)) {
555
                $sql = "UPDATE $tbl_lp_item
556
                        SET next_item_id = $new_item_id
557
                        WHERE c_id = $course_id AND iid = $tmp_previous";
558
                Database::query($sql);
559
            }
560
561
            // Update all the items after the new item.
562
            $sql = "UPDATE $tbl_lp_item
563
                        SET display_order = display_order + 1
564
                    WHERE
565
                        c_id = $course_id AND
566
                        lp_id = ".$this->get_id()." AND
567
                        iid <> $new_item_id AND
568
                        parent_item_id = $parent AND
569
                        display_order > $display_order";
570
            Database::query($sql);
571
572
            // Update the item that should come after the new item.
573
            $sql = "UPDATE $tbl_lp_item
574
                    SET ref = $new_item_id
575
                    WHERE c_id = $course_id AND iid = $new_item_id";
576
            Database::query($sql);
577
578
            $sql = "UPDATE $tbl_lp_item
579
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
580
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
581
            Database::query($sql);
582
583
            // Upload audio.
584
            if (!empty($_FILES['mp3']['name'])) {
585
                // Create the audio folder if it does not exist yet.
586
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
587
                if (!is_dir($filepath.'audio')) {
588
                    mkdir(
589
                        $filepath.'audio',
590
                        api_get_permissions_for_new_directories()
591
                    );
592
                    $audio_id = DocumentManager::addDocument(
593
                        $_course,
594
                        '/audio',
595
                        'folder',
596
                        0,
597
                        'audio',
598
                        '',
599
                        0,
600
                        true,
601
                        null,
602
                        $sessionId,
603
                        $userId
604
                    );
605
                }
606
607
                $file_path = handle_uploaded_document(
608
                    $_course,
609
                    $_FILES['mp3'],
610
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
611
                    '/audio',
612
                    $userId,
613
                    '',
614
                    '',
615
                    '',
616
                    '',
617
                    false
618
                );
619
620
                // Getting the filename only.
621
                $file_components = explode('/', $file_path);
622
                $file = $file_components[count($file_components) - 1];
623
624
                // Store the mp3 file in the lp_item table.
625
                $sql = "UPDATE $tbl_lp_item SET
626
                          audio = '".Database::escape_string($file)."'
627
                        WHERE iid = '".intval($new_item_id)."'";
628
                Database::query($sql);
629
            }
630
        }
631
632
        return $new_item_id;
633
    }
634
635
    /**
636
     * Static admin function allowing addition of a learnpath to a course.
637
     *
638
     * @param string $courseCode
639
     * @param string $name
640
     * @param string $description
641
     * @param string $learnpath
642
     * @param string $origin
643
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
644
     * @param string $publicated_on
645
     * @param string $expired_on
646
     * @param int    $categoryId
647
     * @param int    $userId
648
     *
649
     * @return CLp
650
     */
651
    public static function add_lp(
652
        $courseCode,
653
        $name,
654
        $description = '',
655
        $learnpath = 'guess',
656
        $origin = 'zip',
657
        $zipname = '',
658
        $publicated_on = '',
659
        $expired_on = '',
660
        $categoryId = 0,
661
        $userId = 0
662
    ) {
663
        global $charset;
664
665
        if (!empty($courseCode)) {
666
            $courseInfo = api_get_course_info($courseCode);
667
            $course_id = $courseInfo['real_id'];
668
        } else {
669
            $course_id = api_get_course_int_id();
670
            $courseInfo = api_get_course_info();
671
        }
672
673
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
674
        // Check course code exists.
675
        // Check lp_name doesn't exist, otherwise append something.
676
        $i = 0;
677
        $categoryId = (int) $categoryId;
678
        // Session id.
679
        $session_id = api_get_session_id();
680
        $userId = empty($userId) ? api_get_user_id() : $userId;
681
682
        if (empty($publicated_on)) {
683
            $publicated_on = null;
684
        } else {
685
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
686
        }
687
688
        if (empty($expired_on)) {
689
            $expired_on = null;
690
        } else {
691
            $expired_on = api_get_utc_datetime($expired_on, true, true);
692
        }
693
694
        $check_name = "SELECT * FROM $tbl_lp
695
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
696
        $res_name = Database::query($check_name);
697
698
        while (Database::num_rows($res_name)) {
699
            // There is already one such name, update the current one a bit.
700
            $i++;
701
            $name = $name.' - '.$i;
702
            $check_name = "SELECT * FROM $tbl_lp
703
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
704
            $res_name = Database::query($check_name);
705
        }
706
        // New name does not exist yet; keep it.
707
        // Escape description.
708
        // Kevin: added htmlentities().
709
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
710
        $type = 1;
711
        switch ($learnpath) {
712
            case 'guess':
713
            case 'aicc':
714
                break;
715
            case 'dokeos':
716
            case 'chamilo':
717
                $type = 1;
718
                break;
719
        }
720
721
        $id = null;
722
        $sessionEntity = api_get_session_entity();
723
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
724
725
        switch ($origin) {
726
            case 'zip':
727
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
728
                break;
729
            case 'manual':
730
            default:
731
                $get_max = "SELECT MAX(display_order)
732
                            FROM $tbl_lp WHERE c_id = $course_id";
733
                $res_max = Database::query($get_max);
734
                if (Database::num_rows($res_max) < 1) {
735
                    $dsp = 1;
736
                } else {
737
                    $row = Database::fetch_array($res_max);
738
                    $dsp = $row[0] + 1;
739
                }
740
741
                $category = null;
742
                if (!empty($categoryId)) {
743
                    $category = Container::getLpCategoryRepository()->find($categoryId);
744
                }
745
746
                $lp = new CLp();
747
                $lp
748
                    ->setCId($course_id)
749
                    ->setLpType($type)
750
                    ->setName($name)
751
                    ->setDescription($description)
752
                    ->setDisplayOrder($dsp)
753
                    ->setSessionId($session_id)
754
                    ->setCategory($category)
755
                    ->setPublicatedOn($publicated_on)
756
                    ->setExpiredOn($expired_on)
757
                    ->setParent($courseEntity)
758
                    ->addCourseLink($courseEntity, $sessionEntity)
759
                ;
760
761
                $em = Database::getManager();
762
                $em->persist($lp);
763
                $em->flush();
764
765
                if ($lp->getIid()) {
766
                    $id = $lp->getIid();
767
                }
768
769
                // Insert into item_property.
770
                /*api_item_property_update(
771
                    $courseInfo,
772
                    TOOL_LEARNPATH,
773
                    $id,
774
                    'LearnpathAdded',
775
                    $userId
776
                );
777
                api_set_default_visibility(
778
                    $id,
779
                    TOOL_LEARNPATH,
780
                    0,
781
                    $courseInfo,
782
                    $session_id,
783
                    $userId
784
                );*/
785
786
                break;
787
        }
788
789
        return $lp;
790
    }
791
792
    /**
793
     * Auto completes the parents of an item in case it's been completed or passed.
794
     *
795
     * @param int $item Optional ID of the item from which to look for parents
796
     */
797
    public function autocomplete_parents($item)
798
    {
799
        $debug = $this->debug;
800
801
        if (empty($item)) {
802
            $item = $this->current;
803
        }
804
805
        $currentItem = $this->getItem($item);
806
        if ($currentItem) {
807
            $parent_id = $currentItem->get_parent();
808
            $parent = $this->getItem($parent_id);
809
            if ($parent) {
810
                // if $item points to an object and there is a parent.
811
                if ($debug) {
812
                    error_log(
813
                        'Autocompleting parent of item '.$item.' '.
814
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
815
                        0
816
                    );
817
                }
818
819
                // New experiment including failed and browsed in completed status.
820
                //$current_status = $currentItem->get_status();
821
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
822
                // Fixes chapter auto complete
823
                if (true) {
824
                    // If the current item is completed or passes or succeeded.
825
                    $updateParentStatus = true;
826
                    if ($debug) {
827
                        error_log('Status of current item is alright');
828
                    }
829
830
                    foreach ($parent->get_children() as $childItemId) {
831
                        $childItem = $this->getItem($childItemId);
832
833
                        // If children was not set try to get the info
834
                        if (empty($childItem->db_item_view_id)) {
835
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
836
                        }
837
838
                        // Check all his brothers (parent's children) for completion status.
839
                        if ($childItemId != $item) {
840
                            if ($debug) {
841
                                error_log(
842
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
843
                                    0
844
                                );
845
                            }
846
                            // Trying completing parents of failed and browsed items as well.
847
                            if ($childItem->status_is(
848
                                [
849
                                    'completed',
850
                                    'passed',
851
                                    'succeeded',
852
                                    'browsed',
853
                                    'failed',
854
                                ]
855
                            )
856
                            ) {
857
                                // Keep completion status to true.
858
                                continue;
859
                            } else {
860
                                if ($debug > 2) {
861
                                    error_log(
862
                                        '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,
863
                                        0
864
                                    );
865
                                }
866
                                $updateParentStatus = false;
867
                                break;
868
                            }
869
                        }
870
                    }
871
872
                    if ($updateParentStatus) {
873
                        // If all the children were completed:
874
                        $parent->set_status('completed');
875
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
876
                        // Force the status to "completed"
877
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
878
                        $this->update_queue[$parent->get_id()] = 'completed';
879
                        if ($debug) {
880
                            error_log(
881
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
882
                                print_r($this->update_queue, 1),
883
                                0
884
                            );
885
                        }
886
                        // Recursive call.
887
                        $this->autocomplete_parents($parent->get_id());
888
                    }
889
                }
890
            } else {
891
                if ($debug) {
892
                    error_log("Parent #$parent_id does not exists");
893
                }
894
            }
895
        } else {
896
            if ($debug) {
897
                error_log("#$item is an item that doesn't have parents");
898
            }
899
        }
900
    }
901
902
    /**
903
     * Closes the current resource.
904
     *
905
     * Stops the timer
906
     * Saves into the database if required
907
     * Clears the current resource data from this object
908
     *
909
     * @return bool True on success, false on failure
910
     */
911
    public function close()
912
    {
913
        if (empty($this->lp_id)) {
914
            $this->error = 'Trying to close this learnpath but no ID is set';
915
916
            return false;
917
        }
918
        $this->current_time_stop = time();
919
        $this->ordered_items = [];
920
        $this->index = 0;
921
        unset($this->lp_id);
922
        //unset other stuff
923
        return true;
924
    }
925
926
    /**
927
     * Static admin function allowing removal of a learnpath.
928
     *
929
     * @param array  $courseInfo
930
     * @param int    $id         Learnpath ID
931
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
932
     *
933
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
934
     */
935
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
936
    {
937
        $course_id = api_get_course_int_id();
938
        if (!empty($courseInfo)) {
939
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
940
        }
941
942
        // TODO: Implement a way of getting this to work when the current object is not set.
943
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
944
        // If an ID is specifically given and the current LP is not the same, prevent delete.
945
        if (!empty($id) && ($id != $this->lp_id)) {
946
            return false;
947
        }
948
949
        $lp = Database::get_course_table(TABLE_LP_MAIN);
950
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
951
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
952
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
953
954
        // Delete lp item id.
955
        foreach ($this->items as $lpItemId => $dummy) {
956
            $sql = "DELETE FROM $lp_item_view
957
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
958
            Database::query($sql);
959
        }
960
961
        // Proposed by Christophe (nickname: clefevre)
962
        $sql = "DELETE FROM $lp_item
963
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
964
        Database::query($sql);
965
966
        $sql = "DELETE FROM $lp_view
967
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
968
        Database::query($sql);
969
970
        self::toggleVisibility($this->lp_id, 0);
971
972
        /*if (2 == $this->type || 3 == $this->type) {
973
            // This is a scorm learning path, delete the files as well.
974
            $sql = "SELECT path FROM $lp
975
                    WHERE iid = ".$this->lp_id;
976
            $res = Database::query($sql);
977
            if (Database::num_rows($res) > 0) {
978
                $row = Database::fetch_array($res);
979
                $path = $row['path'];
980
                $sql = "SELECT iid FROM $lp
981
                        WHERE
982
                            c_id = $course_id AND
983
                            path = '$path' AND
984
                            iid != ".$this->lp_id;
985
                $res = Database::query($sql);
986
                if (Database::num_rows($res) > 0) {
987
                    // Another learning path uses this directory, so don't delete it.
988
                    if ($this->debug > 2) {
989
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
990
                    }
991
                } else {
992
                    // No other LP uses that directory, delete it.
993
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
994
                    // The absolute system path for this course.
995
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
996
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
997
                        if ($this->debug > 2) {
998
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
999
                        }
1000
                        // Proposed by Christophe (clefevre).
1001
                        if (0 == strcmp(substr($path, -2), "/.")) {
1002
                            $path = substr($path, 0, -1); // Remove "." at the end.
1003
                        }
1004
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1005
                        rmdirr($course_scorm_dir.$path);
1006
                    }
1007
                }
1008
            }
1009
        }*/
1010
1011
       if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1012
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1013
            $sql = "DELETE FROM $table
1014
                    WHERE
1015
                        lp_id = {$this->lp_id} AND
1016
                        c_id = $course_id ";
1017
            Database::query($sql);
1018
        }
1019
1020
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1021
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1022
        // Delete tools
1023
        $sql = "DELETE FROM $tbl_tool
1024
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1025
        Database::query($sql);*/
1026
1027
        /*$sql = "DELETE FROM $lp
1028
                WHERE iid = ".$this->lp_id;
1029
        Database::query($sql);*/
1030
        $repo = Container::getLpRepository();
1031
        $lp = $repo->find($this->lp_id);
1032
        Database::getManager()->remove($lp);
1033
        Database::getManager()->flush();
1034
1035
        // Updates the display order of all lps.
1036
        $this->update_display_order();
1037
1038
        /*api_item_property_update(
1039
            api_get_course_info(),
1040
            TOOL_LEARNPATH,
1041
            $this->lp_id,
1042
            'delete',
1043
            api_get_user_id()
1044
        );*/
1045
1046
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1047
            api_get_course_id(),
1048
            4,
1049
            $id,
1050
            api_get_session_id()
1051
        );
1052
1053
        if (false !== $link_info) {
1054
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1055
        }
1056
1057
        if ('true' == api_get_setting('search_enabled')) {
1058
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1059
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1060
        }
1061
    }
1062
1063
    /**
1064
     * Removes all the children of one item - dangerous!
1065
     *
1066
     * @param int $id Element ID of which children have to be removed
1067
     *
1068
     * @return int Total number of children removed
1069
     */
1070
    public function delete_children_items($id)
1071
    {
1072
        $course_id = $this->course_info['real_id'];
1073
1074
        $num = 0;
1075
        $id = (int) $id;
1076
        if (empty($id) || empty($course_id)) {
1077
            return false;
1078
        }
1079
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1080
        $sql = "SELECT * FROM $lp_item
1081
                WHERE c_id = $course_id AND parent_item_id = $id";
1082
        $res = Database::query($sql);
1083
        while ($row = Database::fetch_array($res)) {
1084
            $num += $this->delete_children_items($row['iid']);
1085
            $sql = "DELETE FROM $lp_item
1086
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1087
            Database::query($sql);
1088
            $num++;
1089
        }
1090
1091
        return $num;
1092
    }
1093
1094
    /**
1095
     * Removes an item from the current learnpath.
1096
     *
1097
     * @param int $id Elem ID (0 if first)
1098
     *
1099
     * @return int Number of elements moved
1100
     *
1101
     * @todo implement resource removal
1102
     */
1103
    public function delete_item($id)
1104
    {
1105
        $course_id = api_get_course_int_id();
1106
        $id = (int) $id;
1107
        // TODO: Implement the resource removal.
1108
        if (empty($id) || empty($course_id)) {
1109
            return false;
1110
        }
1111
        // First select item to get previous, next, and display order.
1112
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1113
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1114
        $res_sel = Database::query($sql_sel);
1115
        if (Database::num_rows($res_sel) < 1) {
1116
            return false;
1117
        }
1118
        $row = Database::fetch_array($res_sel);
1119
        $previous = $row['previous_item_id'];
1120
        $next = $row['next_item_id'];
1121
        $display = $row['display_order'];
1122
        $parent = $row['parent_item_id'];
1123
        $lp = $row['lp_id'];
1124
        // Delete children items.
1125
        $this->delete_children_items($id);
1126
        // Now delete the item.
1127
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1128
        Database::query($sql_del);
1129
        // Now update surrounding items.
1130
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1131
                    WHERE iid = $previous";
1132
        Database::query($sql_upd);
1133
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1134
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1135
        Database::query($sql_upd);
1136
        // Now update all following items with new display order.
1137
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1138
                    WHERE
1139
                        c_id = $course_id AND
1140
                        lp_id = $lp AND
1141
                        parent_item_id = $parent AND
1142
                        display_order > $display";
1143
        Database::query($sql_all);
1144
1145
        //Removing prerequisites since the item will not longer exist
1146
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1147
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1148
        Database::query($sql_all);
1149
1150
        $sql = "UPDATE $lp_item
1151
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1152
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1153
        Database::query($sql);
1154
1155
        // Remove from search engine if enabled.
1156
        if ('true' === api_get_setting('search_enabled')) {
1157
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1158
            $sql = 'SELECT * FROM %s
1159
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1160
                    LIMIT 1';
1161
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1162
            $res = Database::query($sql);
1163
            if (Database::num_rows($res) > 0) {
1164
                $row2 = Database::fetch_array($res);
1165
                $di = new ChamiloIndexer();
1166
                $di->remove_document($row2['search_did']);
1167
            }
1168
            $sql = 'DELETE FROM %s
1169
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1170
                    LIMIT 1';
1171
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1172
            Database::query($sql);
1173
        }
1174
    }
1175
1176
    /**
1177
     * Updates an item's content in place.
1178
     *
1179
     * @param int    $id               Element ID
1180
     * @param int    $parent           Parent item ID
1181
     * @param int    $previous         Previous item ID
1182
     * @param string $title            Item title
1183
     * @param string $description      Item description
1184
     * @param string $prerequisites    Prerequisites (optional)
1185
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1186
     * @param int    $max_time_allowed
1187
     * @param string $url
1188
     *
1189
     * @return bool True on success, false on error
1190
     */
1191
    public function edit_item(
1192
        $id,
1193
        $parent,
1194
        $previous,
1195
        $title,
1196
        $description,
1197
        $prerequisites = '0',
1198
        $audio = [],
1199
        $max_time_allowed = 0,
1200
        $url = ''
1201
    ) {
1202
        $course_id = api_get_course_int_id();
1203
        $_course = api_get_course_info();
1204
        $id = (int) $id;
1205
1206
        if (empty($max_time_allowed)) {
1207
            $max_time_allowed = 0;
1208
        }
1209
1210
        if (empty($id) || empty($_course)) {
1211
            return false;
1212
        }
1213
1214
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1215
        $sql = "SELECT * FROM $tbl_lp_item
1216
                WHERE iid = $id";
1217
        $res_select = Database::query($sql);
1218
        $row_select = Database::fetch_array($res_select);
1219
        $audio_update_sql = '';
1220
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1221
            // Create the audio folder if it does not exist yet.
1222
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1223
            if (!is_dir($filepath.'audio')) {
1224
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1225
                $audio_id = DocumentManager::addDocument(
1226
                    $_course,
1227
                    '/audio',
1228
                    'folder',
1229
                    0,
1230
                    'audio'
1231
                );
1232
            }
1233
1234
            // Upload file in documents.
1235
            $pi = pathinfo($audio['name']);
1236
            if ('mp3' === $pi['extension']) {
1237
                $c_det = api_get_course_info($this->cc);
1238
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1239
                $path = handle_uploaded_document(
1240
                    $c_det,
1241
                    $audio,
1242
                    $bp,
1243
                    '/audio',
1244
                    api_get_user_id(),
1245
                    0,
1246
                    null,
1247
                    0,
1248
                    'rename',
1249
                    false,
1250
                    0
1251
                );
1252
                $path = substr($path, 7);
1253
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1254
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1255
            }
1256
        }
1257
1258
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1259
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1260
1261
        // TODO: htmlspecialchars to be checked for encoding related problems.
1262
        if ($same_parent && $same_previous) {
1263
            // Only update title and description.
1264
            $sql = "UPDATE $tbl_lp_item
1265
                    SET title = '".Database::escape_string($title)."',
1266
                        prerequisite = '".$prerequisites."',
1267
                        description = '".Database::escape_string($description)."'
1268
                        ".$audio_update_sql.",
1269
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1270
                    WHERE iid = $id";
1271
            Database::query($sql);
1272
        } else {
1273
            $old_parent = $row_select['parent_item_id'];
1274
            $old_previous = $row_select['previous_item_id'];
1275
            $old_next = $row_select['next_item_id'];
1276
            $old_order = $row_select['display_order'];
1277
            $old_prerequisite = $row_select['prerequisite'];
1278
            $old_max_time_allowed = $row_select['max_time_allowed'];
1279
1280
            /* BEGIN -- virtually remove the current item id */
1281
            /* for the next and previous item it is like the current item doesn't exist anymore */
1282
            if (0 != $old_previous) {
1283
                // Next
1284
                $sql = "UPDATE $tbl_lp_item
1285
                        SET next_item_id = $old_next
1286
                        WHERE iid = $old_previous";
1287
                Database::query($sql);
1288
            }
1289
1290
            if (!empty($old_next)) {
1291
                // Previous
1292
                $sql = "UPDATE $tbl_lp_item
1293
                        SET previous_item_id = $old_previous
1294
                        WHERE iid = $old_next";
1295
                Database::query($sql);
1296
            }
1297
1298
            // display_order - 1 for every item with a display_order
1299
            // bigger then the display_order of the current item.
1300
            $sql = "UPDATE $tbl_lp_item
1301
                    SET display_order = display_order - 1
1302
                    WHERE
1303
                        c_id = $course_id AND
1304
                        display_order > $old_order AND
1305
                        lp_id = ".$this->lp_id." AND
1306
                        parent_item_id = $old_parent";
1307
            Database::query($sql);
1308
            /* END -- virtually remove the current item id */
1309
1310
            /* BEGIN -- update the current item id to his new location */
1311
            if (0 == $previous) {
1312
                // Select the data of the item that should come after the current item.
1313
                $sql = "SELECT id, display_order
1314
                        FROM $tbl_lp_item
1315
                        WHERE
1316
                            c_id = $course_id AND
1317
                            lp_id = ".$this->lp_id." AND
1318
                            parent_item_id = $parent AND
1319
                            previous_item_id = $previous";
1320
                $res_select_old = Database::query($sql);
1321
                $row_select_old = Database::fetch_array($res_select_old);
1322
1323
                // If the new parent didn't have children before.
1324
                if (0 == Database::num_rows($res_select_old)) {
1325
                    $new_next = 0;
1326
                    $new_order = 1;
1327
                } else {
1328
                    $new_next = $row_select_old['id'];
1329
                    $new_order = $row_select_old['display_order'];
1330
                }
1331
            } else {
1332
                // Select the data of the item that should come before the current item.
1333
                $sql = "SELECT next_item_id, display_order
1334
                        FROM $tbl_lp_item
1335
                        WHERE iid = $previous";
1336
                $res_select_old = Database::query($sql);
1337
                $row_select_old = Database::fetch_array($res_select_old);
1338
                $new_next = $row_select_old['next_item_id'];
1339
                $new_order = $row_select_old['display_order'] + 1;
1340
            }
1341
1342
            // TODO: htmlspecialchars to be checked for encoding related problems.
1343
            // Update the current item with the new data.
1344
            $sql = "UPDATE $tbl_lp_item
1345
                    SET
1346
                        title = '".Database::escape_string($title)."',
1347
                        description = '".Database::escape_string($description)."',
1348
                        parent_item_id = $parent,
1349
                        previous_item_id = $previous,
1350
                        next_item_id = $new_next,
1351
                        display_order = $new_order
1352
                        $audio_update_sql
1353
                    WHERE iid = $id";
1354
            Database::query($sql);
1355
1356
            if (0 != $previous) {
1357
                // Update the previous item's next_item_id.
1358
                $sql = "UPDATE $tbl_lp_item
1359
                        SET next_item_id = $id
1360
                        WHERE iid = $previous";
1361
                Database::query($sql);
1362
            }
1363
1364
            if (!empty($new_next)) {
1365
                // Update the next item's previous_item_id.
1366
                $sql = "UPDATE $tbl_lp_item
1367
                        SET previous_item_id = $id
1368
                        WHERE iid = $new_next";
1369
                Database::query($sql);
1370
            }
1371
1372
            if ($old_prerequisite != $prerequisites) {
1373
                $sql = "UPDATE $tbl_lp_item
1374
                        SET prerequisite = '$prerequisites'
1375
                        WHERE iid = $id";
1376
                Database::query($sql);
1377
            }
1378
1379
            if ($old_max_time_allowed != $max_time_allowed) {
1380
                // update max time allowed
1381
                $sql = "UPDATE $tbl_lp_item
1382
                        SET max_time_allowed = $max_time_allowed
1383
                        WHERE iid = $id";
1384
                Database::query($sql);
1385
            }
1386
1387
            // Update all the items with the same or a bigger display_order than the current item.
1388
            $sql = "UPDATE $tbl_lp_item
1389
                    SET display_order = display_order + 1
1390
                    WHERE
1391
                       c_id = $course_id AND
1392
                       lp_id = ".$this->get_id()." AND
1393
                       iid <> $id AND
1394
                       parent_item_id = $parent AND
1395
                       display_order >= $new_order";
1396
            Database::query($sql);
1397
        }
1398
1399
        if ('link' == $row_select['item_type']) {
1400
            $link = new Link();
1401
            $linkId = $row_select['path'];
1402
            $link->updateLink($linkId, $url);
1403
        }
1404
    }
1405
1406
    /**
1407
     * Updates an item's prereq in place.
1408
     *
1409
     * @param int    $id              Element ID
1410
     * @param string $prerequisite_id Prerequisite Element ID
1411
     * @param int    $minScore        Prerequisite min score
1412
     * @param int    $maxScore        Prerequisite max score
1413
     *
1414
     * @return bool True on success, false on error
1415
     */
1416
    public function edit_item_prereq(
1417
        $id,
1418
        $prerequisite_id,
1419
        $minScore = 0,
1420
        $maxScore = 100
1421
    ) {
1422
        $id = (int) $id;
1423
1424
        if (empty($id)) {
1425
            return false;
1426
        }
1427
        $prerequisite_id = (int) $prerequisite_id;
1428
1429
        if (empty($minScore) || $minScore < 0) {
1430
            $minScore = 0;
1431
        }
1432
1433
        if (empty($maxScore) || $maxScore < 0) {
1434
            $maxScore = 100;
1435
        }
1436
1437
        $minScore = (float) $minScore;
1438
        $maxScore = (float) $maxScore;
1439
1440
        if (empty($prerequisite_id)) {
1441
            $prerequisite_id = 'NULL';
1442
            $minScore = 0;
1443
            $maxScore = 100;
1444
        }
1445
1446
        $table = Database::get_course_table(TABLE_LP_ITEM);
1447
        $sql = " UPDATE $table
1448
                 SET
1449
                    prerequisite = $prerequisite_id ,
1450
                    prerequisite_min_score = $minScore ,
1451
                    prerequisite_max_score = $maxScore
1452
                 WHERE iid = $id";
1453
        Database::query($sql);
1454
1455
        return true;
1456
    }
1457
1458
    /**
1459
     * Get the specific prefix index terms of this learning path.
1460
     *
1461
     * @param string $prefix
1462
     *
1463
     * @return array Array of terms
1464
     */
1465
    public function get_common_index_terms_by_prefix($prefix)
1466
    {
1467
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1468
        $terms = get_specific_field_values_list_by_prefix(
1469
            $prefix,
1470
            $this->cc,
1471
            TOOL_LEARNPATH,
1472
            $this->lp_id
1473
        );
1474
        $prefix_terms = [];
1475
        if (!empty($terms)) {
1476
            foreach ($terms as $term) {
1477
                $prefix_terms[] = $term['value'];
1478
            }
1479
        }
1480
1481
        return $prefix_terms;
1482
    }
1483
1484
    /**
1485
     * Gets the number of items currently completed.
1486
     *
1487
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1488
     *
1489
     * @return int The number of items currently completed
1490
     */
1491
    public function get_complete_items_count($failedStatusException = false)
1492
    {
1493
        $i = 0;
1494
        $completedStatusList = [
1495
            'completed',
1496
            'passed',
1497
            'succeeded',
1498
            'browsed',
1499
        ];
1500
1501
        if (!$failedStatusException) {
1502
            $completedStatusList[] = 'failed';
1503
        }
1504
1505
        foreach ($this->items as $id => $dummy) {
1506
            // Trying failed and browsed considered "progressed" as well.
1507
            if ($this->items[$id]->status_is($completedStatusList) &&
1508
                'dir' != $this->items[$id]->get_type()
1509
            ) {
1510
                $i++;
1511
            }
1512
        }
1513
1514
        return $i;
1515
    }
1516
1517
    /**
1518
     * Gets the current item ID.
1519
     *
1520
     * @return int The current learnpath item id
1521
     */
1522
    public function get_current_item_id()
1523
    {
1524
        $current = 0;
1525
        if (!empty($this->current)) {
1526
            $current = (int) $this->current;
1527
        }
1528
1529
        return $current;
1530
    }
1531
1532
    /**
1533
     * Force to get the first learnpath item id.
1534
     *
1535
     * @return int The current learnpath item id
1536
     */
1537
    public function get_first_item_id()
1538
    {
1539
        $current = 0;
1540
        if (is_array($this->ordered_items)) {
1541
            $current = $this->ordered_items[0];
1542
        }
1543
1544
        return $current;
1545
    }
1546
1547
    /**
1548
     * Gets the total number of items available for viewing in this SCORM.
1549
     *
1550
     * @return int The total number of items
1551
     */
1552
    public function get_total_items_count()
1553
    {
1554
        return count($this->items);
1555
    }
1556
1557
    /**
1558
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1559
     *
1560
     * @return int The total no-chapters number of items
1561
     */
1562
    public function getTotalItemsCountWithoutDirs()
1563
    {
1564
        $total = 0;
1565
        $typeListNotToCount = self::getChapterTypes();
1566
        foreach ($this->items as $temp2) {
1567
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1568
                $total++;
1569
            }
1570
        }
1571
1572
        return $total;
1573
    }
1574
1575
    /**
1576
     *  Sets the first element URL.
1577
     */
1578
    public function first()
1579
    {
1580
        if ($this->debug > 0) {
1581
            error_log('In learnpath::first()', 0);
1582
            error_log('$this->last_item_seen '.$this->last_item_seen);
1583
        }
1584
1585
        // Test if the last_item_seen exists and is not a dir.
1586
        if (0 == count($this->ordered_items)) {
1587
            $this->index = 0;
1588
        }
1589
1590
        if (!empty($this->last_item_seen) &&
1591
            !empty($this->items[$this->last_item_seen]) &&
1592
            'dir' != $this->items[$this->last_item_seen]->get_type()
1593
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1594
            //&& !$this->items[$this->last_item_seen]->is_done()
1595
        ) {
1596
            if ($this->debug > 2) {
1597
                error_log(
1598
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1599
                    $this->items[$this->last_item_seen]->get_type()
1600
                );
1601
            }
1602
            $index = -1;
1603
            foreach ($this->ordered_items as $myindex => $item_id) {
1604
                if ($item_id == $this->last_item_seen) {
1605
                    $index = $myindex;
1606
                    break;
1607
                }
1608
            }
1609
            if (-1 == $index) {
1610
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1611
                if ($this->debug > 2) {
1612
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1613
                }
1614
1615
                return false;
1616
            } else {
1617
                $this->last = $this->last_item_seen;
1618
                $this->current = $this->last_item_seen;
1619
                $this->index = $index;
1620
            }
1621
        } else {
1622
            if ($this->debug > 2) {
1623
                error_log('In learnpath::first() - No last item seen', 0);
1624
            }
1625
            $index = 0;
1626
            // Loop through all ordered items and stop at the first item that is
1627
            // not a directory *and* that has not been completed yet.
1628
            while (!empty($this->ordered_items[$index]) &&
1629
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1630
                (
1631
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1632
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1633
                ) && $index < $this->max_ordered_items) {
1634
                $index++;
1635
            }
1636
1637
            $this->last = $this->current;
1638
            // current is
1639
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1640
            $this->index = $index;
1641
            if ($this->debug > 2) {
1642
                error_log('$index '.$index);
1643
                error_log('In learnpath::first() - No last item seen');
1644
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1645
            }
1646
        }
1647
        if ($this->debug > 2) {
1648
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1649
        }
1650
    }
1651
1652
    /**
1653
     * Gets the js library from the database.
1654
     *
1655
     * @return string The name of the javascript library to be used
1656
     */
1657
    public function get_js_lib()
1658
    {
1659
        $lib = '';
1660
        if (!empty($this->js_lib)) {
1661
            $lib = $this->js_lib;
1662
        }
1663
1664
        return $lib;
1665
    }
1666
1667
    /**
1668
     * Gets the learnpath database ID.
1669
     *
1670
     * @return int Learnpath ID in the lp table
1671
     */
1672
    public function get_id()
1673
    {
1674
        if (!empty($this->lp_id)) {
1675
            return (int) $this->lp_id;
1676
        }
1677
1678
        return 0;
1679
    }
1680
1681
    /**
1682
     * Gets the last element URL.
1683
     *
1684
     * @return string URL to load into the viewer
1685
     */
1686
    public function get_last()
1687
    {
1688
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1689
        if (count($this->ordered_items) > 0) {
1690
            $this->index = count($this->ordered_items) - 1;
1691
1692
            return $this->ordered_items[$this->index];
1693
        }
1694
1695
        return false;
1696
    }
1697
1698
    /**
1699
     * Get the last element in the first level.
1700
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1701
     *
1702
     * @return mixed
1703
     */
1704
    public function getLastInFirstLevel()
1705
    {
1706
        try {
1707
            $lastId = Database::getManager()
1708
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1709
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1710
                ->setMaxResults(1)
1711
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1712
                ->getSingleScalarResult();
1713
1714
            return $lastId;
1715
        } catch (Exception $exception) {
1716
            return 0;
1717
        }
1718
    }
1719
1720
    /**
1721
     * Get the learning path name by id.
1722
     *
1723
     * @param int $lpId
1724
     *
1725
     * @return mixed
1726
     */
1727
    public static function getLpNameById($lpId)
1728
    {
1729
        $em = Database::getManager();
1730
1731
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1732
            WHERE clp.iid = :iid')
1733
            ->setParameter('iid', $lpId)
1734
            ->getSingleScalarResult();
1735
    }
1736
1737
    /**
1738
     * Gets the navigation bar for the learnpath display screen.
1739
     *
1740
     * @param string $barId
1741
     *
1742
     * @return string The HTML string to use as a navigation bar
1743
     */
1744
    public function get_navigation_bar($barId = '')
1745
    {
1746
        if (empty($barId)) {
1747
            $barId = 'control-top';
1748
        }
1749
        $lpId = $this->lp_id;
1750
        $mycurrentitemid = $this->get_current_item_id();
1751
1752
        $reportingText = get_lang('Reporting');
1753
        $previousText = get_lang('Previous');
1754
        $nextText = get_lang('Next');
1755
        $fullScreenText = get_lang('Back to normal screen');
1756
1757
        $settings = api_get_configuration_value('lp_view_settings');
1758
        $display = isset($settings['display']) ? $settings['display'] : false;
1759
        $reportingIcon = '
1760
            <a class="icon-toolbar"
1761
                id="stats_link"
1762
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1763
                onclick="window.parent.API.save_asset(); return true;"
1764
                target="content_name" title="'.$reportingText.'">
1765
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1766
            </a>';
1767
1768
        if (!empty($display)) {
1769
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1770
            if (false === $showReporting) {
1771
                $reportingIcon = '';
1772
            }
1773
        }
1774
1775
        $hideArrows = false;
1776
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1777
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1778
        }
1779
1780
        $previousIcon = '';
1781
        $nextIcon = '';
1782
        if (false === $hideArrows) {
1783
            $previousIcon = '
1784
                <a class="icon-toolbar" id="scorm-previous" href="#"
1785
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1786
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1787
                </a>';
1788
1789
            $nextIcon = '
1790
                <a class="icon-toolbar" id="scorm-next" href="#"
1791
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1792
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1793
                </a>';
1794
        }
1795
1796
        if ('fullscreen' === $this->mode) {
1797
            $navbar = '
1798
                  <span id="'.$barId.'" class="buttons">
1799
                    '.$reportingIcon.'
1800
                    '.$previousIcon.'
1801
                    '.$nextIcon.'
1802
                    <a class="icon-toolbar" id="view-embedded"
1803
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1804
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1805
                    </a>
1806
                  </span>';
1807
        } else {
1808
            $navbar = '
1809
                 <span id="'.$barId.'" class="buttons text-right">
1810
                    '.$reportingIcon.'
1811
                    '.$previousIcon.'
1812
                    '.$nextIcon.'
1813
                </span>';
1814
        }
1815
1816
        return $navbar;
1817
    }
1818
1819
    /**
1820
     * Gets the next resource in queue (url).
1821
     *
1822
     * @return string URL to load into the viewer
1823
     */
1824
    public function get_next_index()
1825
    {
1826
        // TODO
1827
        $index = $this->index;
1828
        $index++;
1829
        while (
1830
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1831
            $index < $this->max_ordered_items
1832
        ) {
1833
            $index++;
1834
            if ($index == $this->max_ordered_items) {
1835
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1836
                    return $this->index;
1837
                }
1838
1839
                return $index;
1840
            }
1841
        }
1842
        if (empty($this->ordered_items[$index])) {
1843
            return $this->index;
1844
        }
1845
1846
        return $index;
1847
    }
1848
1849
    /**
1850
     * Gets item_id for the next element.
1851
     *
1852
     * @return int Next item (DB) ID
1853
     */
1854
    public function get_next_item_id()
1855
    {
1856
        $new_index = $this->get_next_index();
1857
        if (!empty($new_index)) {
1858
            if (isset($this->ordered_items[$new_index])) {
1859
                return $this->ordered_items[$new_index];
1860
            }
1861
        }
1862
1863
        return 0;
1864
    }
1865
1866
    /**
1867
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1868
     *
1869
     * Generally, the package provided is in the form of a zip file, so the function
1870
     * has been written to test a zip file. If not a zip, the function will return the
1871
     * default return value: ''
1872
     *
1873
     * @param string $file_path the path to the file
1874
     * @param string $file_name the original name of the file
1875
     *
1876
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package'
1877
     * if the package is empty, or '' if the package cannot be recognized
1878
     */
1879
    public static function getPackageType($file_path, $file_name)
1880
    {
1881
        // Get name of the zip file without the extension.
1882
        $file_info = pathinfo($file_name);
1883
        $extension = $file_info['extension']; // Extension only.
1884
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1885
                'dll',
1886
                'exe',
1887
            ])) {
1888
            return 'oogie';
1889
        }
1890
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1891
                'dll',
1892
                'exe',
1893
            ])) {
1894
            return 'woogie';
1895
        }
1896
1897
        $zipFile = new PclZip($file_path);
1898
        // Check the zip content (real size and file extension).
1899
        $zipContentArray = $zipFile->listContent();
1900
        $package_type = '';
1901
        $manifest = '';
1902
        $aicc_match_crs = 0;
1903
        $aicc_match_au = 0;
1904
        $aicc_match_des = 0;
1905
        $aicc_match_cst = 0;
1906
        $countItems = 0;
1907
1908
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1909
        if (is_array($zipContentArray)) {
1910
            $countItems = count($zipContentArray);
1911
            if ($countItems > 0) {
1912
                foreach ($zipContentArray as $thisContent) {
1913
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1914
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1915
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1916
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1917
                        $package_type = 'scorm';
1918
                        break; // Exit the foreach loop.
1919
                    } elseif (
1920
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1921
                        in_array(
1922
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1923
                            ['crs', 'au', 'des', 'cst']
1924
                        )
1925
                    ) {
1926
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1927
                        switch ($ext) {
1928
                            case 'crs':
1929
                                $aicc_match_crs = 1;
1930
                                break;
1931
                            case 'au':
1932
                                $aicc_match_au = 1;
1933
                                break;
1934
                            case 'des':
1935
                                $aicc_match_des = 1;
1936
                                break;
1937
                            case 'cst':
1938
                                $aicc_match_cst = 1;
1939
                                break;
1940
                            default:
1941
                                break;
1942
                        }
1943
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1944
                    } else {
1945
                        $package_type = '';
1946
                    }
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
            // Sometimes users will try to upload an empty zip, or a zip with
1959
            // only a folder. Catch that and make the calling function aware.
1960
            // If the single file was the imsmanifest.xml, then $package_type
1961
            // would be 'scorm' and we wouldn't be here.
1962
            if ($countItems < 2) {
1963
                return 'error-empty-package';
1964
            }
1965
            $package_type = 'chamilo';
1966
        }
1967
1968
        return $package_type;
1969
    }
1970
1971
    /**
1972
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1973
     *
1974
     * @return string URL to load into the viewer
1975
     */
1976
    public function get_previous_index()
1977
    {
1978
        $index = $this->index;
1979
        if (isset($this->ordered_items[$index - 1])) {
1980
            $index--;
1981
            while (isset($this->ordered_items[$index]) &&
1982
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
1983
            ) {
1984
                $index--;
1985
                if ($index < 0) {
1986
                    return $this->index;
1987
                }
1988
            }
1989
        }
1990
1991
        return $index;
1992
    }
1993
1994
    /**
1995
     * Gets item_id for the next element.
1996
     *
1997
     * @return int Previous item (DB) ID
1998
     */
1999
    public function get_previous_item_id()
2000
    {
2001
        $index = $this->get_previous_index();
2002
2003
        return $this->ordered_items[$index];
2004
    }
2005
2006
    /**
2007
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2008
     *
2009
     * @param int    $lpItemId
2010
     * @param string $autostart
2011
     *
2012
     * @return string The mediaplayer HTML
2013
     */
2014
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2015
    {
2016
        $course_id = api_get_course_int_id();
2017
        $courseInfo = api_get_course_info();
2018
        $lpItemId = (int) $lpItemId;
2019
2020
        if (empty($courseInfo) || empty($lpItemId)) {
2021
            return '';
2022
        }
2023
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2024
2025
        if (empty($item)) {
2026
            return '';
2027
        }
2028
2029
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2030
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2031
        $itemViewId = (int) $item->db_item_view_id;
2032
2033
        // Getting all the information about the item.
2034
        $sql = "SELECT lp_view.status
2035
                FROM $tbl_lp_item as lpi
2036
                INNER JOIN $tbl_lp_item_view as lp_view
2037
                ON (lpi.iid = lp_view.lp_item_id)
2038
                WHERE
2039
                    lp_view.iid = $itemViewId AND
2040
                    lpi.iid = $lpItemId AND
2041
                    lp_view.c_id = $course_id";
2042
        $result = Database::query($sql);
2043
        $row = Database::fetch_assoc($result);
2044
        $output = '';
2045
        $audio = $item->audio;
2046
2047
        if (!empty($audio)) {
2048
            $list = $_SESSION['oLP']->get_toc();
2049
2050
            switch ($item->get_type()) {
2051
                case 'quiz':
2052
                    $type_quiz = false;
2053
                    foreach ($list as $toc) {
2054
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2055
                            $type_quiz = true;
2056
                        }
2057
                    }
2058
2059
                    if ($type_quiz) {
2060
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2061
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2062
                        } else {
2063
                            $autostart_audio = $autostart;
2064
                        }
2065
                    }
2066
                    break;
2067
                case TOOL_READOUT_TEXT:
2068
                    $autostart_audio = 'false';
2069
                    break;
2070
                default:
2071
                    $autostart_audio = 'true';
2072
            }
2073
2074
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2075
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
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   $student_id
2180
     * @param array $courseInfo
2181
     * @param int   $sessionId
2182
     *
2183
     * @return bool
2184
     */
2185
    public static function is_lp_visible_for_student(
2186
        CLp $lp,
2187
        $student_id,
2188
        $courseInfo = [],
2189
        $sessionId = 0
2190
    ) {
2191
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2192
        $sessionId = (int) $sessionId;
2193
2194
        if (empty($courseInfo)) {
2195
            return false;
2196
        }
2197
2198
        if (empty($sessionId)) {
2199
            $sessionId = api_get_session_id();
2200
        }
2201
2202
        $courseId = $courseInfo['real_id'];
2203
2204
        /*$itemInfo = api_get_item_property_info(
2205
            $courseId,
2206
            TOOL_LEARNPATH,
2207
            $lp_id,
2208
            $sessionId
2209
        );*/
2210
2211
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2212
        // If the item was deleted.
2213
        if (false === $visibility) {
2214
            return false;
2215
        }
2216
2217
        $lp_id = $lp->getIid();
2218
        // @todo remove this query and load the row info as a parameter
2219
        $table = Database::get_course_table(TABLE_LP_MAIN);
2220
        // Get current prerequisite
2221
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2222
                FROM $table
2223
                WHERE iid = $lp_id";
2224
        $rs = Database::query($sql);
2225
        $now = time();
2226
        if (Database::num_rows($rs) > 0) {
2227
            $row = Database::fetch_array($rs, 'ASSOC');
2228
2229
            if (!empty($row['category_id'])) {
2230
                $em = Database::getManager();
2231
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2232
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2233
                    return false;
2234
                }
2235
            }
2236
2237
            $prerequisite = $row['prerequisite'];
2238
            $is_visible = true;
2239
2240
            $isBlocked = self::isBlockedByPrerequisite(
2241
                $student_id,
2242
                $prerequisite,
2243
                $courseInfo,
2244
                $sessionId
2245
            );
2246
2247
            if ($isBlocked) {
2248
                $is_visible = false;
2249
            }
2250
2251
            // Also check the time availability of the LP
2252
            if ($is_visible) {
2253
                // Adding visibility restrictions
2254
                if (!empty($row['publicated_on'])) {
2255
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2256
                        $is_visible = false;
2257
                    }
2258
                }
2259
                // Blocking empty start times see BT#2800
2260
                global $_custom;
2261
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2262
                    $_custom['lps_hidden_when_no_start_date']
2263
                ) {
2264
                    if (empty($row['publicated_on'])) {
2265
                        $is_visible = false;
2266
                    }
2267
                }
2268
2269
                if (!empty($row['expired_on'])) {
2270
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2271
                        $is_visible = false;
2272
                    }
2273
                }
2274
            }
2275
2276
            if ($is_visible) {
2277
                $subscriptionSettings = self::getSubscriptionSettings();
2278
2279
                // Check if the subscription users/group to a LP is ON
2280
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2281
                    true === $subscriptionSettings['allow_add_users_to_lp']
2282
                ) {
2283
                    // Try group
2284
                    $is_visible = false;
2285
                    // Checking only the user visibility
2286
                    $userVisibility = api_get_item_visibility(
2287
                        $courseInfo,
2288
                        'learnpath',
2289
                        $row['id'],
2290
                        $sessionId,
2291
                        $student_id,
2292
                        'LearnpathSubscription'
2293
                    );
2294
2295
                    if (1 == $userVisibility) {
2296
                        $is_visible = true;
2297
                    } else {
2298
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2299
                        if (!empty($userGroups)) {
2300
                            foreach ($userGroups as $groupInfo) {
2301
                                $groupId = $groupInfo['iid'];
2302
                                $userVisibility = api_get_item_visibility(
2303
                                    $courseInfo,
2304
                                    'learnpath',
2305
                                    $row['id'],
2306
                                    $sessionId,
2307
                                    null,
2308
                                    'LearnpathSubscription',
2309
                                    $groupId
2310
                                );
2311
2312
                                if (1 == $userVisibility) {
2313
                                    $is_visible = true;
2314
                                    break;
2315
                                }
2316
                            }
2317
                        }
2318
                    }
2319
                }
2320
            }
2321
2322
            return $is_visible;
2323
        }
2324
2325
        return false;
2326
    }
2327
2328
    /**
2329
     * @param int $lpId
2330
     * @param int $userId
2331
     * @param int $courseId
2332
     * @param int $sessionId
2333
     *
2334
     * @return int
2335
     */
2336
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2337
    {
2338
        $lpId = (int) $lpId;
2339
        $userId = (int) $userId;
2340
        $courseId = (int) $courseId;
2341
        $sessionId = (int) $sessionId;
2342
2343
        $sessionCondition = api_get_session_condition($sessionId);
2344
        $table = Database::get_course_table(TABLE_LP_VIEW);
2345
        $sql = "SELECT progress FROM $table
2346
                WHERE
2347
                    c_id = $courseId AND
2348
                    lp_id = $lpId AND
2349
                    user_id = $userId $sessionCondition ";
2350
        $res = Database::query($sql);
2351
2352
        $progress = 0;
2353
        if (Database::num_rows($res) > 0) {
2354
            $row = Database::fetch_array($res);
2355
            $progress = (int) $row['progress'];
2356
        }
2357
2358
        return $progress;
2359
    }
2360
2361
    /**
2362
     * @param array $lpList
2363
     * @param int   $userId
2364
     * @param int   $courseId
2365
     * @param int   $sessionId
2366
     *
2367
     * @return array
2368
     */
2369
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2370
    {
2371
        $lpList = array_map('intval', $lpList);
2372
        if (empty($lpList)) {
2373
            return [];
2374
        }
2375
2376
        $lpList = implode("','", $lpList);
2377
2378
        $userId = (int) $userId;
2379
        $courseId = (int) $courseId;
2380
        $sessionId = (int) $sessionId;
2381
2382
        $sessionCondition = api_get_session_condition($sessionId);
2383
        $table = Database::get_course_table(TABLE_LP_VIEW);
2384
        $sql = "SELECT lp_id, progress FROM $table
2385
                WHERE
2386
                    c_id = $courseId AND
2387
                    lp_id IN ('".$lpList."') AND
2388
                    user_id = $userId $sessionCondition ";
2389
        $res = Database::query($sql);
2390
2391
        if (Database::num_rows($res) > 0) {
2392
            $list = [];
2393
            while ($row = Database::fetch_array($res)) {
2394
                $list[$row['lp_id']] = $row['progress'];
2395
            }
2396
2397
            return $list;
2398
        }
2399
2400
        return [];
2401
    }
2402
2403
    /**
2404
     * Displays a progress bar
2405
     * completed so far.
2406
     *
2407
     * @param int    $percentage Progress value to display
2408
     * @param string $text_add   Text to display near the progress value
2409
     *
2410
     * @return string HTML string containing the progress bar
2411
     */
2412
    public static function get_progress_bar($percentage = -1, $text_add = '')
2413
    {
2414
        $text = $percentage.$text_add;
2415
        $output = '<div class="progress">
2416
            <div id="progress_bar_value"
2417
                class="progress-bar progress-bar-success" role="progressbar"
2418
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2419
            '.$text.'
2420
            </div>
2421
        </div>';
2422
2423
        return $output;
2424
    }
2425
2426
    /**
2427
     * @param string $mode can be '%' or 'abs'
2428
     *                     otherwise this value will be used $this->progress_bar_mode
2429
     *
2430
     * @return string
2431
     */
2432
    public function getProgressBar($mode = null)
2433
    {
2434
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2435
2436
        return self::get_progress_bar($percentage, $text_add);
2437
    }
2438
2439
    /**
2440
     * Gets the progress bar info to display inside the progress bar.
2441
     * Also used by scorm_api.php.
2442
     *
2443
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2444
     *                     we display a number of completed elements per total elements
2445
     * @param int    $add  Additional steps to fake as completed
2446
     *
2447
     * @return array Percentage or number and symbol (% or /xx)
2448
     */
2449
    public function get_progress_bar_text($mode = '', $add = 0)
2450
    {
2451
        if (empty($mode)) {
2452
            $mode = $this->progress_bar_mode;
2453
        }
2454
        $text = '';
2455
        $percentage = 0;
2456
        // If the option to use the score as progress is set for this learning
2457
        // path, then the rules are completely different: we assume only one
2458
        // item exists and the progress of the LP depends on the score
2459
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2460
        if (true === $scoreAsProgressSetting) {
2461
            $scoreAsProgress = $this->getUseScoreAsProgress();
2462
            if ($scoreAsProgress) {
2463
                // Get single item's score
2464
                $itemId = $this->get_current_item_id();
2465
                $item = $this->getItem($itemId);
2466
                $score = $item->get_score();
2467
                $maxScore = $item->get_max();
2468
                if ($mode = '%') {
2469
                    if (!empty($maxScore)) {
2470
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2471
                    }
2472
                    $percentage = number_format($percentage, 0);
2473
                    $text = '%';
2474
                } else {
2475
                    $percentage = $score;
2476
                    $text = '/'.$maxScore;
2477
                }
2478
2479
                return [$percentage, $text];
2480
            }
2481
        }
2482
        // otherwise just continue the normal processing of progress
2483
        $total_items = $this->getTotalItemsCountWithoutDirs();
2484
        $completeItems = $this->get_complete_items_count();
2485
        if (0 != $add) {
2486
            $completeItems += $add;
2487
        }
2488
        if ($completeItems > $total_items) {
2489
            $completeItems = $total_items;
2490
        }
2491
        if ('%' == $mode) {
2492
            if ($total_items > 0) {
2493
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2494
            }
2495
            $percentage = number_format($percentage, 0);
2496
            $text = '%';
2497
        } elseif ('abs' === $mode) {
2498
            $percentage = $completeItems;
2499
            $text = '/'.$total_items;
2500
        }
2501
2502
        return [
2503
            $percentage,
2504
            $text,
2505
        ];
2506
    }
2507
2508
    /**
2509
     * Gets the progress bar mode.
2510
     *
2511
     * @return string The progress bar mode attribute
2512
     */
2513
    public function get_progress_bar_mode()
2514
    {
2515
        if (!empty($this->progress_bar_mode)) {
2516
            return $this->progress_bar_mode;
2517
        }
2518
2519
        return '%';
2520
    }
2521
2522
    /**
2523
     * Gets the learnpath theme (remote or local).
2524
     *
2525
     * @return string Learnpath theme
2526
     */
2527
    public function get_theme()
2528
    {
2529
        if (!empty($this->theme)) {
2530
            return $this->theme;
2531
        }
2532
2533
        return '';
2534
    }
2535
2536
    /**
2537
     * Gets the learnpath session id.
2538
     *
2539
     * @return int
2540
     */
2541
    public function get_lp_session_id()
2542
    {
2543
        if (!empty($this->lp_session_id)) {
2544
            return (int) $this->lp_session_id;
2545
        }
2546
2547
        return 0;
2548
    }
2549
2550
    /**
2551
     * Gets the learnpath author.
2552
     *
2553
     * @return string LP's author
2554
     */
2555
    public function get_author()
2556
    {
2557
        if (!empty($this->author)) {
2558
            return $this->author;
2559
        }
2560
2561
        return '';
2562
    }
2563
2564
    /**
2565
     * Gets hide table of contents.
2566
     *
2567
     * @return int
2568
     */
2569
    public function getHideTableOfContents()
2570
    {
2571
        return (int) $this->hide_toc_frame;
2572
    }
2573
2574
    /**
2575
     * Generate a new prerequisites string for a given item. If this item was a sco and
2576
     * its prerequisites were strings (instead of IDs), then transform those strings into
2577
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2578
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2579
     * same rule as the scormExport() method.
2580
     *
2581
     * @param int $item_id Item ID
2582
     *
2583
     * @return string Prerequisites string ready for the export as SCORM
2584
     */
2585
    public function get_scorm_prereq_string($item_id)
2586
    {
2587
        if ($this->debug > 0) {
2588
            error_log('In learnpath::get_scorm_prereq_string()');
2589
        }
2590
        if (!is_object($this->items[$item_id])) {
2591
            return false;
2592
        }
2593
        /** @var learnpathItem $oItem */
2594
        $oItem = $this->items[$item_id];
2595
        $prereq = $oItem->get_prereq_string();
2596
2597
        if (empty($prereq)) {
2598
            return '';
2599
        }
2600
        if (preg_match('/^\d+$/', $prereq) &&
2601
            isset($this->items[$prereq]) &&
2602
            is_object($this->items[$prereq])
2603
        ) {
2604
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2605
            // then simply return it (with the ITEM_ prefix).
2606
            //return 'ITEM_' . $prereq;
2607
            return $this->items[$prereq]->ref;
2608
        } else {
2609
            if (isset($this->refs_list[$prereq])) {
2610
                // It's a simple string item from which the ID can be found in the refs list,
2611
                // so we can transform it directly to an ID for export.
2612
                return $this->items[$this->refs_list[$prereq]]->ref;
2613
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2614
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2615
            } else {
2616
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2617
                // and replace them, one by one, by the internal IDs (chamilo db)
2618
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2619
                // by a space as well.
2620
                $find = [
2621
                    '&',
2622
                    '|',
2623
                    '~',
2624
                    '=',
2625
                    '<>',
2626
                    '{',
2627
                    '}',
2628
                    '*',
2629
                    '(',
2630
                    ')',
2631
                ];
2632
                $replace = [
2633
                    ' ',
2634
                    ' ',
2635
                    ' ',
2636
                    ' ',
2637
                    ' ',
2638
                    ' ',
2639
                    ' ',
2640
                    ' ',
2641
                    ' ',
2642
                    ' ',
2643
                ];
2644
                $prereq_mod = str_replace($find, $replace, $prereq);
2645
                $ids = explode(' ', $prereq_mod);
2646
                foreach ($ids as $id) {
2647
                    $id = trim($id);
2648
                    if (isset($this->refs_list[$id])) {
2649
                        $prereq = preg_replace(
2650
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2651
                            'ITEM_'.$this->refs_list[$id],
2652
                            $prereq
2653
                        );
2654
                    }
2655
                }
2656
2657
                return $prereq;
2658
            }
2659
        }
2660
    }
2661
2662
    /**
2663
     * Returns the XML DOM document's node.
2664
     *
2665
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2666
     * @param string   $id       The identifier to look for
2667
     *
2668
     * @return mixed The reference to the element found with that identifier. False if not found
2669
     */
2670
    public function get_scorm_xml_node(&$children, $id)
2671
    {
2672
        for ($i = 0; $i < $children->length; $i++) {
2673
            $item_temp = $children->item($i);
2674
            if ('item' == $item_temp->nodeName) {
2675
                if ($item_temp->getAttribute('identifier') == $id) {
2676
                    return $item_temp;
2677
                }
2678
            }
2679
            $subchildren = $item_temp->childNodes;
2680
            if ($subchildren && $subchildren->length > 0) {
2681
                $val = $this->get_scorm_xml_node($subchildren, $id);
2682
                if (is_object($val)) {
2683
                    return $val;
2684
                }
2685
            }
2686
        }
2687
2688
        return false;
2689
    }
2690
2691
    /**
2692
     * Gets the status list for all LP's items.
2693
     *
2694
     * @return array Array of [index] => [item ID => current status]
2695
     */
2696
    public function get_items_status_list()
2697
    {
2698
        $list = [];
2699
        foreach ($this->ordered_items as $item_id) {
2700
            $list[] = [
2701
                $item_id => $this->items[$item_id]->get_status(),
2702
            ];
2703
        }
2704
2705
        return $list;
2706
    }
2707
2708
    /**
2709
     * Return the number of interactions for the given learnpath Item View ID.
2710
     * This method can be used as static.
2711
     *
2712
     * @param int $lp_iv_id  Item View ID
2713
     * @param int $course_id course id
2714
     *
2715
     * @return int
2716
     */
2717
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2718
    {
2719
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2720
        $lp_iv_id = (int) $lp_iv_id;
2721
        $course_id = (int) $course_id;
2722
2723
        $sql = "SELECT count(*) FROM $table
2724
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2725
        $res = Database::query($sql);
2726
        $num = 0;
2727
        if (Database::num_rows($res)) {
2728
            $row = Database::fetch_array($res);
2729
            $num = $row[0];
2730
        }
2731
2732
        return $num;
2733
    }
2734
2735
    /**
2736
     * Return the interactions as an array for the given lp_iv_id.
2737
     * This method can be used as static.
2738
     *
2739
     * @param int $lp_iv_id Learnpath Item View ID
2740
     *
2741
     * @return array
2742
     *
2743
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2744
     */
2745
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2746
    {
2747
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2748
        $list = [];
2749
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2750
        $lp_iv_id = (int) $lp_iv_id;
2751
2752
        if (empty($lp_iv_id) || empty($course_id)) {
2753
            return [];
2754
        }
2755
2756
        $sql = "SELECT * FROM $table
2757
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2758
                ORDER BY order_id ASC";
2759
        $res = Database::query($sql);
2760
        $num = Database::num_rows($res);
2761
        if ($num > 0) {
2762
            $list[] = [
2763
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2764
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2765
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2766
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2767
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2768
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2769
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2770
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2771
                'student_response_formatted' => '',
2772
            ];
2773
            while ($row = Database::fetch_array($res)) {
2774
                $studentResponseFormatted = urldecode($row['student_response']);
2775
                $content_student_response = explode('__|', $studentResponseFormatted);
2776
                if (count($content_student_response) > 0) {
2777
                    if (count($content_student_response) >= 3) {
2778
                        // Pop the element off the end of array.
2779
                        array_pop($content_student_response);
2780
                    }
2781
                    $studentResponseFormatted = implode(',', $content_student_response);
2782
                }
2783
2784
                $list[] = [
2785
                    'order_id' => $row['order_id'] + 1,
2786
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2787
                    'type' => $row['interaction_type'],
2788
                    'time' => $row['completion_time'],
2789
                    'correct_responses' => '', // Hide correct responses from students.
2790
                    'student_response' => $row['student_response'],
2791
                    'result' => $row['result'],
2792
                    'latency' => $row['latency'],
2793
                    'student_response_formatted' => $studentResponseFormatted,
2794
                ];
2795
            }
2796
        }
2797
2798
        return $list;
2799
    }
2800
2801
    /**
2802
     * Return the number of objectives for the given learnpath Item View ID.
2803
     * This method can be used as static.
2804
     *
2805
     * @param int $lp_iv_id  Item View ID
2806
     * @param int $course_id Course ID
2807
     *
2808
     * @return int Number of objectives
2809
     */
2810
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2811
    {
2812
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2813
        $course_id = (int) $course_id;
2814
        $lp_iv_id = (int) $lp_iv_id;
2815
        $sql = "SELECT count(*) FROM $table
2816
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2817
        //@todo seems that this always returns 0
2818
        $res = Database::query($sql);
2819
        $num = 0;
2820
        if (Database::num_rows($res)) {
2821
            $row = Database::fetch_array($res);
2822
            $num = $row[0];
2823
        }
2824
2825
        return $num;
2826
    }
2827
2828
    /**
2829
     * Return the objectives as an array for the given lp_iv_id.
2830
     * This method can be used as static.
2831
     *
2832
     * @param int $lpItemViewId Learnpath Item View ID
2833
     * @param int $course_id
2834
     *
2835
     * @return array
2836
     *
2837
     * @todo    Translate labels
2838
     */
2839
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2840
    {
2841
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2842
        $lpItemViewId = (int) $lpItemViewId;
2843
2844
        if (empty($course_id) || empty($lpItemViewId)) {
2845
            return [];
2846
        }
2847
2848
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2849
        $sql = "SELECT * FROM $table
2850
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2851
                ORDER BY order_id ASC";
2852
        $res = Database::query($sql);
2853
        $num = Database::num_rows($res);
2854
        $list = [];
2855
        if ($num > 0) {
2856
            $list[] = [
2857
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2858
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2859
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2860
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2861
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2862
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2863
            ];
2864
            while ($row = Database::fetch_array($res)) {
2865
                $list[] = [
2866
                    'order_id' => $row['order_id'] + 1,
2867
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2868
                    'score_raw' => $row['score_raw'],
2869
                    'score_max' => $row['score_max'],
2870
                    'score_min' => $row['score_min'],
2871
                    'status' => $row['status'],
2872
                ];
2873
            }
2874
        }
2875
2876
        return $list;
2877
    }
2878
2879
    /**
2880
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2881
     * used by get_html_toc() to be ready to display.
2882
     *
2883
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2884
     */
2885
    public function get_toc()
2886
    {
2887
        $toc = [];
2888
        foreach ($this->ordered_items as $item_id) {
2889
            // TODO: Change this link generation and use new function instead.
2890
            $toc[] = [
2891
                'id' => $item_id,
2892
                'title' => $this->items[$item_id]->get_title(),
2893
                'status' => $this->items[$item_id]->get_status(),
2894
                'level' => $this->items[$item_id]->get_level(),
2895
                'type' => $this->items[$item_id]->get_type(),
2896
                'description' => $this->items[$item_id]->get_description(),
2897
                'path' => $this->items[$item_id]->get_path(),
2898
                'parent' => $this->items[$item_id]->get_parent(),
2899
            ];
2900
        }
2901
2902
        return $toc;
2903
    }
2904
2905
    /**
2906
     * Returns the CSS class name associated with a given item status.
2907
     *
2908
     * @param $status string an item status
2909
     *
2910
     * @return string CSS class name
2911
     */
2912
    public static function getStatusCSSClassName($status)
2913
    {
2914
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2915
            return self::STATUS_CSS_CLASS_NAME[$status];
2916
        }
2917
2918
        return '';
2919
    }
2920
2921
    /**
2922
     * Generate the tree of contents for this learnpath as an associative array tree
2923
     * with keys id, title, status, type, description, path, parent_id, children
2924
     * (title and descriptions as secured)
2925
     * and clues for CSS class composition:
2926
     *  - booleans is_current, is_parent_of_current, is_chapter
2927
     *  - string status_css_class_name.
2928
     *
2929
     * @param $parentId int restrict returned list to children of this parent
2930
     *
2931
     * @return array TOC as a table
2932
     */
2933
    public function getTOCTree($parentId = 0)
2934
    {
2935
        $toc = [];
2936
        $currentItemId = $this->get_current_item_id();
2937
2938
        foreach ($this->ordered_items as $itemId) {
2939
            $item = $this->items[$itemId];
2940
            if ($item->get_parent() == $parentId) {
2941
                $title = $item->get_title();
2942
                if (empty($title)) {
2943
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
2944
                }
2945
2946
                $itemData = [
2947
                    'id' => $itemId,
2948
                    'title' => Security::remove_XSS($title),
2949
                    'status' => $item->get_status(),
2950
                    'level' => $item->get_level(), // FIXME should not be needed
2951
                    'type' => $item->get_type(),
2952
                    'description' => Security::remove_XSS($item->get_description()),
2953
                    'path' => $item->get_path(),
2954
                    'parent_id' => $item->get_parent(),
2955
                    'children' => $this->getTOCTree($itemId),
2956
                    'is_current' => ($itemId == $currentItemId),
2957
                    'is_parent_of_current' => false,
2958
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
2959
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
2960
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
2961
                ];
2962
2963
                if (!empty($itemData['children'])) {
2964
                    foreach ($itemData['children'] as $child) {
2965
                        if ($child['is_current'] || $child['is_parent_of_current']) {
2966
                            $itemData['is_parent_of_current'] = true;
2967
                            break;
2968
                        }
2969
                    }
2970
                }
2971
2972
                $toc[] = $itemData;
2973
            }
2974
        }
2975
2976
        return $toc;
2977
    }
2978
2979
    /**
2980
     * Generate and return the table of contents for this learnpath. The JS
2981
     * table returned is used inside of scorm_api.php.
2982
     *
2983
     * @param string $varname
2984
     *
2985
     * @return string A JS array variable construction
2986
     */
2987
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2988
    {
2989
        $toc = $varname.' = new Array();';
2990
        foreach ($this->ordered_items as $item_id) {
2991
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2992
        }
2993
2994
        return $toc;
2995
    }
2996
2997
    /**
2998
     * Gets the learning path type.
2999
     *
3000
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3001
     *
3002
     * @return mixed Type ID or name, depending on the parameter
3003
     */
3004
    public function get_type($get_name = false)
3005
    {
3006
        $res = false;
3007
        if (!empty($this->type) && (!$get_name)) {
3008
            $res = $this->type;
3009
        }
3010
3011
        return $res;
3012
    }
3013
3014
    /**
3015
     * Gets the learning path type as static method.
3016
     *
3017
     * @param int $lp_id
3018
     *
3019
     * @return mixed Type ID or name, depending on the parameter
3020
     */
3021
    public static function get_type_static($lp_id = 0)
3022
    {
3023
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3024
        $lp_id = (int) $lp_id;
3025
        $sql = "SELECT lp_type FROM $tbl_lp
3026
                WHERE iid = $lp_id";
3027
        $res = Database::query($sql);
3028
        if (false === $res) {
3029
            return null;
3030
        }
3031
        if (Database::num_rows($res) <= 0) {
3032
            return null;
3033
        }
3034
        $row = Database::fetch_array($res);
3035
3036
        return $row['lp_type'];
3037
    }
3038
3039
    /**
3040
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3041
     * This method can be used as abstract and is recursive.
3042
     *
3043
     * @param int $lp        Learnpath ID
3044
     * @param int $parent    Parent ID of the items to look for
3045
     * @param int $course_id
3046
     *
3047
     * @return array Ordered list of item IDs (empty array on error)
3048
     */
3049
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3050
    {
3051
        if (empty($course_id)) {
3052
            $course_id = api_get_course_int_id();
3053
        } else {
3054
            $course_id = (int) $course_id;
3055
        }
3056
        $list = [];
3057
3058
        if (empty($lp)) {
3059
            return $list;
3060
        }
3061
3062
        $lp = (int) $lp;
3063
        $parent = (int) $parent;
3064
3065
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3066
        $sql = "SELECT iid FROM $tbl_lp_item
3067
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3068
                ORDER BY display_order";
3069
3070
        $res = Database::query($sql);
3071
        while ($row = Database::fetch_array($res)) {
3072
            $sublist = self::get_flat_ordered_items_list(
3073
                $lp,
3074
                $row['iid'],
3075
                $course_id
3076
            );
3077
            $list[] = $row['iid'];
3078
            foreach ($sublist as $item) {
3079
                $list[] = $item;
3080
            }
3081
        }
3082
3083
        return $list;
3084
    }
3085
3086
    /**
3087
     * @return array
3088
     */
3089
    public static function getChapterTypes()
3090
    {
3091
        return [
3092
            'dir',
3093
        ];
3094
    }
3095
3096
    /**
3097
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3098
     *
3099
     * @param $tree
3100
     *
3101
     * @return array HTML TOC ready to display
3102
     */
3103
    public function getParentToc($tree)
3104
    {
3105
        if (empty($tree)) {
3106
            $tree = $this->get_toc();
3107
        }
3108
        $dirTypes = self::getChapterTypes();
3109
        $myCurrentId = $this->get_current_item_id();
3110
        $listParent = [];
3111
        $listChildren = [];
3112
        $listNotParent = [];
3113
        $list = [];
3114
        foreach ($tree as $subtree) {
3115
            if (in_array($subtree['type'], $dirTypes)) {
3116
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3117
                $subtree['children'] = $listChildren;
3118
                if (!empty($subtree['children'])) {
3119
                    foreach ($subtree['children'] as $subItem) {
3120
                        if ($subItem['id'] == $this->current) {
3121
                            $subtree['parent_current'] = 'in';
3122
                            $subtree['current'] = 'on';
3123
                        }
3124
                    }
3125
                }
3126
                $listParent[] = $subtree;
3127
            }
3128
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3129
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3130
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3131
                }
3132
3133
                $title = Security::remove_XSS($subtree['title']);
3134
                unset($subtree['title']);
3135
3136
                if (empty($title)) {
3137
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3138
                }
3139
                $classStyle = null;
3140
                if ($subtree['id'] == $this->current) {
3141
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3142
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3143
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3144
                }
3145
                $subtree['title'] = $title;
3146
                $subtree['class'] = $classStyle.' '.$cssStatus;
3147
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3148
                $subtree['current_id'] = $myCurrentId;
3149
                $listNotParent[] = $subtree;
3150
            }
3151
        }
3152
3153
        $list['are_parents'] = $listParent;
3154
        $list['not_parents'] = $listNotParent;
3155
3156
        return $list;
3157
    }
3158
3159
    /**
3160
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3161
     *
3162
     * @param array $tree
3163
     * @param int   $id
3164
     * @param bool  $parent
3165
     *
3166
     * @return array HTML TOC ready to display
3167
     */
3168
    public function getChildrenToc($tree, $id, $parent = true)
3169
    {
3170
        if (empty($tree)) {
3171
            $tree = $this->get_toc();
3172
        }
3173
3174
        $dirTypes = self::getChapterTypes();
3175
        $currentItemId = $this->get_current_item_id();
3176
        $list = [];
3177
3178
        foreach ($tree as $subtree) {
3179
            $subtree['tree'] = null;
3180
3181
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3182
                if ($subtree['id'] == $this->current) {
3183
                    $subtree['current'] = 'active';
3184
                } else {
3185
                    $subtree['current'] = null;
3186
                }
3187
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3188
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3189
                }
3190
3191
                $title = Security::remove_XSS($subtree['title']);
3192
                unset($subtree['title']);
3193
                if (empty($title)) {
3194
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3195
                }
3196
3197
                $classStyle = null;
3198
                if ($subtree['id'] == $this->current) {
3199
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3200
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3201
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3202
                }
3203
3204
                if (in_array($subtree['type'], $dirTypes)) {
3205
                    $subtree['title'] = stripslashes($title);
3206
                } else {
3207
                    $subtree['title'] = $title;
3208
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3209
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3210
                    $subtree['current_id'] = $currentItemId;
3211
                }
3212
                $list[] = $subtree;
3213
            }
3214
        }
3215
3216
        return $list;
3217
    }
3218
3219
    /**
3220
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3221
     *
3222
     * @param array $toc_list
3223
     *
3224
     * @return array HTML TOC ready to display
3225
     */
3226
    public function getListArrayToc($toc_list = [])
3227
    {
3228
        if (empty($toc_list)) {
3229
            $toc_list = $this->get_toc();
3230
        }
3231
        // Temporary variables.
3232
        $currentItemId = $this->get_current_item_id();
3233
        $list = [];
3234
        $arrayList = [];
3235
3236
        foreach ($toc_list as $item) {
3237
            $list['id'] = $item['id'];
3238
            $list['status'] = $item['status'];
3239
            $cssStatus = null;
3240
3241
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3242
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3243
            }
3244
3245
            $classStyle = ' ';
3246
            $dirTypes = self::getChapterTypes();
3247
3248
            if (in_array($item['type'], $dirTypes)) {
3249
                $classStyle = 'scorm_item_section ';
3250
            }
3251
            if ($item['id'] == $this->current) {
3252
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3253
            } elseif (!in_array($item['type'], $dirTypes)) {
3254
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3255
            }
3256
            $title = $item['title'];
3257
            if (empty($title)) {
3258
                $title = self::rl_get_resource_name(
3259
                    api_get_course_id(),
3260
                    $this->get_id(),
3261
                    $item['id']
3262
                );
3263
            }
3264
            $title = Security::remove_XSS($item['title']);
3265
3266
            if (empty($item['description'])) {
3267
                $list['description'] = $title;
3268
            } else {
3269
                $list['description'] = $item['description'];
3270
            }
3271
3272
            $list['class'] = $classStyle.' '.$cssStatus;
3273
            $list['level'] = $item['level'];
3274
            $list['type'] = $item['type'];
3275
3276
            if (in_array($item['type'], $dirTypes)) {
3277
                $list['css_level'] = 'level_'.$item['level'];
3278
            } else {
3279
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3280
            }
3281
3282
            if (in_array($item['type'], $dirTypes)) {
3283
                $list['title'] = stripslashes($title);
3284
            } else {
3285
                $list['title'] = stripslashes($title);
3286
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3287
                $list['current_id'] = $currentItemId;
3288
            }
3289
            $arrayList[] = $list;
3290
        }
3291
3292
        return $arrayList;
3293
    }
3294
3295
    /**
3296
     * Returns an HTML-formatted string ready to display with teacher buttons
3297
     * in LP view menu.
3298
     *
3299
     * @return string HTML TOC ready to display
3300
     */
3301
    public function get_teacher_toc_buttons()
3302
    {
3303
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3304
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3305
        $html = '';
3306
        if ($isAllow && false == $hideIcons) {
3307
            if ($this->get_lp_session_id() == api_get_session_id()) {
3308
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3309
                $html .= '<div class="btn-group">';
3310
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3311
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3312
                $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'>".
3313
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3314
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3315
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3316
                $html .= '</div>';
3317
                $html .= '</div>';
3318
            }
3319
        }
3320
3321
        return $html;
3322
    }
3323
3324
    /**
3325
     * Gets the learnpath maker name - generally the editor's name.
3326
     *
3327
     * @return string Learnpath maker name
3328
     */
3329
    public function get_maker()
3330
    {
3331
        if (!empty($this->maker)) {
3332
            return $this->maker;
3333
        }
3334
3335
        return '';
3336
    }
3337
3338
    /**
3339
     * Gets the learnpath name/title.
3340
     *
3341
     * @return string Learnpath name/title
3342
     */
3343
    public function get_name()
3344
    {
3345
        if (!empty($this->name)) {
3346
            return $this->name;
3347
        }
3348
3349
        return 'N/A';
3350
    }
3351
3352
    /**
3353
     * @return string
3354
     */
3355
    public function getNameNoTags()
3356
    {
3357
        return strip_tags($this->get_name());
3358
    }
3359
3360
    /**
3361
     * Gets a link to the resource from the present location, depending on item ID.
3362
     *
3363
     * @param string $type         Type of link expected
3364
     * @param int    $item_id      Learnpath item ID
3365
     * @param bool   $provided_toc
3366
     *
3367
     * @return string $provided_toc Link to the lp_item resource
3368
     */
3369
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3370
    {
3371
        $course_id = $this->get_course_int_id();
3372
        $item_id = (int) $item_id;
3373
3374
        if (empty($item_id)) {
3375
            $item_id = $this->get_current_item_id();
3376
3377
            if (empty($item_id)) {
3378
                //still empty, this means there was no item_id given and we are not in an object context or
3379
                //the object property is empty, return empty link
3380
                $this->first();
3381
3382
                return '';
3383
            }
3384
        }
3385
3386
        $file = '';
3387
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3388
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3389
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3390
3391
        $sql = "SELECT
3392
                    l.lp_type as ltype,
3393
                    l.path as lpath,
3394
                    li.item_type as litype,
3395
                    li.path as lipath,
3396
                    li.parameters as liparams
3397
        		FROM $lp_table l
3398
                INNER JOIN $lp_item_table li
3399
                ON (li.lp_id = l.iid)
3400
        		WHERE
3401
        		    li.iid = $item_id
3402
        		";
3403
        $res = Database::query($sql);
3404
        if (Database::num_rows($res) > 0) {
3405
            $row = Database::fetch_array($res);
3406
            $lp_type = $row['ltype'];
3407
            $lp_path = $row['lpath'];
3408
            $lp_item_type = $row['litype'];
3409
            $lp_item_path = $row['lipath'];
3410
            $lp_item_params = $row['liparams'];
3411
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3412
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3413
            }
3414
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3415
            if ('http' === $type) {
3416
                //web path
3417
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3418
            } else {
3419
                //$course_path = $sys_course_path; //system path
3420
            }
3421
3422
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3423
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3424
            if (in_array(
3425
                $lp_item_type,
3426
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3427
            )
3428
            ) {
3429
                $lp_type = CLp::LP_TYPE;
3430
            }
3431
3432
            // Now go through the specific cases to get the end of the path
3433
            // @todo Use constants instead of int values.
3434
            switch ($lp_type) {
3435
                case CLp::LP_TYPE:
3436
                    $file = self::rl_get_resource_link_for_learnpath(
3437
                        $course_id,
3438
                        $this->get_id(),
3439
                        $item_id,
3440
                        $this->get_view_id()
3441
                    );
3442
                    switch ($lp_item_type) {
3443
                        case 'document':
3444
                            // Shows a button to download the file instead of just downloading the file directly.
3445
                            $documentPathInfo = pathinfo($file);
3446
                            if (isset($documentPathInfo['extension'])) {
3447
                                $parsed = parse_url($documentPathInfo['extension']);
3448
                                if (isset($parsed['path'])) {
3449
                                    $extension = $parsed['path'];
3450
                                    $extensionsToDownload = [
3451
                                        'zip',
3452
                                        'ppt',
3453
                                        'pptx',
3454
                                        'ods',
3455
                                        'xlsx',
3456
                                        'xls',
3457
                                        'csv',
3458
                                        'doc',
3459
                                        'docx',
3460
                                        'dot',
3461
                                    ];
3462
3463
                                    if (in_array($extension, $extensionsToDownload)) {
3464
                                        $file = api_get_path(WEB_CODE_PATH).
3465
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3466
                                    }
3467
                                }
3468
                            }
3469
                            break;
3470
                        case 'dir':
3471
                            $file = 'lp_content.php?type=dir';
3472
                            break;
3473
                        case 'link':
3474
                            if (Link::is_youtube_link($file)) {
3475
                                $src = Link::get_youtube_video_id($file);
3476
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3477
                            } elseif (Link::isVimeoLink($file)) {
3478
                                $src = Link::getVimeoLinkId($file);
3479
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3480
                            } else {
3481
                                // If the current site is HTTPS and the link is
3482
                                // HTTP, browsers will refuse opening the link
3483
                                $urlId = api_get_current_access_url_id();
3484
                                $url = api_get_access_url($urlId, false);
3485
                                $protocol = substr($url['url'], 0, 5);
3486
                                if ('https' === $protocol) {
3487
                                    $linkProtocol = substr($file, 0, 5);
3488
                                    if ('http:' === $linkProtocol) {
3489
                                        //this is the special intervention case
3490
                                        $file = api_get_path(WEB_CODE_PATH).
3491
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
3492
                                    }
3493
                                }
3494
                            }
3495
                            break;
3496
                        case 'quiz':
3497
                            // Check how much attempts of a exercise exits in lp
3498
                            $lp_item_id = $this->get_current_item_id();
3499
                            $lp_view_id = $this->get_view_id();
3500
3501
                            $prevent_reinit = null;
3502
                            if (isset($this->items[$this->current])) {
3503
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3504
                            }
3505
3506
                            if (empty($provided_toc)) {
3507
                                $list = $this->get_toc();
3508
                            } else {
3509
                                $list = $provided_toc;
3510
                            }
3511
3512
                            $type_quiz = false;
3513
                            foreach ($list as $toc) {
3514
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
3515
                                    $type_quiz = true;
3516
                                }
3517
                            }
3518
3519
                            if ($type_quiz) {
3520
                                $lp_item_id = (int) $lp_item_id;
3521
                                $lp_view_id = (int) $lp_view_id;
3522
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3523
                                        WHERE
3524
                                            c_id = $course_id AND
3525
                                            lp_item_id='".$lp_item_id."' AND
3526
                                            lp_view_id ='".$lp_view_id."' AND
3527
                                            status='completed'";
3528
                                $result = Database::query($sql);
3529
                                $row_count = Database:: fetch_row($result);
3530
                                $count_item_view = (int) $row_count[0];
3531
                                $not_multiple_attempt = 0;
3532
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3533
                                    $not_multiple_attempt = 1;
3534
                                }
3535
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3536
                            }
3537
                            break;
3538
                    }
3539
3540
                    $tmp_array = explode('/', $file);
3541
                    $document_name = $tmp_array[count($tmp_array) - 1];
3542
                    if (strpos($document_name, '_DELETED_')) {
3543
                        $file = 'blank.php?error=document_deleted';
3544
                    }
3545
                    break;
3546
                case CLp::SCORM_TYPE:
3547
                    if ('dir' !== $lp_item_type) {
3548
                        // Quite complex here:
3549
                        // We want to make sure 'http://' (and similar) links can
3550
                        // be loaded as is (withouth the Chamilo path in front) but
3551
                        // some contents use this form: resource.htm?resource=http://blablabla
3552
                        // which means we have to find a protocol at the path's start, otherwise
3553
                        // it should not be considered as an external URL.
3554
                        // if ($this->prerequisites_match($item_id)) {
3555
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3556
                            if ($this->debug > 2) {
3557
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3558
                            }
3559
                            // Distant url, return as is.
3560
                            $file = $lp_item_path;
3561
                        } else {
3562
                            if ($this->debug > 2) {
3563
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
3564
                            }
3565
                            // Prevent getting untranslatable urls.
3566
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3567
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3568
3569
                            /*$asset = $this->getEntity()->getAsset();
3570
                            $folder = Container::getAssetRepository()->getFolder($asset);
3571
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
3572
                            $file = null;
3573
                            if ($hasFile) {
3574
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
3575
                            }*/
3576
                            $file = $this->scormUrl.$lp_item_path;
3577
3578
                            // Prepare the path.
3579
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3580
                            // TODO: Fix this for urls with protocol header.
3581
                            $file = str_replace('//', '/', $file);
3582
                            $file = str_replace(':/', '://', $file);
3583
                            if ('/' === substr($lp_path, -1)) {
3584
                                $lp_path = substr($lp_path, 0, -1);
3585
                            }*/
3586
                            /*if (!$hasFile) {
3587
                                // if file not found.
3588
                                $decoded = html_entity_decode($lp_item_path);
3589
                                [$decoded] = explode('?', $decoded);
3590
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3591
                                    $file = self::rl_get_resource_link_for_learnpath(
3592
                                        $course_id,
3593
                                        $this->get_id(),
3594
                                        $item_id,
3595
                                        $this->get_view_id()
3596
                                    );
3597
                                    if (empty($file)) {
3598
                                        $file = 'blank.php?error=document_not_found';
3599
                                    } else {
3600
                                        $tmp_array = explode('/', $file);
3601
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3602
                                        if (strpos($document_name, '_DELETED_')) {
3603
                                            $file = 'blank.php?error=document_deleted';
3604
                                        } else {
3605
                                            $file = 'blank.php?error=document_not_found';
3606
                                        }
3607
                                    }
3608
                                } else {
3609
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3610
                                }
3611
                            }*/
3612
                        }
3613
3614
                        // We want to use parameters if they were defined in the imsmanifest
3615
                        if (false === strpos($file, 'blank.php')) {
3616
                            $lp_item_params = ltrim($lp_item_params, '?');
3617
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3618
                        }
3619
                    } else {
3620
                        $file = 'lp_content.php?type=dir';
3621
                    }
3622
                    break;
3623
                case CLp::AICC_TYPE:
3624
                    // Formatting AICC HACP append URL.
3625
                    $aicc_append = '?aicc_sid='.
3626
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3627
                    if (!empty($lp_item_params)) {
3628
                        $aicc_append .= $lp_item_params.'&';
3629
                    }
3630
                    if ('dir' !== $lp_item_type) {
3631
                        // Quite complex here:
3632
                        // We want to make sure 'http://' (and similar) links can
3633
                        // be loaded as is (withouth the Chamilo path in front) but
3634
                        // some contents use this form: resource.htm?resource=http://blablabla
3635
                        // which means we have to find a protocol at the path's start, otherwise
3636
                        // it should not be considered as an external URL.
3637
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3638
                            if ($this->debug > 2) {
3639
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3640
                            }
3641
                            // Distant url, return as is.
3642
                            $file = $lp_item_path;
3643
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3644
                            /*
3645
                            if (stristr($file,'<servername>') !== false) {
3646
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3647
                            }
3648
                            */
3649
                            if (false !== stripos($file, '<servername>')) {
3650
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3651
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3652
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3653
                            }
3654
3655
                            $file .= $aicc_append;
3656
                        } else {
3657
                            if ($this->debug > 2) {
3658
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3659
                            }
3660
                            // Prevent getting untranslatable urls.
3661
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3662
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3663
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3664
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3665
                            // TODO: Fix this for urls with protocol header.
3666
                            $file = str_replace('//', '/', $file);
3667
                            $file = str_replace(':/', '://', $file);
3668
                            $file .= $aicc_append;
3669
                        }
3670
                    } else {
3671
                        $file = 'lp_content.php?type=dir';
3672
                    }
3673
                    break;
3674
                case 4:
3675
                default:
3676
                    break;
3677
            }
3678
            // Replace &amp; by & because &amp; will break URL with params
3679
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3680
        }
3681
        if ($this->debug > 2) {
3682
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3683
        }
3684
3685
        return $file;
3686
    }
3687
3688
    /**
3689
     * Gets the latest usable view or generate a new one.
3690
     *
3691
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3692
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3693
     *
3694
     * @return int DB lp_view id
3695
     */
3696
    public function get_view($attempt_num = 0, $userId = null)
3697
    {
3698
        $search = '';
3699
        // Use $attempt_num to enable multi-views management (disabled so far).
3700
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3701
            $search = 'AND view_count = '.$attempt_num;
3702
        }
3703
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3704
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3705
3706
        $course_id = api_get_course_int_id();
3707
        $sessionId = api_get_session_id();
3708
3709
        // Check user ID.
3710
        if (empty($userId)) {
3711
            if (empty($this->get_user_id())) {
3712
                $this->error = 'User ID is empty in learnpath::get_view()';
3713
3714
                return null;
3715
            } else {
3716
                $userId = $this->get_user_id();
3717
            }
3718
        }
3719
3720
        $sql = "SELECT iid, view_count FROM $lp_view_table
3721
        		WHERE
3722
        		    c_id = $course_id AND
3723
        		    lp_id = ".$this->get_id()." AND
3724
        		    user_id = ".$userId." AND
3725
        		    session_id = $sessionId
3726
        		    $search
3727
                ORDER BY view_count DESC";
3728
        $res = Database::query($sql);
3729
        if (Database::num_rows($res) > 0) {
3730
            $row = Database::fetch_array($res);
3731
            $this->lp_view_id = $row['iid'];
3732
        } elseif (!api_is_invitee()) {
3733
            // There is no database record, create one.
3734
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3735
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3736
            Database::query($sql);
3737
            $id = Database::insert_id();
3738
            $this->lp_view_id = $id;
3739
        }
3740
3741
        return $this->lp_view_id;
3742
    }
3743
3744
    /**
3745
     * Gets the current view id.
3746
     *
3747
     * @return int View ID (from lp_view)
3748
     */
3749
    public function get_view_id()
3750
    {
3751
        if (!empty($this->lp_view_id)) {
3752
            return (int) $this->lp_view_id;
3753
        }
3754
3755
        return 0;
3756
    }
3757
3758
    /**
3759
     * Gets the update queue.
3760
     *
3761
     * @return array Array containing IDs of items to be updated by JavaScript
3762
     */
3763
    public function get_update_queue()
3764
    {
3765
        return $this->update_queue;
3766
    }
3767
3768
    /**
3769
     * Gets the user ID.
3770
     *
3771
     * @return int User ID
3772
     */
3773
    public function get_user_id()
3774
    {
3775
        if (!empty($this->user_id)) {
3776
            return (int) $this->user_id;
3777
        }
3778
3779
        return false;
3780
    }
3781
3782
    /**
3783
     * Checks if any of the items has an audio element attached.
3784
     *
3785
     * @return bool True or false
3786
     */
3787
    public function has_audio()
3788
    {
3789
        $has = false;
3790
        foreach ($this->items as $i => $item) {
3791
            if (!empty($this->items[$i]->audio)) {
3792
                $has = true;
3793
                break;
3794
            }
3795
        }
3796
3797
        return $has;
3798
    }
3799
3800
    /**
3801
     * Moves an item up and down at its level.
3802
     *
3803
     * @param int    $id        Item to move up and down
3804
     * @param string $direction Direction 'up' or 'down'
3805
     *
3806
     * @return bool|int
3807
     */
3808
    public function move_item($id, $direction)
3809
    {
3810
        $course_id = api_get_course_int_id();
3811
        if (empty($id) || empty($direction)) {
3812
            return false;
3813
        }
3814
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3815
        $sql_sel = "SELECT *
3816
                    FROM $tbl_lp_item
3817
                    WHERE
3818
                        iid = $id
3819
                    ";
3820
        $res_sel = Database::query($sql_sel);
3821
        // Check if elem exists.
3822
        if (Database::num_rows($res_sel) < 1) {
3823
            return false;
3824
        }
3825
        // Gather data.
3826
        $row = Database::fetch_array($res_sel);
3827
        $previous = $row['previous_item_id'];
3828
        $next = $row['next_item_id'];
3829
        $display = $row['display_order'];
3830
        $parent = $row['parent_item_id'];
3831
        $lp = $row['lp_id'];
3832
        // Update the item (switch with previous/next one).
3833
        switch ($direction) {
3834
            case 'up':
3835
                if ($display > 1) {
3836
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3837
                                 WHERE iid = $previous";
3838
                    $res_sel2 = Database::query($sql_sel2);
3839
                    if (Database::num_rows($res_sel2) < 1) {
3840
                        $previous_previous = 0;
3841
                    }
3842
                    // Gather data.
3843
                    $row2 = Database::fetch_array($res_sel2);
3844
                    $previous_previous = $row2['previous_item_id'];
3845
                    // Update previous_previous item (switch "next" with current).
3846
                    if (0 != $previous_previous) {
3847
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3848
                                        next_item_id = $id
3849
                                    WHERE iid = $previous_previous";
3850
                        Database::query($sql_upd2);
3851
                    }
3852
                    // Update previous item (switch with current).
3853
                    if (0 != $previous) {
3854
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3855
                                    next_item_id = $next,
3856
                                    previous_item_id = $id,
3857
                                    display_order = display_order +1
3858
                                    WHERE iid = $previous";
3859
                        Database::query($sql_upd2);
3860
                    }
3861
3862
                    // Update current item (switch with previous).
3863
                    if (0 != $id) {
3864
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3865
                                        next_item_id = $previous,
3866
                                        previous_item_id = $previous_previous,
3867
                                        display_order = display_order-1
3868
                                    WHERE c_id = ".$course_id." AND id = $id";
3869
                        Database::query($sql_upd2);
3870
                    }
3871
                    // Update next item (new previous item).
3872
                    if (!empty($next)) {
3873
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3874
                                     WHERE iid = $next";
3875
                        Database::query($sql_upd2);
3876
                    }
3877
                    $display = $display - 1;
3878
                }
3879
                break;
3880
            case 'down':
3881
                if (0 != $next) {
3882
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3883
                                 WHERE iid = $next";
3884
                    $res_sel2 = Database::query($sql_sel2);
3885
                    if (Database::num_rows($res_sel2) < 1) {
3886
                        $next_next = 0;
3887
                    }
3888
                    // Gather data.
3889
                    $row2 = Database::fetch_array($res_sel2);
3890
                    $next_next = $row2['next_item_id'];
3891
                    // Update previous item (switch with current).
3892
                    if (0 != $previous) {
3893
                        $sql_upd2 = "UPDATE $tbl_lp_item
3894
                                     SET next_item_id = $next
3895
                                     WHERE iid = $previous";
3896
                        Database::query($sql_upd2);
3897
                    }
3898
                    // Update current item (switch with previous).
3899
                    if (0 != $id) {
3900
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3901
                                     previous_item_id = $next,
3902
                                     next_item_id = $next_next,
3903
                                     display_order = display_order + 1
3904
                                     WHERE iid = $id";
3905
                        Database::query($sql_upd2);
3906
                    }
3907
3908
                    // Update next item (new previous item).
3909
                    if (0 != $next) {
3910
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3911
                                     previous_item_id = $previous,
3912
                                     next_item_id = $id,
3913
                                     display_order = display_order-1
3914
                                     WHERE iid = $next";
3915
                        Database::query($sql_upd2);
3916
                    }
3917
3918
                    // Update next_next item (switch "previous" with current).
3919
                    if (0 != $next_next) {
3920
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3921
                                     previous_item_id = $id
3922
                                     WHERE iid = $next_next";
3923
                        Database::query($sql_upd2);
3924
                    }
3925
                    $display = $display + 1;
3926
                }
3927
                break;
3928
            default:
3929
                return false;
3930
        }
3931
3932
        return $display;
3933
    }
3934
3935
    /**
3936
     * Move a LP up (display_order).
3937
     *
3938
     * @param int $lp_id      Learnpath ID
3939
     * @param int $categoryId Category ID
3940
     *
3941
     * @return bool
3942
     */
3943
    public static function move_up($lp_id, $categoryId = 0)
3944
    {
3945
        $courseId = api_get_course_int_id();
3946
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3947
3948
        $categoryCondition = '';
3949
        if (!empty($categoryId)) {
3950
            $categoryId = (int) $categoryId;
3951
            $categoryCondition = " AND category_id = $categoryId";
3952
        }
3953
        $sql = "SELECT * FROM $lp_table
3954
                WHERE c_id = $courseId
3955
                $categoryCondition
3956
                ORDER BY display_order";
3957
        $res = Database::query($sql);
3958
        if (false === $res) {
3959
            return false;
3960
        }
3961
3962
        $lps = [];
3963
        $lp_order = [];
3964
        $num = Database::num_rows($res);
3965
        // First check the order is correct, globally (might be wrong because
3966
        // of versions < 1.8.4)
3967
        if ($num > 0) {
3968
            $i = 1;
3969
            while ($row = Database::fetch_array($res)) {
3970
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3971
                    $sql = "UPDATE $lp_table SET display_order = $i
3972
                            WHERE iid = ".$row['iid'];
3973
                    Database::query($sql);
3974
                }
3975
                $row['display_order'] = $i;
3976
                $lps[$row['iid']] = $row;
3977
                $lp_order[$i] = $row['iid'];
3978
                $i++;
3979
            }
3980
        }
3981
        if ($num > 1) { // If there's only one element, no need to sort.
3982
            $order = $lps[$lp_id]['display_order'];
3983
            if ($order > 1) { // If it's the first element, no need to move up.
3984
                $sql = "UPDATE $lp_table SET display_order = $order
3985
                        WHERE iid = ".$lp_order[$order - 1];
3986
                Database::query($sql);
3987
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3988
                        WHERE iid = $lp_id";
3989
                Database::query($sql);
3990
            }
3991
        }
3992
3993
        return true;
3994
    }
3995
3996
    /**
3997
     * Move a learnpath down (display_order).
3998
     *
3999
     * @param int $lp_id      Learnpath ID
4000
     * @param int $categoryId Category ID
4001
     *
4002
     * @return bool
4003
     */
4004
    public static function move_down($lp_id, $categoryId = 0)
4005
    {
4006
        $courseId = api_get_course_int_id();
4007
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4008
4009
        $categoryCondition = '';
4010
        if (!empty($categoryId)) {
4011
            $categoryId = (int) $categoryId;
4012
            $categoryCondition = " AND category_id = $categoryId";
4013
        }
4014
4015
        $sql = "SELECT * FROM $lp_table
4016
                WHERE c_id = $courseId
4017
                $categoryCondition
4018
                ORDER BY display_order";
4019
        $res = Database::query($sql);
4020
        if (false === $res) {
4021
            return false;
4022
        }
4023
        $lps = [];
4024
        $lp_order = [];
4025
        $num = Database::num_rows($res);
4026
        $max = 0;
4027
        // First check the order is correct, globally (might be wrong because
4028
        // of versions < 1.8.4).
4029
        if ($num > 0) {
4030
            $i = 1;
4031
            while ($row = Database::fetch_array($res)) {
4032
                $max = $i;
4033
                if ($row['display_order'] != $i) {
4034
                    // If we find a gap in the order, we need to fix it.
4035
                    $sql = "UPDATE $lp_table SET display_order = $i
4036
                              WHERE iid = ".$row['iid'];
4037
                    Database::query($sql);
4038
                }
4039
                $row['display_order'] = $i;
4040
                $lps[$row['iid']] = $row;
4041
                $lp_order[$i] = $row['iid'];
4042
                $i++;
4043
            }
4044
        }
4045
        if ($num > 1) { // If there's only one element, no need to sort.
4046
            $order = $lps[$lp_id]['display_order'];
4047
            if ($order < $max) { // If it's the first element, no need to move up.
4048
                $sql = "UPDATE $lp_table SET display_order = $order
4049
                        WHERE iid = ".$lp_order[$order + 1];
4050
                Database::query($sql);
4051
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4052
                        WHERE iid = $lp_id";
4053
                Database::query($sql);
4054
            }
4055
        }
4056
4057
        return true;
4058
    }
4059
4060
    /**
4061
     * Updates learnpath attributes to point to the next element
4062
     * The last part is similar to set_current_item but processing the other way around.
4063
     */
4064
    public function next()
4065
    {
4066
        if ($this->debug > 0) {
4067
            error_log('In learnpath::next()', 0);
4068
        }
4069
        $this->last = $this->get_current_item_id();
4070
        $this->items[$this->last]->save(
4071
            false,
4072
            $this->prerequisites_match($this->last)
4073
        );
4074
        $this->autocomplete_parents($this->last);
4075
        $new_index = $this->get_next_index();
4076
        if ($this->debug > 2) {
4077
            error_log('New index: '.$new_index, 0);
4078
        }
4079
        $this->index = $new_index;
4080
        if ($this->debug > 2) {
4081
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4082
        }
4083
        $this->current = $this->ordered_items[$new_index];
4084
        if ($this->debug > 2) {
4085
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4086
        }
4087
    }
4088
4089
    /**
4090
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4091
     * class, this might be redefined to allow several behaviours depending on the document type.
4092
     *
4093
     * @param int $id Resource ID
4094
     */
4095
    public function open($id)
4096
    {
4097
        // TODO:
4098
        // set the current resource attribute to this resource
4099
        // switch on element type (redefine in child class?)
4100
        // set status for this item to "opened"
4101
        // start timer
4102
        // initialise score
4103
        $this->index = 0; //or = the last item seen (see $this->last)
4104
    }
4105
4106
    /**
4107
     * Check that all prerequisites are fulfilled. Returns true and an
4108
     * empty string on success, returns false
4109
     * and the prerequisite string on error.
4110
     * This function is based on the rules for aicc_script language as
4111
     * described in the SCORM 1.2 CAM documentation page 108.
4112
     *
4113
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4114
     *
4115
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4116
     *              string otherwise
4117
     */
4118
    public function prerequisites_match($itemId = null)
4119
    {
4120
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4121
        if ($allow) {
4122
            if (api_is_allowed_to_edit() ||
4123
                api_is_platform_admin(true) ||
4124
                api_is_drh() ||
4125
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4126
            ) {
4127
                return true;
4128
            }
4129
        }
4130
4131
        $debug = $this->debug;
4132
        if ($debug > 0) {
4133
            error_log('In learnpath::prerequisites_match()');
4134
        }
4135
4136
        if (empty($itemId)) {
4137
            $itemId = $this->current;
4138
        }
4139
4140
        $currentItem = $this->getItem($itemId);
4141
4142
        if ($currentItem) {
4143
            if (2 == $this->type) {
4144
                // Getting prereq from scorm
4145
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4146
            } else {
4147
                $prereq_string = $currentItem->get_prereq_string();
4148
            }
4149
4150
            if (empty($prereq_string)) {
4151
                if ($debug > 0) {
4152
                    error_log('Found prereq_string is empty return true');
4153
                }
4154
4155
                return true;
4156
            }
4157
4158
            // Clean spaces.
4159
            $prereq_string = str_replace(' ', '', $prereq_string);
4160
            if ($debug > 0) {
4161
                error_log('Found prereq_string: '.$prereq_string, 0);
4162
            }
4163
4164
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4165
            $result = $currentItem->parse_prereq(
4166
                $prereq_string,
4167
                $this->items,
4168
                $this->refs_list,
4169
                $this->get_user_id()
4170
            );
4171
4172
            if (false === $result) {
4173
                $this->set_error_msg($currentItem->prereq_alert);
4174
            }
4175
        } else {
4176
            $result = true;
4177
            if ($debug > 1) {
4178
                error_log('$this->items['.$itemId.'] was not an object', 0);
4179
            }
4180
        }
4181
4182
        if ($debug > 1) {
4183
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4184
        }
4185
4186
        return $result;
4187
    }
4188
4189
    /**
4190
     * Updates learnpath attributes to point to the previous element
4191
     * The last part is similar to set_current_item but processing the other way around.
4192
     */
4193
    public function previous()
4194
    {
4195
        $this->last = $this->get_current_item_id();
4196
        $this->items[$this->last]->save(
4197
            false,
4198
            $this->prerequisites_match($this->last)
4199
        );
4200
        $this->autocomplete_parents($this->last);
4201
        $new_index = $this->get_previous_index();
4202
        $this->index = $new_index;
4203
        $this->current = $this->ordered_items[$new_index];
4204
    }
4205
4206
    /**
4207
     * Publishes a learnpath. This basically means show or hide the learnpath
4208
     * to normal users.
4209
     * Can be used as abstract.
4210
     *
4211
     * @param int $id         Learnpath ID
4212
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
4213
     *
4214
     * @return bool
4215
     */
4216
    public static function toggleVisibility($id, $visibility = 1)
4217
    {
4218
        $repo = Container::getLpRepository();
4219
        $lp = $repo->find($id);
4220
4221
        if (!$lp) {
4222
            return false;
4223
        }
4224
4225
        $visibility = (int) $visibility;
4226
4227
        if (1 === $visibility) {
4228
            $repo->setVisibilityPublished($lp);
4229
        } else {
4230
            $repo->setVisibilityDraft($lp);
4231
        }
4232
4233
        return true;
4234
4235
        /*$action = 'visible';
4236
        if (1 != $set_visibility) {
4237
            $action = 'invisible';
4238
            self::toggle_publish($lp_id, 'i');
4239
        }
4240
4241
        return api_item_property_update(
4242
            api_get_course_info(),
4243
            TOOL_LEARNPATH,
4244
            $lp_id,
4245
            $action,
4246
            api_get_user_id()
4247
        );*/
4248
    }
4249
4250
    /**
4251
     * Publishes a learnpath category.
4252
     * This basically means show or hide the learnpath category to normal users.
4253
     *
4254
     * @param int $id
4255
     * @param int $visibility
4256
     *
4257
     * @return bool
4258
     */
4259
    public static function toggleCategoryVisibility($id, $visibility = 1)
4260
    {
4261
        $repo = Container::getLpCategoryRepository();
4262
        $resource = $repo->find($id);
4263
4264
        if (!$resource) {
4265
            return false;
4266
        }
4267
4268
        $visibility = (int) $visibility;
4269
4270
        if (1 === $visibility) {
4271
            $repo->setVisibilityPublished($resource);
4272
        } else {
4273
            $repo->setVisibilityDraft($resource);
4274
            self::toggleCategoryPublish($id, 0);
4275
        }
4276
4277
        return false;
4278
        /*
4279
        $action = 'visible';
4280
        if (1 != $visibility) {
4281
            self::toggleCategoryPublish($id, 0);
4282
            $action = 'invisible';
4283
        }
4284
4285
        return api_item_property_update(
4286
            api_get_course_info(),
4287
            TOOL_LEARNPATH_CATEGORY,
4288
            $id,
4289
            $action,
4290
            api_get_user_id()
4291
        );*/
4292
    }
4293
4294
    /**
4295
     * Publishes a learnpath. This basically means show or hide the learnpath
4296
     * on the course homepage.
4297
     *
4298
     * @param int    $id            Learnpath id
4299
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4300
     *
4301
     * @return bool
4302
     */
4303
    public static function togglePublish($id, $setVisibility = 'v')
4304
    {
4305
        $addShortcut = false;
4306
        if ('v' === $setVisibility) {
4307
            $addShortcut = true;
4308
        }
4309
        $repo = Container::getLpRepository();
4310
        /** @var CLp $lp */
4311
        $lp = $repo->find($id);
4312
        if (null === $lp) {
4313
            return false;
4314
        }
4315
        $repoShortcut = Container::getShortcutRepository();
4316
        $courseEntity = api_get_course_entity();
4317
4318
        if ($addShortcut) {
4319
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4320
        } else {
4321
            $repoShortcut->removeShortCut($lp);
4322
        }
4323
4324
        return true;
4325
4326
        /*
4327
        $course_id = api_get_course_int_id();
4328
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4329
        $lp_id = (int) $lp_id;
4330
        $sql = "SELECT * FROM $tbl_lp
4331
                WHERE iid = $lp_id";
4332
        $result = Database::query($sql);
4333
4334
        if (Database::num_rows($result)) {
4335
            $row = Database::fetch_array($result);
4336
            $name = Database::escape_string($row['name']);
4337
            if ($set_visibility == 'i') {
4338
                $v = 0;
4339
            }
4340
            if ($set_visibility == 'v') {
4341
                $v = 1;
4342
            }
4343
4344
            $session_id = api_get_session_id();
4345
            $session_condition = api_get_session_condition($session_id);
4346
4347
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4348
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4349
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4350
4351
            $sql = "SELECT * FROM $tbl_tool
4352
                    WHERE
4353
                        c_id = $course_id AND
4354
                        (link = '$link' OR link = '$oldLink') AND
4355
                        image = 'scormbuilder.gif' AND
4356
                        (
4357
                            link LIKE '$link%' OR
4358
                            link LIKE '$oldLink%'
4359
                        )
4360
                        $session_condition
4361
                    ";
4362
4363
            $result = Database::query($sql);
4364
            $num = Database::num_rows($result);
4365
            if ($set_visibility == 'i' && $num > 0) {
4366
                $sql = "DELETE FROM $tbl_tool
4367
                        WHERE
4368
                            c_id = $course_id AND
4369
                            (link = '$link' OR link = '$oldLink') AND
4370
                            image='scormbuilder.gif'
4371
                            $session_condition";
4372
                Database::query($sql);
4373
            } elseif ($set_visibility == 'v' && $num == 0) {
4374
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4375
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4376
                Database::query($sql);
4377
                $insertId = Database::insert_id();
4378
                if ($insertId) {
4379
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4380
                    Database::query($sql);
4381
                }
4382
            } elseif ($set_visibility == 'v' && $num > 0) {
4383
                $sql = "UPDATE $tbl_tool SET
4384
                            c_id = $course_id,
4385
                            name = '$name',
4386
                            link = '$link',
4387
                            image = 'scormbuilder.gif',
4388
                            visibility = '$v',
4389
                            admin = '0',
4390
                            address = 'pastillegris.gif',
4391
                            added_tool = 0,
4392
                            session_id = $session_id
4393
                        WHERE
4394
                            c_id = ".$course_id." AND
4395
                            (link = '$link' OR link = '$oldLink') AND
4396
                            image='scormbuilder.gif'
4397
                            $session_condition
4398
                        ";
4399
                Database::query($sql);
4400
            } else {
4401
                // Parameter and database incompatible, do nothing, exit.
4402
                return false;
4403
            }
4404
        } else {
4405
            return false;
4406
        }*/
4407
    }
4408
4409
    /**
4410
     * Show or hide the learnpath category on the course homepage.
4411
     *
4412
     * @param int $id
4413
     * @param int $setVisibility
4414
     *
4415
     * @return bool
4416
     */
4417
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4418
    {
4419
        $setVisibility = (int) $setVisibility;
4420
        $addShortcut = false;
4421
        if (1 === $setVisibility) {
4422
            $addShortcut = true;
4423
        }
4424
4425
        $repo = Container::getLpCategoryRepository();
4426
        /** @var CLpCategory $lp */
4427
        $category = $repo->find($id);
4428
4429
        if (null === $category) {
4430
            return false;
4431
        }
4432
4433
        $repoShortcut = Container::getShortcutRepository();
4434
        if ($addShortcut) {
4435
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4436
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4437
        } else {
4438
            $repoShortcut->removeShortCut($category);
4439
        }
4440
4441
        return true;
4442
4443
        $em = Database::getManager();
0 ignored issues
show
Unused Code introduced by
$em = Database::getManager() 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...
4444
4445
        /** @var CLpCategory $category */
4446
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4447
4448
        if (!$category) {
4449
            return false;
4450
        }
4451
4452
        if (empty($courseId)) {
4453
            return false;
4454
        }
4455
4456
        $link = self::getCategoryLinkForTool($id);
4457
4458
        /** @var CTool $tool */
4459
        $tool = $em->createQuery("
4460
                SELECT t FROM ChamiloCourseBundle:CTool t
4461
                WHERE
4462
                    t.course = :course AND
4463
                    t.link = :link1 AND
4464
                    t.image LIKE 'lp_category.%' AND
4465
                    t.link LIKE :link2
4466
                    $sessionCondition
4467
            ")
4468
            ->setParameters([
4469
                'course' => $courseId,
4470
                'link1' => $link,
4471
                'link2' => "$link%",
4472
            ])
4473
            ->getOneOrNullResult();
4474
4475
        if (0 == $setVisibility && $tool) {
4476
            $em->remove($tool);
4477
            $em->flush();
4478
4479
            return true;
4480
        }
4481
4482
        if (1 == $setVisibility && !$tool) {
4483
            $tool = new CTool();
4484
            $tool
4485
                ->setCategory('authoring')
4486
                ->setCourse(api_get_course_entity($courseId))
4487
                ->setName(strip_tags($category->getName()))
4488
                ->setLink($link)
4489
                ->setImage('lp_category.png')
4490
                ->setVisibility(1)
4491
                ->setAdmin(0)
4492
                ->setAddress('pastillegris.gif')
4493
                ->setAddedTool(0)
4494
                ->setSessionId($sessionId)
4495
                ->setTarget('_self');
4496
4497
            $em->persist($tool);
4498
            $em->flush();
4499
4500
            $tool->setId($tool->getIid());
4501
4502
            $em->persist($tool);
4503
            $em->flush();
4504
4505
            return true;
4506
        }
4507
4508
        if (1 == $setVisibility && $tool) {
4509
            $tool
4510
                ->setName(strip_tags($category->getName()))
4511
                ->setVisibility(1);
4512
4513
            $em->persist($tool);
4514
            $em->flush();
4515
4516
            return true;
4517
        }
4518
4519
        return false;
4520
    }
4521
4522
    /**
4523
     * Check if the learnpath category is visible for a user.
4524
     *
4525
     * @param int
4526
     * @param int
4527
     *
4528
     * @return bool
4529
     */
4530
    public static function categoryIsVisibleForStudent(
4531
        CLpCategory $category,
4532
        User $user,
4533
        $courseId = 0,
4534
        $sessionId = 0
4535
    ) {
4536
        if (empty($category)) {
4537
            return false;
4538
        }
4539
4540
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4541
4542
        if ($isAllowedToEdit) {
4543
            return true;
4544
        }
4545
4546
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4547
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4548
4549
        $courseInfo = api_get_course_info_by_id($courseId);
4550
4551
        $categoryVisibility = api_get_item_visibility(
4552
            $courseInfo,
4553
            TOOL_LEARNPATH_CATEGORY,
4554
            $category->getId(),
4555
            $sessionId
4556
        );
4557
4558
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4559
            return false;
4560
        }
4561
4562
        $subscriptionSettings = self::getSubscriptionSettings();
4563
4564
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4565
            return true;
4566
        }
4567
4568
        $noUserSubscribed = false;
4569
        $noGroupSubscribed = true;
4570
        $users = $category->getUsers();
4571
        if (empty($users) || !$users->count()) {
4572
            $noUserSubscribed = true;
4573
        } elseif ($category->hasUserAdded($user)) {
4574
            return true;
4575
        }
4576
4577
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4578
        $em = Database::getManager();
4579
4580
        /** @var ItemPropertyRepository $itemRepo */
4581
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4582
4583
        /** @var CourseRepository $courseRepo */
4584
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4585
        $session = null;
4586
        if (!empty($sessionId)) {
4587
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4588
        }
4589
4590
        $course = $courseRepo->find($courseId);
4591
4592
        if (0 != $courseId) {
4593
            // Subscribed groups to a LP
4594
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4595
                    TOOL_LEARNPATH_CATEGORY,
4596
                    $category->getId(),
4597
                    $course,
4598
                    $session
4599
                );
4600
        }
4601
4602
        if (!empty($subscribedGroupsInLp)) {
4603
            $noGroupSubscribed = false;
4604
            if (!empty($groups)) {
4605
                $groups = array_column($groups, 'iid');
4606
                /** @var CItemProperty $item */
4607
                foreach ($subscribedGroupsInLp as $item) {
4608
                    if ($item->getGroup() &&
4609
                        in_array($item->getGroup()->getId(), $groups)
4610
                    ) {
4611
                        return true;
4612
                    }
4613
                }
4614
            }
4615
        }
4616
        $response = $noGroupSubscribed && $noUserSubscribed;
4617
4618
        return $response;
4619
    }
4620
4621
    /**
4622
     * Check if a learnpath category is published as course tool.
4623
     *
4624
     * @param int $courseId
4625
     *
4626
     * @return bool
4627
     */
4628
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4629
    {
4630
        return false;
4631
        $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...
4632
        $em = Database::getManager();
4633
4634
        $tools = $em
4635
            ->createQuery("
4636
                SELECT t FROM ChamiloCourseBundle:CTool t
4637
                WHERE t.course = :course AND
4638
                    t.name = :name AND
4639
                    t.image LIKE 'lp_category.%' AND
4640
                    t.link LIKE :link
4641
            ")
4642
            ->setParameters([
4643
                'course' => $courseId,
4644
                'name' => strip_tags($category->getName()),
4645
                'link' => "$link%",
4646
            ])
4647
            ->getResult();
4648
4649
        /** @var CTool $tool */
4650
        $tool = current($tools);
4651
4652
        return $tool ? $tool->getVisibility() : false;
4653
    }
4654
4655
    /**
4656
     * Restart the whole learnpath. Return the URL of the first element.
4657
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4658
     * To use a similar method  statically, use the create_new_attempt() method.
4659
     *
4660
     * @return bool
4661
     */
4662
    public function restart()
4663
    {
4664
        if ($this->debug > 0) {
4665
            error_log('In learnpath::restart()', 0);
4666
        }
4667
        // TODO
4668
        // Call autosave method to save the current progress.
4669
        //$this->index = 0;
4670
        if (api_is_invitee()) {
4671
            return false;
4672
        }
4673
        $session_id = api_get_session_id();
4674
        $course_id = api_get_course_int_id();
4675
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4676
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4677
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4678
        if ($this->debug > 2) {
4679
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4680
        }
4681
        Database::query($sql);
4682
        $view_id = Database::insert_id();
4683
4684
        if ($view_id) {
4685
            $this->lp_view_id = $view_id;
4686
            $this->attempt = $this->attempt + 1;
4687
        } else {
4688
            $this->error = 'Could not insert into item_view table...';
4689
4690
            return false;
4691
        }
4692
        $this->autocomplete_parents($this->current);
4693
        foreach ($this->items as $index => $dummy) {
4694
            $this->items[$index]->restart();
4695
            $this->items[$index]->set_lp_view($this->lp_view_id);
4696
        }
4697
        $this->first();
4698
4699
        return true;
4700
    }
4701
4702
    /**
4703
     * Saves the current item.
4704
     *
4705
     * @return bool
4706
     */
4707
    public function save_current()
4708
    {
4709
        $debug = $this->debug;
4710
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4711
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4712
        if ($debug) {
4713
            error_log('save_current() saving item '.$this->current, 0);
4714
            error_log(''.print_r($this->items, true), 0);
4715
        }
4716
        if (isset($this->items[$this->current]) &&
4717
            is_object($this->items[$this->current])
4718
        ) {
4719
            if ($debug) {
4720
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4721
            }
4722
4723
            $res = $this->items[$this->current]->save(
4724
                false,
4725
                $this->prerequisites_match($this->current)
4726
            );
4727
            $this->autocomplete_parents($this->current);
4728
            $status = $this->items[$this->current]->get_status();
4729
            $this->update_queue[$this->current] = $status;
4730
4731
            if ($debug) {
4732
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4733
            }
4734
4735
            return $res;
4736
        }
4737
4738
        return false;
4739
    }
4740
4741
    /**
4742
     * Saves the given item.
4743
     *
4744
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4745
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4746
     *
4747
     * @return bool
4748
     */
4749
    public function save_item($item_id = null, $from_outside = true)
4750
    {
4751
        $debug = $this->debug;
4752
        if ($debug) {
4753
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4754
        }
4755
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4756
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4757
        if (empty($item_id)) {
4758
            $item_id = (int) $_REQUEST['id'];
4759
        }
4760
4761
        if (empty($item_id)) {
4762
            $item_id = $this->get_current_item_id();
4763
        }
4764
        if (isset($this->items[$item_id]) &&
4765
            is_object($this->items[$item_id])
4766
        ) {
4767
            if ($debug) {
4768
                error_log('Object exists');
4769
            }
4770
4771
            // Saving the item.
4772
            $res = $this->items[$item_id]->save(
4773
                $from_outside,
4774
                $this->prerequisites_match($item_id)
4775
            );
4776
4777
            if ($debug) {
4778
                error_log('update_queue before:');
4779
                error_log(print_r($this->update_queue, 1));
4780
            }
4781
            $this->autocomplete_parents($item_id);
4782
4783
            $status = $this->items[$item_id]->get_status();
4784
            $this->update_queue[$item_id] = $status;
4785
4786
            if ($debug) {
4787
                error_log('get_status(): '.$status);
4788
                error_log('update_queue after:');
4789
                error_log(print_r($this->update_queue, 1));
4790
            }
4791
4792
            return $res;
4793
        }
4794
4795
        return false;
4796
    }
4797
4798
    /**
4799
     * Saves the last item seen's ID only in case.
4800
     */
4801
    public function save_last()
4802
    {
4803
        $course_id = api_get_course_int_id();
4804
        $debug = $this->debug;
4805
        if ($debug) {
4806
            error_log('In learnpath::save_last()', 0);
4807
        }
4808
        $session_condition = api_get_session_condition(
4809
            api_get_session_id(),
4810
            true,
4811
            false
4812
        );
4813
        $table = Database::get_course_table(TABLE_LP_VIEW);
4814
4815
        $userId = $this->get_user_id();
4816
        if (empty($userId)) {
4817
            $userId = api_get_user_id();
4818
            if ($debug) {
4819
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4820
            }
4821
        }
4822
        if (isset($this->current) && !api_is_invitee()) {
4823
            if ($debug) {
4824
                error_log('Saving current item ('.$this->current.') for later review', 0);
4825
            }
4826
            $sql = "UPDATE $table SET
4827
                        last_item = ".$this->get_current_item_id()."
4828
                    WHERE
4829
                        c_id = $course_id AND
4830
                        lp_id = ".$this->get_id()." AND
4831
                        user_id = ".$userId." ".$session_condition;
4832
4833
            if ($debug) {
4834
                error_log('Saving last item seen : '.$sql, 0);
4835
            }
4836
            Database::query($sql);
4837
        }
4838
4839
        if (!api_is_invitee()) {
4840
            // Save progress.
4841
            [$progress] = $this->get_progress_bar_text('%');
4842
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4843
            $scoreAsProgress = $this->getUseScoreAsProgress();
4844
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4845
                if ($debug) {
4846
                    error_log("Return false: Dont save score: $score");
4847
                    error_log("progress: $progress");
4848
                }
4849
4850
                return false;
4851
            }
4852
4853
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4854
                $storedProgress = self::getProgress(
4855
                    $this->get_id(),
4856
                    $userId,
4857
                    $course_id,
4858
                    $this->get_lp_session_id()
4859
                );
4860
4861
                // Check if the stored progress is higher than the new value
4862
                if ($storedProgress >= $progress) {
4863
                    if ($debug) {
4864
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4865
                    }
4866
4867
                    return false;
4868
                }
4869
            }
4870
            if ($progress >= 0 && $progress <= 100) {
4871
                $progress = (int) $progress;
4872
                $sql = "UPDATE $table SET
4873
                            progress = $progress
4874
                        WHERE
4875
                            c_id = $course_id AND
4876
                            lp_id = ".$this->get_id()." AND
4877
                            user_id = ".$userId." ".$session_condition;
4878
                // Ignore errors as some tables might not have the progress field just yet.
4879
                Database::query($sql);
4880
                $this->progress_db = $progress;
4881
            }
4882
        }
4883
    }
4884
4885
    /**
4886
     * Sets the current item ID (checks if valid and authorized first).
4887
     *
4888
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4889
     */
4890
    public function set_current_item($item_id = null)
4891
    {
4892
        $debug = $this->debug;
4893
        if ($debug) {
4894
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4895
        }
4896
        if (empty($item_id)) {
4897
            if ($debug) {
4898
                error_log('No new current item given, ignore...', 0);
4899
            }
4900
            // Do nothing.
4901
        } else {
4902
            if ($debug) {
4903
                error_log('New current item given is '.$item_id.'...', 0);
4904
            }
4905
            if (is_numeric($item_id)) {
4906
                $item_id = (int) $item_id;
4907
                // TODO: Check in database here.
4908
                $this->last = $this->current;
4909
                $this->current = $item_id;
4910
                // TODO: Update $this->index as well.
4911
                foreach ($this->ordered_items as $index => $item) {
4912
                    if ($item == $this->current) {
4913
                        $this->index = $index;
4914
                        break;
4915
                    }
4916
                }
4917
                if ($debug) {
4918
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4919
                }
4920
            } else {
4921
                if ($debug) {
4922
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4923
                }
4924
            }
4925
        }
4926
    }
4927
4928
    /**
4929
     * Sets the encoding.
4930
     *
4931
     * @param string $enc New encoding
4932
     *
4933
     * @return bool
4934
     *
4935
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4936
     */
4937
    public function set_encoding($enc = 'UTF-8')
4938
    {
4939
        $enc = api_refine_encoding_id($enc);
4940
        if (empty($enc)) {
4941
            $enc = api_get_system_encoding();
4942
        }
4943
        if (api_is_encoding_supported($enc)) {
4944
            $lp = $this->get_id();
4945
            if (0 != $lp) {
4946
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4947
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4948
                        WHERE iid = ".$lp;
4949
                $res = Database::query($sql);
4950
4951
                return $res;
4952
            }
4953
        }
4954
4955
        return false;
4956
    }
4957
4958
    /**
4959
     * Sets the JS lib setting in the database directly.
4960
     * This is the JavaScript library file this lp needs to load on startup.
4961
     *
4962
     * @param string $lib Proximity setting
4963
     *
4964
     * @return bool True on update success. False otherwise.
4965
     */
4966
    public function set_jslib($lib = '')
4967
    {
4968
        $lp = $this->get_id();
4969
4970
        if (0 != $lp) {
4971
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4972
            $lib = Database::escape_string($lib);
4973
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4974
                    WHERE iid = $lp";
4975
            $res = Database::query($sql);
4976
4977
            return $res;
4978
        }
4979
4980
        return false;
4981
    }
4982
4983
    /**
4984
     * Set index specified prefix terms for all items in this path.
4985
     *
4986
     * @param string $terms_string Comma-separated list of terms
4987
     * @param string $prefix       Xapian term prefix
4988
     *
4989
     * @return bool False on error, true otherwise
4990
     */
4991
    public function set_terms_by_prefix($terms_string, $prefix)
4992
    {
4993
        $course_id = api_get_course_int_id();
4994
        if ('true' !== api_get_setting('search_enabled')) {
4995
            return false;
4996
        }
4997
4998
        if (!extension_loaded('xapian')) {
4999
            return false;
5000
        }
5001
5002
        $terms_string = trim($terms_string);
5003
        $terms = explode(',', $terms_string);
5004
        array_walk($terms, 'trim_value');
5005
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5006
5007
        // Don't do anything if no change, verify only at DB, not the search engine.
5008
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5009
            return false;
5010
        }
5011
5012
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5013
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5014
5015
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5016
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5017
        $lp_id = (int) $_POST['lp_id'];
5018
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5019
        $result = Database::query($sql);
5020
        $di = new ChamiloIndexer();
5021
5022
        while ($lp_item = Database::fetch_array($result)) {
5023
            // Get search_did.
5024
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5025
            $sql = 'SELECT * FROM %s
5026
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5027
                    LIMIT 1';
5028
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5029
5030
            //echo $sql; echo '<br>';
5031
            $res = Database::query($sql);
5032
            if (Database::num_rows($res) > 0) {
5033
                $se_ref = Database::fetch_array($res);
5034
                // Compare terms.
5035
                $doc = $di->get_document($se_ref['search_did']);
5036
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5037
                $xterms = [];
5038
                foreach ($xapian_terms as $xapian_term) {
5039
                    $xterms[] = substr($xapian_term['name'], 1);
5040
                }
5041
5042
                $dterms = $terms;
5043
                $missing_terms = array_diff($dterms, $xterms);
5044
                $deprecated_terms = array_diff($xterms, $dterms);
5045
5046
                // Save it to search engine.
5047
                foreach ($missing_terms as $term) {
5048
                    $doc->add_term($prefix.$term, 1);
5049
                }
5050
                foreach ($deprecated_terms as $term) {
5051
                    $doc->remove_term($prefix.$term);
5052
                }
5053
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5054
                $di->getDb()->flush();
5055
            }
5056
        }
5057
5058
        return true;
5059
    }
5060
5061
    /**
5062
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5063
     *
5064
     * @param int $id DB ID of the item
5065
     */
5066
    public function set_previous_item($id)
5067
    {
5068
        if ($this->debug > 0) {
5069
            error_log('In learnpath::set_previous_item()', 0);
5070
        }
5071
        $this->last = $id;
5072
    }
5073
5074
    /**
5075
     * Sets use_max_score.
5076
     *
5077
     * @param int $use_max_score Optional string giving the new location of this learnpath
5078
     *
5079
     * @return bool True on success / False on error
5080
     */
5081
    public function set_use_max_score($use_max_score = 1)
5082
    {
5083
        $use_max_score = (int) $use_max_score;
5084
        $this->use_max_score = $use_max_score;
5085
        $table = Database::get_course_table(TABLE_LP_MAIN);
5086
        $lp_id = $this->get_id();
5087
        $sql = "UPDATE $table SET
5088
                    use_max_score = '".$this->use_max_score."'
5089
                WHERE iid = $lp_id";
5090
        Database::query($sql);
5091
5092
        return true;
5093
    }
5094
5095
    /**
5096
     * Sets and saves the expired_on date.
5097
     *
5098
     * @return bool Returns true if author's name is not empty
5099
     */
5100
    public function set_modified_on()
5101
    {
5102
        $this->modified_on = api_get_utc_datetime();
5103
        $table = Database::get_course_table(TABLE_LP_MAIN);
5104
        $lp_id = $this->get_id();
5105
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5106
                WHERE iid = $lp_id";
5107
        Database::query($sql);
5108
5109
        return true;
5110
    }
5111
5112
    /**
5113
     * Sets the object's error message.
5114
     *
5115
     * @param string $error Error message. If empty, reinits the error string
5116
     */
5117
    public function set_error_msg($error = '')
5118
    {
5119
        if ($this->debug > 0) {
5120
            error_log('In learnpath::set_error_msg()', 0);
5121
        }
5122
        if (empty($error)) {
5123
            $this->error = '';
5124
        } else {
5125
            $this->error .= $error;
5126
        }
5127
    }
5128
5129
    /**
5130
     * Launches the current item if not 'sco'
5131
     * (starts timer and make sure there is a record ready in the DB).
5132
     *
5133
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5134
     *
5135
     * @return bool
5136
     */
5137
    public function start_current_item($allow_new_attempt = false)
5138
    {
5139
        $debug = $this->debug;
5140
        if ($debug) {
5141
            error_log('In learnpath::start_current_item()');
5142
            error_log('current: '.$this->current);
5143
        }
5144
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5145
            $type = $this->get_type();
5146
            $item_type = $this->items[$this->current]->get_type();
5147
            if ((2 == $type && 'sco' != $item_type) ||
5148
                (3 == $type && 'au' != $item_type) ||
5149
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5150
            ) {
5151
                if ($debug) {
5152
                    error_log('item type: '.$item_type);
5153
                    error_log('lp type: '.$type);
5154
                }
5155
                $this->items[$this->current]->open($allow_new_attempt);
5156
                $this->autocomplete_parents($this->current);
5157
                $prereq_check = $this->prerequisites_match($this->current);
5158
                if ($debug) {
5159
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5160
                }
5161
                $this->items[$this->current]->save(false, $prereq_check);
5162
            }
5163
            // If sco, then it is supposed to have been updated by some other call.
5164
            if ('sco' == $item_type) {
5165
                $this->items[$this->current]->restart();
5166
            }
5167
        }
5168
        if ($debug) {
5169
            error_log('lp_view_session_id');
5170
            error_log($this->lp_view_session_id);
5171
            error_log('api session id');
5172
            error_log(api_get_session_id());
5173
            error_log('End of learnpath::start_current_item()');
5174
        }
5175
5176
        return true;
5177
    }
5178
5179
    /**
5180
     * Stops the processing and counters for the old item (as held in $this->last).
5181
     *
5182
     * @return bool True/False
5183
     */
5184
    public function stop_previous_item()
5185
    {
5186
        $debug = $this->debug;
5187
        if ($debug) {
5188
            error_log('In learnpath::stop_previous_item()', 0);
5189
        }
5190
5191
        if (0 != $this->last && $this->last != $this->current &&
5192
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5193
        ) {
5194
            if ($debug) {
5195
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5196
            }
5197
            switch ($this->get_type()) {
5198
                case '3':
5199
                    if ('au' != $this->items[$this->last]->get_type()) {
5200
                        if ($debug) {
5201
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5202
                        }
5203
                        $this->items[$this->last]->close();
5204
                    } else {
5205
                        if ($debug) {
5206
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5207
                        }
5208
                    }
5209
                    break;
5210
                case '2':
5211
                    if ('sco' != $this->items[$this->last]->get_type()) {
5212
                        if ($debug) {
5213
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5214
                        }
5215
                        $this->items[$this->last]->close();
5216
                    } else {
5217
                        if ($debug) {
5218
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5219
                        }
5220
                    }
5221
                    break;
5222
                case '1':
5223
                default:
5224
                    if ($debug) {
5225
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5226
                    }
5227
                    $this->items[$this->last]->close();
5228
                    break;
5229
            }
5230
        } else {
5231
            if ($debug) {
5232
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5233
            }
5234
5235
            return false;
5236
        }
5237
5238
        return true;
5239
    }
5240
5241
    /**
5242
     * Updates the default view mode from fullscreen to embedded and inversely.
5243
     *
5244
     * @return string The current default view mode ('fullscreen' or 'embedded')
5245
     */
5246
    public function update_default_view_mode()
5247
    {
5248
        $table = Database::get_course_table(TABLE_LP_MAIN);
5249
        $sql = "SELECT * FROM $table
5250
                WHERE iid = ".$this->get_id();
5251
        $res = Database::query($sql);
5252
        if (Database::num_rows($res) > 0) {
5253
            $row = Database::fetch_array($res);
5254
            $default_view_mode = $row['default_view_mod'];
5255
            $view_mode = $default_view_mode;
5256
            switch ($default_view_mode) {
5257
                case 'fullscreen': // default with popup
5258
                    $view_mode = 'embedded';
5259
                    break;
5260
                case 'embedded': // default view with left menu
5261
                    $view_mode = 'embedframe';
5262
                    break;
5263
                case 'embedframe': //folded menu
5264
                    $view_mode = 'impress';
5265
                    break;
5266
                case 'impress':
5267
                    $view_mode = 'fullscreen';
5268
                    break;
5269
            }
5270
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5271
                    WHERE iid = ".$this->get_id();
5272
            Database::query($sql);
5273
            $this->mode = $view_mode;
5274
5275
            return $view_mode;
5276
        }
5277
5278
        return -1;
5279
    }
5280
5281
    /**
5282
     * Updates the default behaviour about auto-commiting SCORM updates.
5283
     *
5284
     * @return bool True if auto-commit has been set to 'on', false otherwise
5285
     */
5286
    public function update_default_scorm_commit()
5287
    {
5288
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5289
        $sql = "SELECT * FROM $lp_table
5290
                WHERE iid = ".$this->get_id();
5291
        $res = Database::query($sql);
5292
        if (Database::num_rows($res) > 0) {
5293
            $row = Database::fetch_array($res);
5294
            $force = $row['force_commit'];
5295
            if (1 == $force) {
5296
                $force = 0;
5297
                $force_return = false;
5298
            } elseif (0 == $force) {
5299
                $force = 1;
5300
                $force_return = true;
5301
            }
5302
            $sql = "UPDATE $lp_table SET force_commit = $force
5303
                    WHERE iid = ".$this->get_id();
5304
            Database::query($sql);
5305
            $this->force_commit = $force_return;
5306
5307
            return $force_return;
5308
        }
5309
5310
        return -1;
5311
    }
5312
5313
    /**
5314
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5315
     *
5316
     * @return bool True on success, false on failure
5317
     */
5318
    public function update_display_order()
5319
    {
5320
        $course_id = api_get_course_int_id();
5321
        $table = Database::get_course_table(TABLE_LP_MAIN);
5322
        $sql = "SELECT * FROM $table
5323
                WHERE c_id = $course_id
5324
                ORDER BY display_order";
5325
        $res = Database::query($sql);
5326
        if (false === $res) {
5327
            return false;
5328
        }
5329
5330
        $num = Database::num_rows($res);
5331
        // First check the order is correct, globally (might be wrong because
5332
        // of versions < 1.8.4).
5333
        if ($num > 0) {
5334
            $i = 1;
5335
            while ($row = Database::fetch_array($res)) {
5336
                if ($row['display_order'] != $i) {
5337
                    // If we find a gap in the order, we need to fix it.
5338
                    $sql = "UPDATE $table SET display_order = $i
5339
                            WHERE iid = ".$row['iid'];
5340
                    Database::query($sql);
5341
                }
5342
                $i++;
5343
            }
5344
        }
5345
5346
        return true;
5347
    }
5348
5349
    /**
5350
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5351
     *
5352
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5353
     */
5354
    public function update_reinit()
5355
    {
5356
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5357
        $sql = "SELECT * FROM $lp_table
5358
                WHERE iid = ".$this->get_id();
5359
        $res = Database::query($sql);
5360
        if (Database::num_rows($res) > 0) {
5361
            $row = Database::fetch_array($res);
5362
            $force = $row['prevent_reinit'];
5363
            if (1 == $force) {
5364
                $force = 0;
5365
            } elseif (0 == $force) {
5366
                $force = 1;
5367
            }
5368
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5369
                    WHERE iid = ".$this->get_id();
5370
            Database::query($sql);
5371
            $this->prevent_reinit = $force;
5372
5373
            return $force;
5374
        }
5375
5376
        return -1;
5377
    }
5378
5379
    /**
5380
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5381
     *
5382
     * @return string 'single', 'multi' or 'seriousgame'
5383
     *
5384
     * @author ndiechburg <[email protected]>
5385
     */
5386
    public function get_attempt_mode()
5387
    {
5388
        //Set default value for seriousgame_mode
5389
        if (!isset($this->seriousgame_mode)) {
5390
            $this->seriousgame_mode = 0;
5391
        }
5392
        // Set default value for prevent_reinit
5393
        if (!isset($this->prevent_reinit)) {
5394
            $this->prevent_reinit = 1;
5395
        }
5396
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5397
            return 'seriousgame';
5398
        }
5399
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5400
            return 'single';
5401
        }
5402
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5403
            return 'multiple';
5404
        }
5405
5406
        return 'single';
5407
    }
5408
5409
    /**
5410
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5411
     *
5412
     * @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...
5413
     *
5414
     * @return bool
5415
     *
5416
     * @author ndiechburg <[email protected]>
5417
     */
5418
    public function set_attempt_mode($mode)
5419
    {
5420
        switch ($mode) {
5421
            case 'seriousgame':
5422
                $sg_mode = 1;
5423
                $prevent_reinit = 1;
5424
                break;
5425
            case 'single':
5426
                $sg_mode = 0;
5427
                $prevent_reinit = 1;
5428
                break;
5429
            case 'multiple':
5430
                $sg_mode = 0;
5431
                $prevent_reinit = 0;
5432
                break;
5433
            default:
5434
                $sg_mode = 0;
5435
                $prevent_reinit = 0;
5436
                break;
5437
        }
5438
        $this->prevent_reinit = $prevent_reinit;
5439
        $this->seriousgame_mode = $sg_mode;
5440
        $table = Database::get_course_table(TABLE_LP_MAIN);
5441
        $sql = "UPDATE $table SET
5442
                prevent_reinit = $prevent_reinit ,
5443
                seriousgame_mode = $sg_mode
5444
                WHERE iid = ".$this->get_id();
5445
        $res = Database::query($sql);
5446
        if ($res) {
5447
            return true;
5448
        } else {
5449
            return false;
5450
        }
5451
    }
5452
5453
    /**
5454
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5455
     *
5456
     * @author ndiechburg <[email protected]>
5457
     */
5458
    public function switch_attempt_mode()
5459
    {
5460
        $mode = $this->get_attempt_mode();
5461
        switch ($mode) {
5462
            case 'single':
5463
                $next_mode = 'multiple';
5464
                break;
5465
            case 'multiple':
5466
                $next_mode = 'seriousgame';
5467
                break;
5468
            case 'seriousgame':
5469
            default:
5470
                $next_mode = 'single';
5471
                break;
5472
        }
5473
        $this->set_attempt_mode($next_mode);
5474
    }
5475
5476
    /**
5477
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5478
     * but possibility to do again a completed item.
5479
     *
5480
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5481
     *
5482
     * @author ndiechburg <[email protected]>
5483
     */
5484
    public function set_seriousgame_mode()
5485
    {
5486
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5487
        $sql = "SELECT * FROM $lp_table
5488
                WHERE iid = ".$this->get_id();
5489
        $res = Database::query($sql);
5490
        if (Database::num_rows($res) > 0) {
5491
            $row = Database::fetch_array($res);
5492
            $force = $row['seriousgame_mode'];
5493
            if (1 == $force) {
5494
                $force = 0;
5495
            } elseif (0 == $force) {
5496
                $force = 1;
5497
            }
5498
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5499
			        WHERE iid = ".$this->get_id();
5500
            Database::query($sql);
5501
            $this->seriousgame_mode = $force;
5502
5503
            return $force;
5504
        }
5505
5506
        return -1;
5507
    }
5508
5509
    /**
5510
     * Updates the "scorm_debug" value that shows or hide the debug window.
5511
     *
5512
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5513
     */
5514
    public function update_scorm_debug()
5515
    {
5516
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5517
        $sql = "SELECT * FROM $lp_table
5518
                WHERE iid = ".$this->get_id();
5519
        $res = Database::query($sql);
5520
        if (Database::num_rows($res) > 0) {
5521
            $row = Database::fetch_array($res);
5522
            $force = $row['debug'];
5523
            if (1 == $force) {
5524
                $force = 0;
5525
            } elseif (0 == $force) {
5526
                $force = 1;
5527
            }
5528
            $sql = "UPDATE $lp_table SET debug = $force
5529
                    WHERE iid = ".$this->get_id();
5530
            Database::query($sql);
5531
            $this->scorm_debug = $force;
5532
5533
            return $force;
5534
        }
5535
5536
        return -1;
5537
    }
5538
5539
    /**
5540
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5541
     *
5542
     * @author Kevin Van Den Haute
5543
     *
5544
     * @param  array
5545
     */
5546
    public function tree_array($array)
5547
    {
5548
        $array = $this->sort_tree_array($array);
5549
        $this->create_tree_array($array);
5550
    }
5551
5552
    /**
5553
     * Creates an array with the elements of the learning path tree in it.
5554
     *
5555
     * @author Kevin Van Den Haute
5556
     *
5557
     * @param array $array
5558
     * @param int   $parent
5559
     * @param int   $depth
5560
     * @param array $tmp
5561
     */
5562
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5563
    {
5564
        if (is_array($array)) {
5565
            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...
5566
                if ($array[$i]['parent_item_id'] == $parent) {
5567
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5568
                        $tmp[] = $array[$i]['parent_item_id'];
5569
                        $depth++;
5570
                    }
5571
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5572
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5573
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5574
5575
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5576
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5577
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5578
                    $this->arrMenu[] = [
5579
                        'id' => $array[$i]['id'],
5580
                        'ref' => $ref,
5581
                        'item_type' => $array[$i]['item_type'],
5582
                        'title' => $array[$i]['title'],
5583
                        'title_raw' => $array[$i]['title_raw'],
5584
                        'path' => $path,
5585
                        'description' => $array[$i]['description'],
5586
                        'parent_item_id' => $array[$i]['parent_item_id'],
5587
                        'previous_item_id' => $array[$i]['previous_item_id'],
5588
                        'next_item_id' => $array[$i]['next_item_id'],
5589
                        'min_score' => $array[$i]['min_score'],
5590
                        'max_score' => $array[$i]['max_score'],
5591
                        'mastery_score' => $array[$i]['mastery_score'],
5592
                        'display_order' => $array[$i]['display_order'],
5593
                        'prerequisite' => $preq,
5594
                        'depth' => $depth,
5595
                        'audio' => $audio,
5596
                        'prerequisite_min_score' => $prerequisiteMinScore,
5597
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5598
                    ];
5599
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5600
                }
5601
            }
5602
        }
5603
    }
5604
5605
    /**
5606
     * Sorts a multi dimensional array by parent id and display order.
5607
     *
5608
     * @author Kevin Van Den Haute
5609
     *
5610
     * @param array $array (array with al the learning path items in it)
5611
     *
5612
     * @return array
5613
     */
5614
    public function sort_tree_array($array)
5615
    {
5616
        foreach ($array as $key => $row) {
5617
            $parent[$key] = $row['parent_item_id'];
5618
            $position[$key] = $row['display_order'];
5619
        }
5620
5621
        if (count($array) > 0) {
5622
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5623
        }
5624
5625
        return $array;
5626
    }
5627
5628
    /**
5629
     * Function that creates a html list of learning path items so that we can add audio files to them.
5630
     *
5631
     * @author Kevin Van Den Haute
5632
     *
5633
     * @return string
5634
     */
5635
    public function overview()
5636
    {
5637
        $return = '';
5638
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5639
5640
        // we need to start a form when we want to update all the mp3 files
5641
        if ('true' == $update_audio) {
5642
            $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">';
5643
        }
5644
        $return .= '<div id="message"></div>';
5645
        if (0 == count($this->items)) {
5646
            $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');
5647
        } else {
5648
            $return_audio = '<table class="table table-hover table-striped data_table">';
5649
            $return_audio .= '<tr>';
5650
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5651
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5652
            $return_audio .= '</tr>';
5653
5654
            if ('true' != $update_audio) {
5655
                $return .= '<div class="col-md-12">';
5656
                $return .= self::return_new_tree($update_audio);
5657
                $return .= '</div>';
5658
                $return .= Display::div(
5659
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5660
                    ['style' => 'float:left; margin-top:15px;width:100%']
5661
                );
5662
            } else {
5663
                $return_audio .= self::return_new_tree($update_audio);
5664
                $return .= $return_audio.'</table>';
5665
            }
5666
5667
            // We need to close the form when we are updating the mp3 files.
5668
            if ('true' == $update_audio) {
5669
                $return .= '<div class="footer-audio">';
5670
                $return .= Display::button(
5671
                    'save_audio',
5672
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5673
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5674
                );
5675
                $return .= '</div>';
5676
            }
5677
        }
5678
5679
        // We need to close the form when we are updating the mp3 files.
5680
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5681
            $return .= '</form>';
5682
        }
5683
5684
        return $return;
5685
    }
5686
5687
    /**
5688
     * @param string $update_audio
5689
     *
5690
     * @return array
5691
     */
5692
    public function processBuildMenuElements($update_audio = 'false')
5693
    {
5694
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5695
        $arrLP = $this->getItemsForForm();
5696
5697
        $this->tree_array($arrLP);
5698
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5699
        unset($this->arrMenu);
5700
        $default_data = null;
5701
        $default_content = null;
5702
        $elements = [];
5703
        $return_audio = null;
5704
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5705
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5706
        $countItems = count($arrLP);
5707
5708
        $upIcon = Display::return_icon(
5709
            'up.png',
5710
            get_lang('Up'),
5711
            [],
5712
            ICON_SIZE_TINY
5713
        );
5714
5715
        $disableUpIcon = Display::return_icon(
5716
            'up_na.png',
5717
            get_lang('Up'),
5718
            [],
5719
            ICON_SIZE_TINY
5720
        );
5721
5722
        $downIcon = Display::return_icon(
5723
            'down.png',
5724
            get_lang('Down'),
5725
            [],
5726
            ICON_SIZE_TINY
5727
        );
5728
5729
        $disableDownIcon = Display::return_icon(
5730
            'down_na.png',
5731
            get_lang('Down'),
5732
            [],
5733
            ICON_SIZE_TINY
5734
        );
5735
5736
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5737
5738
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5739
        $plugin = null;
5740
        if ($pluginCalendar) {
5741
            $plugin = LearningCalendarPlugin::create();
5742
        }
5743
5744
        for ($i = 0; $i < $countItems; $i++) {
5745
            $parent_id = $arrLP[$i]['parent_item_id'];
5746
            $title = $arrLP[$i]['title'];
5747
            $title_cut = $arrLP[$i]['title_raw'];
5748
            if (false === $show) {
5749
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5750
            }
5751
            // Link for the documents
5752
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5753
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5754
                $title_cut = Display::url(
5755
                    $title_cut,
5756
                    $url,
5757
                    [
5758
                        'class' => 'ajax moved',
5759
                        'data-title' => $title,
5760
                        'title' => $title,
5761
                    ]
5762
                );
5763
            }
5764
5765
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5766
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5767
                Session::write('pathItem', $arrLP[$i]['path']);
5768
            }
5769
5770
            $oddClass = 'row_even';
5771
            if (0 == ($i % 2)) {
5772
                $oddClass = 'row_odd';
5773
            }
5774
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5775
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5776
5777
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5778
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5779
            } else {
5780
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5781
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5782
                } else {
5783
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5784
                        $icon = Display::return_icon('certificate.png');
5785
                    } else {
5786
                        $icon = Display::return_icon('folder_document.png');
5787
                    }
5788
                }
5789
            }
5790
5791
            // The audio column.
5792
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5793
            $audio = '';
5794
            if (!$update_audio || 'true' != $update_audio) {
5795
                if (empty($arrLP[$i]['audio'])) {
5796
                    $audio .= '';
5797
                }
5798
            } else {
5799
                $types = self::getChapterTypes();
5800
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5801
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5802
                    if (!empty($arrLP[$i]['audio'])) {
5803
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5804
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5805
                    }
5806
                }
5807
            }
5808
5809
            $return_audio .= Display::span($icon.' '.$title).
5810
                Display::tag(
5811
                    'td',
5812
                    $audio,
5813
                    ['style' => '']
5814
                );
5815
            $return_audio .= '</td>';
5816
            $move_icon = '';
5817
            $move_item_icon = '';
5818
            $edit_icon = '';
5819
            $delete_icon = '';
5820
            $audio_icon = '';
5821
            $prerequisities_icon = '';
5822
            $forumIcon = '';
5823
            $previewIcon = '';
5824
            $pluginCalendarIcon = '';
5825
            $orderIcons = '';
5826
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5827
5828
            if ($is_allowed_to_edit) {
5829
                if (!$update_audio || 'true' != $update_audio) {
5830
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5831
                        $move_icon .= '<a class="moved" href="#">';
5832
                        $move_icon .= Display::return_icon(
5833
                            'move_everywhere.png',
5834
                            get_lang('Move'),
5835
                            [],
5836
                            ICON_SIZE_TINY
5837
                        );
5838
                        $move_icon .= '</a>';
5839
                    }
5840
                }
5841
5842
                // No edit for this item types
5843
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5844
                    if ('dir' != $arrLP[$i]['item_type']) {
5845
                        $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">';
5846
                        $edit_icon .= Display::return_icon(
5847
                            'edit.png',
5848
                            get_lang('Edit section description/name'),
5849
                            [],
5850
                            ICON_SIZE_TINY
5851
                        );
5852
                        $edit_icon .= '</a>';
5853
5854
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5855
                            $forumThread = null;
5856
                            if (isset($this->items[$arrLP[$i]['id']])) {
5857
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5858
                                    $this->course_int_id,
5859
                                    $this->lp_session_id
5860
                                );
5861
                            }
5862
                            if ($forumThread) {
5863
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5864
                                        'action' => 'dissociate_forum',
5865
                                        'id' => $arrLP[$i]['id'],
5866
                                        'lp_id' => $this->lp_id,
5867
                                    ]);
5868
                                $forumIcon = Display::url(
5869
                                    Display::return_icon(
5870
                                        'forum.png',
5871
                                        get_lang('Dissociate the forum of this learning path item'),
5872
                                        [],
5873
                                        ICON_SIZE_TINY
5874
                                    ),
5875
                                    $forumIconUrl,
5876
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
5877
                                );
5878
                            } else {
5879
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5880
                                        'action' => 'create_forum',
5881
                                        'id' => $arrLP[$i]['id'],
5882
                                        'lp_id' => $this->lp_id,
5883
                                    ]);
5884
                                $forumIcon = Display::url(
5885
                                    Display::return_icon(
5886
                                        'forum.png',
5887
                                        get_lang('Associate a forum to this learning path item'),
5888
                                        [],
5889
                                        ICON_SIZE_TINY
5890
                                    ),
5891
                                    $forumIconUrl,
5892
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
5893
                                );
5894
                            }
5895
                        }
5896
                    } else {
5897
                        $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">';
5898
                        $edit_icon .= Display::return_icon(
5899
                            'edit.png',
5900
                            get_lang('Edit section description/name'),
5901
                            [],
5902
                            ICON_SIZE_TINY
5903
                        );
5904
                        $edit_icon .= '</a>';
5905
                    }
5906
                } else {
5907
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5908
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
5909
                        $edit_icon .= Display::return_icon(
5910
                            'edit.png',
5911
                            get_lang('Edit'),
5912
                            [],
5913
                            ICON_SIZE_TINY
5914
                        );
5915
                        $edit_icon .= '</a>';
5916
                    }
5917
                }
5918
5919
                if ($pluginCalendar) {
5920
                    $pluginLink = $pluginUrl.
5921
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5922
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5923
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
5924
                    if ($itemInfo && 1 == $itemInfo['value']) {
5925
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5926
                    }
5927
                    $pluginCalendarIcon = Display::url(
5928
                        $iconCalendar,
5929
                        $pluginLink,
5930
                        ['class' => 'btn btn-default']
5931
                    );
5932
                }
5933
5934
                if ('final_item' != $arrLP[$i]['item_type']) {
5935
                    $orderIcons = Display::url(
5936
                        $upIcon,
5937
                        'javascript:void(0)',
5938
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
5939
                    );
5940
                    $orderIcons .= Display::url(
5941
                        $downIcon,
5942
                        'javascript:void(0)',
5943
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
5944
                    );
5945
                }
5946
5947
                $delete_icon .= ' <a
5948
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
5949
                    onclick="return confirmation(\''.addslashes($title).'\');"
5950
                    class="btn btn-default">';
5951
                $delete_icon .= Display::return_icon(
5952
                    'delete.png',
5953
                    get_lang('Delete section'),
5954
                    [],
5955
                    ICON_SIZE_TINY
5956
                );
5957
                $delete_icon .= '</a>';
5958
5959
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5960
                $previewImage = Display::return_icon(
5961
                    'preview_view.png',
5962
                    get_lang('Preview'),
5963
                    [],
5964
                    ICON_SIZE_TINY
5965
                );
5966
5967
                switch ($arrLP[$i]['item_type']) {
5968
                    case TOOL_DOCUMENT:
5969
                    case TOOL_LP_FINAL_ITEM:
5970
                    case TOOL_READOUT_TEXT:
5971
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5972
                        $previewIcon = Display::url(
5973
                            $previewImage,
5974
                            $urlPreviewLink,
5975
                            [
5976
                                'target' => '_blank',
5977
                                'class' => 'btn btn-default',
5978
                                'data-title' => $arrLP[$i]['title'],
5979
                                'title' => $arrLP[$i]['title'],
5980
                            ]
5981
                        );
5982
                        break;
5983
                    case TOOL_THREAD:
5984
                    case TOOL_FORUM:
5985
                    case TOOL_QUIZ:
5986
                    case TOOL_STUDENTPUBLICATION:
5987
                    case TOOL_LINK:
5988
                        $class = 'btn btn-default';
5989
                        $target = '_blank';
5990
                        $link = self::rl_get_resource_link_for_learnpath(
5991
                            $this->course_int_id,
5992
                            $this->lp_id,
5993
                            $arrLP[$i]['id'],
5994
                            0
5995
                        );
5996
                        $previewIcon = Display::url(
5997
                            $previewImage,
5998
                            $link,
5999
                            [
6000
                                'class' => $class,
6001
                                'data-title' => $arrLP[$i]['title'],
6002
                                'title' => $arrLP[$i]['title'],
6003
                                'target' => $target,
6004
                            ]
6005
                        );
6006
                        break;
6007
                    default:
6008
                        $previewIcon = Display::url(
6009
                            $previewImage,
6010
                            $url.'&action=view_item',
6011
                            ['class' => 'btn btn-default', 'target' => '_blank']
6012
                        );
6013
                        break;
6014
                }
6015
6016
                if ('dir' != $arrLP[$i]['item_type']) {
6017
                    $prerequisities_icon = Display::url(
6018
                        Display::return_icon(
6019
                            'accept.png',
6020
                            get_lang('Prerequisites'),
6021
                            [],
6022
                            ICON_SIZE_TINY
6023
                        ),
6024
                        $url.'&action=edit_item_prereq',
6025
                        ['class' => 'btn btn-default']
6026
                    );
6027
                    if ('final_item' != $arrLP[$i]['item_type']) {
6028
                        /*$move_item_icon = Display::url(
6029
                            Display::return_icon(
6030
                                'move.png',
6031
                                get_lang('Move'),
6032
                                [],
6033
                                ICON_SIZE_TINY
6034
                            ),
6035
                            $url.'&action=move_item',
6036
                            ['class' => 'btn btn-default']
6037
                        );*/
6038
                    }
6039
                    $audio_icon = Display::url(
6040
                        Display::return_icon(
6041
                            'audio.png',
6042
                            get_lang('Upload'),
6043
                            [],
6044
                            ICON_SIZE_TINY
6045
                        ),
6046
                        $url.'&action=add_audio',
6047
                        ['class' => 'btn btn-default']
6048
                    );
6049
                }
6050
            }
6051
            if ('true' != $update_audio) {
6052
                $row = $move_icon.' '.$icon.
6053
                    Display::span($title_cut).
6054
                    Display::tag(
6055
                        'div',
6056
                        "<div class=\"btn-group btn-group-xs\">
6057
                                    $previewIcon
6058
                                    $audio
6059
                                    $edit_icon
6060
                                    $pluginCalendarIcon
6061
                                    $forumIcon
6062
                                    $prerequisities_icon
6063
                                    $move_item_icon
6064
                                    $audio_icon
6065
                                    $orderIcons
6066
                                    $delete_icon
6067
                                </div>",
6068
                        ['class' => 'btn-toolbar button_actions']
6069
                    );
6070
            } else {
6071
                $row =
6072
                    Display::span($title.$icon).
6073
                    Display::span($audio, ['class' => 'button_actions']);
6074
            }
6075
6076
            $default_data[$arrLP[$i]['id']] = $row;
6077
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6078
6079
            if (empty($parent_id)) {
6080
                $elements[$arrLP[$i]['id']]['data'] = $row;
6081
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6082
            } else {
6083
                $parent_arrays = [];
6084
                if ($arrLP[$i]['depth'] > 1) {
6085
                    // Getting list of parents
6086
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6087
                        foreach ($arrLP as $item) {
6088
                            if ($item['id'] == $parent_id) {
6089
                                if (0 == $item['parent_item_id']) {
6090
                                    $parent_id = $item['id'];
6091
                                    break;
6092
                                } else {
6093
                                    $parent_id = $item['parent_item_id'];
6094
                                    if (empty($parent_arrays)) {
6095
                                        $parent_arrays[] = intval($item['id']);
6096
                                    }
6097
                                    $parent_arrays[] = $parent_id;
6098
                                    break;
6099
                                }
6100
                            }
6101
                        }
6102
                    }
6103
                }
6104
6105
                if (!empty($parent_arrays)) {
6106
                    $parent_arrays = array_reverse($parent_arrays);
6107
                    $val = '$elements';
6108
                    $x = 0;
6109
                    foreach ($parent_arrays as $item) {
6110
                        if ($x != count($parent_arrays) - 1) {
6111
                            $val .= '["'.$item.'"]["children"]';
6112
                        } else {
6113
                            $val .= '["'.$item.'"]["children"]';
6114
                        }
6115
                        $x++;
6116
                    }
6117
                    $val .= "";
6118
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6119
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6120
                } else {
6121
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6122
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6123
                }
6124
            }
6125
        }
6126
6127
        return [
6128
            'elements' => $elements,
6129
            'default_data' => $default_data,
6130
            'default_content' => $default_content,
6131
            'return_audio' => $return_audio,
6132
        ];
6133
    }
6134
6135
    /**
6136
     * @param string $updateAudio true/false strings
6137
     *
6138
     * @return string
6139
     */
6140
    public function returnLpItemList($updateAudio)
6141
    {
6142
        $result = $this->processBuildMenuElements($updateAudio);
6143
6144
        $html = self::print_recursive(
6145
            $result['elements'],
6146
            $result['default_data'],
6147
            $result['default_content']
6148
        );
6149
6150
        if (!empty($html)) {
6151
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6152
        }
6153
6154
        return $html;
6155
    }
6156
6157
    /**
6158
     * @param string $update_audio
6159
     * @param bool   $drop_element_here
6160
     *
6161
     * @return string
6162
     */
6163
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6164
    {
6165
        $result = $this->processBuildMenuElements($update_audio);
6166
6167
        $list = '<ul id="lp_item_list">';
6168
        $tree = $this->print_recursive(
6169
            $result['elements'],
6170
            $result['default_data'],
6171
            $result['default_content']
6172
        );
6173
6174
        if (!empty($tree)) {
6175
            $list .= $tree;
6176
        } else {
6177
            if ($drop_element_here) {
6178
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6179
            }
6180
        }
6181
        $list .= '</ul>';
6182
6183
        $return = Display::panelCollapse(
6184
            $this->name,
6185
            $list,
6186
            'scorm-list',
6187
            null,
6188
            'scorm-list-accordion',
6189
            'scorm-list-collapse'
6190
        );
6191
6192
        if ('true' === $update_audio) {
6193
            $return = $result['return_audio'];
6194
        }
6195
6196
        return $return;
6197
    }
6198
6199
    /**
6200
     * @param array $elements
6201
     * @param array $default_data
6202
     * @param array $default_content
6203
     *
6204
     * @return string
6205
     */
6206
    public function print_recursive($elements, $default_data, $default_content)
6207
    {
6208
        $return = '';
6209
        foreach ($elements as $key => $item) {
6210
            if (isset($item['load_data']) || empty($item['data'])) {
6211
                $item['data'] = $default_data[$item['load_data']];
6212
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6213
            }
6214
            $sub_list = '';
6215
            if (isset($item['type']) && 'dir' === $item['type']) {
6216
                // empty value
6217
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6218
            }
6219
            if (empty($item['children'])) {
6220
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6221
                $active = null;
6222
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6223
                    $active = 'active';
6224
                }
6225
                $return .= Display::tag(
6226
                    'li',
6227
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6228
                    ['id' => $key, 'class' => 'record li_container']
6229
                );
6230
            } else {
6231
                // Sections
6232
                $data = '';
6233
                if (isset($item['children'])) {
6234
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6235
                }
6236
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6237
                $return .= Display::tag(
6238
                    'li',
6239
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6240
                    ['id' => $key, 'class' => 'record li_container']
6241
                );
6242
            }
6243
        }
6244
6245
        return $return;
6246
    }
6247
6248
    /**
6249
     * This function builds the action menu.
6250
     *
6251
     * @param bool   $returnString           Optional
6252
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6253
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6254
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6255
     * @param string $action
6256
     * @param array  $extraField
6257
     *
6258
     * @return string
6259
     */
6260
    public function build_action_menu(
6261
        $returnString = false,
6262
        $showRequirementButtons = true,
6263
        $isConfigPage = false,
6264
        $allowExpand = true,
6265
        $action = '',
6266
        $extraField = []
6267
    ) {
6268
        $actionsRight = '';
6269
        $lpId = $this->lp_id;
6270
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6271
            $back = Display::url(
6272
                Display::return_icon(
6273
                    'back.png',
6274
                    get_lang('Back to learning paths'),
6275
                    '',
6276
                    ICON_SIZE_MEDIUM
6277
                ),
6278
                'lp_controller.php?'.api_get_cidreq()
6279
            );
6280
        } else {
6281
            $back = Display::url(
6282
                Display::return_icon(
6283
                    'back.png',
6284
                    get_lang('Back'),
6285
                    '',
6286
                    ICON_SIZE_MEDIUM
6287
                ),
6288
                $extraField['backTo']
6289
            );
6290
        }
6291
6292
        /*if ($backToBuild) {
6293
            $back = Display::url(
6294
                Display::return_icon(
6295
                    'back.png',
6296
                    get_lang('GoBack'),
6297
                    '',
6298
                    ICON_SIZE_MEDIUM
6299
                ),
6300
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6301
            );
6302
        }*/
6303
6304
        $actionsLeft = $back;
6305
6306
        $actionsLeft .= Display::url(
6307
            Display::return_icon(
6308
                'preview_view.png',
6309
                get_lang('Preview'),
6310
                '',
6311
                ICON_SIZE_MEDIUM
6312
            ),
6313
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6314
                'action' => 'view',
6315
                'lp_id' => $lpId,
6316
                'isStudentView' => 'true',
6317
            ])
6318
        );
6319
6320
        $actionsLeft .= Display::url(
6321
            Display::return_icon(
6322
                'upload_audio.png',
6323
                get_lang('Add audio'),
6324
                '',
6325
                ICON_SIZE_MEDIUM
6326
            ),
6327
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6328
                'action' => 'admin_view',
6329
                'lp_id' => $lpId,
6330
                'updateaudio' => 'true',
6331
            ])
6332
        );
6333
6334
        $subscriptionSettings = self::getSubscriptionSettings();
6335
6336
        $request = api_request_uri();
6337
        if (false === strpos($request, 'edit')) {
6338
            $actionsLeft .= Display::url(
6339
                Display::return_icon(
6340
                    'settings.png',
6341
                    get_lang('Course settings'),
6342
                    '',
6343
                    ICON_SIZE_MEDIUM
6344
                ),
6345
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6346
                    'action' => 'edit',
6347
                    'lp_id' => $lpId,
6348
                ])
6349
            );
6350
        }
6351
6352
        if ((false === strpos($request, 'build') &&
6353
            false === strpos($request, 'add_item')) ||
6354
            in_array($action, ['add_audio'])
6355
        ) {
6356
            $actionsLeft .= Display::url(
6357
                Display::return_icon(
6358
                    'edit.png',
6359
                    get_lang('Edit'),
6360
                    '',
6361
                    ICON_SIZE_MEDIUM
6362
                ),
6363
                'lp_controller.php?'.http_build_query([
6364
                    'action' => 'build',
6365
                    'lp_id' => $lpId,
6366
                ]).'&'.api_get_cidreq()
6367
            );
6368
        }
6369
6370
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6371
            if (1 == $this->subscribeUsers &&
6372
                $subscriptionSettings['allow_add_users_to_lp']) {
6373
                $actionsLeft .= Display::url(
6374
                    Display::return_icon(
6375
                        'user.png',
6376
                        get_lang('Subscribe users to learning path'),
6377
                        '',
6378
                        ICON_SIZE_MEDIUM
6379
                    ),
6380
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6381
                );
6382
            }
6383
        }
6384
6385
        if ($allowExpand) {
6386
            $actionsLeft .= Display::url(
6387
                Display::return_icon(
6388
                    'expand.png',
6389
                    get_lang('Expand'),
6390
                    ['id' => 'expand'],
6391
                    ICON_SIZE_MEDIUM
6392
                ).
6393
                Display::return_icon(
6394
                    'contract.png',
6395
                    get_lang('Collapse'),
6396
                    ['id' => 'contract', 'class' => 'hide'],
6397
                    ICON_SIZE_MEDIUM
6398
                ),
6399
                '#',
6400
                ['role' => 'button', 'id' => 'hide_bar_template']
6401
            );
6402
        }
6403
6404
        if ($showRequirementButtons) {
6405
            $buttons = [
6406
                [
6407
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6408
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6409
                        'action' => 'set_previous_step_as_prerequisite',
6410
                        'lp_id' => $lpId,
6411
                    ]),
6412
                ],
6413
                [
6414
                    'title' => get_lang('Clear all prerequisites'),
6415
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6416
                        'action' => 'clear_prerequisites',
6417
                        'lp_id' => $lpId,
6418
                    ]),
6419
                ],
6420
            ];
6421
            $actionsRight = Display::groupButtonWithDropDown(
6422
                get_lang('Prerequisites options'),
6423
                $buttons,
6424
                true
6425
            );
6426
        }
6427
6428
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6429
            $actionsLeft .= Display::url(
6430
                Display::return_icon(
6431
                    'add-groups.png',
6432
                    get_lang('Author'),
6433
                    '',
6434
                    ICON_SIZE_MEDIUM
6435
                ),
6436
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6437
                    'action' => 'author_view',
6438
                    'lp_id' => $lpId,
6439
                ])
6440
            );
6441
        }
6442
6443
        $toolbar = Display::toolbarAction(
6444
            'actions-lp-controller',
6445
            [$actionsLeft, $actionsRight]
6446
        );
6447
6448
        if ($returnString) {
6449
            return $toolbar;
6450
        }
6451
6452
        echo $toolbar;
6453
    }
6454
6455
    /**
6456
     * Creates the default learning path folder.
6457
     *
6458
     * @param array $course
6459
     * @param int   $creatorId
6460
     *
6461
     * @return bool
6462
     */
6463
    public static function generate_learning_path_folder($course, $creatorId = 0)
6464
    {
6465
        // Creating learning_path folder
6466
        $dir = 'learning_path';
6467
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6468
        $folder = false;
6469
6470
        $folderData = create_unexisting_directory(
6471
            $course,
6472
            $creatorId,
6473
            0,
6474
            null,
6475
            0,
6476
            '',
6477
            $dir,
6478
            get_lang('Learning paths'),
6479
            0
6480
        );
6481
6482
        if (!empty($folderData)) {
6483
            $folder = true;
6484
        }
6485
6486
        return $folder;
6487
    }
6488
6489
    /**
6490
     * @param array  $course
6491
     * @param string $lp_name
6492
     * @param int    $creatorId
6493
     *
6494
     * @return array
6495
     */
6496
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6497
    {
6498
        $filepath = '';
6499
        $dir = '/learning_path/';
6500
6501
        if (empty($lp_name)) {
6502
            $lp_name = $this->name;
6503
        }
6504
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6505
        $folder = self::generate_learning_path_folder($course, $creatorId);
6506
6507
        // Limits title size
6508
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6509
        $dir = $dir.$title;
6510
6511
        // Creating LP folder
6512
        $documentId = null;
6513
        if ($folder) {
6514
            $folderData = create_unexisting_directory(
6515
                $course,
6516
                $creatorId,
6517
                0,
6518
                0,
6519
                0,
6520
                $filepath,
6521
                $dir,
6522
                $lp_name
6523
            );
6524
            if (!empty($folderData)) {
6525
                $folder = true;
6526
            }
6527
6528
            $documentId = $folderData->getIid();
6529
            $dir = $dir.'/';
6530
            if ($folder) {
6531
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6532
            }
6533
        }
6534
6535
        if (empty($documentId)) {
6536
            $dir = api_remove_trailing_slash($dir);
6537
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6538
        }
6539
6540
        $array = [
6541
            'dir' => $dir,
6542
            'filepath' => $filepath,
6543
            'folder' => $folder,
6544
            'id' => $documentId,
6545
        ];
6546
6547
        return $array;
6548
    }
6549
6550
    /**
6551
     * Create a new document //still needs some finetuning.
6552
     *
6553
     * @param array  $courseInfo
6554
     * @param string $content
6555
     * @param string $title
6556
     * @param string $extension
6557
     * @param int    $parentId
6558
     * @param int    $creatorId  creator id
6559
     *
6560
     * @return int
6561
     */
6562
    public function create_document(
6563
        $courseInfo,
6564
        $content = '',
6565
        $title = '',
6566
        $extension = 'html',
6567
        $parentId = 0,
6568
        $creatorId = 0
6569
    ) {
6570
        if (!empty($courseInfo)) {
6571
            $course_id = $courseInfo['real_id'];
6572
        } else {
6573
            $course_id = api_get_course_int_id();
6574
        }
6575
6576
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6577
        $sessionId = api_get_session_id();
6578
6579
        // Generates folder
6580
        $result = $this->generate_lp_folder($courseInfo);
6581
        $dir = $result['dir'];
6582
6583
        if (empty($parentId) || '/' == $parentId) {
6584
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6585
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6586
6587
            if ('/' === $parentId) {
6588
                $dir = '/';
6589
            }
6590
6591
            // Please, do not modify this dirname formatting.
6592
            if (strstr($dir, '..')) {
6593
                $dir = '/';
6594
            }
6595
6596
            if (!empty($dir[0]) && '.' == $dir[0]) {
6597
                $dir = substr($dir, 1);
6598
            }
6599
            if (!empty($dir[0]) && '/' != $dir[0]) {
6600
                $dir = '/'.$dir;
6601
            }
6602
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6603
                $dir .= '/';
6604
            }
6605
        } else {
6606
            $parentInfo = DocumentManager::get_document_data_by_id(
6607
                $parentId,
6608
                $courseInfo['code']
6609
            );
6610
            if (!empty($parentInfo)) {
6611
                $dir = $parentInfo['path'].'/';
6612
            }
6613
        }
6614
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6615
        // is already escaped twice when it gets here.
6616
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6617
        if (!empty($title)) {
6618
            $title = api_replace_dangerous_char(stripslashes($title));
6619
        } else {
6620
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6621
        }
6622
6623
        $title = disable_dangerous_file($title);
6624
        $filename = $title;
6625
        $content = !empty($content) ? $content : $_POST['content_lp'];
6626
        $tmp_filename = $filename;
6627
        /*$i = 0;
6628
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6629
            $tmp_filename = $filename.'_'.++$i;
6630
        }*/
6631
        $filename = $tmp_filename.'.'.$extension;
6632
6633
        if ('html' === $extension) {
6634
            $content = stripslashes($content);
6635
            $content = str_replace(
6636
                api_get_path(WEB_COURSE_PATH),
6637
                api_get_path(REL_PATH).'courses/',
6638
                $content
6639
            );
6640
6641
            // Change the path of mp3 to absolute.
6642
            // The first regexp deals with :// urls.
6643
            $content = preg_replace(
6644
                "|(flashvars=\"file=)([^:/]+)/|",
6645
                "$1".api_get_path(
6646
                    REL_COURSE_PATH
6647
                ).$courseInfo['path'].'/document/',
6648
                $content
6649
            );
6650
            // The second regexp deals with audio/ urls.
6651
            $content = preg_replace(
6652
                "|(flashvars=\"file=)([^/]+)/|",
6653
                "$1".api_get_path(
6654
                    REL_COURSE_PATH
6655
                ).$courseInfo['path'].'/document/$2/',
6656
                $content
6657
            );
6658
            // For flv player: To prevent edition problem with firefox,
6659
            // we have to use a strange tip (don't blame me please).
6660
            $content = str_replace(
6661
                '</body>',
6662
                '<style type="text/css">body{}</style></body>',
6663
                $content
6664
            );
6665
        }
6666
6667
        $save_file_path = $dir.$filename;
6668
6669
        $document = DocumentManager::addDocument(
6670
            $courseInfo,
6671
            $save_file_path,
6672
            'file',
6673
            '',
6674
            $tmp_filename,
6675
            '',
6676
            0, //readonly
6677
            true,
6678
            null,
6679
            $sessionId,
6680
            $creatorId,
6681
            false,
6682
            $content,
6683
            $parentId
6684
        );
6685
6686
        $document_id = $document->getIid();
6687
        if ($document_id) {
6688
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6689
            $new_title = $originalTitle;
6690
6691
            if ($new_comment || $new_title) {
6692
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6693
                $ct = '';
6694
                if ($new_comment) {
6695
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6696
                }
6697
                if ($new_title) {
6698
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6699
                }
6700
6701
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6702
                        WHERE iid = $document_id ";
6703
                Database::query($sql);
6704
            }
6705
        }
6706
6707
        return $document_id;
6708
    }
6709
6710
    /**
6711
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6712
     */
6713
    public function edit_document()
6714
    {
6715
        $repo = Container::getDocumentRepository();
6716
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6717
            $id = (int) $_REQUEST['document_id'];
6718
            /** @var CDocument $document */
6719
            $document = $repo->find($id);
6720
6721
            if ($document->getResourceNode()->hasEditableTextContent()) {
6722
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6723
            }
6724
6725
            $document->setTitle($_REQUEST['title']);
6726
            $repo->update($document);
6727
        }
6728
    }
6729
6730
    /**
6731
     * Displays the selected item, with a panel for manipulating the item.
6732
     *
6733
     * @param CLpItem $lpItem
6734
     * @param string  $msg
6735
     * @param bool    $show_actions
6736
     *
6737
     * @return string
6738
     */
6739
    public function display_item($lpItem, $msg = null, $show_actions = true)
6740
    {
6741
        $course_id = api_get_course_int_id();
6742
        $return = '';
6743
6744
        if (empty($lpItem)) {
6745
            return '';
6746
        }
6747
        $item_id = $lpItem->getIid();
6748
        $itemType = $lpItem->getItemType();
6749
        $lpId = $lpItem->getLp()->getIid();
6750
        $path = $lpItem->getPath();
6751
6752
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6753
6754
        // Prevents wrong parent selection for document, see Bug#1251.
6755
        if ('dir' !== $itemType) {
6756
            Session::write('parent_item_id', $lpItem->getParentItemId());
6757
        }
6758
6759
        if ($show_actions) {
6760
            $return .= $this->displayItemMenu($lpItem);
6761
        }
6762
        $return .= '<div style="padding:10px;">';
6763
6764
        if ('' != $msg) {
6765
            $return .= $msg;
6766
        }
6767
6768
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6769
6770
        switch ($itemType) {
6771
            case TOOL_THREAD:
6772
                $link = $this->rl_get_resource_link_for_learnpath(
6773
                    $course_id,
6774
                    $lpId,
6775
                    $item_id,
6776
                    0
6777
                );
6778
                $return .= Display::url(
6779
                    get_lang('Go to thread'),
6780
                    $link,
6781
                    ['class' => 'btn btn-primary']
6782
                );
6783
                break;
6784
            case TOOL_FORUM:
6785
                $return .= Display::url(
6786
                    get_lang('Go to the forum'),
6787
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6788
                    ['class' => 'btn btn-primary']
6789
                );
6790
                break;
6791
            case TOOL_QUIZ:
6792
                if (!empty($path)) {
6793
                    $exercise = new Exercise();
6794
                    $exercise->read($path);
6795
                    $return .= $exercise->description.'<br />';
6796
                    $return .= Display::url(
6797
                        get_lang('Go to exercise'),
6798
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6799
                        ['class' => 'btn btn-primary']
6800
                    );
6801
                }
6802
                break;
6803
            case TOOL_LP_FINAL_ITEM:
6804
                $return .= $this->getSavedFinalItem();
6805
                break;
6806
            case TOOL_DOCUMENT:
6807
            case TOOL_READOUT_TEXT:
6808
                $repo = Container::getDocumentRepository();
6809
                /** @var CDocument $document */
6810
                $document = $repo->find($lpItem->getPath());
6811
                $return .= $this->display_document($document, true, true);
6812
                break;
6813
            case TOOL_HOTPOTATOES:
6814
                $return .= $this->display_document($document, false, true);
6815
                break;
6816
        }
6817
        $return .= '</div>';
6818
6819
        return $return;
6820
    }
6821
6822
    /**
6823
     * Shows the needed forms for editing a specific item.
6824
     *
6825
     * @param CLpItem $lpItem
6826
     *
6827
     * @throws Exception
6828
     * @throws HTML_QuickForm_Error
6829
     *
6830
     * @return string
6831
     */
6832
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6833
    {
6834
        $course_id = api_get_course_int_id();
6835
        $return = '';
6836
6837
        if (empty($lpItem)) {
6838
            return '';
6839
        }
6840
        $item_id = $lpItem->getIid();
6841
        $itemType = $lpItem->getItemType();
6842
        $path = $lpItem->getPath();
6843
6844
        switch ($itemType) {
6845
            case 'dir':
6846
            case 'asset':
6847
            case 'sco':
6848
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6849
                    $return .= $this->displayItemMenu($lpItem);
6850
                    $return .= $this->display_item_form(
6851
                        $lpItem,
6852
                        'edit'
6853
                    );
6854
                } else {
6855
                    $return .= $this->display_item_form(
6856
                        $lpItem,
6857
                        'edit_item'
6858
                    );
6859
                }
6860
                break;
6861
            case TOOL_LP_FINAL_ITEM:
6862
            case TOOL_DOCUMENT:
6863
            case TOOL_READOUT_TEXT:
6864
                $return .= $this->displayItemMenu($lpItem);
6865
                $return .= $this->displayDocumentForm('edit', $lpItem);
6866
                break;
6867
            case TOOL_LINK:
6868
                $link = null;
6869
                if (!empty($path)) {
6870
                    $repo = Container::getLinkRepository();
6871
                    $link = $repo->find($path);
6872
                }
6873
                $return .= $this->displayItemMenu($lpItem);
6874
                $return .= $this->display_link_form('edit', $lpItem, $link);
6875
6876
                break;
6877
            case TOOL_QUIZ:
6878
                if (!empty($path)) {
6879
                    $repo = Container::getQuizRepository();
6880
                    $resource = $repo->find($path);
6881
                }
6882
                $return .= $this->displayItemMenu($lpItem);
6883
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6884
                break;
6885
            /*case TOOL_HOTPOTATOES:
6886
                $return .= $this->displayItemMenu($lpItem);
6887
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6888
                break;*/
6889
            case TOOL_STUDENTPUBLICATION:
6890
                if (!empty($path)) {
6891
                    $repo = Container::getStudentPublicationRepository();
6892
                    $resource = $repo->find($path);
6893
                }
6894
                $return .= $this->displayItemMenu($lpItem);
6895
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6896
                break;
6897
            case TOOL_FORUM:
6898
                if (!empty($path)) {
6899
                    $repo = Container::getForumRepository();
6900
                    $resource = $repo->find($path);
6901
                }
6902
                $return .= $this->displayItemMenu($lpItem);
6903
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6904
                break;
6905
            case TOOL_THREAD:
6906
                if (!empty($path)) {
6907
                    $repo = Container::getForumPostRepository();
6908
                    $resource = $repo->find($path);
6909
                }
6910
                $return .= $this->displayItemMenu($lpItem);
6911
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6912
                break;
6913
        }
6914
6915
        return $return;
6916
    }
6917
6918
    /**
6919
     * Function that displays a list with al the resources that
6920
     * could be added to the learning path.
6921
     *
6922
     * @throws Exception
6923
     * @throws HTML_QuickForm_Error
6924
     *
6925
     * @return bool
6926
     */
6927
    public function displayResources()
6928
    {
6929
        // Get all the docs.
6930
        $documents = $this->get_documents(true);
6931
6932
        // Get all the exercises.
6933
        $exercises = $this->get_exercises();
6934
6935
        // Get all the links.
6936
        $links = $this->get_links();
6937
6938
        // Get all the student publications.
6939
        $works = $this->get_student_publications();
6940
6941
        // Get all the forums.
6942
        $forums = $this->get_forums();
6943
6944
        // Get the final item form (see BT#11048) .
6945
        $finish = $this->getFinalItemForm();
6946
6947
        $headers = [
6948
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
6949
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
6950
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
6951
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
6952
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
6953
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
6954
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
6955
        ];
6956
6957
        echo Display::return_message(
6958
            get_lang('Click on the [Learner view] button to see your learning path'),
6959
            'normal'
6960
        );
6961
        $section = $this->displayNewSectionForm();
6962
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6963
6964
        echo Display::tabs(
6965
            $headers,
6966
            [
6967
                $documents,
6968
                $exercises,
6969
                $links,
6970
                $works,
6971
                $forums,
6972
                $section,
6973
                $finish,
6974
            ],
6975
            'resource_tab',
6976
            [],
6977
            [],
6978
            $selected
6979
        );
6980
6981
        return true;
6982
    }
6983
6984
    /**
6985
     * Returns the extension of a document.
6986
     *
6987
     * @param string $filename
6988
     *
6989
     * @return string Extension (part after the last dot)
6990
     */
6991
    public function get_extension($filename)
6992
    {
6993
        $explode = explode('.', $filename);
6994
6995
        return $explode[count($explode) - 1];
6996
    }
6997
6998
    /**
6999
     * @return string
7000
     */
7001
    public function getCurrentBuildingModeURL()
7002
    {
7003
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7004
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7005
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7006
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7007
7008
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7009
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7010
7011
        return $currentUrl;
7012
    }
7013
7014
    /**
7015
     * Displays a document by id.
7016
     *
7017
     * @param CDocument $document
7018
     * @param bool      $show_title
7019
     * @param bool      $iframe
7020
     * @param bool      $edit_link
7021
     *
7022
     * @return string
7023
     */
7024
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7025
    {
7026
        $return = '';
7027
        if (!$document) {
7028
            return '';
7029
        }
7030
7031
        $repo = Container::getDocumentRepository();
7032
7033
        // TODO: Add a path filter.
7034
        if ($iframe) {
7035
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7036
            $url = $repo->getResourceFileUrl($document);
7037
7038
            $return .= '<iframe
7039
                id="learnpath_preview_frame"
7040
                frameborder="0"
7041
                height="400"
7042
                width="100%"
7043
                scrolling="auto"
7044
                src="'.$url.'"></iframe>';
7045
        } else {
7046
            $return = $repo->getResourceFileContent($document);
7047
        }
7048
7049
        return $return;
7050
    }
7051
7052
    /**
7053
     * Return HTML form to add/edit a link item.
7054
     *
7055
     * @param string  $action (add/edit)
7056
     * @param CLpItem $lpItem
7057
     * @param CLink   $link
7058
     *
7059
     * @throws Exception
7060
     * @throws HTML_QuickForm_Error
7061
     *
7062
     * @return string HTML form
7063
     */
7064
    public function display_link_form($action = 'add', $lpItem, $link)
7065
    {
7066
        $item_url = '';
7067
        if ($link) {
7068
            $item_url = stripslashes($link->getUrl());
7069
        }
7070
        $form = new FormValidator(
7071
            'edit_link',
7072
            'POST',
7073
            $this->getCurrentBuildingModeURL()
7074
        );
7075
7076
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7077
7078
        $urlAttributes = ['class' => 'learnpath_item_form'];
7079
        $urlAttributes['disabled'] = 'disabled';
7080
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7081
        $form->setDefault('url', $item_url);
7082
7083
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7084
7085
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7086
    }
7087
7088
    /**
7089
     * Return HTML form to add/edit a quiz.
7090
     *
7091
     * @param string  $action   Action (add/edit)
7092
     * @param CLpItem $lpItem   Item ID if already exists
7093
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7094
     *
7095
     * @throws Exception
7096
     *
7097
     * @return string HTML form
7098
     */
7099
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7100
    {
7101
        $form = new FormValidator(
7102
            'quiz_form',
7103
            'POST',
7104
            $this->getCurrentBuildingModeURL()
7105
        );
7106
7107
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7108
7109
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7110
7111
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7112
    }
7113
7114
    /**
7115
     * Return the form to display the forum edit/add option.
7116
     *
7117
     * @param CLpItem $lpItem
7118
     *
7119
     * @throws Exception
7120
     *
7121
     * @return string HTML form
7122
     */
7123
    public function display_forum_form($action = 'add', $lpItem, $resource)
7124
    {
7125
        $form = new FormValidator(
7126
            'forum_form',
7127
            'POST',
7128
            $this->getCurrentBuildingModeURL()
7129
        );
7130
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7131
7132
        if ('add' === $action) {
7133
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7134
        } else {
7135
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7136
        }
7137
7138
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7139
    }
7140
7141
    /**
7142
     * Return HTML form to add/edit forum threads.
7143
     *
7144
     * @param string  $action
7145
     * @param CLpItem $lpItem
7146
     * @param string  $resource
7147
     *
7148
     * @throws Exception
7149
     *
7150
     * @return string HTML form
7151
     */
7152
    public function display_thread_form($action = 'add', $lpItem, $resource)
7153
    {
7154
        $form = new FormValidator(
7155
            'thread_form',
7156
            'POST',
7157
            $this->getCurrentBuildingModeURL()
7158
        );
7159
7160
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7161
7162
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7163
7164
        return $form->returnForm();
7165
    }
7166
7167
    /**
7168
     * Return the HTML form to display an item (generally a dir item).
7169
     *
7170
     * @param CLpItem $lpItem
7171
     * @param string  $action
7172
     *
7173
     * @throws Exception
7174
     * @throws HTML_QuickForm_Error
7175
     *
7176
     * @return string HTML form
7177
     */
7178
    public function display_item_form(
7179
        $lpItem,
7180
        $action = 'add_item'
7181
    ) {
7182
        $item_type = $lpItem->getItemType();
7183
7184
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7185
7186
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7187
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7188
7189
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7190
7191
        return $form->returnForm();
7192
    }
7193
7194
    /**
7195
     * Return HTML form to add/edit a student publication (work).
7196
     *
7197
     * @param string              $action
7198
     * @param CStudentPublication $resource
7199
     *
7200
     * @throws Exception
7201
     *
7202
     * @return string HTML form
7203
     */
7204
    public function display_student_publication_form(
7205
        $action = 'add',
7206
        CLpItem $lpItem,
7207
        $resource
7208
    ) {
7209
        $form = new FormValidator('frm_student_publication', 'post', '#');
7210
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7211
7212
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7213
7214
        $return = '<div class="sectioncomment">';
7215
        $return .= $form->returnForm();
7216
        $return .= '</div>';
7217
7218
        return $return;
7219
    }
7220
7221
    public function displayNewSectionForm()
7222
    {
7223
        $action = 'add_item';
7224
        $item_type = 'dir';
7225
7226
        $lpItem = new CLpItem();
7227
        $lpItem->setItemType('dir');
7228
7229
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7230
7231
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7232
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7233
7234
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7235
        $form->addElement('hidden', 'type', 'dir');
7236
7237
        return $form->returnForm();
7238
    }
7239
7240
    /**
7241
     * Returns the form to update or create a document.
7242
     *
7243
     * @param string  $action (add/edit)
7244
     * @param CLpItem $lpItem
7245
     *
7246
     * @throws HTML_QuickForm_Error
7247
     * @throws Exception
7248
     *
7249
     * @return string HTML form
7250
     */
7251
    public function displayDocumentForm($action = 'add', $lpItem = null)
7252
    {
7253
        if (empty($lpItem)) {
7254
            return '';
7255
        }
7256
7257
        $courseInfo = api_get_course_info();
7258
7259
        $form = new FormValidator(
7260
            'form',
7261
            'POST',
7262
            $this->getCurrentBuildingModeURL(),
7263
            '',
7264
            ['enctype' => 'multipart/form-data']
7265
        );
7266
7267
        $data = $this->generate_lp_folder($courseInfo);
7268
7269
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7270
7271
        switch ($action) {
7272
            case 'add':
7273
                $folders = DocumentManager::get_all_document_folders(
7274
                    $courseInfo,
7275
                    0,
7276
                    true
7277
                );
7278
                DocumentManager::build_directory_selector(
7279
                    $folders,
7280
                    '',
7281
                    [],
7282
                    true,
7283
                    $form,
7284
                    'directory_parent_id'
7285
                );
7286
7287
                if (isset($data['id'])) {
7288
                    $defaults['directory_parent_id'] = $data['id'];
7289
                }
7290
7291
                break;
7292
        }
7293
7294
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7295
7296
        return $form->returnForm();
7297
    }
7298
7299
    /**
7300
     * @param array  $courseInfo
7301
     * @param string $content
7302
     * @param string $title
7303
     * @param int    $parentId
7304
     *
7305
     * @throws \Doctrine\ORM\ORMException
7306
     * @throws \Doctrine\ORM\OptimisticLockException
7307
     * @throws \Doctrine\ORM\TransactionRequiredException
7308
     *
7309
     * @return int
7310
     */
7311
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7312
    {
7313
        $creatorId = api_get_user_id();
7314
        $sessionId = api_get_session_id();
7315
7316
        // Generates folder
7317
        $result = $this->generate_lp_folder($courseInfo);
7318
        $dir = $result['dir'];
7319
7320
        if (empty($parentId) || '/' == $parentId) {
7321
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7322
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7323
7324
            if ('/' === $parentId) {
7325
                $dir = '/';
7326
            }
7327
7328
            // Please, do not modify this dirname formatting.
7329
            if (strstr($dir, '..')) {
7330
                $dir = '/';
7331
            }
7332
7333
            if (!empty($dir[0]) && '.' == $dir[0]) {
7334
                $dir = substr($dir, 1);
7335
            }
7336
            if (!empty($dir[0]) && '/' != $dir[0]) {
7337
                $dir = '/'.$dir;
7338
            }
7339
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7340
                $dir .= '/';
7341
            }
7342
        } else {
7343
            $parentInfo = DocumentManager::get_document_data_by_id(
7344
                $parentId,
7345
                $courseInfo['code']
7346
            );
7347
            if (!empty($parentInfo)) {
7348
                $dir = $parentInfo['path'].'/';
7349
            }
7350
        }
7351
7352
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7353
7354
        if (!is_dir($filepath)) {
7355
            $dir = '/';
7356
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7357
        }
7358
7359
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7360
7361
        if (!empty($title)) {
7362
            $title = api_replace_dangerous_char(stripslashes($title));
7363
        } else {
7364
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7365
        }
7366
7367
        $title = disable_dangerous_file($title);
7368
        $filename = $title;
7369
        $content = !empty($content) ? $content : $_POST['content_lp'];
7370
        $tmpFileName = $filename;
7371
7372
        $i = 0;
7373
        while (file_exists($filepath.$tmpFileName.'.html')) {
7374
            $tmpFileName = $filename.'_'.++$i;
7375
        }
7376
7377
        $filename = $tmpFileName.'.html';
7378
        $content = stripslashes($content);
7379
7380
        if (file_exists($filepath.$filename)) {
7381
            return 0;
7382
        }
7383
7384
        $putContent = file_put_contents($filepath.$filename, $content);
7385
7386
        if (false === $putContent) {
7387
            return 0;
7388
        }
7389
7390
        $fileSize = filesize($filepath.$filename);
7391
        $saveFilePath = $dir.$filename;
7392
7393
        $document = DocumentManager::addDocument(
7394
            $courseInfo,
7395
            $saveFilePath,
7396
            'file',
7397
            $fileSize,
7398
            $tmpFileName,
7399
            '',
7400
            0, //readonly
7401
            true,
7402
            null,
7403
            $sessionId,
7404
            $creatorId
7405
        );
7406
7407
        $documentId = $document->getId();
7408
7409
        if (!$document) {
7410
            return 0;
7411
        }
7412
7413
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7414
        $newTitle = $originalTitle;
7415
7416
        if ($newComment || $newTitle) {
7417
            $em = Database::getManager();
7418
7419
            if ($newComment) {
7420
                $document->setComment($newComment);
7421
            }
7422
7423
            if ($newTitle) {
7424
                $document->setTitle($newTitle);
7425
            }
7426
7427
            $em->persist($document);
7428
            $em->flush();
7429
        }
7430
7431
        return $documentId;
7432
    }
7433
7434
    /**
7435
     * Displays the menu for manipulating a step.
7436
     *
7437
     * @return string
7438
     */
7439
    public function displayItemMenu(CLpItem $lpItem)
7440
    {
7441
        $item_id = $lpItem->getIid();
7442
        $audio = $lpItem->getAudio();
7443
        $itemType = $lpItem->getItemType();
7444
        $path = $lpItem->getPath();
7445
7446
        $return = '<div class="actions">';
7447
        $audio_player = null;
7448
        // We display an audio player if needed.
7449
        if (!empty($audio)) {
7450
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7451
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7452
                .'<audio src="'.$webAudioPath.'" controls>'
7453
                .'</div><br>';*/
7454
        }
7455
7456
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7457
7458
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7459
            $return .= Display::url(
7460
                Display::return_icon(
7461
                    'edit.png',
7462
                    get_lang('Edit'),
7463
                    [],
7464
                    ICON_SIZE_SMALL
7465
                ),
7466
                $url.'&action=edit_item&path_item='.$path
7467
            );
7468
7469
            /*$return .= Display::url(
7470
                Display::return_icon(
7471
                    'move.png',
7472
                    get_lang('Move'),
7473
                    [],
7474
                    ICON_SIZE_SMALL
7475
                ),
7476
                $url.'&action=move_item'
7477
            );*/
7478
        }
7479
7480
        // Commented for now as prerequisites cannot be added to chapters.
7481
        if ('dir' !== $itemType) {
7482
            $return .= Display::url(
7483
                Display::return_icon(
7484
                    'accept.png',
7485
                    get_lang('Prerequisites'),
7486
                    [],
7487
                    ICON_SIZE_SMALL
7488
                ),
7489
                $url.'&action=edit_item_prereq'
7490
            );
7491
        }
7492
        $return .= Display::url(
7493
            Display::return_icon(
7494
                'delete.png',
7495
                get_lang('Delete'),
7496
                [],
7497
                ICON_SIZE_SMALL
7498
            ),
7499
            $url.'&action=delete_item'
7500
        );
7501
7502
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7503
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7504
            if (empty($documentData)) {
7505
                // Try with iid
7506
                $table = Database::get_course_table(TABLE_DOCUMENT);
7507
                $sql = "SELECT path FROM $table
7508
                        WHERE
7509
                              c_id = ".api_get_course_int_id()." AND
7510
                              iid = ".$path." AND
7511
                              path NOT LIKE '%_DELETED_%'";
7512
                $result = Database::query($sql);
7513
                $documentData = Database::fetch_array($result);
7514
                if ($documentData) {
7515
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7516
                }
7517
            }
7518
            if (isset($documentData['absolute_path_from_document'])) {
7519
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7520
            }
7521
        }*/
7522
7523
        $return .= '</div>';
7524
7525
        if (!empty($audio_player)) {
7526
            $return .= $audio_player;
7527
        }
7528
7529
        return $return;
7530
    }
7531
7532
    /**
7533
     * Creates the javascript needed for filling up the checkboxes without page reload.
7534
     *
7535
     * @return string
7536
     */
7537
    public function get_js_dropdown_array()
7538
    {
7539
        $course_id = api_get_course_int_id();
7540
        $return = 'var child_name = new Array();'."\n";
7541
        $return .= 'var child_value = new Array();'."\n\n";
7542
        $return .= 'child_name[0] = new Array();'."\n";
7543
        $return .= 'child_value[0] = new Array();'."\n\n";
7544
7545
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7546
        $sql = "SELECT * FROM ".$tbl_lp_item."
7547
                WHERE
7548
                    c_id = $course_id AND
7549
                    lp_id = ".$this->lp_id." AND
7550
                    parent_item_id = 0
7551
                ORDER BY display_order ASC";
7552
        Database::query($sql);
7553
        $i = 0;
7554
7555
        $list = $this->getItemsForForm(true);
7556
7557
        foreach ($list as $row_zero) {
7558
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7559
                if (TOOL_QUIZ == $row_zero['item_type']) {
7560
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7561
                }
7562
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7563
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7564
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7565
            }
7566
        }
7567
7568
        $return .= "\n";
7569
        $sql = "SELECT * FROM $tbl_lp_item
7570
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7571
        $res = Database::query($sql);
7572
        while ($row = Database::fetch_array($res)) {
7573
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7574
                           WHERE
7575
                                c_id = ".$course_id." AND
7576
                                parent_item_id = ".$row['iid']."
7577
                           ORDER BY display_order ASC";
7578
            $res_parent = Database::query($sql_parent);
7579
            $i = 0;
7580
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7581
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7582
7583
            while ($row_parent = Database::fetch_array($res_parent)) {
7584
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7585
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7586
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7587
            }
7588
            $return .= "\n";
7589
        }
7590
7591
        $return .= "
7592
            function load_cbo(id) {
7593
                if (!id) {
7594
                    return false;
7595
                }
7596
7597
                var cbo = document.getElementById('previous');
7598
                for(var i = cbo.length - 1; i > 0; i--) {
7599
                    cbo.options[i] = null;
7600
                }
7601
7602
                var k=0;
7603
                for(var i = 1; i <= child_name[id].length; i++){
7604
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7605
                    option.style.paddingLeft = '40px';
7606
                    cbo.options[i] = option;
7607
                    k = i;
7608
                }
7609
7610
                cbo.options[k].selected = true;
7611
                //$('#previous').selectpicker('refresh');
7612
            }";
7613
7614
        return $return;
7615
    }
7616
7617
    /**
7618
     * Display the form to allow moving an item.
7619
     *
7620
     * @param CLpItem $lpItem
7621
     *
7622
     * @throws Exception
7623
     * @throws HTML_QuickForm_Error
7624
     *
7625
     * @return string HTML form
7626
     */
7627
    public function display_move_item($lpItem)
7628
    {
7629
        $return = '';
7630
        $path = $lpItem->getPath();
7631
7632
        if ($lpItem) {
7633
            $itemType = $lpItem->getItemType();
7634
            switch ($itemType) {
7635
                case 'dir':
7636
                case 'asset':
7637
                    $return .= $this->displayItemMenu($lpItem);
7638
                    $return .= $this->display_item_form(
7639
                        $lpItem,
7640
                        get_lang('Move the current section'),
7641
                        'move',
7642
                        $row
7643
                    );
7644
                    break;
7645
                case TOOL_DOCUMENT:
7646
                    $return .= $this->displayItemMenu($lpItem);
7647
                    $return .= $this->displayDocumentForm('move', $lpItem);
7648
                    break;
7649
                case TOOL_LINK:
7650
                    $link = null;
7651
                    if (!empty($path)) {
7652
                        $repo = Container::getLinkRepository();
7653
                        $link = $repo->find($path);
7654
                    }
7655
                    $return .= $this->displayItemMenu($lpItem);
7656
                    $return .= $this->display_link_form('move', $lpItem, $link);
7657
                    break;
7658
                case TOOL_HOTPOTATOES:
7659
                    $return .= $this->displayItemMenu($lpItem);
7660
                    $return .= $this->display_link_form('move', $lpItem, $row);
7661
                    break;
7662
                case TOOL_QUIZ:
7663
                    $return .= $this->displayItemMenu($lpItem);
7664
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7665
                    break;
7666
                case TOOL_STUDENTPUBLICATION:
7667
                    $return .= $this->displayItemMenu($lpItem);
7668
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7669
                    break;
7670
                case TOOL_FORUM:
7671
                    $return .= $this->displayItemMenu($lpItem);
7672
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7673
                    break;
7674
                case TOOL_THREAD:
7675
                    $return .= $this->displayItemMenu($lpItem);
7676
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7677
                    break;
7678
            }
7679
        }
7680
7681
        return $return;
7682
    }
7683
7684
    /**
7685
     * Return HTML form to allow prerequisites selection.
7686
     *
7687
     * @todo use FormValidator
7688
     *
7689
     * @return string HTML form
7690
     */
7691
    public function display_item_prerequisites_form(CLpItem $lpItem)
7692
    {
7693
        $course_id = api_get_course_int_id();
7694
        $prerequisiteId = $lpItem->getPrerequisite();
7695
        $itemId = $lpItem->getIid();
7696
7697
        $return = '<legend>';
7698
        $return .= get_lang('Add/edit prerequisites');
7699
        $return .= '</legend>';
7700
        $return .= '<form method="POST">';
7701
        $return .= '<div class="table-responsive">';
7702
        $return .= '<table class="table table-hover">';
7703
        $return .= '<thead>';
7704
        $return .= '<tr>';
7705
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7706
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7707
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7708
        $return .= '</tr>';
7709
        $return .= '</thead>';
7710
7711
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7712
        $return .= '<tbody>';
7713
        $return .= '<tr>';
7714
        $return .= '<td colspan="3">';
7715
        $return .= '<div class="radio learnpath"><label for="idnone">';
7716
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7717
        $return .= get_lang('none').'</label>';
7718
        $return .= '</div>';
7719
        $return .= '</tr>';
7720
7721
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7722
        $sql = "SELECT * FROM $tbl_lp_item
7723
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7724
        $result = Database::query($sql);
7725
7726
        $selectedMinScore = [];
7727
        $selectedMaxScore = [];
7728
        $masteryScore = [];
7729
        while ($row = Database::fetch_array($result)) {
7730
            if ($row['iid'] == $itemId) {
7731
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7732
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7733
            }
7734
            $masteryScore[$row['iid']] = $row['mastery_score'];
7735
        }
7736
7737
        $arrLP = $this->getItemsForForm();
7738
        $this->tree_array($arrLP);
7739
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7740
        unset($this->arrMenu);
7741
7742
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
11362
                $item_id = $sameParentLpItemList->list[$i]->id;
11363
                // display_order
11364
                $display_order = $i + 1;
11365
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11366
                // previous_item_id
11367
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11368
                $previous_item_id = $item_id;
11369
                // next_item_id
11370
                $next_item_id = 0;
11371
                if ($i < count($sameParentLpItemList->list) - 1) {
11372
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11373
                }
11374
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11375
            }
11376
        }
11377
11378
        $table = Database::get_course_table(TABLE_LP_ITEM);
11379
11380
        foreach ($itemList->list as $item) {
11381
            $params = [];
11382
            $params['display_order'] = $item->display_order;
11383
            $params['previous_item_id'] = $item->previous_item_id;
11384
            $params['next_item_id'] = $item->next_item_id;
11385
            $params['parent_item_id'] = $item->parent_item_id;
11386
11387
            Database::update(
11388
                $table,
11389
                $params,
11390
                [
11391
                    'iid = ? AND c_id = ? ' => [
11392
                        (int) $item->id,
11393
                        (int) $courseId,
11394
                    ],
11395
                ]
11396
            );
11397
        }
11398
    }
11399
11400
    /**
11401
     * Get the depth level of LP item.
11402
     *
11403
     * @param array $items
11404
     * @param int   $currentItemId
11405
     *
11406
     * @return int
11407
     */
11408
    private static function get_level_for_item($items, $currentItemId)
11409
    {
11410
        $parentItemId = 0;
11411
        if (isset($items[$currentItemId])) {
11412
            $parentItemId = $items[$currentItemId]->parent;
11413
        }
11414
11415
        if (0 == $parentItemId) {
11416
            return 0;
11417
        } else {
11418
            return self::get_level_for_item($items, $parentItemId) + 1;
11419
        }
11420
    }
11421
11422
    /**
11423
     * Generate the link for a learnpath category as course tool.
11424
     *
11425
     * @param int $categoryId
11426
     *
11427
     * @return string
11428
     */
11429
    private static function getCategoryLinkForTool($categoryId)
11430
    {
11431
        $categoryId = (int) $categoryId;
11432
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11433
            .http_build_query(
11434
                [
11435
                    'action' => 'view_category',
11436
                    'id' => $categoryId,
11437
                ]
11438
            );
11439
11440
        return $link;
11441
    }
11442
11443
    /**
11444
     * Return the scorm item type object with spaces replaced with _
11445
     * The return result is use to build a css classname like scorm_type_$return.
11446
     *
11447
     * @param $in_type
11448
     *
11449
     * @return mixed
11450
     */
11451
    private static function format_scorm_type_item($in_type)
11452
    {
11453
        return str_replace(' ', '_', $in_type);
11454
    }
11455
11456
    /**
11457
     * Check and obtain the lp final item if exist.
11458
     *
11459
     * @return learnpathItem
11460
     */
11461
    private function getFinalItem()
11462
    {
11463
        if (empty($this->items)) {
11464
            return null;
11465
        }
11466
11467
        foreach ($this->items as $item) {
11468
            if ('final_item' !== $item->type) {
11469
                continue;
11470
            }
11471
11472
            return $item;
11473
        }
11474
    }
11475
11476
    /**
11477
     * Get the LP Final Item Template.
11478
     *
11479
     * @return string
11480
     */
11481
    private function getFinalItemTemplate()
11482
    {
11483
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11484
    }
11485
11486
    /**
11487
     * Get the LP Final Item Url.
11488
     *
11489
     * @return string
11490
     */
11491
    private function getSavedFinalItem()
11492
    {
11493
        $finalItem = $this->getFinalItem();
11494
11495
        $repo = Container::getDocumentRepository();
11496
        /** @var CDocument $document */
11497
        $document = $repo->find($finalItem->path);
11498
11499
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11500
            return  $repo->getResourceFileContent($document);
11501
        }
11502
11503
        return '';
11504
    }
11505
}
11506