Passed
Push — master ( 5d82a6...ffd823 )
by Julito
18:09 queued 09:19
created

learnpath::generate_lp_folder()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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