Passed
Push — master ( 0085e5...a02707 )
by Julito
10:26
created

learnpath::processBuildMenuElements()   F

Complexity

Conditions 53
Paths > 20000

Size

Total Lines 441
Code Lines 305

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 53
eloc 305
nc 55308804
nop 1
dl 0
loc 441
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\CourseRepository;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
11
use Chamilo\CourseBundle\Entity\CDocument;
12
use Chamilo\CourseBundle\Entity\CLink;
13
use Chamilo\CourseBundle\Entity\CLp;
14
use Chamilo\CourseBundle\Entity\CLpCategory;
15
use Chamilo\CourseBundle\Entity\CLpItem;
16
use Chamilo\CourseBundle\Entity\CLpItemView;
17
use Chamilo\CourseBundle\Entity\CQuiz;
18
use Chamilo\CourseBundle\Entity\CStudentPublication;
19
use Chamilo\CourseBundle\Entity\CTool;
20
use ChamiloSession as Session;
21
use Gedmo\Sortable\Entity\Repository\SortableRepository;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
25
26
/**
27
 * Class learnpath
28
 * This class defines the parent attributes and methods for Chamilo learnpaths
29
 * and SCORM learnpaths. It is used by the scorm class.
30
 *
31
 * @todo decouple class
32
 *
33
 * @author  Yannick Warnier <[email protected]>
34
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
35
 */
36
class learnpath
37
{
38
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
39
    public const STATUS_CSS_CLASS_NAME = [
40
        'not attempted' => 'scorm_not_attempted',
41
        'incomplete' => 'scorm_not_attempted',
42
        'failed' => 'scorm_failed',
43
        'completed' => 'scorm_completed',
44
        'passed' => 'scorm_completed',
45
        'succeeded' => 'scorm_completed',
46
        'browsed' => 'scorm_completed',
47
    ];
48
49
    public $attempt = 0; // The number for the current ID view.
50
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
51
    public $current; // Id of the current item the user is viewing.
52
    public $current_score; // The score of the current item.
53
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
54
    public $current_time_stop; // The time the user closed this resource.
55
    public $default_status = 'not attempted';
56
    public $encoding = 'UTF-8';
57
    public $error = '';
58
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
59
    public $index; // The index of the active learnpath_item in $ordered_items array.
60
    /** @var learnpathItem[] */
61
    public $items = [];
62
    public $last; // item_id of last item viewed in the learning path.
63
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
64
    public $license; // Which license this course has been given - not used yet on 20060522.
65
    public $lp_id; // DB iid for this learnpath.
66
    public $lp_view_id; // DB ID for lp_view
67
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
68
    public $message = '';
69
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
70
    public $name; // Learnpath name (they generally have one).
71
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
72
    public $path = ''; // Path inside the scorm directory (if scorm).
73
    public $theme; // The current theme of the learning path.
74
    public $preview_image; // The current image 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 = null;
108
    public $course_int_id;
109
    public $course_info = [];
110
    public $categoryId;
111
    public $entity;
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
     * @param string $course  Course code
119
     * @param int    $lp_id   c_lp.iid
120
     * @param int    $user_id
121
     */
122
    public function __construct($course, $lp_id, $user_id)
123
    {
124
        $debug = $this->debug;
125
        $this->encoding = api_get_system_encoding();
126
        if (empty($course)) {
127
            $course = api_get_course_id();
128
        }
129
        $course_info = api_get_course_info($course);
130
        if (!empty($course_info)) {
131
            $this->cc = $course_info['code'];
132
            $this->course_info = $course_info;
133
            $course_id = $course_info['real_id'];
134
        } else {
135
            $this->error = 'Course code does not exist in database.';
136
        }
137
138
        $lp_id = (int) $lp_id;
139
        $course_id = (int) $course_id;
140
        $this->set_course_int_id($course_id);
141
        // Check learnpath ID.
142
        if (empty($lp_id) || empty($course_id)) {
143
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
144
        } else {
145
            $repo = Container::getLpRepository();
146
            /** @var CLp $entity */
147
            $entity = $repo->find($lp_id);
148
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
149
                $this->entity = $entity;
150
                $this->lp_id = $lp_id;
151
                $this->type = $entity->getLpType();
152
                $this->name = stripslashes($entity->getName());
153
                $this->proximity = $entity->getContentLocal();
154
                $this->theme = $entity->getTheme();
155
                $this->maker = $entity->getContentLocal();
156
                $this->prevent_reinit = $entity->getPreventReinit();
157
                $this->seriousgame_mode = $entity->getSeriousgameMode();
158
                $this->license = $entity->getContentLicense();
159
                $this->scorm_debug = $entity->getDebug();
160
                $this->js_lib = $entity->getJsLib();
161
                $this->path = $entity->getPath();
162
                $this->preview_image = $entity->getPreviewImage();
163
                $this->author = $entity->getAuthor();
164
                $this->hide_toc_frame = $entity->getHideTocFrame();
165
                $this->lp_session_id = $entity->getSessionId();
166
                $this->use_max_score = $entity->getUseMaxScore();
167
                $this->subscribeUsers = $entity->getSubscribeUsers();
168
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
169
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
170
                $this->ref = $entity->getRef();
171
                $this->categoryId = 0;
172
                if ($entity->getCategory()) {
173
                    $this->categoryId = $entity->getCategory()->getIid();
174
                }
175
176
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
177
178
                if (!empty($entity->getPublicatedOn())) {
179
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
180
                }
181
182
                if (!empty($entity->getExpiredOn())) {
183
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
184
                }
185
                if (2 == $this->type) {
186
                    if (1 == $entity->getForceCommit()) {
187
                        $this->force_commit = true;
188
                    }
189
                }
190
                $this->mode = $entity->getDefaultViewMod();
191
192
                // Check user ID.
193
                if (empty($user_id)) {
194
                    $this->error = 'User ID is empty';
195
                } else {
196
                    $userInfo = api_get_user_info($user_id);
197
                    if (!empty($userInfo)) {
198
                        $this->user_id = $userInfo['user_id'];
199
                    } else {
200
                        $this->error = 'User ID does not exist in database #'.$user_id;
201
                    }
202
                }
203
204
                // End of variables checking.
205
                $session_id = api_get_session_id();
206
                //  Get the session condition for learning paths of the base + session.
207
                $session = api_get_session_condition($session_id);
208
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
209
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
210
211
                // Selecting by view_count descending allows to get the highest view_count first.
212
                $sql = "SELECT * FROM $lp_table
213
                        WHERE
214
                            c_id = $course_id AND
215
                            lp_id = $lp_id AND
216
                            user_id = $user_id
217
                            $session
218
                        ORDER BY view_count DESC";
219
                $res = Database::query($sql);
220
221
                if (Database::num_rows($res) > 0) {
222
                    $row = Database::fetch_array($res);
223
                    $this->attempt = $row['view_count'];
224
                    $this->lp_view_id = $row['iid'];
225
                    $this->last_item_seen = $row['last_item'];
226
                    $this->progress_db = $row['progress'];
227
                    $this->lp_view_session_id = $row['session_id'];
228
                } elseif (!api_is_invitee()) {
229
                    $this->attempt = 1;
230
                    $params = [
231
                        'c_id' => $course_id,
232
                        'lp_id' => $lp_id,
233
                        'user_id' => $user_id,
234
                        'view_count' => 1,
235
                        'session_id' => $session_id,
236
                        'last_item' => 0,
237
                    ];
238
                    $this->last_item_seen = 0;
239
                    $this->lp_view_session_id = $session_id;
240
                    $this->lp_view_id = Database::insert($lp_table, $params);
241
                }
242
243
                // Initialise items.
244
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
245
                $sql = "SELECT * FROM $lp_item_table
246
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
247
                        ORDER BY parent_item_id, display_order";
248
                $res = Database::query($sql);
249
250
                $lp_item_id_list = [];
251
                while ($row = Database::fetch_array($res)) {
252
                    $lp_item_id_list[] = $row['iid'];
253
                    switch ($this->type) {
254
                        case 3: //aicc
255
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
256
                            if (is_object($oItem)) {
257
                                $my_item_id = $oItem->get_id();
258
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
259
                                $oItem->set_prevent_reinit($this->prevent_reinit);
260
                                // Don't use reference here as the next loop will make the pointed object change.
261
                                $this->items[$my_item_id] = $oItem;
262
                                $this->refs_list[$oItem->ref] = $my_item_id;
263
                            }
264
                            break;
265
                        case 2:
266
                            $oItem = new scormItem('db', $row['iid'], $course_id);
267
                            if (is_object($oItem)) {
268
                                $my_item_id = $oItem->get_id();
269
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
270
                                $oItem->set_prevent_reinit($this->prevent_reinit);
271
                                // Don't use reference here as the next loop will make the pointed object change.
272
                                $this->items[$my_item_id] = $oItem;
273
                                $this->refs_list[$oItem->ref] = $my_item_id;
274
                            }
275
                            break;
276
                        case 1:
277
                        default:
278
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
279
                            if (is_object($oItem)) {
280
                                $my_item_id = $oItem->get_id();
281
                                // Moved down to when we are sure the item_view exists.
282
                                //$oItem->set_lp_view($this->lp_view_id);
283
                                $oItem->set_prevent_reinit($this->prevent_reinit);
284
                                // Don't use reference here as the next loop will make the pointed object change.
285
                                $this->items[$my_item_id] = $oItem;
286
                                $this->refs_list[$my_item_id] = $my_item_id;
287
                            }
288
                            break;
289
                    }
290
291
                    // Setting the object level with variable $this->items[$i][parent]
292
                    foreach ($this->items as $itemLPObject) {
293
                        $level = self::get_level_for_item(
294
                            $this->items,
295
                            $itemLPObject->db_id
296
                        );
297
                        $itemLPObject->level = $level;
298
                    }
299
300
                    // Setting the view in the item object.
301
                    if (is_object($this->items[$row['iid']])) {
302
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
303
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
304
                            $this->items[$row['iid']]->current_start_time = 0;
305
                            $this->items[$row['iid']]->current_stop_time = 0;
306
                        }
307
                    }
308
                }
309
310
                if (!empty($lp_item_id_list)) {
311
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
312
                    if (!empty($lp_item_id_list_to_string)) {
313
                        // Get last viewing vars.
314
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
315
                        // This query should only return one or zero result.
316
                        $sql = "SELECT lp_item_id, status
317
                                FROM $itemViewTable
318
                                WHERE
319
                                    c_id = $course_id AND
320
                                    lp_view_id = ".$this->get_view_id()." AND
321
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
322
                                ORDER BY view_count DESC ";
323
                        $status_list = [];
324
                        $res = Database::query($sql);
325
                        while ($row = Database:: fetch_array($res)) {
326
                            $status_list[$row['lp_item_id']] = $row['status'];
327
                        }
328
329
                        foreach ($lp_item_id_list as $item_id) {
330
                            if (isset($status_list[$item_id])) {
331
                                $status = $status_list[$item_id];
332
                                if (is_object($this->items[$item_id])) {
333
                                    $this->items[$item_id]->set_status($status);
334
                                    if (empty($status)) {
335
                                        $this->items[$item_id]->set_status(
336
                                            $this->default_status
337
                                        );
338
                                    }
339
                                }
340
                            } else {
341
                                if (!api_is_invitee()) {
342
                                    if (is_object($this->items[$item_id])) {
343
                                        $this->items[$item_id]->set_status(
344
                                            $this->default_status
345
                                        );
346
                                    }
347
348
                                    if (!empty($this->lp_view_id)) {
349
                                        // Add that row to the lp_item_view table so that
350
                                        // we have something to show in the stats page.
351
                                        $params = [
352
                                            'c_id' => $course_id,
353
                                            'lp_item_id' => $item_id,
354
                                            'lp_view_id' => $this->lp_view_id,
355
                                            'view_count' => 1,
356
                                            'status' => 'not attempted',
357
                                            'start_time' => time(),
358
                                            'total_time' => 0,
359
                                            'score' => 0,
360
                                        ];
361
                                        $insertId = Database::insert($itemViewTable, $params);
362
363
                                        $this->items[$item_id]->set_lp_view(
364
                                            $this->lp_view_id,
365
                                            $course_id
366
                                        );
367
                                    }
368
                                }
369
                            }
370
                        }
371
                    }
372
                }
373
374
                $this->ordered_items = self::get_flat_ordered_items_list(
375
                    $this->get_id(),
376
                    0,
377
                    $course_id
378
                );
379
                $this->max_ordered_items = 0;
380
                foreach ($this->ordered_items as $index => $dummy) {
381
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
382
                        $this->max_ordered_items = $index;
383
                    }
384
                }
385
                // TODO: Define the current item better.
386
                $this->first();
387
                if ($debug) {
388
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
389
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
390
                }
391
            } else {
392
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
393
            }
394
        }
395
    }
396
397
    public function getEntity(): CLp
398
    {
399
        return $this->entity;
400
    }
401
402
    /**
403
     * @return string
404
     */
405
    public function getCourseCode()
406
    {
407
        return $this->cc;
408
    }
409
410
    /**
411
     * @return int
412
     */
413
    public function get_course_int_id()
414
    {
415
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
416
    }
417
418
    /**
419
     * @param $course_id
420
     *
421
     * @return int
422
     */
423
    public function set_course_int_id($course_id)
424
    {
425
        return $this->course_int_id = (int) $course_id;
426
    }
427
428
    /**
429
     * Function rewritten based on old_add_item() from Yannick Warnier.
430
     * Due the fact that users can decide where the item should come, I had to overlook this function and
431
     * I found it better to rewrite it. Old function is still available.
432
     * Added also the possibility to add a description.
433
     *
434
     * @param int    $parent
435
     * @param int    $previous
436
     * @param string $type
437
     * @param int    $id               resource ID (ref)
438
     * @param string $title
439
     * @param string $description
440
     * @param int    $prerequisites
441
     * @param int    $max_time_allowed
442
     * @param int    $userId
443
     *
444
     * @return int
445
     */
446
    public function add_item(
447
        $parent,
448
        $previous,
449
        $type = 'dir',
450
        $id,
451
        $title,
452
        $description,
453
        $prerequisites = 0,
454
        $max_time_allowed = 0,
455
        $userId = 0
456
    ) {
457
        $course_id = $this->course_info['real_id'];
458
        if (empty($course_id)) {
459
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
460
            $this->course_info = api_get_course_info($this->cc);
461
            $course_id = $this->course_info['real_id'];
462
        }
463
        $userId = empty($userId) ? api_get_user_id() : $userId;
464
        $sessionId = api_get_session_id();
465
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
466
        $_course = $this->course_info;
467
        $parent = (int) $parent;
468
        $previous = (int) $previous;
469
        $id = (int) $id;
470
        $max_time_allowed = htmlentities($max_time_allowed);
471
        if (empty($max_time_allowed)) {
472
            $max_time_allowed = 0;
473
        }
474
        $sql = "SELECT COUNT(iid) AS num
475
                FROM $tbl_lp_item
476
                WHERE
477
                    c_id = $course_id AND
478
                    lp_id = ".$this->get_id()." AND
479
                    parent_item_id = $parent ";
480
481
        $res_count = Database::query($sql);
482
        $row = Database::fetch_array($res_count);
483
        $num = $row['num'];
484
485
        $tmp_previous = 0;
486
        $display_order = 0;
487
        $next = 0;
488
        if ($num > 0) {
489
            if (empty($previous)) {
490
                $sql = "SELECT iid, next_item_id, display_order
491
                        FROM $tbl_lp_item
492
                        WHERE
493
                            c_id = $course_id AND
494
                            lp_id = ".$this->get_id()." AND
495
                            parent_item_id = $parent AND
496
                            previous_item_id = 0 OR
497
                            previous_item_id = $parent ";
498
                $result = Database::query($sql);
499
                $row = Database::fetch_array($result);
500
                if ($row) {
501
                    $next = $row['iid'];
502
                }
503
            } else {
504
                $previous = (int) $previous;
505
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
506
						FROM $tbl_lp_item
507
                        WHERE
508
                            c_id = $course_id AND
509
                            lp_id = ".$this->get_id()." AND
510
                            iid = $previous";
511
                $result = Database::query($sql);
512
                $row = Database::fetch_array($result);
513
                if ($row) {
514
                    $tmp_previous = $row['iid'];
515
                    $next = $row['next_item_id'];
516
                    $display_order = $row['display_order'];
517
                }
518
            }
519
        }
520
521
        $id = (int) $id;
522
        $typeCleaned = Database::escape_string($type);
523
        $max_score = 100;
524
        if ('quiz' === $type && $id) {
525
            $sql = 'SELECT SUM(ponderation)
526
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
527
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
528
                    ON
529
                        quiz_question.id = quiz_rel_question.question_id AND
530
                        quiz_question.c_id = quiz_rel_question.c_id
531
                    WHERE
532
                        quiz_rel_question.exercice_id = '.$id." AND
533
                        quiz_question.c_id = $course_id AND
534
                        quiz_rel_question.c_id = $course_id ";
535
            $rsQuiz = Database::query($sql);
536
            $max_score = Database::result($rsQuiz, 0, 0);
537
538
            // Disabling the exercise if we add it inside a LP
539
            $exercise = new Exercise($course_id);
540
            $exercise->read($id);
541
            $exercise->disable();
542
            $exercise->save();
543
        }
544
545
        $params = [
546
            'c_id' => $course_id,
547
            'lp_id' => $this->get_id(),
548
            'item_type' => $typeCleaned,
549
            'ref' => '',
550
            'title' => $title,
551
            'description' => $description,
552
            'path' => $id,
553
            'max_score' => $max_score,
554
            'parent_item_id' => $parent,
555
            'previous_item_id' => $previous,
556
            'next_item_id' => (int) $next,
557
            'display_order' => $display_order + 1,
558
            'prerequisite' => $prerequisites,
559
            'max_time_allowed' => $max_time_allowed,
560
            'min_score' => 0,
561
            'launch_data' => '',
562
        ];
563
564
        if (0 != $prerequisites) {
565
            $params['prerequisite'] = $prerequisites;
566
        }
567
568
        $new_item_id = Database::insert($tbl_lp_item, $params);
569
        if ($new_item_id) {
570
            if (!empty($next)) {
571
                $sql = "UPDATE $tbl_lp_item
572
                        SET previous_item_id = $new_item_id
573
                        WHERE c_id = $course_id AND iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
574
                Database::query($sql);
575
            }
576
577
            // Update the item that should be before the new item.
578
            if (!empty($tmp_previous)) {
579
                $sql = "UPDATE $tbl_lp_item
580
                        SET next_item_id = $new_item_id
581
                        WHERE c_id = $course_id AND iid = $tmp_previous";
582
                Database::query($sql);
583
            }
584
585
            // Update all the items after the new item.
586
            $sql = "UPDATE $tbl_lp_item
587
                        SET display_order = display_order + 1
588
                    WHERE
589
                        c_id = $course_id AND
590
                        lp_id = ".$this->get_id()." AND
591
                        iid <> $new_item_id AND
592
                        parent_item_id = $parent AND
593
                        display_order > $display_order";
594
            Database::query($sql);
595
596
            // Update the item that should come after the new item.
597
            $sql = "UPDATE $tbl_lp_item
598
                    SET ref = $new_item_id
599
                    WHERE c_id = $course_id AND iid = $new_item_id";
600
            Database::query($sql);
601
602
            $sql = "UPDATE $tbl_lp_item
603
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
604
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
605
            Database::query($sql);
606
607
            // Upload audio.
608
            if (!empty($_FILES['mp3']['name'])) {
609
                // Create the audio folder if it does not exist yet.
610
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
611
                if (!is_dir($filepath.'audio')) {
612
                    mkdir(
613
                        $filepath.'audio',
614
                        api_get_permissions_for_new_directories()
615
                    );
616
                    $audio_id = DocumentManager::addDocument(
617
                        $_course,
618
                        '/audio',
619
                        'folder',
620
                        0,
621
                        'audio',
622
                        '',
623
                        0,
624
                        true,
625
                        null,
626
                        $sessionId,
627
                        $userId
628
                    );
629
                }
630
631
                $file_path = handle_uploaded_document(
632
                    $_course,
633
                    $_FILES['mp3'],
634
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
635
                    '/audio',
636
                    $userId,
637
                    '',
638
                    '',
639
                    '',
640
                    '',
641
                    false
642
                );
643
644
                // Getting the filename only.
645
                $file_components = explode('/', $file_path);
646
                $file = $file_components[count($file_components) - 1];
647
648
                // Store the mp3 file in the lp_item table.
649
                $sql = "UPDATE $tbl_lp_item SET
650
                          audio = '".Database::escape_string($file)."'
651
                        WHERE iid = '".intval($new_item_id)."'";
652
                Database::query($sql);
653
            }
654
        }
655
656
        return $new_item_id;
657
    }
658
659
    /**
660
     * Static admin function allowing addition of a learnpath to a course.
661
     *
662
     * @param string $courseCode
663
     * @param string $name
664
     * @param string $description
665
     * @param string $learnpath
666
     * @param string $origin
667
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
668
     * @param string $publicated_on
669
     * @param string $expired_on
670
     * @param int    $categoryId
671
     * @param int    $userId
672
     *
673
     * @return int The new learnpath ID on success, 0 on failure
674
     */
675
    public static function add_lp(
676
        $courseCode,
677
        $name,
678
        $description = '',
679
        $learnpath = 'guess',
680
        $origin = 'zip',
681
        $zipname = '',
682
        $publicated_on = '',
683
        $expired_on = '',
684
        $categoryId = 0,
685
        $userId = 0
686
    ) {
687
        global $charset;
688
689
        if (!empty($courseCode)) {
690
            $courseInfo = api_get_course_info($courseCode);
691
            $course_id = $courseInfo['real_id'];
692
        } else {
693
            $course_id = api_get_course_int_id();
694
            $courseInfo = api_get_course_info();
695
        }
696
697
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
698
        // Check course code exists.
699
        // Check lp_name doesn't exist, otherwise append something.
700
        $i = 0;
701
        $categoryId = (int) $categoryId;
702
        // Session id.
703
        $session_id = api_get_session_id();
704
        $userId = empty($userId) ? api_get_user_id() : $userId;
705
706
        if (empty($publicated_on)) {
707
            $publicated_on = null;
708
        } else {
709
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
710
        }
711
712
        if (empty($expired_on)) {
713
            $expired_on = null;
714
        } else {
715
            $expired_on = api_get_utc_datetime($expired_on, true, true);
716
        }
717
718
        $check_name = "SELECT * FROM $tbl_lp
719
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
720
        $res_name = Database::query($check_name);
721
722
        while (Database::num_rows($res_name)) {
723
            // There is already one such name, update the current one a bit.
724
            $i++;
725
            $name = $name.' - '.$i;
726
            $check_name = "SELECT * FROM $tbl_lp
727
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
728
            $res_name = Database::query($check_name);
729
        }
730
        // New name does not exist yet; keep it.
731
        // Escape description.
732
        // Kevin: added htmlentities().
733
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
734
        $type = 1;
735
        switch ($learnpath) {
736
            case 'guess':
737
            case 'aicc':
738
                break;
739
            case 'dokeos':
740
            case 'chamilo':
741
                $type = 1;
742
                break;
743
        }
744
745
        $id = null;
746
        $sessionEntity = api_get_session_entity();
747
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
748
749
        switch ($origin) {
750
            case 'zip':
751
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
752
                break;
753
            case 'manual':
754
            default:
755
                $get_max = "SELECT MAX(display_order)
756
                            FROM $tbl_lp WHERE c_id = $course_id";
757
                $res_max = Database::query($get_max);
758
                if (Database::num_rows($res_max) < 1) {
759
                    $dsp = 1;
760
                } else {
761
                    $row = Database::fetch_array($res_max);
762
                    $dsp = $row[0] + 1;
763
                }
764
765
                $category = null;
766
                if (!empty($categoryId)) {
767
                    $category = Container::getLpCategoryRepository()->find($categoryId);
768
                }
769
770
                $lp = new CLp();
771
                $lp
772
                    ->setCId($course_id)
773
                    ->setLpType($type)
774
                    ->setName($name)
775
                    ->setDescription($description)
776
                    ->setDisplayOrder($dsp)
777
                    ->setSessionId($session_id)
778
                    ->setCategory($category)
779
                    ->setPublicatedOn($publicated_on)
780
                    ->setExpiredOn($expired_on)
781
                    ->setParent($courseEntity)
782
                    ->addCourseLink($courseEntity, $sessionEntity)
783
                ;
784
785
                $repo = Container::getLpRepository();
786
                $em = $repo->getEntityManager();
787
                $em->persist($lp);
788
                $em->flush();
789
790
                if ($lp->getIid()) {
791
                    $id = $lp->getIid();
792
                }
793
794
                // Insert into item_property.
795
                /*api_item_property_update(
796
                    $courseInfo,
797
                    TOOL_LEARNPATH,
798
                    $id,
799
                    'LearnpathAdded',
800
                    $userId
801
                );
802
                api_set_default_visibility(
803
                    $id,
804
                    TOOL_LEARNPATH,
805
                    0,
806
                    $courseInfo,
807
                    $session_id,
808
                    $userId
809
                );*/
810
811
                break;
812
        }
813
814
        return $id;
815
    }
816
817
    /**
818
     * Auto completes the parents of an item in case it's been completed or passed.
819
     *
820
     * @param int $item Optional ID of the item from which to look for parents
821
     */
822
    public function autocomplete_parents($item)
823
    {
824
        $debug = $this->debug;
825
826
        if (empty($item)) {
827
            $item = $this->current;
828
        }
829
830
        $currentItem = $this->getItem($item);
831
        if ($currentItem) {
832
            $parent_id = $currentItem->get_parent();
833
            $parent = $this->getItem($parent_id);
834
            if ($parent) {
835
                // if $item points to an object and there is a parent.
836
                if ($debug) {
837
                    error_log(
838
                        'Autocompleting parent of item '.$item.' '.
839
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
840
                        0
841
                    );
842
                }
843
844
                // New experiment including failed and browsed in completed status.
845
                //$current_status = $currentItem->get_status();
846
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
847
                // Fixes chapter auto complete
848
                if (true) {
849
                    // If the current item is completed or passes or succeeded.
850
                    $updateParentStatus = true;
851
                    if ($debug) {
852
                        error_log('Status of current item is alright');
853
                    }
854
855
                    foreach ($parent->get_children() as $childItemId) {
856
                        $childItem = $this->getItem($childItemId);
857
858
                        // If children was not set try to get the info
859
                        if (empty($childItem->db_item_view_id)) {
860
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
861
                        }
862
863
                        // Check all his brothers (parent's children) for completion status.
864
                        if ($childItemId != $item) {
865
                            if ($debug) {
866
                                error_log(
867
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
868
                                    0
869
                                );
870
                            }
871
                            // Trying completing parents of failed and browsed items as well.
872
                            if ($childItem->status_is(
873
                                [
874
                                    'completed',
875
                                    'passed',
876
                                    'succeeded',
877
                                    'browsed',
878
                                    'failed',
879
                                ]
880
                            )
881
                            ) {
882
                                // Keep completion status to true.
883
                                continue;
884
                            } else {
885
                                if ($debug > 2) {
886
                                    error_log(
887
                                        '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,
888
                                        0
889
                                    );
890
                                }
891
                                $updateParentStatus = false;
892
                                break;
893
                            }
894
                        }
895
                    }
896
897
                    if ($updateParentStatus) {
898
                        // If all the children were completed:
899
                        $parent->set_status('completed');
900
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
901
                        // Force the status to "completed"
902
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
903
                        $this->update_queue[$parent->get_id()] = 'completed';
904
                        if ($debug) {
905
                            error_log(
906
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
907
                                print_r($this->update_queue, 1),
908
                                0
909
                            );
910
                        }
911
                        // Recursive call.
912
                        $this->autocomplete_parents($parent->get_id());
913
                    }
914
                }
915
            } else {
916
                if ($debug) {
917
                    error_log("Parent #$parent_id does not exists");
918
                }
919
            }
920
        } else {
921
            if ($debug) {
922
                error_log("#$item is an item that doesn't have parents");
923
            }
924
        }
925
    }
926
927
    /**
928
     * Closes the current resource.
929
     *
930
     * Stops the timer
931
     * Saves into the database if required
932
     * Clears the current resource data from this object
933
     *
934
     * @return bool True on success, false on failure
935
     */
936
    public function close()
937
    {
938
        if (empty($this->lp_id)) {
939
            $this->error = 'Trying to close this learnpath but no ID is set';
940
941
            return false;
942
        }
943
        $this->current_time_stop = time();
944
        $this->ordered_items = [];
945
        $this->index = 0;
946
        unset($this->lp_id);
947
        //unset other stuff
948
        return true;
949
    }
950
951
    /**
952
     * Static admin function allowing removal of a learnpath.
953
     *
954
     * @param array  $courseInfo
955
     * @param int    $id         Learnpath ID
956
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
957
     *
958
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
959
     */
960
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
961
    {
962
        $course_id = api_get_course_int_id();
963
        if (!empty($courseInfo)) {
964
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
965
        }
966
967
        // TODO: Implement a way of getting this to work when the current object is not set.
968
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
969
        // If an ID is specifically given and the current LP is not the same, prevent delete.
970
        if (!empty($id) && ($id != $this->lp_id)) {
971
            return false;
972
        }
973
974
        $lp = Database::get_course_table(TABLE_LP_MAIN);
975
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
976
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
977
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
978
979
        // Delete lp item id.
980
        foreach ($this->items as $lpItemId => $dummy) {
981
            $sql = "DELETE FROM $lp_item_view
982
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
983
            Database::query($sql);
984
        }
985
986
        // Proposed by Christophe (nickname: clefevre)
987
        $sql = "DELETE FROM $lp_item
988
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
989
        Database::query($sql);
990
991
        $sql = "DELETE FROM $lp_view
992
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
993
        Database::query($sql);
994
995
        self::toggleVisibility($this->lp_id, 0);
996
997
        if (2 == $this->type || 3 == $this->type) {
998
            // This is a scorm learning path, delete the files as well.
999
            $sql = "SELECT path FROM $lp
1000
                    WHERE iid = ".$this->lp_id;
1001
            $res = Database::query($sql);
1002
            if (Database::num_rows($res) > 0) {
1003
                $row = Database::fetch_array($res);
1004
                $path = $row['path'];
1005
                $sql = "SELECT id FROM $lp
1006
                        WHERE
1007
                            c_id = $course_id AND
1008
                            path = '$path' AND
1009
                            iid != ".$this->lp_id;
1010
                $res = Database::query($sql);
1011
                if (Database::num_rows($res) > 0) {
1012
                    // Another learning path uses this directory, so don't delete it.
1013
                    if ($this->debug > 2) {
1014
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1015
                    }
1016
                } else {
1017
                    // No other LP uses that directory, delete it.
1018
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1019
                    // The absolute system path for this course.
1020
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1021
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1022
                        if ($this->debug > 2) {
1023
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1024
                        }
1025
                        // Proposed by Christophe (clefevre).
1026
                        if (0 == strcmp(substr($path, -2), "/.")) {
1027
                            $path = substr($path, 0, -1); // Remove "." at the end.
1028
                        }
1029
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1030
                        rmdirr($course_scorm_dir.$path);
1031
                    }
1032
                }
1033
            }
1034
        }
1035
1036
       if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1037
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1038
            $sql = "DELETE FROM $table
1039
                    WHERE
1040
                        lp_id = {$this->lp_id} AND
1041
                        c_id = $course_id ";
1042
            Database::query($sql);
1043
        }
1044
1045
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1046
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1047
        // Delete tools
1048
        $sql = "DELETE FROM $tbl_tool
1049
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1050
        Database::query($sql);*/
1051
1052
        /*$sql = "DELETE FROM $lp
1053
                WHERE iid = ".$this->lp_id;
1054
        Database::query($sql);*/
1055
        $repo = Container::getLpRepository();
1056
        $lp = $repo->find($this->lp_id);
1057
        $repo->getEntityManager()->remove($lp);
1058
        $repo->getEntityManager()->flush();
1059
1060
        // Updates the display order of all lps.
1061
        $this->update_display_order();
1062
1063
        /*api_item_property_update(
1064
            api_get_course_info(),
1065
            TOOL_LEARNPATH,
1066
            $this->lp_id,
1067
            'delete',
1068
            api_get_user_id()
1069
        );*/
1070
1071
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1072
            api_get_course_id(),
1073
            4,
1074
            $id,
1075
            api_get_session_id()
1076
        );
1077
1078
        if (false !== $link_info) {
1079
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1080
        }
1081
1082
        if ('true' == api_get_setting('search_enabled')) {
1083
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1084
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1085
        }
1086
    }
1087
1088
    /**
1089
     * Removes all the children of one item - dangerous!
1090
     *
1091
     * @param int $id Element ID of which children have to be removed
1092
     *
1093
     * @return int Total number of children removed
1094
     */
1095
    public function delete_children_items($id)
1096
    {
1097
        $course_id = $this->course_info['real_id'];
1098
1099
        $num = 0;
1100
        $id = (int) $id;
1101
        if (empty($id) || empty($course_id)) {
1102
            return false;
1103
        }
1104
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1105
        $sql = "SELECT * FROM $lp_item
1106
                WHERE c_id = $course_id AND parent_item_id = $id";
1107
        $res = Database::query($sql);
1108
        while ($row = Database::fetch_array($res)) {
1109
            $num += $this->delete_children_items($row['iid']);
1110
            $sql = "DELETE FROM $lp_item
1111
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1112
            Database::query($sql);
1113
            $num++;
1114
        }
1115
1116
        return $num;
1117
    }
1118
1119
    /**
1120
     * Removes an item from the current learnpath.
1121
     *
1122
     * @param int $id Elem ID (0 if first)
1123
     *
1124
     * @return int Number of elements moved
1125
     *
1126
     * @todo implement resource removal
1127
     */
1128
    public function delete_item($id)
1129
    {
1130
        $course_id = api_get_course_int_id();
1131
        $id = (int) $id;
1132
        // TODO: Implement the resource removal.
1133
        if (empty($id) || empty($course_id)) {
1134
            return false;
1135
        }
1136
        // First select item to get previous, next, and display order.
1137
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1138
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1139
        $res_sel = Database::query($sql_sel);
1140
        if (Database::num_rows($res_sel) < 1) {
1141
            return false;
1142
        }
1143
        $row = Database::fetch_array($res_sel);
1144
        $previous = $row['previous_item_id'];
1145
        $next = $row['next_item_id'];
1146
        $display = $row['display_order'];
1147
        $parent = $row['parent_item_id'];
1148
        $lp = $row['lp_id'];
1149
        // Delete children items.
1150
        $this->delete_children_items($id);
1151
        // Now delete the item.
1152
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1153
        Database::query($sql_del);
1154
        // Now update surrounding items.
1155
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1156
                    WHERE iid = $previous";
1157
        Database::query($sql_upd);
1158
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1159
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1160
        Database::query($sql_upd);
1161
        // Now update all following items with new display order.
1162
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1163
                    WHERE
1164
                        c_id = $course_id AND
1165
                        lp_id = $lp AND
1166
                        parent_item_id = $parent AND
1167
                        display_order > $display";
1168
        Database::query($sql_all);
1169
1170
        //Removing prerequisites since the item will not longer exist
1171
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1172
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1173
        Database::query($sql_all);
1174
1175
        $sql = "UPDATE $lp_item
1176
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1177
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1178
        Database::query($sql);
1179
1180
        // Remove from search engine if enabled.
1181
        if ('true' === api_get_setting('search_enabled')) {
1182
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1183
            $sql = 'SELECT * FROM %s
1184
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1185
                    LIMIT 1';
1186
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1187
            $res = Database::query($sql);
1188
            if (Database::num_rows($res) > 0) {
1189
                $row2 = Database::fetch_array($res);
1190
                $di = new ChamiloIndexer();
1191
                $di->remove_document($row2['search_did']);
1192
            }
1193
            $sql = 'DELETE FROM %s
1194
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1195
                    LIMIT 1';
1196
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1197
            Database::query($sql);
1198
        }
1199
    }
1200
1201
    /**
1202
     * Updates an item's content in place.
1203
     *
1204
     * @param int    $id               Element ID
1205
     * @param int    $parent           Parent item ID
1206
     * @param int    $previous         Previous item ID
1207
     * @param string $title            Item title
1208
     * @param string $description      Item description
1209
     * @param string $prerequisites    Prerequisites (optional)
1210
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1211
     * @param int    $max_time_allowed
1212
     * @param string $url
1213
     *
1214
     * @return bool True on success, false on error
1215
     */
1216
    public function edit_item(
1217
        $id,
1218
        $parent,
1219
        $previous,
1220
        $title,
1221
        $description,
1222
        $prerequisites = '0',
1223
        $audio = [],
1224
        $max_time_allowed = 0,
1225
        $url = ''
1226
    ) {
1227
        $course_id = api_get_course_int_id();
1228
        $_course = api_get_course_info();
1229
        $id = (int) $id;
1230
1231
        if (empty($max_time_allowed)) {
1232
            $max_time_allowed = 0;
1233
        }
1234
1235
        if (empty($id) || empty($_course)) {
1236
            return false;
1237
        }
1238
1239
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1240
        $sql = "SELECT * FROM $tbl_lp_item
1241
                WHERE iid = $id";
1242
        $res_select = Database::query($sql);
1243
        $row_select = Database::fetch_array($res_select);
1244
        $audio_update_sql = '';
1245
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1246
            // Create the audio folder if it does not exist yet.
1247
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1248
            if (!is_dir($filepath.'audio')) {
1249
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1250
                $audio_id = DocumentManager::addDocument(
1251
                    $_course,
1252
                    '/audio',
1253
                    'folder',
1254
                    0,
1255
                    'audio'
1256
                );
1257
            }
1258
1259
            // Upload file in documents.
1260
            $pi = pathinfo($audio['name']);
1261
            if ('mp3' === $pi['extension']) {
1262
                $c_det = api_get_course_info($this->cc);
1263
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1264
                $path = handle_uploaded_document(
1265
                    $c_det,
1266
                    $audio,
1267
                    $bp,
1268
                    '/audio',
1269
                    api_get_user_id(),
1270
                    0,
1271
                    null,
1272
                    0,
1273
                    'rename',
1274
                    false,
1275
                    0
1276
                );
1277
                $path = substr($path, 7);
1278
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1279
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1280
            }
1281
        }
1282
1283
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1284
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1285
1286
        // TODO: htmlspecialchars to be checked for encoding related problems.
1287
        if ($same_parent && $same_previous) {
1288
            // Only update title and description.
1289
            $sql = "UPDATE $tbl_lp_item
1290
                    SET title = '".Database::escape_string($title)."',
1291
                        prerequisite = '".$prerequisites."',
1292
                        description = '".Database::escape_string($description)."'
1293
                        ".$audio_update_sql.",
1294
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1295
                    WHERE iid = $id";
1296
            Database::query($sql);
1297
        } else {
1298
            $old_parent = $row_select['parent_item_id'];
1299
            $old_previous = $row_select['previous_item_id'];
1300
            $old_next = $row_select['next_item_id'];
1301
            $old_order = $row_select['display_order'];
1302
            $old_prerequisite = $row_select['prerequisite'];
1303
            $old_max_time_allowed = $row_select['max_time_allowed'];
1304
1305
            /* BEGIN -- virtually remove the current item id */
1306
            /* for the next and previous item it is like the current item doesn't exist anymore */
1307
            if (0 != $old_previous) {
1308
                // Next
1309
                $sql = "UPDATE $tbl_lp_item
1310
                        SET next_item_id = $old_next
1311
                        WHERE iid = $old_previous";
1312
                Database::query($sql);
1313
            }
1314
1315
            if (!empty($old_next)) {
1316
                // Previous
1317
                $sql = "UPDATE $tbl_lp_item
1318
                        SET previous_item_id = $old_previous
1319
                        WHERE iid = $old_next";
1320
                Database::query($sql);
1321
            }
1322
1323
            // display_order - 1 for every item with a display_order
1324
            // bigger then the display_order of the current item.
1325
            $sql = "UPDATE $tbl_lp_item
1326
                    SET display_order = display_order - 1
1327
                    WHERE
1328
                        c_id = $course_id AND
1329
                        display_order > $old_order AND
1330
                        lp_id = ".$this->lp_id." AND
1331
                        parent_item_id = $old_parent";
1332
            Database::query($sql);
1333
            /* END -- virtually remove the current item id */
1334
1335
            /* BEGIN -- update the current item id to his new location */
1336
            if (0 == $previous) {
1337
                // Select the data of the item that should come after the current item.
1338
                $sql = "SELECT id, display_order
1339
                        FROM $tbl_lp_item
1340
                        WHERE
1341
                            c_id = $course_id AND
1342
                            lp_id = ".$this->lp_id." AND
1343
                            parent_item_id = $parent AND
1344
                            previous_item_id = $previous";
1345
                $res_select_old = Database::query($sql);
1346
                $row_select_old = Database::fetch_array($res_select_old);
1347
1348
                // If the new parent didn't have children before.
1349
                if (0 == Database::num_rows($res_select_old)) {
1350
                    $new_next = 0;
1351
                    $new_order = 1;
1352
                } else {
1353
                    $new_next = $row_select_old['id'];
1354
                    $new_order = $row_select_old['display_order'];
1355
                }
1356
            } else {
1357
                // Select the data of the item that should come before the current item.
1358
                $sql = "SELECT next_item_id, display_order
1359
                        FROM $tbl_lp_item
1360
                        WHERE iid = $previous";
1361
                $res_select_old = Database::query($sql);
1362
                $row_select_old = Database::fetch_array($res_select_old);
1363
                $new_next = $row_select_old['next_item_id'];
1364
                $new_order = $row_select_old['display_order'] + 1;
1365
            }
1366
1367
            // TODO: htmlspecialchars to be checked for encoding related problems.
1368
            // Update the current item with the new data.
1369
            $sql = "UPDATE $tbl_lp_item
1370
                    SET
1371
                        title = '".Database::escape_string($title)."',
1372
                        description = '".Database::escape_string($description)."',
1373
                        parent_item_id = $parent,
1374
                        previous_item_id = $previous,
1375
                        next_item_id = $new_next,
1376
                        display_order = $new_order
1377
                        $audio_update_sql
1378
                    WHERE iid = $id";
1379
            Database::query($sql);
1380
1381
            if (0 != $previous) {
1382
                // Update the previous item's next_item_id.
1383
                $sql = "UPDATE $tbl_lp_item
1384
                        SET next_item_id = $id
1385
                        WHERE iid = $previous";
1386
                Database::query($sql);
1387
            }
1388
1389
            if (!empty($new_next)) {
1390
                // Update the next item's previous_item_id.
1391
                $sql = "UPDATE $tbl_lp_item
1392
                        SET previous_item_id = $id
1393
                        WHERE iid = $new_next";
1394
                Database::query($sql);
1395
            }
1396
1397
            if ($old_prerequisite != $prerequisites) {
1398
                $sql = "UPDATE $tbl_lp_item
1399
                        SET prerequisite = '$prerequisites'
1400
                        WHERE iid = $id";
1401
                Database::query($sql);
1402
            }
1403
1404
            if ($old_max_time_allowed != $max_time_allowed) {
1405
                // update max time allowed
1406
                $sql = "UPDATE $tbl_lp_item
1407
                        SET max_time_allowed = $max_time_allowed
1408
                        WHERE iid = $id";
1409
                Database::query($sql);
1410
            }
1411
1412
            // Update all the items with the same or a bigger display_order than the current item.
1413
            $sql = "UPDATE $tbl_lp_item
1414
                    SET display_order = display_order + 1
1415
                    WHERE
1416
                       c_id = $course_id AND
1417
                       lp_id = ".$this->get_id()." AND
1418
                       iid <> $id AND
1419
                       parent_item_id = $parent AND
1420
                       display_order >= $new_order";
1421
            Database::query($sql);
1422
        }
1423
1424
        if ('link' == $row_select['item_type']) {
1425
            $link = new Link();
1426
            $linkId = $row_select['path'];
1427
            $link->updateLink($linkId, $url);
1428
        }
1429
    }
1430
1431
    /**
1432
     * Updates an item's prereq in place.
1433
     *
1434
     * @param int    $id              Element ID
1435
     * @param string $prerequisite_id Prerequisite Element ID
1436
     * @param int    $minScore        Prerequisite min score
1437
     * @param int    $maxScore        Prerequisite max score
1438
     *
1439
     * @return bool True on success, false on error
1440
     */
1441
    public function edit_item_prereq(
1442
        $id,
1443
        $prerequisite_id,
1444
        $minScore = 0,
1445
        $maxScore = 100
1446
    ) {
1447
        $id = (int) $id;
1448
        $prerequisite_id = (int) $prerequisite_id;
1449
1450
        if (empty($id)) {
1451
            return false;
1452
        }
1453
1454
        if (empty($minScore) || $minScore < 0) {
1455
            $minScore = 0;
1456
        }
1457
1458
        if (empty($maxScore) || $maxScore < 0) {
1459
            $maxScore = 100;
1460
        }
1461
1462
        $minScore = (float) $minScore;
1463
        $maxScore = (float) $maxScore;
1464
1465
        if (empty($prerequisite_id)) {
1466
            $prerequisite_id = 'NULL';
1467
            $minScore = 0;
1468
            $maxScore = 100;
1469
        }
1470
1471
        $table = Database::get_course_table(TABLE_LP_ITEM);
1472
        $sql = " UPDATE $table
1473
                 SET
1474
                    prerequisite = $prerequisite_id ,
1475
                    prerequisite_min_score = $minScore ,
1476
                    prerequisite_max_score = $maxScore
1477
                 WHERE iid = $id";
1478
        Database::query($sql);
1479
1480
        return true;
1481
    }
1482
1483
    /**
1484
     * Get the specific prefix index terms of this learning path.
1485
     *
1486
     * @param string $prefix
1487
     *
1488
     * @return array Array of terms
1489
     */
1490
    public function get_common_index_terms_by_prefix($prefix)
1491
    {
1492
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1493
        $terms = get_specific_field_values_list_by_prefix(
1494
            $prefix,
1495
            $this->cc,
1496
            TOOL_LEARNPATH,
1497
            $this->lp_id
1498
        );
1499
        $prefix_terms = [];
1500
        if (!empty($terms)) {
1501
            foreach ($terms as $term) {
1502
                $prefix_terms[] = $term['value'];
1503
            }
1504
        }
1505
1506
        return $prefix_terms;
1507
    }
1508
1509
    /**
1510
     * Gets the number of items currently completed.
1511
     *
1512
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1513
     *
1514
     * @return int The number of items currently completed
1515
     */
1516
    public function get_complete_items_count($failedStatusException = false)
1517
    {
1518
        $i = 0;
1519
        $completedStatusList = [
1520
            'completed',
1521
            'passed',
1522
            'succeeded',
1523
            'browsed',
1524
        ];
1525
1526
        if (!$failedStatusException) {
1527
            $completedStatusList[] = 'failed';
1528
        }
1529
1530
        foreach ($this->items as $id => $dummy) {
1531
            // Trying failed and browsed considered "progressed" as well.
1532
            if ($this->items[$id]->status_is($completedStatusList) &&
1533
                'dir' != $this->items[$id]->get_type()
1534
            ) {
1535
                $i++;
1536
            }
1537
        }
1538
1539
        return $i;
1540
    }
1541
1542
    /**
1543
     * Gets the current item ID.
1544
     *
1545
     * @return int The current learnpath item id
1546
     */
1547
    public function get_current_item_id()
1548
    {
1549
        $current = 0;
1550
        if (!empty($this->current)) {
1551
            $current = (int) $this->current;
1552
        }
1553
1554
        return $current;
1555
    }
1556
1557
    /**
1558
     * Force to get the first learnpath item id.
1559
     *
1560
     * @return int The current learnpath item id
1561
     */
1562
    public function get_first_item_id()
1563
    {
1564
        $current = 0;
1565
        if (is_array($this->ordered_items)) {
1566
            $current = $this->ordered_items[0];
1567
        }
1568
1569
        return $current;
1570
    }
1571
1572
    /**
1573
     * Gets the total number of items available for viewing in this SCORM.
1574
     *
1575
     * @return int The total number of items
1576
     */
1577
    public function get_total_items_count()
1578
    {
1579
        return count($this->items);
1580
    }
1581
1582
    /**
1583
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1584
     *
1585
     * @return int The total no-chapters number of items
1586
     */
1587
    public function getTotalItemsCountWithoutDirs()
1588
    {
1589
        $total = 0;
1590
        $typeListNotToCount = self::getChapterTypes();
1591
        foreach ($this->items as $temp2) {
1592
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1593
                $total++;
1594
            }
1595
        }
1596
1597
        return $total;
1598
    }
1599
1600
    /**
1601
     *  Sets the first element URL.
1602
     */
1603
    public function first()
1604
    {
1605
        if ($this->debug > 0) {
1606
            error_log('In learnpath::first()', 0);
1607
            error_log('$this->last_item_seen '.$this->last_item_seen);
1608
        }
1609
1610
        // Test if the last_item_seen exists and is not a dir.
1611
        if (0 == count($this->ordered_items)) {
1612
            $this->index = 0;
1613
        }
1614
1615
        if (!empty($this->last_item_seen) &&
1616
            !empty($this->items[$this->last_item_seen]) &&
1617
            'dir' != $this->items[$this->last_item_seen]->get_type()
1618
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1619
            //&& !$this->items[$this->last_item_seen]->is_done()
1620
        ) {
1621
            if ($this->debug > 2) {
1622
                error_log(
1623
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1624
                    $this->items[$this->last_item_seen]->get_type()
1625
                );
1626
            }
1627
            $index = -1;
1628
            foreach ($this->ordered_items as $myindex => $item_id) {
1629
                if ($item_id == $this->last_item_seen) {
1630
                    $index = $myindex;
1631
                    break;
1632
                }
1633
            }
1634
            if (-1 == $index) {
1635
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1636
                if ($this->debug > 2) {
1637
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1638
                }
1639
1640
                return false;
1641
            } else {
1642
                $this->last = $this->last_item_seen;
1643
                $this->current = $this->last_item_seen;
1644
                $this->index = $index;
1645
            }
1646
        } else {
1647
            if ($this->debug > 2) {
1648
                error_log('In learnpath::first() - No last item seen', 0);
1649
            }
1650
            $index = 0;
1651
            // Loop through all ordered items and stop at the first item that is
1652
            // not a directory *and* that has not been completed yet.
1653
            while (!empty($this->ordered_items[$index]) &&
1654
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1655
                (
1656
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1657
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1658
                ) && $index < $this->max_ordered_items) {
1659
                $index++;
1660
            }
1661
1662
            $this->last = $this->current;
1663
            // current is
1664
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1665
            $this->index = $index;
1666
            if ($this->debug > 2) {
1667
                error_log('$index '.$index);
1668
                error_log('In learnpath::first() - No last item seen');
1669
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1670
            }
1671
        }
1672
        if ($this->debug > 2) {
1673
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1674
        }
1675
    }
1676
1677
    /**
1678
     * Gets the js library from the database.
1679
     *
1680
     * @return string The name of the javascript library to be used
1681
     */
1682
    public function get_js_lib()
1683
    {
1684
        $lib = '';
1685
        if (!empty($this->js_lib)) {
1686
            $lib = $this->js_lib;
1687
        }
1688
1689
        return $lib;
1690
    }
1691
1692
    /**
1693
     * Gets the learnpath database ID.
1694
     *
1695
     * @return int Learnpath ID in the lp table
1696
     */
1697
    public function get_id()
1698
    {
1699
        if (!empty($this->lp_id)) {
1700
            return (int) $this->lp_id;
1701
        }
1702
1703
        return 0;
1704
    }
1705
1706
    /**
1707
     * Gets the last element URL.
1708
     *
1709
     * @return string URL to load into the viewer
1710
     */
1711
    public function get_last()
1712
    {
1713
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1714
        if (count($this->ordered_items) > 0) {
1715
            $this->index = count($this->ordered_items) - 1;
1716
1717
            return $this->ordered_items[$this->index];
1718
        }
1719
1720
        return false;
1721
    }
1722
1723
    /**
1724
     * Get the last element in the first level.
1725
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1726
     *
1727
     * @return mixed
1728
     */
1729
    public function getLastInFirstLevel()
1730
    {
1731
        try {
1732
            $lastId = Database::getManager()
1733
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1734
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1735
                ->setMaxResults(1)
1736
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1737
                ->getSingleScalarResult();
1738
1739
            return $lastId;
1740
        } catch (Exception $exception) {
1741
            return 0;
1742
        }
1743
    }
1744
1745
    /**
1746
     * Get the learning path name by id.
1747
     *
1748
     * @param int $lpId
1749
     *
1750
     * @return mixed
1751
     */
1752
    public static function getLpNameById($lpId)
1753
    {
1754
        $em = Database::getManager();
1755
1756
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1757
            WHERE clp.iid = :iid')
1758
            ->setParameter('iid', $lpId)
1759
            ->getSingleScalarResult();
1760
    }
1761
1762
    /**
1763
     * Gets the navigation bar for the learnpath display screen.
1764
     *
1765
     * @param string $barId
1766
     *
1767
     * @return string The HTML string to use as a navigation bar
1768
     */
1769
    public function get_navigation_bar($barId = '')
1770
    {
1771
        if (empty($barId)) {
1772
            $barId = 'control-top';
1773
        }
1774
        $lpId = $this->lp_id;
1775
        $mycurrentitemid = $this->get_current_item_id();
1776
1777
        $reportingText = get_lang('Reporting');
1778
        $previousText = get_lang('Previous');
1779
        $nextText = get_lang('Next');
1780
        $fullScreenText = get_lang('Back to normal screen');
1781
1782
        $settings = api_get_configuration_value('lp_view_settings');
1783
        $display = isset($settings['display']) ? $settings['display'] : false;
1784
        $reportingIcon = '
1785
            <a class="icon-toolbar"
1786
                id="stats_link"
1787
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1788
                onclick="window.parent.API.save_asset(); return true;"
1789
                target="content_name" title="'.$reportingText.'">
1790
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1791
            </a>';
1792
1793
        if (!empty($display)) {
1794
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1795
            if (false === $showReporting) {
1796
                $reportingIcon = '';
1797
            }
1798
        }
1799
1800
        $hideArrows = false;
1801
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1802
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1803
        }
1804
1805
        $previousIcon = '';
1806
        $nextIcon = '';
1807
        if (false === $hideArrows) {
1808
            $previousIcon = '
1809
                <a class="icon-toolbar" id="scorm-previous" href="#"
1810
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1811
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1812
                </a>';
1813
1814
            $nextIcon = '
1815
                <a class="icon-toolbar" id="scorm-next" href="#"
1816
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1817
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1818
                </a>';
1819
        }
1820
1821
        if ('fullscreen' === $this->mode) {
1822
            $navbar = '
1823
                  <span id="'.$barId.'" class="buttons">
1824
                    '.$reportingIcon.'
1825
                    '.$previousIcon.'
1826
                    '.$nextIcon.'
1827
                    <a class="icon-toolbar" id="view-embedded"
1828
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1829
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1830
                    </a>
1831
                  </span>';
1832
        } else {
1833
            $navbar = '
1834
                 <span id="'.$barId.'" class="buttons text-right">
1835
                    '.$reportingIcon.'
1836
                    '.$previousIcon.'
1837
                    '.$nextIcon.'
1838
                </span>';
1839
        }
1840
1841
        return $navbar;
1842
    }
1843
1844
    /**
1845
     * Gets the next resource in queue (url).
1846
     *
1847
     * @return string URL to load into the viewer
1848
     */
1849
    public function get_next_index()
1850
    {
1851
        // TODO
1852
        $index = $this->index;
1853
        $index++;
1854
        while (
1855
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1856
            $index < $this->max_ordered_items
1857
        ) {
1858
            $index++;
1859
            if ($index == $this->max_ordered_items) {
1860
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1861
                    return $this->index;
1862
                }
1863
1864
                return $index;
1865
            }
1866
        }
1867
        if (empty($this->ordered_items[$index])) {
1868
            return $this->index;
1869
        }
1870
1871
        return $index;
1872
    }
1873
1874
    /**
1875
     * Gets item_id for the next element.
1876
     *
1877
     * @return int Next item (DB) ID
1878
     */
1879
    public function get_next_item_id()
1880
    {
1881
        $new_index = $this->get_next_index();
1882
        if (!empty($new_index)) {
1883
            if (isset($this->ordered_items[$new_index])) {
1884
                return $this->ordered_items[$new_index];
1885
            }
1886
        }
1887
1888
        return 0;
1889
    }
1890
1891
    /**
1892
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1893
     *
1894
     * Generally, the package provided is in the form of a zip file, so the function
1895
     * has been written to test a zip file. If not a zip, the function will return the
1896
     * default return value: ''
1897
     *
1898
     * @param string $file_path the path to the file
1899
     * @param string $file_name the original name of the file
1900
     *
1901
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1902
     */
1903
    public static function getPackageType($file_path, $file_name)
1904
    {
1905
        // Get name of the zip file without the extension.
1906
        $file_info = pathinfo($file_name);
1907
        $extension = $file_info['extension']; // Extension only.
1908
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1909
                'dll',
1910
                'exe',
1911
            ])) {
1912
            return 'oogie';
1913
        }
1914
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1915
                'dll',
1916
                'exe',
1917
            ])) {
1918
            return 'woogie';
1919
        }
1920
1921
        $zipFile = new PclZip($file_path);
1922
        // Check the zip content (real size and file extension).
1923
        $zipContentArray = $zipFile->listContent();
1924
        $package_type = '';
1925
        $manifest = '';
1926
        $aicc_match_crs = 0;
1927
        $aicc_match_au = 0;
1928
        $aicc_match_des = 0;
1929
        $aicc_match_cst = 0;
1930
        $countItems = 0;
1931
1932
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1933
        if (is_array($zipContentArray)) {
1934
            $countItems = count($zipContentArray);
1935
            if ($countItems > 0) {
1936
                foreach ($zipContentArray as $thisContent) {
1937
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1938
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1939
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1940
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1941
                        $package_type = 'scorm';
1942
                        break; // Exit the foreach loop.
1943
                    } elseif (
1944
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1945
                        in_array(
1946
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1947
                            ['crs', 'au', 'des', 'cst']
1948
                        )
1949
                    ) {
1950
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1951
                        switch ($ext) {
1952
                            case 'crs':
1953
                                $aicc_match_crs = 1;
1954
                                break;
1955
                            case 'au':
1956
                                $aicc_match_au = 1;
1957
                                break;
1958
                            case 'des':
1959
                                $aicc_match_des = 1;
1960
                                break;
1961
                            case 'cst':
1962
                                $aicc_match_cst = 1;
1963
                                break;
1964
                            default:
1965
                                break;
1966
                        }
1967
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1968
                    } else {
1969
                        $package_type = '';
1970
                    }
1971
                }
1972
            }
1973
        }
1974
1975
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1976
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1977
            $package_type = 'aicc';
1978
        }
1979
1980
        // Try with chamilo course builder
1981
        if (empty($package_type)) {
1982
            // Sometimes users will try to upload an empty zip, or a zip with
1983
            // only a folder. Catch that and make the calling function aware.
1984
            // If the single file was the imsmanifest.xml, then $package_type
1985
            // would be 'scorm' and we wouldn't be here.
1986
            if ($countItems < 2) {
1987
                return 'error-empty-package';
1988
            }
1989
            $package_type = 'chamilo';
1990
        }
1991
1992
        return $package_type;
1993
    }
1994
1995
    /**
1996
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1997
     *
1998
     * @return string URL to load into the viewer
1999
     */
2000
    public function get_previous_index()
2001
    {
2002
        $index = $this->index;
2003
        if (isset($this->ordered_items[$index - 1])) {
2004
            $index--;
2005
            while (isset($this->ordered_items[$index]) &&
2006
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
2007
            ) {
2008
                $index--;
2009
                if ($index < 0) {
2010
                    return $this->index;
2011
                }
2012
            }
2013
        }
2014
2015
        return $index;
2016
    }
2017
2018
    /**
2019
     * Gets item_id for the next element.
2020
     *
2021
     * @return int Previous item (DB) ID
2022
     */
2023
    public function get_previous_item_id()
2024
    {
2025
        $index = $this->get_previous_index();
2026
2027
        return $this->ordered_items[$index];
2028
    }
2029
2030
    /**
2031
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2032
     *
2033
     * @param int    $lpItemId
2034
     * @param string $autostart
2035
     *
2036
     * @return string The mediaplayer HTML
2037
     */
2038
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2039
    {
2040
        $course_id = api_get_course_int_id();
2041
        $courseInfo = api_get_course_info();
2042
        $lpItemId = (int) $lpItemId;
2043
2044
        if (empty($courseInfo) || empty($lpItemId)) {
2045
            return '';
2046
        }
2047
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2048
2049
        if (empty($item)) {
2050
            return '';
2051
        }
2052
2053
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2054
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2055
        $itemViewId = (int) $item->db_item_view_id;
2056
2057
        // Getting all the information about the item.
2058
        $sql = "SELECT lp_view.status
2059
                FROM $tbl_lp_item as lpi
2060
                INNER JOIN $tbl_lp_item_view as lp_view
2061
                ON (lpi.iid = lp_view.lp_item_id)
2062
                WHERE
2063
                    lp_view.iid = $itemViewId AND
2064
                    lpi.iid = $lpItemId AND
2065
                    lp_view.c_id = $course_id";
2066
        $result = Database::query($sql);
2067
        $row = Database::fetch_assoc($result);
2068
        $output = '';
2069
        $audio = $item->audio;
2070
2071
        if (!empty($audio)) {
2072
            $list = $_SESSION['oLP']->get_toc();
2073
2074
            switch ($item->get_type()) {
2075
                case 'quiz':
2076
                    $type_quiz = false;
2077
                    foreach ($list as $toc) {
2078
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2079
                            $type_quiz = true;
2080
                        }
2081
                    }
2082
2083
                    if ($type_quiz) {
2084
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2085
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2086
                        } else {
2087
                            $autostart_audio = $autostart;
2088
                        }
2089
                    }
2090
                    break;
2091
                case TOOL_READOUT_TEXT:
2092
                    $autostart_audio = 'false';
2093
                    break;
2094
                default:
2095
                    $autostart_audio = 'true';
2096
            }
2097
2098
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2099
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2100
2101
            $player = Display::getMediaPlayer(
2102
                $file,
2103
                [
2104
                    'id' => 'lp_audio_media_player',
2105
                    'url' => $url,
2106
                    'autoplay' => $autostart_audio,
2107
                    'width' => '100%',
2108
                ]
2109
            );
2110
2111
            // The mp3 player.
2112
            $output = '<div id="container">';
2113
            $output .= $player;
2114
            $output .= '</div>';
2115
        }
2116
2117
        return $output;
2118
    }
2119
2120
    /**
2121
     * @param int   $studentId
2122
     * @param int   $prerequisite
2123
     * @param array $courseInfo
2124
     * @param int   $sessionId
2125
     *
2126
     * @return bool
2127
     */
2128
    public static function isBlockedByPrerequisite(
2129
        $studentId,
2130
        $prerequisite,
2131
        $courseInfo,
2132
        $sessionId
2133
    ) {
2134
        if (empty($courseInfo)) {
2135
            return false;
2136
        }
2137
2138
        $courseId = $courseInfo['real_id'];
2139
2140
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2141
        if ($allow) {
2142
            if (api_is_allowed_to_edit() ||
2143
                api_is_platform_admin(true) ||
2144
                api_is_drh() ||
2145
                api_is_coach($sessionId, $courseId, false)
2146
            ) {
2147
                return false;
2148
            }
2149
        }
2150
2151
        $isBlocked = false;
2152
        if (!empty($prerequisite)) {
2153
            $progress = self::getProgress(
2154
                $prerequisite,
2155
                $studentId,
2156
                $courseId,
2157
                $sessionId
2158
            );
2159
            if ($progress < 100) {
2160
                $isBlocked = true;
2161
            }
2162
2163
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2164
                // Block if it does not exceed minimum time
2165
                // Minimum time (in minutes) to pass the learning path
2166
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2167
2168
                if ($accumulateWorkTime > 0) {
2169
                    // Total time in course (sum of times in learning paths from course)
2170
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2171
2172
                    // Connect with the plugin_licences_course_session table
2173
                    // which indicates what percentage of the time applies
2174
                    // Minimum connection percentage
2175
                    $perc = 100;
2176
                    // Time from the course
2177
                    $tc = $accumulateWorkTimeTotal;
2178
2179
                    // Percentage of the learning paths
2180
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2181
                    // Minimum time for each learning path
2182
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2183
2184
                    // Spent time (in seconds) so far in the learning path
2185
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2186
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2187
2188
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2189
                        $isBlocked = true;
2190
                    }
2191
                }
2192
            }
2193
        }
2194
2195
        return $isBlocked;
2196
    }
2197
2198
    /**
2199
     * Checks if the learning path is visible for student after the progress
2200
     * of its prerequisite is completed, considering the time availability and
2201
     * the LP visibility.
2202
     *
2203
     * @param int   $student_id
2204
     * @param array $courseInfo
2205
     * @param int   $sessionId
2206
     *
2207
     * @return bool
2208
     */
2209
    public static function is_lp_visible_for_student(
2210
        CLp $lp,
2211
        $student_id,
2212
        $courseInfo = [],
2213
        $sessionId = 0
2214
    ) {
2215
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2216
        $sessionId = (int) $sessionId;
2217
2218
        if (empty($courseInfo)) {
2219
            return false;
2220
        }
2221
2222
        if (empty($sessionId)) {
2223
            $sessionId = api_get_session_id();
2224
        }
2225
2226
        $courseId = $courseInfo['real_id'];
2227
2228
        /*$itemInfo = api_get_item_property_info(
2229
            $courseId,
2230
            TOOL_LEARNPATH,
2231
            $lp_id,
2232
            $sessionId
2233
        );*/
2234
2235
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2236
        // If the item was deleted.
2237
        if (false === $visibility) {
2238
            return false;
2239
        }
2240
2241
        $lp_id = $lp->getIid();
2242
        // @todo remove this query and load the row info as a parameter
2243
        $table = Database::get_course_table(TABLE_LP_MAIN);
2244
        // Get current prerequisite
2245
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2246
                FROM $table
2247
                WHERE iid = $lp_id";
2248
        $rs = Database::query($sql);
2249
        $now = time();
2250
        if (Database::num_rows($rs) > 0) {
2251
            $row = Database::fetch_array($rs, 'ASSOC');
2252
2253
            if (!empty($row['category_id'])) {
2254
                $em = Database::getManager();
2255
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2256
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2257
                    return false;
2258
                }
2259
            }
2260
2261
            $prerequisite = $row['prerequisite'];
2262
            $is_visible = true;
2263
2264
            $isBlocked = self::isBlockedByPrerequisite(
2265
                $student_id,
2266
                $prerequisite,
2267
                $courseInfo,
2268
                $sessionId
2269
            );
2270
2271
            if ($isBlocked) {
2272
                $is_visible = false;
2273
            }
2274
2275
            // Also check the time availability of the LP
2276
            if ($is_visible) {
2277
                // Adding visibility restrictions
2278
                if (!empty($row['publicated_on'])) {
2279
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2280
                        $is_visible = false;
2281
                    }
2282
                }
2283
                // Blocking empty start times see BT#2800
2284
                global $_custom;
2285
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2286
                    $_custom['lps_hidden_when_no_start_date']
2287
                ) {
2288
                    if (empty($row['publicated_on'])) {
2289
                        $is_visible = false;
2290
                    }
2291
                }
2292
2293
                if (!empty($row['expired_on'])) {
2294
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2295
                        $is_visible = false;
2296
                    }
2297
                }
2298
            }
2299
2300
            if ($is_visible) {
2301
                $subscriptionSettings = self::getSubscriptionSettings();
2302
2303
                // Check if the subscription users/group to a LP is ON
2304
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2305
                    true === $subscriptionSettings['allow_add_users_to_lp']
2306
                ) {
2307
                    // Try group
2308
                    $is_visible = false;
2309
                    // Checking only the user visibility
2310
                    $userVisibility = api_get_item_visibility(
2311
                        $courseInfo,
2312
                        'learnpath',
2313
                        $row['id'],
2314
                        $sessionId,
2315
                        $student_id,
2316
                        'LearnpathSubscription'
2317
                    );
2318
2319
                    if (1 == $userVisibility) {
2320
                        $is_visible = true;
2321
                    } else {
2322
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2323
                        if (!empty($userGroups)) {
2324
                            foreach ($userGroups as $groupInfo) {
2325
                                $groupId = $groupInfo['iid'];
2326
                                $userVisibility = api_get_item_visibility(
2327
                                    $courseInfo,
2328
                                    'learnpath',
2329
                                    $row['id'],
2330
                                    $sessionId,
2331
                                    null,
2332
                                    'LearnpathSubscription',
2333
                                    $groupId
2334
                                );
2335
2336
                                if (1 == $userVisibility) {
2337
                                    $is_visible = true;
2338
                                    break;
2339
                                }
2340
                            }
2341
                        }
2342
                    }
2343
                }
2344
            }
2345
2346
            return $is_visible;
2347
        }
2348
2349
        return false;
2350
    }
2351
2352
    /**
2353
     * @param int $lpId
2354
     * @param int $userId
2355
     * @param int $courseId
2356
     * @param int $sessionId
2357
     *
2358
     * @return int
2359
     */
2360
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2361
    {
2362
        $lpId = (int) $lpId;
2363
        $userId = (int) $userId;
2364
        $courseId = (int) $courseId;
2365
        $sessionId = (int) $sessionId;
2366
2367
        $sessionCondition = api_get_session_condition($sessionId);
2368
        $table = Database::get_course_table(TABLE_LP_VIEW);
2369
        $sql = "SELECT progress FROM $table
2370
                WHERE
2371
                    c_id = $courseId AND
2372
                    lp_id = $lpId AND
2373
                    user_id = $userId $sessionCondition ";
2374
        $res = Database::query($sql);
2375
2376
        $progress = 0;
2377
        if (Database::num_rows($res) > 0) {
2378
            $row = Database::fetch_array($res);
2379
            $progress = (int) $row['progress'];
2380
        }
2381
2382
        return $progress;
2383
    }
2384
2385
    /**
2386
     * @param array $lpList
2387
     * @param int   $userId
2388
     * @param int   $courseId
2389
     * @param int   $sessionId
2390
     *
2391
     * @return array
2392
     */
2393
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2394
    {
2395
        $lpList = array_map('intval', $lpList);
2396
        if (empty($lpList)) {
2397
            return [];
2398
        }
2399
2400
        $lpList = implode("','", $lpList);
2401
2402
        $userId = (int) $userId;
2403
        $courseId = (int) $courseId;
2404
        $sessionId = (int) $sessionId;
2405
2406
        $sessionCondition = api_get_session_condition($sessionId);
2407
        $table = Database::get_course_table(TABLE_LP_VIEW);
2408
        $sql = "SELECT lp_id, progress FROM $table
2409
                WHERE
2410
                    c_id = $courseId AND
2411
                    lp_id IN ('".$lpList."') AND
2412
                    user_id = $userId $sessionCondition ";
2413
        $res = Database::query($sql);
2414
2415
        if (Database::num_rows($res) > 0) {
2416
            $list = [];
2417
            while ($row = Database::fetch_array($res)) {
2418
                $list[$row['lp_id']] = $row['progress'];
2419
            }
2420
2421
            return $list;
2422
        }
2423
2424
        return [];
2425
    }
2426
2427
    /**
2428
     * Displays a progress bar
2429
     * completed so far.
2430
     *
2431
     * @param int    $percentage Progress value to display
2432
     * @param string $text_add   Text to display near the progress value
2433
     *
2434
     * @return string HTML string containing the progress bar
2435
     */
2436
    public static function get_progress_bar($percentage = -1, $text_add = '')
2437
    {
2438
        $text = $percentage.$text_add;
2439
        $output = '<div class="progress">
2440
            <div id="progress_bar_value"
2441
                class="progress-bar progress-bar-success" role="progressbar"
2442
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2443
            '.$text.'
2444
            </div>
2445
        </div>';
2446
2447
        return $output;
2448
    }
2449
2450
    /**
2451
     * @param string $mode can be '%' or 'abs'
2452
     *                     otherwise this value will be used $this->progress_bar_mode
2453
     *
2454
     * @return string
2455
     */
2456
    public function getProgressBar($mode = null)
2457
    {
2458
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2459
2460
        return self::get_progress_bar($percentage, $text_add);
2461
    }
2462
2463
    /**
2464
     * Gets the progress bar info to display inside the progress bar.
2465
     * Also used by scorm_api.php.
2466
     *
2467
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2468
     *                     we display a number of completed elements per total elements
2469
     * @param int    $add  Additional steps to fake as completed
2470
     *
2471
     * @return array Percentage or number and symbol (% or /xx)
2472
     */
2473
    public function get_progress_bar_text($mode = '', $add = 0)
2474
    {
2475
        if (empty($mode)) {
2476
            $mode = $this->progress_bar_mode;
2477
        }
2478
        $text = '';
2479
        $percentage = 0;
2480
        // If the option to use the score as progress is set for this learning
2481
        // path, then the rules are completely different: we assume only one
2482
        // item exists and the progress of the LP depends on the score
2483
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2484
        if (true === $scoreAsProgressSetting) {
2485
            $scoreAsProgress = $this->getUseScoreAsProgress();
2486
            if ($scoreAsProgress) {
2487
                // Get single item's score
2488
                $itemId = $this->get_current_item_id();
2489
                $item = $this->getItem($itemId);
2490
                $score = $item->get_score();
2491
                $maxScore = $item->get_max();
2492
                if ($mode = '%') {
2493
                    if (!empty($maxScore)) {
2494
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2495
                    }
2496
                    $percentage = number_format($percentage, 0);
2497
                    $text = '%';
2498
                } else {
2499
                    $percentage = $score;
2500
                    $text = '/'.$maxScore;
2501
                }
2502
2503
                return [$percentage, $text];
2504
            }
2505
        }
2506
        // otherwise just continue the normal processing of progress
2507
        $total_items = $this->getTotalItemsCountWithoutDirs();
2508
        $completeItems = $this->get_complete_items_count();
2509
        if (0 != $add) {
2510
            $completeItems += $add;
2511
        }
2512
        if ($completeItems > $total_items) {
2513
            $completeItems = $total_items;
2514
        }
2515
        if ('%' == $mode) {
2516
            if ($total_items > 0) {
2517
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2518
            }
2519
            $percentage = number_format($percentage, 0);
2520
            $text = '%';
2521
        } elseif ('abs' === $mode) {
2522
            $percentage = $completeItems;
2523
            $text = '/'.$total_items;
2524
        }
2525
2526
        return [
2527
            $percentage,
2528
            $text,
2529
        ];
2530
    }
2531
2532
    /**
2533
     * Gets the progress bar mode.
2534
     *
2535
     * @return string The progress bar mode attribute
2536
     */
2537
    public function get_progress_bar_mode()
2538
    {
2539
        if (!empty($this->progress_bar_mode)) {
2540
            return $this->progress_bar_mode;
2541
        }
2542
2543
        return '%';
2544
    }
2545
2546
    /**
2547
     * Gets the learnpath theme (remote or local).
2548
     *
2549
     * @return string Learnpath theme
2550
     */
2551
    public function get_theme()
2552
    {
2553
        if (!empty($this->theme)) {
2554
            return $this->theme;
2555
        }
2556
2557
        return '';
2558
    }
2559
2560
    /**
2561
     * Gets the learnpath session id.
2562
     *
2563
     * @return int
2564
     */
2565
    public function get_lp_session_id()
2566
    {
2567
        if (!empty($this->lp_session_id)) {
2568
            return (int) $this->lp_session_id;
2569
        }
2570
2571
        return 0;
2572
    }
2573
2574
    /**
2575
     * Gets the learnpath image.
2576
     *
2577
     * @return string Web URL of the LP image
2578
     */
2579
    public function get_preview_image()
2580
    {
2581
        if (!empty($this->preview_image)) {
2582
            return $this->preview_image;
2583
        }
2584
2585
        return '';
2586
    }
2587
2588
    /**
2589
     * @param string $size
2590
     * @param string $path_type
2591
     *
2592
     * @return bool|string
2593
     */
2594
    public function get_preview_image_path($size = null, $path_type = 'web')
2595
    {
2596
        $preview_image = $this->get_preview_image();
2597
        if (isset($preview_image) && !empty($preview_image)) {
2598
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2599
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2600
2601
            if (isset($size)) {
2602
                $info = pathinfo($preview_image);
2603
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2604
2605
                if (file_exists($image_sys_path.$image_custom_size)) {
2606
                    if ('web' == $path_type) {
2607
                        return $image_path.$image_custom_size;
2608
                    } else {
2609
                        return $image_sys_path.$image_custom_size;
2610
                    }
2611
                }
2612
            } else {
2613
                if ('web' == $path_type) {
2614
                    return $image_path.$preview_image;
2615
                } else {
2616
                    return $image_sys_path.$preview_image;
2617
                }
2618
            }
2619
        }
2620
2621
        return false;
2622
    }
2623
2624
    /**
2625
     * Gets the learnpath author.
2626
     *
2627
     * @return string LP's author
2628
     */
2629
    public function get_author()
2630
    {
2631
        if (!empty($this->author)) {
2632
            return $this->author;
2633
        }
2634
2635
        return '';
2636
    }
2637
2638
    /**
2639
     * Gets hide table of contents.
2640
     *
2641
     * @return int
2642
     */
2643
    public function getHideTableOfContents()
2644
    {
2645
        return (int) $this->hide_toc_frame;
2646
    }
2647
2648
    /**
2649
     * Generate a new prerequisites string for a given item. If this item was a sco and
2650
     * its prerequisites were strings (instead of IDs), then transform those strings into
2651
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2652
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2653
     * same rule as the scormExport() method.
2654
     *
2655
     * @param int $item_id Item ID
2656
     *
2657
     * @return string Prerequisites string ready for the export as SCORM
2658
     */
2659
    public function get_scorm_prereq_string($item_id)
2660
    {
2661
        if ($this->debug > 0) {
2662
            error_log('In learnpath::get_scorm_prereq_string()');
2663
        }
2664
        if (!is_object($this->items[$item_id])) {
2665
            return false;
2666
        }
2667
        /** @var learnpathItem $oItem */
2668
        $oItem = $this->items[$item_id];
2669
        $prereq = $oItem->get_prereq_string();
2670
2671
        if (empty($prereq)) {
2672
            return '';
2673
        }
2674
        if (preg_match('/^\d+$/', $prereq) &&
2675
            isset($this->items[$prereq]) &&
2676
            is_object($this->items[$prereq])
2677
        ) {
2678
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2679
            // then simply return it (with the ITEM_ prefix).
2680
            //return 'ITEM_' . $prereq;
2681
            return $this->items[$prereq]->ref;
2682
        } else {
2683
            if (isset($this->refs_list[$prereq])) {
2684
                // It's a simple string item from which the ID can be found in the refs list,
2685
                // so we can transform it directly to an ID for export.
2686
                return $this->items[$this->refs_list[$prereq]]->ref;
2687
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2688
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2689
            } else {
2690
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2691
                // and replace them, one by one, by the internal IDs (chamilo db)
2692
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2693
                // by a space as well.
2694
                $find = [
2695
                    '&',
2696
                    '|',
2697
                    '~',
2698
                    '=',
2699
                    '<>',
2700
                    '{',
2701
                    '}',
2702
                    '*',
2703
                    '(',
2704
                    ')',
2705
                ];
2706
                $replace = [
2707
                    ' ',
2708
                    ' ',
2709
                    ' ',
2710
                    ' ',
2711
                    ' ',
2712
                    ' ',
2713
                    ' ',
2714
                    ' ',
2715
                    ' ',
2716
                    ' ',
2717
                ];
2718
                $prereq_mod = str_replace($find, $replace, $prereq);
2719
                $ids = explode(' ', $prereq_mod);
2720
                foreach ($ids as $id) {
2721
                    $id = trim($id);
2722
                    if (isset($this->refs_list[$id])) {
2723
                        $prereq = preg_replace(
2724
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2725
                            'ITEM_'.$this->refs_list[$id],
2726
                            $prereq
2727
                        );
2728
                    }
2729
                }
2730
2731
                return $prereq;
2732
            }
2733
        }
2734
    }
2735
2736
    /**
2737
     * Returns the XML DOM document's node.
2738
     *
2739
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2740
     * @param string   $id       The identifier to look for
2741
     *
2742
     * @return mixed The reference to the element found with that identifier. False if not found
2743
     */
2744
    public function get_scorm_xml_node(&$children, $id)
2745
    {
2746
        for ($i = 0; $i < $children->length; $i++) {
2747
            $item_temp = $children->item($i);
2748
            if ('item' == $item_temp->nodeName) {
2749
                if ($item_temp->getAttribute('identifier') == $id) {
2750
                    return $item_temp;
2751
                }
2752
            }
2753
            $subchildren = $item_temp->childNodes;
2754
            if ($subchildren && $subchildren->length > 0) {
2755
                $val = $this->get_scorm_xml_node($subchildren, $id);
2756
                if (is_object($val)) {
2757
                    return $val;
2758
                }
2759
            }
2760
        }
2761
2762
        return false;
2763
    }
2764
2765
    /**
2766
     * Gets the status list for all LP's items.
2767
     *
2768
     * @return array Array of [index] => [item ID => current status]
2769
     */
2770
    public function get_items_status_list()
2771
    {
2772
        $list = [];
2773
        foreach ($this->ordered_items as $item_id) {
2774
            $list[] = [
2775
                $item_id => $this->items[$item_id]->get_status(),
2776
            ];
2777
        }
2778
2779
        return $list;
2780
    }
2781
2782
    /**
2783
     * Return the number of interactions for the given learnpath Item View ID.
2784
     * This method can be used as static.
2785
     *
2786
     * @param int $lp_iv_id  Item View ID
2787
     * @param int $course_id course id
2788
     *
2789
     * @return int
2790
     */
2791
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2792
    {
2793
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2794
        $lp_iv_id = (int) $lp_iv_id;
2795
        $course_id = (int) $course_id;
2796
2797
        $sql = "SELECT count(*) FROM $table
2798
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2799
        $res = Database::query($sql);
2800
        $num = 0;
2801
        if (Database::num_rows($res)) {
2802
            $row = Database::fetch_array($res);
2803
            $num = $row[0];
2804
        }
2805
2806
        return $num;
2807
    }
2808
2809
    /**
2810
     * Return the interactions as an array for the given lp_iv_id.
2811
     * This method can be used as static.
2812
     *
2813
     * @param int $lp_iv_id Learnpath Item View ID
2814
     *
2815
     * @return array
2816
     *
2817
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2818
     */
2819
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2820
    {
2821
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2822
        $list = [];
2823
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2824
        $lp_iv_id = (int) $lp_iv_id;
2825
2826
        if (empty($lp_iv_id) || empty($course_id)) {
2827
            return [];
2828
        }
2829
2830
        $sql = "SELECT * FROM $table
2831
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2832
                ORDER BY order_id ASC";
2833
        $res = Database::query($sql);
2834
        $num = Database::num_rows($res);
2835
        if ($num > 0) {
2836
            $list[] = [
2837
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2838
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2839
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2840
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2841
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2842
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2843
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2844
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2845
                'student_response_formatted' => '',
2846
            ];
2847
            while ($row = Database::fetch_array($res)) {
2848
                $studentResponseFormatted = urldecode($row['student_response']);
2849
                $content_student_response = explode('__|', $studentResponseFormatted);
2850
                if (count($content_student_response) > 0) {
2851
                    if (count($content_student_response) >= 3) {
2852
                        // Pop the element off the end of array.
2853
                        array_pop($content_student_response);
2854
                    }
2855
                    $studentResponseFormatted = implode(',', $content_student_response);
2856
                }
2857
2858
                $list[] = [
2859
                    'order_id' => $row['order_id'] + 1,
2860
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2861
                    'type' => $row['interaction_type'],
2862
                    'time' => $row['completion_time'],
2863
                    'correct_responses' => '', // Hide correct responses from students.
2864
                    'student_response' => $row['student_response'],
2865
                    'result' => $row['result'],
2866
                    'latency' => $row['latency'],
2867
                    'student_response_formatted' => $studentResponseFormatted,
2868
                ];
2869
            }
2870
        }
2871
2872
        return $list;
2873
    }
2874
2875
    /**
2876
     * Return the number of objectives for the given learnpath Item View ID.
2877
     * This method can be used as static.
2878
     *
2879
     * @param int $lp_iv_id  Item View ID
2880
     * @param int $course_id Course ID
2881
     *
2882
     * @return int Number of objectives
2883
     */
2884
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2885
    {
2886
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2887
        $course_id = (int) $course_id;
2888
        $lp_iv_id = (int) $lp_iv_id;
2889
        $sql = "SELECT count(*) FROM $table
2890
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2891
        //@todo seems that this always returns 0
2892
        $res = Database::query($sql);
2893
        $num = 0;
2894
        if (Database::num_rows($res)) {
2895
            $row = Database::fetch_array($res);
2896
            $num = $row[0];
2897
        }
2898
2899
        return $num;
2900
    }
2901
2902
    /**
2903
     * Return the objectives as an array for the given lp_iv_id.
2904
     * This method can be used as static.
2905
     *
2906
     * @param int $lpItemViewId Learnpath Item View ID
2907
     * @param int $course_id
2908
     *
2909
     * @return array
2910
     *
2911
     * @todo    Translate labels
2912
     */
2913
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2914
    {
2915
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2916
        $lpItemViewId = (int) $lpItemViewId;
2917
2918
        if (empty($course_id) || empty($lpItemViewId)) {
2919
            return [];
2920
        }
2921
2922
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2923
        $sql = "SELECT * FROM $table
2924
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2925
                ORDER BY order_id ASC";
2926
        $res = Database::query($sql);
2927
        $num = Database::num_rows($res);
2928
        $list = [];
2929
        if ($num > 0) {
2930
            $list[] = [
2931
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2932
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2933
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2934
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2935
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2936
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2937
            ];
2938
            while ($row = Database::fetch_array($res)) {
2939
                $list[] = [
2940
                    'order_id' => $row['order_id'] + 1,
2941
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2942
                    'score_raw' => $row['score_raw'],
2943
                    'score_max' => $row['score_max'],
2944
                    'score_min' => $row['score_min'],
2945
                    'status' => $row['status'],
2946
                ];
2947
            }
2948
        }
2949
2950
        return $list;
2951
    }
2952
2953
    /**
2954
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2955
     * used by get_html_toc() to be ready to display.
2956
     *
2957
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2958
     */
2959
    public function get_toc()
2960
    {
2961
        $toc = [];
2962
        foreach ($this->ordered_items as $item_id) {
2963
            // TODO: Change this link generation and use new function instead.
2964
            $toc[] = [
2965
                'id' => $item_id,
2966
                'title' => $this->items[$item_id]->get_title(),
2967
                'status' => $this->items[$item_id]->get_status(),
2968
                'level' => $this->items[$item_id]->get_level(),
2969
                'type' => $this->items[$item_id]->get_type(),
2970
                'description' => $this->items[$item_id]->get_description(),
2971
                'path' => $this->items[$item_id]->get_path(),
2972
                'parent' => $this->items[$item_id]->get_parent(),
2973
            ];
2974
        }
2975
2976
        return $toc;
2977
    }
2978
2979
    /**
2980
     * Returns the CSS class name associated with a given item status.
2981
     *
2982
     * @param $status string an item status
2983
     *
2984
     * @return string CSS class name
2985
     */
2986
    public static function getStatusCSSClassName($status)
2987
    {
2988
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2989
            return self::STATUS_CSS_CLASS_NAME[$status];
2990
        }
2991
2992
        return '';
2993
    }
2994
2995
    /**
2996
     * Generate the tree of contents for this learnpath as an associative array tree
2997
     * with keys id, title, status, type, description, path, parent_id, children
2998
     * (title and descriptions as secured)
2999
     * and clues for CSS class composition:
3000
     *  - booleans is_current, is_parent_of_current, is_chapter
3001
     *  - string status_css_class_name.
3002
     *
3003
     * @param $parentId int restrict returned list to children of this parent
3004
     *
3005
     * @return array TOC as a table
3006
     */
3007
    public function getTOCTree($parentId = 0)
3008
    {
3009
        $toc = [];
3010
        $currentItemId = $this->get_current_item_id();
3011
3012
        foreach ($this->ordered_items as $itemId) {
3013
            $item = $this->items[$itemId];
3014
            if ($item->get_parent() == $parentId) {
3015
                $title = $item->get_title();
3016
                if (empty($title)) {
3017
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3018
                }
3019
3020
                $itemData = [
3021
                    'id' => $itemId,
3022
                    'title' => Security::remove_XSS($title),
3023
                    'status' => $item->get_status(),
3024
                    'level' => $item->get_level(), // FIXME should not be needed
3025
                    'type' => $item->get_type(),
3026
                    'description' => Security::remove_XSS($item->get_description()),
3027
                    'path' => $item->get_path(),
3028
                    'parent_id' => $item->get_parent(),
3029
                    'children' => $this->getTOCTree($itemId),
3030
                    'is_current' => ($itemId == $currentItemId),
3031
                    'is_parent_of_current' => false,
3032
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3033
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3034
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3035
                ];
3036
3037
                if (!empty($itemData['children'])) {
3038
                    foreach ($itemData['children'] as $child) {
3039
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3040
                            $itemData['is_parent_of_current'] = true;
3041
                            break;
3042
                        }
3043
                    }
3044
                }
3045
3046
                $toc[] = $itemData;
3047
            }
3048
        }
3049
3050
        return $toc;
3051
    }
3052
3053
    /**
3054
     * Generate and return the table of contents for this learnpath. The JS
3055
     * table returned is used inside of scorm_api.php.
3056
     *
3057
     * @param string $varname
3058
     *
3059
     * @return string A JS array variable construction
3060
     */
3061
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3062
    {
3063
        $toc = $varname.' = new Array();';
3064
        foreach ($this->ordered_items as $item_id) {
3065
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3066
        }
3067
3068
        return $toc;
3069
    }
3070
3071
    /**
3072
     * Gets the learning path type.
3073
     *
3074
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3075
     *
3076
     * @return mixed Type ID or name, depending on the parameter
3077
     */
3078
    public function get_type($get_name = false)
3079
    {
3080
        $res = false;
3081
        if (!empty($this->type) && (!$get_name)) {
3082
            $res = $this->type;
3083
        }
3084
3085
        return $res;
3086
    }
3087
3088
    /**
3089
     * Gets the learning path type as static method.
3090
     *
3091
     * @param int $lp_id
3092
     *
3093
     * @return mixed Type ID or name, depending on the parameter
3094
     */
3095
    public static function get_type_static($lp_id = 0)
3096
    {
3097
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3098
        $lp_id = (int) $lp_id;
3099
        $sql = "SELECT lp_type FROM $tbl_lp
3100
                WHERE iid = $lp_id";
3101
        $res = Database::query($sql);
3102
        if (false === $res) {
3103
            return null;
3104
        }
3105
        if (Database::num_rows($res) <= 0) {
3106
            return null;
3107
        }
3108
        $row = Database::fetch_array($res);
3109
3110
        return $row['lp_type'];
3111
    }
3112
3113
    /**
3114
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3115
     * This method can be used as abstract and is recursive.
3116
     *
3117
     * @param int $lp        Learnpath ID
3118
     * @param int $parent    Parent ID of the items to look for
3119
     * @param int $course_id
3120
     *
3121
     * @return array Ordered list of item IDs (empty array on error)
3122
     */
3123
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3124
    {
3125
        if (empty($course_id)) {
3126
            $course_id = api_get_course_int_id();
3127
        } else {
3128
            $course_id = (int) $course_id;
3129
        }
3130
        $list = [];
3131
3132
        if (empty($lp)) {
3133
            return $list;
3134
        }
3135
3136
        $lp = (int) $lp;
3137
        $parent = (int) $parent;
3138
3139
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3140
        $sql = "SELECT iid FROM $tbl_lp_item
3141
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3142
                ORDER BY display_order";
3143
3144
        $res = Database::query($sql);
3145
        while ($row = Database::fetch_array($res)) {
3146
            $sublist = self::get_flat_ordered_items_list(
3147
                $lp,
3148
                $row['iid'],
3149
                $course_id
3150
            );
3151
            $list[] = $row['iid'];
3152
            foreach ($sublist as $item) {
3153
                $list[] = $item;
3154
            }
3155
        }
3156
3157
        return $list;
3158
    }
3159
3160
    /**
3161
     * @return array
3162
     */
3163
    public static function getChapterTypes()
3164
    {
3165
        return [
3166
            'dir',
3167
        ];
3168
    }
3169
3170
    /**
3171
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3172
     *
3173
     * @param $tree
3174
     *
3175
     * @return array HTML TOC ready to display
3176
     */
3177
    public function getParentToc($tree)
3178
    {
3179
        if (empty($tree)) {
3180
            $tree = $this->get_toc();
3181
        }
3182
        $dirTypes = self::getChapterTypes();
3183
        $myCurrentId = $this->get_current_item_id();
3184
        $listParent = [];
3185
        $listChildren = [];
3186
        $listNotParent = [];
3187
        $list = [];
3188
        foreach ($tree as $subtree) {
3189
            if (in_array($subtree['type'], $dirTypes)) {
3190
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3191
                $subtree['children'] = $listChildren;
3192
                if (!empty($subtree['children'])) {
3193
                    foreach ($subtree['children'] as $subItem) {
3194
                        if ($subItem['id'] == $this->current) {
3195
                            $subtree['parent_current'] = 'in';
3196
                            $subtree['current'] = 'on';
3197
                        }
3198
                    }
3199
                }
3200
                $listParent[] = $subtree;
3201
            }
3202
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3203
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3204
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3205
                }
3206
3207
                $title = Security::remove_XSS($subtree['title']);
3208
                unset($subtree['title']);
3209
3210
                if (empty($title)) {
3211
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3212
                }
3213
                $classStyle = null;
3214
                if ($subtree['id'] == $this->current) {
3215
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3216
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3217
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3218
                }
3219
                $subtree['title'] = $title;
3220
                $subtree['class'] = $classStyle.' '.$cssStatus;
3221
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3222
                $subtree['current_id'] = $myCurrentId;
3223
                $listNotParent[] = $subtree;
3224
            }
3225
        }
3226
3227
        $list['are_parents'] = $listParent;
3228
        $list['not_parents'] = $listNotParent;
3229
3230
        return $list;
3231
    }
3232
3233
    /**
3234
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3235
     *
3236
     * @param array $tree
3237
     * @param int   $id
3238
     * @param bool  $parent
3239
     *
3240
     * @return array HTML TOC ready to display
3241
     */
3242
    public function getChildrenToc($tree, $id, $parent = true)
3243
    {
3244
        if (empty($tree)) {
3245
            $tree = $this->get_toc();
3246
        }
3247
3248
        $dirTypes = self::getChapterTypes();
3249
        $currentItemId = $this->get_current_item_id();
3250
        $list = [];
3251
3252
        foreach ($tree as $subtree) {
3253
            $subtree['tree'] = null;
3254
3255
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3256
                if ($subtree['id'] == $this->current) {
3257
                    $subtree['current'] = 'active';
3258
                } else {
3259
                    $subtree['current'] = null;
3260
                }
3261
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3262
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3263
                }
3264
3265
                $title = Security::remove_XSS($subtree['title']);
3266
                unset($subtree['title']);
3267
                if (empty($title)) {
3268
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3269
                }
3270
3271
                $classStyle = null;
3272
                if ($subtree['id'] == $this->current) {
3273
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3274
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3275
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3276
                }
3277
3278
                if (in_array($subtree['type'], $dirTypes)) {
3279
                    $subtree['title'] = stripslashes($title);
3280
                } else {
3281
                    $subtree['title'] = $title;
3282
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3283
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3284
                    $subtree['current_id'] = $currentItemId;
3285
                }
3286
                $list[] = $subtree;
3287
            }
3288
        }
3289
3290
        return $list;
3291
    }
3292
3293
    /**
3294
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3295
     *
3296
     * @param array $toc_list
3297
     *
3298
     * @return array HTML TOC ready to display
3299
     */
3300
    public function getListArrayToc($toc_list = [])
3301
    {
3302
        if (empty($toc_list)) {
3303
            $toc_list = $this->get_toc();
3304
        }
3305
        // Temporary variables.
3306
        $currentItemId = $this->get_current_item_id();
3307
        $list = [];
3308
        $arrayList = [];
3309
3310
        foreach ($toc_list as $item) {
3311
            $list['id'] = $item['id'];
3312
            $list['status'] = $item['status'];
3313
            $cssStatus = null;
3314
3315
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3316
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3317
            }
3318
3319
            $classStyle = ' ';
3320
            $dirTypes = self::getChapterTypes();
3321
3322
            if (in_array($item['type'], $dirTypes)) {
3323
                $classStyle = 'scorm_item_section ';
3324
            }
3325
            if ($item['id'] == $this->current) {
3326
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3327
            } elseif (!in_array($item['type'], $dirTypes)) {
3328
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3329
            }
3330
            $title = $item['title'];
3331
            if (empty($title)) {
3332
                $title = self::rl_get_resource_name(
3333
                    api_get_course_id(),
3334
                    $this->get_id(),
3335
                    $item['id']
3336
                );
3337
            }
3338
            $title = Security::remove_XSS($item['title']);
3339
3340
            if (empty($item['description'])) {
3341
                $list['description'] = $title;
3342
            } else {
3343
                $list['description'] = $item['description'];
3344
            }
3345
3346
            $list['class'] = $classStyle.' '.$cssStatus;
3347
            $list['level'] = $item['level'];
3348
            $list['type'] = $item['type'];
3349
3350
            if (in_array($item['type'], $dirTypes)) {
3351
                $list['css_level'] = 'level_'.$item['level'];
3352
            } else {
3353
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3354
            }
3355
3356
            if (in_array($item['type'], $dirTypes)) {
3357
                $list['title'] = stripslashes($title);
3358
            } else {
3359
                $list['title'] = stripslashes($title);
3360
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3361
                $list['current_id'] = $currentItemId;
3362
            }
3363
            $arrayList[] = $list;
3364
        }
3365
3366
        return $arrayList;
3367
    }
3368
3369
    /**
3370
     * Returns an HTML-formatted string ready to display with teacher buttons
3371
     * in LP view menu.
3372
     *
3373
     * @return string HTML TOC ready to display
3374
     */
3375
    public function get_teacher_toc_buttons()
3376
    {
3377
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3378
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3379
        $html = '';
3380
        if ($isAllow && false == $hideIcons) {
3381
            if ($this->get_lp_session_id() == api_get_session_id()) {
3382
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3383
                $html .= '<div class="btn-group">';
3384
                $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'>".
3385
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3386
                $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'>".
3387
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3388
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3389
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3390
                $html .= '</div>';
3391
                $html .= '</div>';
3392
            }
3393
        }
3394
3395
        return $html;
3396
    }
3397
3398
    /**
3399
     * Gets the learnpath maker name - generally the editor's name.
3400
     *
3401
     * @return string Learnpath maker name
3402
     */
3403
    public function get_maker()
3404
    {
3405
        if (!empty($this->maker)) {
3406
            return $this->maker;
3407
        }
3408
3409
        return '';
3410
    }
3411
3412
    /**
3413
     * Gets the learnpath name/title.
3414
     *
3415
     * @return string Learnpath name/title
3416
     */
3417
    public function get_name()
3418
    {
3419
        if (!empty($this->name)) {
3420
            return $this->name;
3421
        }
3422
3423
        return 'N/A';
3424
    }
3425
3426
    /**
3427
     * @return string
3428
     */
3429
    public function getNameNoTags()
3430
    {
3431
        return strip_tags($this->get_name());
3432
    }
3433
3434
    /**
3435
     * Gets a link to the resource from the present location, depending on item ID.
3436
     *
3437
     * @param string $type         Type of link expected
3438
     * @param int    $item_id      Learnpath item ID
3439
     * @param bool   $provided_toc
3440
     *
3441
     * @return string $provided_toc Link to the lp_item resource
3442
     */
3443
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3444
    {
3445
        $course_id = $this->get_course_int_id();
3446
        $item_id = (int) $item_id;
3447
3448
        if (empty($item_id)) {
3449
            $item_id = $this->get_current_item_id();
3450
3451
            if (empty($item_id)) {
3452
                //still empty, this means there was no item_id given and we are not in an object context or
3453
                //the object property is empty, return empty link
3454
                $this->first();
3455
3456
                return '';
3457
            }
3458
        }
3459
3460
        $file = '';
3461
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3462
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3463
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3464
3465
        $sql = "SELECT
3466
                    l.lp_type as ltype,
3467
                    l.path as lpath,
3468
                    li.item_type as litype,
3469
                    li.path as lipath,
3470
                    li.parameters as liparams
3471
        		FROM $lp_table l
3472
                INNER JOIN $lp_item_table li
3473
                ON (li.lp_id = l.iid)
3474
        		WHERE
3475
        		    li.iid = $item_id
3476
        		";
3477
        $res = Database::query($sql);
3478
        if (Database::num_rows($res) > 0) {
3479
            $row = Database::fetch_array($res);
3480
            $lp_type = $row['ltype'];
3481
            $lp_path = $row['lpath'];
3482
            $lp_item_type = $row['litype'];
3483
            $lp_item_path = $row['lipath'];
3484
            $lp_item_params = $row['liparams'];
3485
3486
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3487
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3488
            }
3489
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3490
            if ('http' === $type) {
3491
                //web path
3492
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3493
            } else {
3494
                //$course_path = $sys_course_path; //system path
3495
            }
3496
3497
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3498
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3499
            if (in_array(
3500
                $lp_item_type,
3501
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3502
            )
3503
            ) {
3504
                $lp_type = 1;
3505
            }
3506
3507
            // Now go through the specific cases to get the end of the path
3508
            // @todo Use constants instead of int values.
3509
            switch ($lp_type) {
3510
                case 1:
3511
                    $file = self::rl_get_resource_link_for_learnpath(
3512
                        $course_id,
3513
                        $this->get_id(),
3514
                        $item_id,
3515
                        $this->get_view_id()
3516
                    );
3517
                    switch ($lp_item_type) {
3518
                        case 'document':
3519
                            // Shows a button to download the file instead of just downloading the file directly.
3520
                            $documentPathInfo = pathinfo($file);
3521
                            if (isset($documentPathInfo['extension'])) {
3522
                                $parsed = parse_url($documentPathInfo['extension']);
3523
                                if (isset($parsed['path'])) {
3524
                                    $extension = $parsed['path'];
3525
                                    $extensionsToDownload = [
3526
                                        'zip',
3527
                                        'ppt',
3528
                                        'pptx',
3529
                                        'ods',
3530
                                        'xlsx',
3531
                                        'xls',
3532
                                        'csv',
3533
                                        'doc',
3534
                                        'docx',
3535
                                        'dot',
3536
                                    ];
3537
3538
                                    if (in_array($extension, $extensionsToDownload)) {
3539
                                        $file = api_get_path(WEB_CODE_PATH).
3540
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3541
                                    }
3542
                                }
3543
                            }
3544
                            break;
3545
                        case 'dir':
3546
                            $file = 'lp_content.php?type=dir';
3547
                            break;
3548
                        case 'link':
3549
                            if (Link::is_youtube_link($file)) {
3550
                                $src = Link::get_youtube_video_id($file);
3551
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3552
                            } elseif (Link::isVimeoLink($file)) {
3553
                                $src = Link::getVimeoLinkId($file);
3554
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3555
                            } else {
3556
                                // If the current site is HTTPS and the link is
3557
                                // HTTP, browsers will refuse opening the link
3558
                                $urlId = api_get_current_access_url_id();
3559
                                $url = api_get_access_url($urlId, false);
3560
                                $protocol = substr($url['url'], 0, 5);
3561
                                if ('https' === $protocol) {
3562
                                    $linkProtocol = substr($file, 0, 5);
3563
                                    if ('http:' === $linkProtocol) {
3564
                                        //this is the special intervention case
3565
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3566
                                    }
3567
                                }
3568
                            }
3569
                            break;
3570
                        case 'quiz':
3571
                            // Check how much attempts of a exercise exits in lp
3572
                            $lp_item_id = $this->get_current_item_id();
3573
                            $lp_view_id = $this->get_view_id();
3574
3575
                            $prevent_reinit = null;
3576
                            if (isset($this->items[$this->current])) {
3577
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3578
                            }
3579
3580
                            if (empty($provided_toc)) {
3581
                                $list = $this->get_toc();
3582
                            } else {
3583
                                $list = $provided_toc;
3584
                            }
3585
3586
                            $type_quiz = false;
3587
                            foreach ($list as $toc) {
3588
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3589
                                    $type_quiz = true;
3590
                                }
3591
                            }
3592
3593
                            if ($type_quiz) {
3594
                                $lp_item_id = (int) $lp_item_id;
3595
                                $lp_view_id = (int) $lp_view_id;
3596
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3597
                                        WHERE
3598
                                            c_id = $course_id AND
3599
                                            lp_item_id='".$lp_item_id."' AND
3600
                                            lp_view_id ='".$lp_view_id."' AND
3601
                                            status='completed'";
3602
                                $result = Database::query($sql);
3603
                                $row_count = Database:: fetch_row($result);
3604
                                $count_item_view = (int) $row_count[0];
3605
                                $not_multiple_attempt = 0;
3606
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3607
                                    $not_multiple_attempt = 1;
3608
                                }
3609
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3610
                            }
3611
                            break;
3612
                    }
3613
3614
                    $tmp_array = explode('/', $file);
3615
                    $document_name = $tmp_array[count($tmp_array) - 1];
3616
                    if (strpos($document_name, '_DELETED_')) {
3617
                        $file = 'blank.php?error=document_deleted';
3618
                    }
3619
                    break;
3620
                case 2:
3621
                    if ('dir' !== $lp_item_type) {
3622
                        // Quite complex here:
3623
                        // We want to make sure 'http://' (and similar) links can
3624
                        // be loaded as is (withouth the Chamilo path in front) but
3625
                        // some contents use this form: resource.htm?resource=http://blablabla
3626
                        // which means we have to find a protocol at the path's start, otherwise
3627
                        // it should not be considered as an external URL.
3628
                        // if ($this->prerequisites_match($item_id)) {
3629
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3630
                            if ($this->debug > 2) {
3631
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3632
                            }
3633
                            // Distant url, return as is.
3634
                            $file = $lp_item_path;
3635
                        } else {
3636
                            if ($this->debug > 2) {
3637
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3638
                            }
3639
                            // Prevent getting untranslatable urls.
3640
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3641
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3642
                            // Prepare the path.
3643
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3644
                            // TODO: Fix this for urls with protocol header.
3645
                            $file = str_replace('//', '/', $file);
3646
                            $file = str_replace(':/', '://', $file);
3647
                            if ('/' == substr($lp_path, -1)) {
3648
                                $lp_path = substr($lp_path, 0, -1);
3649
                            }
3650
3651
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3652
                                // if file not found.
3653
                                $decoded = html_entity_decode($lp_item_path);
3654
                                list($decoded) = explode('?', $decoded);
3655
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3656
                                    $file = self::rl_get_resource_link_for_learnpath(
3657
                                        $course_id,
3658
                                        $this->get_id(),
3659
                                        $item_id,
3660
                                        $this->get_view_id()
3661
                                    );
3662
                                    if (empty($file)) {
3663
                                        $file = 'blank.php?error=document_not_found';
3664
                                    } else {
3665
                                        $tmp_array = explode('/', $file);
3666
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3667
                                        if (strpos($document_name, '_DELETED_')) {
3668
                                            $file = 'blank.php?error=document_deleted';
3669
                                        } else {
3670
                                            $file = 'blank.php?error=document_not_found';
3671
                                        }
3672
                                    }
3673
                                } else {
3674
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3675
                                }
3676
                            }
3677
                        }
3678
3679
                        // We want to use parameters if they were defined in the imsmanifest
3680
                        if (false === strpos($file, 'blank.php')) {
3681
                            $lp_item_params = ltrim($lp_item_params, '?');
3682
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3683
                        }
3684
                    } else {
3685
                        $file = 'lp_content.php?type=dir';
3686
                    }
3687
                    break;
3688
                case 3:
3689
                    // Formatting AICC HACP append URL.
3690
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3691
                    if (!empty($lp_item_params)) {
3692
                        $aicc_append .= $lp_item_params.'&';
3693
                    }
3694
                    if ('dir' !== $lp_item_type) {
3695
                        // Quite complex here:
3696
                        // We want to make sure 'http://' (and similar) links can
3697
                        // be loaded as is (withouth the Chamilo path in front) but
3698
                        // some contents use this form: resource.htm?resource=http://blablabla
3699
                        // which means we have to find a protocol at the path's start, otherwise
3700
                        // it should not be considered as an external URL.
3701
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3702
                            if ($this->debug > 2) {
3703
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3704
                            }
3705
                            // Distant url, return as is.
3706
                            $file = $lp_item_path;
3707
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3708
                            /*
3709
                            if (stristr($file,'<servername>') !== false) {
3710
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3711
                            }
3712
                            */
3713
                            if (false !== stripos($file, '<servername>')) {
3714
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3715
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3716
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3717
                            }
3718
3719
                            $file .= $aicc_append;
3720
                        } else {
3721
                            if ($this->debug > 2) {
3722
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3723
                            }
3724
                            // Prevent getting untranslatable urls.
3725
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3726
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3727
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3728
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3729
                            // TODO: Fix this for urls with protocol header.
3730
                            $file = str_replace('//', '/', $file);
3731
                            $file = str_replace(':/', '://', $file);
3732
                            $file .= $aicc_append;
3733
                        }
3734
                    } else {
3735
                        $file = 'lp_content.php?type=dir';
3736
                    }
3737
                    break;
3738
                case 4:
3739
                    break;
3740
                default:
3741
                    break;
3742
            }
3743
            // Replace &amp; by & because &amp; will break URL with params
3744
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3745
        }
3746
        if ($this->debug > 2) {
3747
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3748
        }
3749
3750
        return $file;
3751
    }
3752
3753
    /**
3754
     * Gets the latest usable view or generate a new one.
3755
     *
3756
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3757
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3758
     *
3759
     * @return int DB lp_view id
3760
     */
3761
    public function get_view($attempt_num = 0, $userId = null)
3762
    {
3763
        $search = '';
3764
        // Use $attempt_num to enable multi-views management (disabled so far).
3765
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3766
            $search = 'AND view_count = '.$attempt_num;
3767
        }
3768
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3769
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3770
3771
        $course_id = api_get_course_int_id();
3772
        $sessionId = api_get_session_id();
3773
3774
        // Check user ID.
3775
        if (empty($userId)) {
3776
            if (empty($this->get_user_id())) {
3777
                $this->error = 'User ID is empty in learnpath::get_view()';
3778
3779
                return null;
3780
            } else {
3781
                $userId = $this->get_user_id();
3782
            }
3783
        }
3784
3785
        $sql = "SELECT iid, view_count FROM $lp_view_table
3786
        		WHERE
3787
        		    c_id = $course_id AND
3788
        		    lp_id = ".$this->get_id()." AND
3789
        		    user_id = ".$userId." AND
3790
        		    session_id = $sessionId
3791
        		    $search
3792
                ORDER BY view_count DESC";
3793
        $res = Database::query($sql);
3794
        if (Database::num_rows($res) > 0) {
3795
            $row = Database::fetch_array($res);
3796
            $this->lp_view_id = $row['iid'];
3797
        } elseif (!api_is_invitee()) {
3798
            // There is no database record, create one.
3799
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3800
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3801
            Database::query($sql);
3802
            $id = Database::insert_id();
3803
            $this->lp_view_id = $id;
3804
        }
3805
3806
        return $this->lp_view_id;
3807
    }
3808
3809
    /**
3810
     * Gets the current view id.
3811
     *
3812
     * @return int View ID (from lp_view)
3813
     */
3814
    public function get_view_id()
3815
    {
3816
        if (!empty($this->lp_view_id)) {
3817
            return (int) $this->lp_view_id;
3818
        }
3819
3820
        return 0;
3821
    }
3822
3823
    /**
3824
     * Gets the update queue.
3825
     *
3826
     * @return array Array containing IDs of items to be updated by JavaScript
3827
     */
3828
    public function get_update_queue()
3829
    {
3830
        return $this->update_queue;
3831
    }
3832
3833
    /**
3834
     * Gets the user ID.
3835
     *
3836
     * @return int User ID
3837
     */
3838
    public function get_user_id()
3839
    {
3840
        if (!empty($this->user_id)) {
3841
            return (int) $this->user_id;
3842
        }
3843
3844
        return false;
3845
    }
3846
3847
    /**
3848
     * Checks if any of the items has an audio element attached.
3849
     *
3850
     * @return bool True or false
3851
     */
3852
    public function has_audio()
3853
    {
3854
        $has = false;
3855
        foreach ($this->items as $i => $item) {
3856
            if (!empty($this->items[$i]->audio)) {
3857
                $has = true;
3858
                break;
3859
            }
3860
        }
3861
3862
        return $has;
3863
    }
3864
3865
    /**
3866
     * Moves an item up and down at its level.
3867
     *
3868
     * @param int    $id        Item to move up and down
3869
     * @param string $direction Direction 'up' or 'down'
3870
     *
3871
     * @return bool|int
3872
     */
3873
    public function move_item($id, $direction)
3874
    {
3875
        $course_id = api_get_course_int_id();
3876
        if (empty($id) || empty($direction)) {
3877
            return false;
3878
        }
3879
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3880
        $sql_sel = "SELECT *
3881
                    FROM $tbl_lp_item
3882
                    WHERE
3883
                        iid = $id
3884
                    ";
3885
        $res_sel = Database::query($sql_sel);
3886
        // Check if elem exists.
3887
        if (Database::num_rows($res_sel) < 1) {
3888
            return false;
3889
        }
3890
        // Gather data.
3891
        $row = Database::fetch_array($res_sel);
3892
        $previous = $row['previous_item_id'];
3893
        $next = $row['next_item_id'];
3894
        $display = $row['display_order'];
3895
        $parent = $row['parent_item_id'];
3896
        $lp = $row['lp_id'];
3897
        // Update the item (switch with previous/next one).
3898
        switch ($direction) {
3899
            case 'up':
3900
                if ($display > 1) {
3901
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3902
                                 WHERE iid = $previous";
3903
                    $res_sel2 = Database::query($sql_sel2);
3904
                    if (Database::num_rows($res_sel2) < 1) {
3905
                        $previous_previous = 0;
3906
                    }
3907
                    // Gather data.
3908
                    $row2 = Database::fetch_array($res_sel2);
3909
                    $previous_previous = $row2['previous_item_id'];
3910
                    // Update previous_previous item (switch "next" with current).
3911
                    if (0 != $previous_previous) {
3912
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3913
                                        next_item_id = $id
3914
                                    WHERE iid = $previous_previous";
3915
                        Database::query($sql_upd2);
3916
                    }
3917
                    // Update previous item (switch with current).
3918
                    if (0 != $previous) {
3919
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3920
                                    next_item_id = $next,
3921
                                    previous_item_id = $id,
3922
                                    display_order = display_order +1
3923
                                    WHERE iid = $previous";
3924
                        Database::query($sql_upd2);
3925
                    }
3926
3927
                    // Update current item (switch with previous).
3928
                    if (0 != $id) {
3929
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3930
                                        next_item_id = $previous,
3931
                                        previous_item_id = $previous_previous,
3932
                                        display_order = display_order-1
3933
                                    WHERE c_id = ".$course_id." AND id = $id";
3934
                        Database::query($sql_upd2);
3935
                    }
3936
                    // Update next item (new previous item).
3937
                    if (!empty($next)) {
3938
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3939
                                     WHERE iid = $next";
3940
                        Database::query($sql_upd2);
3941
                    }
3942
                    $display = $display - 1;
3943
                }
3944
                break;
3945
            case 'down':
3946
                if (0 != $next) {
3947
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3948
                                 WHERE iid = $next";
3949
                    $res_sel2 = Database::query($sql_sel2);
3950
                    if (Database::num_rows($res_sel2) < 1) {
3951
                        $next_next = 0;
3952
                    }
3953
                    // Gather data.
3954
                    $row2 = Database::fetch_array($res_sel2);
3955
                    $next_next = $row2['next_item_id'];
3956
                    // Update previous item (switch with current).
3957
                    if (0 != $previous) {
3958
                        $sql_upd2 = "UPDATE $tbl_lp_item
3959
                                     SET next_item_id = $next
3960
                                     WHERE iid = $previous";
3961
                        Database::query($sql_upd2);
3962
                    }
3963
                    // Update current item (switch with previous).
3964
                    if (0 != $id) {
3965
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3966
                                     previous_item_id = $next,
3967
                                     next_item_id = $next_next,
3968
                                     display_order = display_order + 1
3969
                                     WHERE iid = $id";
3970
                        Database::query($sql_upd2);
3971
                    }
3972
3973
                    // Update next item (new previous item).
3974
                    if (0 != $next) {
3975
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3976
                                     previous_item_id = $previous,
3977
                                     next_item_id = $id,
3978
                                     display_order = display_order-1
3979
                                     WHERE iid = $next";
3980
                        Database::query($sql_upd2);
3981
                    }
3982
3983
                    // Update next_next item (switch "previous" with current).
3984
                    if (0 != $next_next) {
3985
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3986
                                     previous_item_id = $id
3987
                                     WHERE iid = $next_next";
3988
                        Database::query($sql_upd2);
3989
                    }
3990
                    $display = $display + 1;
3991
                }
3992
                break;
3993
            default:
3994
                return false;
3995
        }
3996
3997
        return $display;
3998
    }
3999
4000
    /**
4001
     * Move a LP up (display_order).
4002
     *
4003
     * @param int $lp_id      Learnpath ID
4004
     * @param int $categoryId Category ID
4005
     *
4006
     * @return bool
4007
     */
4008
    public static function move_up($lp_id, $categoryId = 0)
4009
    {
4010
        $courseId = api_get_course_int_id();
4011
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4012
4013
        $categoryCondition = '';
4014
        if (!empty($categoryId)) {
4015
            $categoryId = (int) $categoryId;
4016
            $categoryCondition = " AND category_id = $categoryId";
4017
        }
4018
        $sql = "SELECT * FROM $lp_table
4019
                WHERE c_id = $courseId
4020
                $categoryCondition
4021
                ORDER BY display_order";
4022
        $res = Database::query($sql);
4023
        if (false === $res) {
4024
            return false;
4025
        }
4026
4027
        $lps = [];
4028
        $lp_order = [];
4029
        $num = Database::num_rows($res);
4030
        // First check the order is correct, globally (might be wrong because
4031
        // of versions < 1.8.4)
4032
        if ($num > 0) {
4033
            $i = 1;
4034
            while ($row = Database::fetch_array($res)) {
4035
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4036
                    $sql = "UPDATE $lp_table SET display_order = $i
4037
                            WHERE iid = ".$row['iid'];
4038
                    Database::query($sql);
4039
                }
4040
                $row['display_order'] = $i;
4041
                $lps[$row['iid']] = $row;
4042
                $lp_order[$i] = $row['iid'];
4043
                $i++;
4044
            }
4045
        }
4046
        if ($num > 1) { // If there's only one element, no need to sort.
4047
            $order = $lps[$lp_id]['display_order'];
4048
            if ($order > 1) { // If it's the first element, no need to move up.
4049
                $sql = "UPDATE $lp_table SET display_order = $order
4050
                        WHERE iid = ".$lp_order[$order - 1];
4051
                Database::query($sql);
4052
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4053
                        WHERE iid = $lp_id";
4054
                Database::query($sql);
4055
            }
4056
        }
4057
4058
        return true;
4059
    }
4060
4061
    /**
4062
     * Move a learnpath down (display_order).
4063
     *
4064
     * @param int $lp_id      Learnpath ID
4065
     * @param int $categoryId Category ID
4066
     *
4067
     * @return bool
4068
     */
4069
    public static function move_down($lp_id, $categoryId = 0)
4070
    {
4071
        $courseId = api_get_course_int_id();
4072
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4073
4074
        $categoryCondition = '';
4075
        if (!empty($categoryId)) {
4076
            $categoryId = (int) $categoryId;
4077
            $categoryCondition = " AND category_id = $categoryId";
4078
        }
4079
4080
        $sql = "SELECT * FROM $lp_table
4081
                WHERE c_id = $courseId
4082
                $categoryCondition
4083
                ORDER BY display_order";
4084
        $res = Database::query($sql);
4085
        if (false === $res) {
4086
            return false;
4087
        }
4088
        $lps = [];
4089
        $lp_order = [];
4090
        $num = Database::num_rows($res);
4091
        $max = 0;
4092
        // First check the order is correct, globally (might be wrong because
4093
        // of versions < 1.8.4).
4094
        if ($num > 0) {
4095
            $i = 1;
4096
            while ($row = Database::fetch_array($res)) {
4097
                $max = $i;
4098
                if ($row['display_order'] != $i) {
4099
                    // If we find a gap in the order, we need to fix it.
4100
                    $sql = "UPDATE $lp_table SET display_order = $i
4101
                              WHERE iid = ".$row['iid'];
4102
                    Database::query($sql);
4103
                }
4104
                $row['display_order'] = $i;
4105
                $lps[$row['iid']] = $row;
4106
                $lp_order[$i] = $row['iid'];
4107
                $i++;
4108
            }
4109
        }
4110
        if ($num > 1) { // If there's only one element, no need to sort.
4111
            $order = $lps[$lp_id]['display_order'];
4112
            if ($order < $max) { // If it's the first element, no need to move up.
4113
                $sql = "UPDATE $lp_table SET display_order = $order
4114
                        WHERE iid = ".$lp_order[$order + 1];
4115
                Database::query($sql);
4116
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4117
                        WHERE iid = $lp_id";
4118
                Database::query($sql);
4119
            }
4120
        }
4121
4122
        return true;
4123
    }
4124
4125
    /**
4126
     * Updates learnpath attributes to point to the next element
4127
     * The last part is similar to set_current_item but processing the other way around.
4128
     */
4129
    public function next()
4130
    {
4131
        if ($this->debug > 0) {
4132
            error_log('In learnpath::next()', 0);
4133
        }
4134
        $this->last = $this->get_current_item_id();
4135
        $this->items[$this->last]->save(
4136
            false,
4137
            $this->prerequisites_match($this->last)
4138
        );
4139
        $this->autocomplete_parents($this->last);
4140
        $new_index = $this->get_next_index();
4141
        if ($this->debug > 2) {
4142
            error_log('New index: '.$new_index, 0);
4143
        }
4144
        $this->index = $new_index;
4145
        if ($this->debug > 2) {
4146
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4147
        }
4148
        $this->current = $this->ordered_items[$new_index];
4149
        if ($this->debug > 2) {
4150
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4151
        }
4152
    }
4153
4154
    /**
4155
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4156
     * class, this might be redefined to allow several behaviours depending on the document type.
4157
     *
4158
     * @param int $id Resource ID
4159
     */
4160
    public function open($id)
4161
    {
4162
        // TODO:
4163
        // set the current resource attribute to this resource
4164
        // switch on element type (redefine in child class?)
4165
        // set status for this item to "opened"
4166
        // start timer
4167
        // initialise score
4168
        $this->index = 0; //or = the last item seen (see $this->last)
4169
    }
4170
4171
    /**
4172
     * Check that all prerequisites are fulfilled. Returns true and an
4173
     * empty string on success, returns false
4174
     * and the prerequisite string on error.
4175
     * This function is based on the rules for aicc_script language as
4176
     * described in the SCORM 1.2 CAM documentation page 108.
4177
     *
4178
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4179
     *
4180
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4181
     *              string otherwise
4182
     */
4183
    public function prerequisites_match($itemId = null)
4184
    {
4185
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4186
        if ($allow) {
4187
            if (api_is_allowed_to_edit() ||
4188
                api_is_platform_admin(true) ||
4189
                api_is_drh() ||
4190
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4191
            ) {
4192
                return true;
4193
            }
4194
        }
4195
4196
        $debug = $this->debug;
4197
        if ($debug > 0) {
4198
            error_log('In learnpath::prerequisites_match()');
4199
        }
4200
4201
        if (empty($itemId)) {
4202
            $itemId = $this->current;
4203
        }
4204
4205
        $currentItem = $this->getItem($itemId);
4206
4207
        if ($currentItem) {
4208
            if (2 == $this->type) {
4209
                // Getting prereq from scorm
4210
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4211
            } else {
4212
                $prereq_string = $currentItem->get_prereq_string();
4213
            }
4214
4215
            if (empty($prereq_string)) {
4216
                if ($debug > 0) {
4217
                    error_log('Found prereq_string is empty return true');
4218
                }
4219
4220
                return true;
4221
            }
4222
4223
            // Clean spaces.
4224
            $prereq_string = str_replace(' ', '', $prereq_string);
4225
            if ($debug > 0) {
4226
                error_log('Found prereq_string: '.$prereq_string, 0);
4227
            }
4228
4229
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4230
            $result = $currentItem->parse_prereq(
4231
                $prereq_string,
4232
                $this->items,
4233
                $this->refs_list,
4234
                $this->get_user_id()
4235
            );
4236
4237
            if (false === $result) {
4238
                $this->set_error_msg($currentItem->prereq_alert);
4239
            }
4240
        } else {
4241
            $result = true;
4242
            if ($debug > 1) {
4243
                error_log('$this->items['.$itemId.'] was not an object', 0);
4244
            }
4245
        }
4246
4247
        if ($debug > 1) {
4248
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4249
        }
4250
4251
        return $result;
4252
    }
4253
4254
    /**
4255
     * Updates learnpath attributes to point to the previous element
4256
     * The last part is similar to set_current_item but processing the other way around.
4257
     */
4258
    public function previous()
4259
    {
4260
        $this->last = $this->get_current_item_id();
4261
        $this->items[$this->last]->save(
4262
            false,
4263
            $this->prerequisites_match($this->last)
4264
        );
4265
        $this->autocomplete_parents($this->last);
4266
        $new_index = $this->get_previous_index();
4267
        $this->index = $new_index;
4268
        $this->current = $this->ordered_items[$new_index];
4269
    }
4270
4271
    /**
4272
     * Publishes a learnpath. This basically means show or hide the learnpath
4273
     * to normal users.
4274
     * Can be used as abstract.
4275
     *
4276
     * @param int $id         Learnpath ID
4277
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
4278
     *
4279
     * @return bool
4280
     */
4281
    public static function toggleVisibility($id, $visibility = 1)
4282
    {
4283
        $repo = Container::getLpRepository();
4284
        $lp = $repo->find($id);
4285
4286
        if (!$lp) {
4287
            return false;
4288
        }
4289
4290
        $visibility = (int) $visibility;
4291
4292
        if (1 === $visibility) {
4293
            $repo->setVisibilityPublished($lp);
4294
        } else {
4295
            $repo->setVisibilityDraft($lp);
4296
        }
4297
4298
        return true;
4299
4300
        /*$action = 'visible';
4301
        if (1 != $set_visibility) {
4302
            $action = 'invisible';
4303
            self::toggle_publish($lp_id, 'i');
4304
        }
4305
4306
        return api_item_property_update(
4307
            api_get_course_info(),
4308
            TOOL_LEARNPATH,
4309
            $lp_id,
4310
            $action,
4311
            api_get_user_id()
4312
        );*/
4313
    }
4314
4315
    /**
4316
     * Publishes a learnpath category.
4317
     * This basically means show or hide the learnpath category to normal users.
4318
     *
4319
     * @param int $id
4320
     * @param int $visibility
4321
     *
4322
     * @return bool
4323
     */
4324
    public static function toggleCategoryVisibility($id, $visibility = 1)
4325
    {
4326
        $repo = Container::getLpCategoryRepository();
4327
        $resource = $repo->find($id);
4328
4329
        if (!$resource) {
4330
            return false;
4331
        }
4332
4333
        $visibility = (int) $visibility;
4334
4335
        if (1 === $visibility) {
4336
            $repo->setVisibilityPublished($resource);
4337
        } else {
4338
            $repo->setVisibilityDraft($resource);
4339
            self::toggleCategoryPublish($id, 0);
4340
        }
4341
4342
        return false;
4343
        /*
4344
        $action = 'visible';
4345
        if (1 != $visibility) {
4346
            self::toggleCategoryPublish($id, 0);
4347
            $action = 'invisible';
4348
        }
4349
4350
        return api_item_property_update(
4351
            api_get_course_info(),
4352
            TOOL_LEARNPATH_CATEGORY,
4353
            $id,
4354
            $action,
4355
            api_get_user_id()
4356
        );*/
4357
    }
4358
4359
    /**
4360
     * Publishes a learnpath. This basically means show or hide the learnpath
4361
     * on the course homepage.
4362
     *
4363
     * @param int    $id            Learnpath id
4364
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4365
     *
4366
     * @return bool
4367
     */
4368
    public static function togglePublish($id, $setVisibility = 'v')
4369
    {
4370
        $addShortcut = false;
4371
        if ('v' === $setVisibility) {
4372
            $addShortcut = true;
4373
        }
4374
        $repo = Container::getLpRepository();
4375
        /** @var CLp $lp */
4376
        $lp = $repo->find($id);
4377
        if (null === $lp) {
4378
            return false;
4379
        }
4380
        $repoShortcut = Container::getShortcutRepository();
4381
        $courseEntity = api_get_course_entity();
4382
4383
        if ($addShortcut) {
4384
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4385
        } else {
4386
            $repoShortcut->removeShortCut($lp);
4387
        }
4388
4389
        return true;
4390
4391
        /*
4392
        $course_id = api_get_course_int_id();
4393
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4394
        $lp_id = (int) $lp_id;
4395
        $sql = "SELECT * FROM $tbl_lp
4396
                WHERE iid = $lp_id";
4397
        $result = Database::query($sql);
4398
4399
        if (Database::num_rows($result)) {
4400
            $row = Database::fetch_array($result);
4401
            $name = Database::escape_string($row['name']);
4402
            if ($set_visibility == 'i') {
4403
                $v = 0;
4404
            }
4405
            if ($set_visibility == 'v') {
4406
                $v = 1;
4407
            }
4408
4409
            $session_id = api_get_session_id();
4410
            $session_condition = api_get_session_condition($session_id);
4411
4412
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4413
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4414
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4415
4416
            $sql = "SELECT * FROM $tbl_tool
4417
                    WHERE
4418
                        c_id = $course_id AND
4419
                        (link = '$link' OR link = '$oldLink') AND
4420
                        image = 'scormbuilder.gif' AND
4421
                        (
4422
                            link LIKE '$link%' OR
4423
                            link LIKE '$oldLink%'
4424
                        )
4425
                        $session_condition
4426
                    ";
4427
4428
            $result = Database::query($sql);
4429
            $num = Database::num_rows($result);
4430
            if ($set_visibility == 'i' && $num > 0) {
4431
                $sql = "DELETE FROM $tbl_tool
4432
                        WHERE
4433
                            c_id = $course_id AND
4434
                            (link = '$link' OR link = '$oldLink') AND
4435
                            image='scormbuilder.gif'
4436
                            $session_condition";
4437
                Database::query($sql);
4438
            } elseif ($set_visibility == 'v' && $num == 0) {
4439
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4440
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4441
                Database::query($sql);
4442
                $insertId = Database::insert_id();
4443
                if ($insertId) {
4444
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4445
                    Database::query($sql);
4446
                }
4447
            } elseif ($set_visibility == 'v' && $num > 0) {
4448
                $sql = "UPDATE $tbl_tool SET
4449
                            c_id = $course_id,
4450
                            name = '$name',
4451
                            link = '$link',
4452
                            image = 'scormbuilder.gif',
4453
                            visibility = '$v',
4454
                            admin = '0',
4455
                            address = 'pastillegris.gif',
4456
                            added_tool = 0,
4457
                            session_id = $session_id
4458
                        WHERE
4459
                            c_id = ".$course_id." AND
4460
                            (link = '$link' OR link = '$oldLink') AND
4461
                            image='scormbuilder.gif'
4462
                            $session_condition
4463
                        ";
4464
                Database::query($sql);
4465
            } else {
4466
                // Parameter and database incompatible, do nothing, exit.
4467
                return false;
4468
            }
4469
        } else {
4470
            return false;
4471
        }*/
4472
    }
4473
4474
    /**
4475
     * Show or hide the learnpath category on the course homepage.
4476
     *
4477
     * @param int $id
4478
     * @param int $setVisibility
4479
     *
4480
     * @return bool
4481
     */
4482
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4483
    {
4484
        $setVisibility = (int) $setVisibility;
4485
        $addShortcut = false;
4486
        if (1 === $setVisibility) {
4487
            $addShortcut = true;
4488
        }
4489
4490
        $repo = Container::getLpCategoryRepository();
4491
        /** @var CLpCategory $lp */
4492
        $category = $repo->find($id);
4493
4494
        if (null === $category) {
4495
            return false;
4496
        }
4497
4498
        $repoShortcut = Container::getShortcutRepository();
4499
        if ($addShortcut) {
4500
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4501
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4502
        } else {
4503
            $repoShortcut->removeShortCut($category);
4504
        }
4505
4506
        return true;
4507
4508
        $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...
4509
4510
        /** @var CLpCategory $category */
4511
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4512
4513
        if (!$category) {
4514
            return false;
4515
        }
4516
4517
        if (empty($courseId)) {
4518
            return false;
4519
        }
4520
4521
        $link = self::getCategoryLinkForTool($id);
4522
4523
        /** @var CTool $tool */
4524
        $tool = $em->createQuery("
4525
                SELECT t FROM ChamiloCourseBundle:CTool t
4526
                WHERE
4527
                    t.course = :course AND
4528
                    t.link = :link1 AND
4529
                    t.image LIKE 'lp_category.%' AND
4530
                    t.link LIKE :link2
4531
                    $sessionCondition
4532
            ")
4533
            ->setParameters([
4534
                'course' => $courseId,
4535
                'link1' => $link,
4536
                'link2' => "$link%",
4537
            ])
4538
            ->getOneOrNullResult();
4539
4540
        if (0 == $setVisibility && $tool) {
4541
            $em->remove($tool);
4542
            $em->flush();
4543
4544
            return true;
4545
        }
4546
4547
        if (1 == $setVisibility && !$tool) {
4548
            $tool = new CTool();
4549
            $tool
4550
                ->setCategory('authoring')
4551
                ->setCourse(api_get_course_entity($courseId))
4552
                ->setName(strip_tags($category->getName()))
4553
                ->setLink($link)
4554
                ->setImage('lp_category.png')
4555
                ->setVisibility(1)
4556
                ->setAdmin(0)
4557
                ->setAddress('pastillegris.gif')
4558
                ->setAddedTool(0)
4559
                ->setSessionId($sessionId)
4560
                ->setTarget('_self');
4561
4562
            $em->persist($tool);
4563
            $em->flush();
4564
4565
            $tool->setId($tool->getIid());
4566
4567
            $em->persist($tool);
4568
            $em->flush();
4569
4570
            return true;
4571
        }
4572
4573
        if (1 == $setVisibility && $tool) {
4574
            $tool
4575
                ->setName(strip_tags($category->getName()))
4576
                ->setVisibility(1);
4577
4578
            $em->persist($tool);
4579
            $em->flush();
4580
4581
            return true;
4582
        }
4583
4584
        return false;
4585
    }
4586
4587
    /**
4588
     * Check if the learnpath category is visible for a user.
4589
     *
4590
     * @param int
4591
     * @param int
4592
     *
4593
     * @return bool
4594
     */
4595
    public static function categoryIsVisibleForStudent(
4596
        CLpCategory $category,
4597
        User $user,
4598
        $courseId = 0,
4599
        $sessionId = 0
4600
    ) {
4601
        if (empty($category)) {
4602
            return false;
4603
        }
4604
4605
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4606
4607
        if ($isAllowedToEdit) {
4608
            return true;
4609
        }
4610
4611
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4612
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4613
4614
        $courseInfo = api_get_course_info_by_id($courseId);
4615
4616
        $categoryVisibility = api_get_item_visibility(
4617
            $courseInfo,
4618
            TOOL_LEARNPATH_CATEGORY,
4619
            $category->getId(),
4620
            $sessionId
4621
        );
4622
4623
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4624
            return false;
4625
        }
4626
4627
        $subscriptionSettings = self::getSubscriptionSettings();
4628
4629
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4630
            return true;
4631
        }
4632
4633
        $noUserSubscribed = false;
4634
        $noGroupSubscribed = true;
4635
        $users = $category->getUsers();
4636
        if (empty($users) || !$users->count()) {
4637
            $noUserSubscribed = true;
4638
        } elseif ($category->hasUserAdded($user)) {
4639
            return true;
4640
        }
4641
4642
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4643
        $em = Database::getManager();
4644
4645
        /** @var ItemPropertyRepository $itemRepo */
4646
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4647
4648
        /** @var CourseRepository $courseRepo */
4649
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4650
        $session = null;
4651
        if (!empty($sessionId)) {
4652
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4653
        }
4654
4655
        $course = $courseRepo->find($courseId);
4656
4657
        if (0 != $courseId) {
4658
            // Subscribed groups to a LP
4659
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4660
                    TOOL_LEARNPATH_CATEGORY,
4661
                    $category->getId(),
4662
                    $course,
4663
                    $session
4664
                );
4665
        }
4666
4667
        if (!empty($subscribedGroupsInLp)) {
4668
            $noGroupSubscribed = false;
4669
            if (!empty($groups)) {
4670
                $groups = array_column($groups, 'iid');
4671
                /** @var CItemProperty $item */
4672
                foreach ($subscribedGroupsInLp as $item) {
4673
                    if ($item->getGroup() &&
4674
                        in_array($item->getGroup()->getId(), $groups)
4675
                    ) {
4676
                        return true;
4677
                    }
4678
                }
4679
            }
4680
        }
4681
        $response = $noGroupSubscribed && $noUserSubscribed;
4682
4683
        return $response;
4684
    }
4685
4686
    /**
4687
     * Check if a learnpath category is published as course tool.
4688
     *
4689
     * @param int $courseId
4690
     *
4691
     * @return bool
4692
     */
4693
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4694
    {
4695
        return false;
4696
        $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...
4697
        $em = Database::getManager();
4698
4699
        $tools = $em
4700
            ->createQuery("
4701
                SELECT t FROM ChamiloCourseBundle:CTool t
4702
                WHERE t.course = :course AND
4703
                    t.name = :name AND
4704
                    t.image LIKE 'lp_category.%' AND
4705
                    t.link LIKE :link
4706
            ")
4707
            ->setParameters([
4708
                'course' => $courseId,
4709
                'name' => strip_tags($category->getName()),
4710
                'link' => "$link%",
4711
            ])
4712
            ->getResult();
4713
4714
        /** @var CTool $tool */
4715
        $tool = current($tools);
4716
4717
        return $tool ? $tool->getVisibility() : false;
4718
    }
4719
4720
    /**
4721
     * Restart the whole learnpath. Return the URL of the first element.
4722
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4723
     * To use a similar method  statically, use the create_new_attempt() method.
4724
     *
4725
     * @return bool
4726
     */
4727
    public function restart()
4728
    {
4729
        if ($this->debug > 0) {
4730
            error_log('In learnpath::restart()', 0);
4731
        }
4732
        // TODO
4733
        // Call autosave method to save the current progress.
4734
        //$this->index = 0;
4735
        if (api_is_invitee()) {
4736
            return false;
4737
        }
4738
        $session_id = api_get_session_id();
4739
        $course_id = api_get_course_int_id();
4740
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4741
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4742
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4743
        if ($this->debug > 2) {
4744
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4745
        }
4746
        Database::query($sql);
4747
        $view_id = Database::insert_id();
4748
4749
        if ($view_id) {
4750
            $this->lp_view_id = $view_id;
4751
            $this->attempt = $this->attempt + 1;
4752
        } else {
4753
            $this->error = 'Could not insert into item_view table...';
4754
4755
            return false;
4756
        }
4757
        $this->autocomplete_parents($this->current);
4758
        foreach ($this->items as $index => $dummy) {
4759
            $this->items[$index]->restart();
4760
            $this->items[$index]->set_lp_view($this->lp_view_id);
4761
        }
4762
        $this->first();
4763
4764
        return true;
4765
    }
4766
4767
    /**
4768
     * Saves the current item.
4769
     *
4770
     * @return bool
4771
     */
4772
    public function save_current()
4773
    {
4774
        $debug = $this->debug;
4775
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4776
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4777
        if ($debug) {
4778
            error_log('save_current() saving item '.$this->current, 0);
4779
            error_log(''.print_r($this->items, true), 0);
4780
        }
4781
        if (isset($this->items[$this->current]) &&
4782
            is_object($this->items[$this->current])
4783
        ) {
4784
            if ($debug) {
4785
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4786
            }
4787
4788
            $res = $this->items[$this->current]->save(
4789
                false,
4790
                $this->prerequisites_match($this->current)
4791
            );
4792
            $this->autocomplete_parents($this->current);
4793
            $status = $this->items[$this->current]->get_status();
4794
            $this->update_queue[$this->current] = $status;
4795
4796
            if ($debug) {
4797
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4798
            }
4799
4800
            return $res;
4801
        }
4802
4803
        return false;
4804
    }
4805
4806
    /**
4807
     * Saves the given item.
4808
     *
4809
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4810
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4811
     *
4812
     * @return bool
4813
     */
4814
    public function save_item($item_id = null, $from_outside = true)
4815
    {
4816
        $debug = $this->debug;
4817
        if ($debug) {
4818
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4819
        }
4820
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4821
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4822
        if (empty($item_id)) {
4823
            $item_id = (int) $_REQUEST['id'];
4824
        }
4825
4826
        if (empty($item_id)) {
4827
            $item_id = $this->get_current_item_id();
4828
        }
4829
        if (isset($this->items[$item_id]) &&
4830
            is_object($this->items[$item_id])
4831
        ) {
4832
            if ($debug) {
4833
                error_log('Object exists');
4834
            }
4835
4836
            // Saving the item.
4837
            $res = $this->items[$item_id]->save(
4838
                $from_outside,
4839
                $this->prerequisites_match($item_id)
4840
            );
4841
4842
            if ($debug) {
4843
                error_log('update_queue before:');
4844
                error_log(print_r($this->update_queue, 1));
4845
            }
4846
            $this->autocomplete_parents($item_id);
4847
4848
            $status = $this->items[$item_id]->get_status();
4849
            $this->update_queue[$item_id] = $status;
4850
4851
            if ($debug) {
4852
                error_log('get_status(): '.$status);
4853
                error_log('update_queue after:');
4854
                error_log(print_r($this->update_queue, 1));
4855
            }
4856
4857
            return $res;
4858
        }
4859
4860
        return false;
4861
    }
4862
4863
    /**
4864
     * Saves the last item seen's ID only in case.
4865
     */
4866
    public function save_last()
4867
    {
4868
        $course_id = api_get_course_int_id();
4869
        $debug = $this->debug;
4870
        if ($debug) {
4871
            error_log('In learnpath::save_last()', 0);
4872
        }
4873
        $session_condition = api_get_session_condition(
4874
            api_get_session_id(),
4875
            true,
4876
            false
4877
        );
4878
        $table = Database::get_course_table(TABLE_LP_VIEW);
4879
4880
        $userId = $this->get_user_id();
4881
        if (empty($userId)) {
4882
            $userId = api_get_user_id();
4883
            if ($debug) {
4884
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4885
            }
4886
        }
4887
        if (isset($this->current) && !api_is_invitee()) {
4888
            if ($debug) {
4889
                error_log('Saving current item ('.$this->current.') for later review', 0);
4890
            }
4891
            $sql = "UPDATE $table SET
4892
                        last_item = ".$this->get_current_item_id()."
4893
                    WHERE
4894
                        c_id = $course_id AND
4895
                        lp_id = ".$this->get_id()." AND
4896
                        user_id = ".$userId." ".$session_condition;
4897
4898
            if ($debug) {
4899
                error_log('Saving last item seen : '.$sql, 0);
4900
            }
4901
            Database::query($sql);
4902
        }
4903
4904
        if (!api_is_invitee()) {
4905
            // Save progress.
4906
            list($progress) = $this->get_progress_bar_text('%');
4907
            if ($progress >= 0 && $progress <= 100) {
4908
                $progress = (int) $progress;
4909
                $sql = "UPDATE $table SET
4910
                            progress = $progress
4911
                        WHERE
4912
                            c_id = $course_id AND
4913
                            lp_id = ".$this->get_id()." AND
4914
                            user_id = ".$userId." ".$session_condition;
4915
                // Ignore errors as some tables might not have the progress field just yet.
4916
                Database::query($sql);
4917
                $this->progress_db = $progress;
4918
            }
4919
        }
4920
    }
4921
4922
    /**
4923
     * Sets the current item ID (checks if valid and authorized first).
4924
     *
4925
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4926
     */
4927
    public function set_current_item($item_id = null)
4928
    {
4929
        $debug = $this->debug;
4930
        if ($debug) {
4931
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4932
        }
4933
        if (empty($item_id)) {
4934
            if ($debug) {
4935
                error_log('No new current item given, ignore...', 0);
4936
            }
4937
            // Do nothing.
4938
        } else {
4939
            if ($debug) {
4940
                error_log('New current item given is '.$item_id.'...', 0);
4941
            }
4942
            if (is_numeric($item_id)) {
4943
                $item_id = (int) $item_id;
4944
                // TODO: Check in database here.
4945
                $this->last = $this->current;
4946
                $this->current = $item_id;
4947
                // TODO: Update $this->index as well.
4948
                foreach ($this->ordered_items as $index => $item) {
4949
                    if ($item == $this->current) {
4950
                        $this->index = $index;
4951
                        break;
4952
                    }
4953
                }
4954
                if ($debug) {
4955
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4956
                }
4957
            } else {
4958
                if ($debug) {
4959
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4960
                }
4961
            }
4962
        }
4963
    }
4964
4965
    /**
4966
     * Sets the encoding.
4967
     *
4968
     * @param string $enc New encoding
4969
     *
4970
     * @return bool
4971
     *
4972
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4973
     */
4974
    public function set_encoding($enc = 'UTF-8')
4975
    {
4976
        $enc = api_refine_encoding_id($enc);
4977
        if (empty($enc)) {
4978
            $enc = api_get_system_encoding();
4979
        }
4980
        if (api_is_encoding_supported($enc)) {
4981
            $lp = $this->get_id();
4982
            if (0 != $lp) {
4983
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4984
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4985
                        WHERE iid = ".$lp;
4986
                $res = Database::query($sql);
4987
4988
                return $res;
4989
            }
4990
        }
4991
4992
        return false;
4993
    }
4994
4995
    /**
4996
     * Sets the JS lib setting in the database directly.
4997
     * This is the JavaScript library file this lp needs to load on startup.
4998
     *
4999
     * @param string $lib Proximity setting
5000
     *
5001
     * @return bool True on update success. False otherwise.
5002
     */
5003
    public function set_jslib($lib = '')
5004
    {
5005
        $lp = $this->get_id();
5006
5007
        if (0 != $lp) {
5008
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5009
            $lib = Database::escape_string($lib);
5010
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5011
                    WHERE iid = $lp";
5012
            $res = Database::query($sql);
5013
5014
            return $res;
5015
        }
5016
5017
        return false;
5018
    }
5019
5020
    /**
5021
     * Sets the name of the LP maker (publisher) (and save).
5022
     *
5023
     * @param string $name Optional string giving the new content_maker of this learnpath
5024
     *
5025
     * @return bool True
5026
     */
5027
    public function set_maker($name = '')
5028
    {
5029
        if (empty($name)) {
5030
            return false;
5031
        }
5032
        $this->maker = $name;
5033
        $table = Database::get_course_table(TABLE_LP_MAIN);
5034
        $lp_id = $this->get_id();
5035
        $sql = "UPDATE $table SET
5036
                content_maker = '".Database::escape_string($this->maker)."'
5037
                WHERE iid = $lp_id";
5038
        Database::query($sql);
5039
5040
        return true;
5041
    }
5042
5043
    /**
5044
     * Sets the name of the current learnpath (and save).
5045
     *
5046
     * @param string $name Optional string giving the new name of this learnpath
5047
     *
5048
     * @return bool True/False
5049
     */
5050
    public function set_name($name = null)
5051
    {
5052
        if (empty($name)) {
5053
            return false;
5054
        }
5055
        $this->name = $name;
5056
5057
        $lp_id = $this->get_id();
5058
5059
        $repo = Container::getLpRepository();
5060
        /** @var CLp $lp */
5061
        $lp = $repo->find($lp_id);
5062
        $lp->setName($name);
5063
        $repo->updateNodeForResource($lp);
5064
5065
        /*
5066
        $course_id = $this->course_info['real_id'];
5067
        $sql = "UPDATE $lp_table SET
5068
            name = '$name'
5069
            WHERE iid = $lp_id";
5070
        $result = Database::query($sql);
5071
        // If the lp is visible on the homepage, change his name there.
5072
        if (Database::affected_rows($result)) {
5073
        $session_id = api_get_session_id();
5074
        $session_condition = api_get_session_condition($session_id);
5075
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5076
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5077
        $sql = "UPDATE $tbl_tool SET name = '$name'
5078
        	    WHERE
5079
        	        c_id = $course_id AND
5080
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5081
        Database::query($sql);*/
5082
5083
        //return true;
5084
        //}
5085
5086
        return false;
5087
    }
5088
5089
    /**
5090
     * Set index specified prefix terms for all items in this path.
5091
     *
5092
     * @param string $terms_string Comma-separated list of terms
5093
     * @param string $prefix       Xapian term prefix
5094
     *
5095
     * @return bool False on error, true otherwise
5096
     */
5097
    public function set_terms_by_prefix($terms_string, $prefix)
5098
    {
5099
        $course_id = api_get_course_int_id();
5100
        if ('true' !== api_get_setting('search_enabled')) {
5101
            return false;
5102
        }
5103
5104
        if (!extension_loaded('xapian')) {
5105
            return false;
5106
        }
5107
5108
        $terms_string = trim($terms_string);
5109
        $terms = explode(',', $terms_string);
5110
        array_walk($terms, 'trim_value');
5111
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5112
5113
        // Don't do anything if no change, verify only at DB, not the search engine.
5114
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5115
            return false;
5116
        }
5117
5118
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5119
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5120
5121
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5122
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5123
        $lp_id = (int) $_POST['lp_id'];
5124
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5125
        $result = Database::query($sql);
5126
        $di = new ChamiloIndexer();
5127
5128
        while ($lp_item = Database::fetch_array($result)) {
5129
            // Get search_did.
5130
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5131
            $sql = 'SELECT * FROM %s
5132
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5133
                    LIMIT 1';
5134
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5135
5136
            //echo $sql; echo '<br>';
5137
            $res = Database::query($sql);
5138
            if (Database::num_rows($res) > 0) {
5139
                $se_ref = Database::fetch_array($res);
5140
                // Compare terms.
5141
                $doc = $di->get_document($se_ref['search_did']);
5142
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5143
                $xterms = [];
5144
                foreach ($xapian_terms as $xapian_term) {
5145
                    $xterms[] = substr($xapian_term['name'], 1);
5146
                }
5147
5148
                $dterms = $terms;
5149
                $missing_terms = array_diff($dterms, $xterms);
5150
                $deprecated_terms = array_diff($xterms, $dterms);
5151
5152
                // Save it to search engine.
5153
                foreach ($missing_terms as $term) {
5154
                    $doc->add_term($prefix.$term, 1);
5155
                }
5156
                foreach ($deprecated_terms as $term) {
5157
                    $doc->remove_term($prefix.$term);
5158
                }
5159
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5160
                $di->getDb()->flush();
5161
            }
5162
        }
5163
5164
        return true;
5165
    }
5166
5167
    /**
5168
     * Sets the theme of the LP (local/remote) (and save).
5169
     *
5170
     * @param string $name Optional string giving the new theme of this learnpath
5171
     *
5172
     * @return bool Returns true if theme name is not empty
5173
     */
5174
    public function set_theme($name = '')
5175
    {
5176
        $this->theme = $name;
5177
        $table = Database::get_course_table(TABLE_LP_MAIN);
5178
        $lp_id = $this->get_id();
5179
        $sql = "UPDATE $table
5180
                SET theme = '".Database::escape_string($this->theme)."'
5181
                WHERE iid = $lp_id";
5182
        Database::query($sql);
5183
5184
        return true;
5185
    }
5186
5187
    /**
5188
     * Sets the image of an LP (and save).
5189
     *
5190
     * @param string $name Optional string giving the new image of this learnpath
5191
     *
5192
     * @return bool Returns true if theme name is not empty
5193
     */
5194
    public function set_preview_image($name = '')
5195
    {
5196
        $this->preview_image = $name;
5197
        $table = Database::get_course_table(TABLE_LP_MAIN);
5198
        $lp_id = $this->get_id();
5199
        $sql = "UPDATE $table SET
5200
                preview_image = '".Database::escape_string($this->preview_image)."'
5201
                WHERE iid = $lp_id";
5202
        Database::query($sql);
5203
5204
        return true;
5205
    }
5206
5207
    /**
5208
     * Sets the author of a LP (and save).
5209
     *
5210
     * @param string $name Optional string giving the new author of this learnpath
5211
     *
5212
     * @return bool Returns true if author's name is not empty
5213
     */
5214
    public function set_author($name = '')
5215
    {
5216
        $this->author = $name;
5217
        $table = Database::get_course_table(TABLE_LP_MAIN);
5218
        $lp_id = $this->get_id();
5219
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5220
                WHERE iid = $lp_id";
5221
        Database::query($sql);
5222
5223
        return true;
5224
    }
5225
5226
    /**
5227
     * Sets the hide_toc_frame parameter of a LP (and save).
5228
     *
5229
     * @param int $hide 1 if frame is hidden 0 then else
5230
     *
5231
     * @return bool Returns true if author's name is not empty
5232
     */
5233
    public function set_hide_toc_frame($hide)
5234
    {
5235
        if (intval($hide) == $hide) {
5236
            $this->hide_toc_frame = $hide;
5237
            $table = Database::get_course_table(TABLE_LP_MAIN);
5238
            $lp_id = $this->get_id();
5239
            $sql = "UPDATE $table SET
5240
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5241
                    WHERE iid = $lp_id";
5242
            Database::query($sql);
5243
5244
            return true;
5245
        }
5246
5247
        return false;
5248
    }
5249
5250
    /**
5251
     * Sets the prerequisite of a LP (and save).
5252
     *
5253
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5254
     *
5255
     * @return bool returns true if prerequisite is not empty
5256
     */
5257
    public function set_prerequisite($prerequisite)
5258
    {
5259
        $this->prerequisite = (int) $prerequisite;
5260
        $table = Database::get_course_table(TABLE_LP_MAIN);
5261
        $lp_id = $this->get_id();
5262
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5263
                WHERE iid = $lp_id";
5264
        Database::query($sql);
5265
5266
        return true;
5267
    }
5268
5269
    /**
5270
     * Sets the location/proximity of the LP (local/remote) (and save).
5271
     *
5272
     * @param string $name Optional string giving the new location of this learnpath
5273
     *
5274
     * @return bool True on success / False on error
5275
     */
5276
    public function set_proximity($name = '')
5277
    {
5278
        if (empty($name)) {
5279
            return false;
5280
        }
5281
5282
        $this->proximity = $name;
5283
        $table = Database::get_course_table(TABLE_LP_MAIN);
5284
        $lp_id = $this->get_id();
5285
        $sql = "UPDATE $table SET
5286
                    content_local = '".Database::escape_string($name)."'
5287
                WHERE iid = $lp_id";
5288
        Database::query($sql);
5289
5290
        return true;
5291
    }
5292
5293
    /**
5294
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5295
     *
5296
     * @param int $id DB ID of the item
5297
     */
5298
    public function set_previous_item($id)
5299
    {
5300
        if ($this->debug > 0) {
5301
            error_log('In learnpath::set_previous_item()', 0);
5302
        }
5303
        $this->last = $id;
5304
    }
5305
5306
    /**
5307
     * Sets use_max_score.
5308
     *
5309
     * @param int $use_max_score Optional string giving the new location of this learnpath
5310
     *
5311
     * @return bool True on success / False on error
5312
     */
5313
    public function set_use_max_score($use_max_score = 1)
5314
    {
5315
        $use_max_score = (int) $use_max_score;
5316
        $this->use_max_score = $use_max_score;
5317
        $table = Database::get_course_table(TABLE_LP_MAIN);
5318
        $lp_id = $this->get_id();
5319
        $sql = "UPDATE $table SET
5320
                    use_max_score = '".$this->use_max_score."'
5321
                WHERE iid = $lp_id";
5322
        Database::query($sql);
5323
5324
        return true;
5325
    }
5326
5327
    /**
5328
     * Sets and saves the expired_on date.
5329
     *
5330
     * @param string $expired_on Optional string giving the new author of this learnpath
5331
     *
5332
     * @throws \Doctrine\ORM\OptimisticLockException
5333
     *
5334
     * @return bool Returns true if author's name is not empty
5335
     */
5336
    public function set_expired_on($expired_on)
5337
    {
5338
        $em = Database::getManager();
5339
        /** @var CLp $lp */
5340
        $lp = $em
5341
            ->getRepository('ChamiloCourseBundle:CLp')
5342
            ->findOneBy(
5343
                [
5344
                    'iid' => $this->get_id(),
5345
                ]
5346
            );
5347
5348
        if (!$lp) {
5349
            return false;
5350
        }
5351
5352
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5353
5354
        $lp->setExpiredOn($this->expired_on);
5355
        $em->persist($lp);
5356
        $em->flush();
5357
5358
        return true;
5359
    }
5360
5361
    /**
5362
     * Sets and saves the publicated_on date.
5363
     *
5364
     * @param string $publicated_on Optional string giving the new author of this learnpath
5365
     *
5366
     * @throws \Doctrine\ORM\OptimisticLockException
5367
     *
5368
     * @return bool Returns true if author's name is not empty
5369
     */
5370
    public function set_publicated_on($publicated_on)
5371
    {
5372
        $em = Database::getManager();
5373
        /** @var CLp $lp */
5374
        $lp = $em
5375
            ->getRepository('ChamiloCourseBundle:CLp')
5376
            ->findOneBy(
5377
                [
5378
                    'iid' => $this->get_id(),
5379
                ]
5380
            );
5381
5382
        if (!$lp) {
5383
            return false;
5384
        }
5385
5386
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5387
        $lp->setPublicatedOn($this->publicated_on);
5388
        $em->persist($lp);
5389
        $em->flush();
5390
5391
        return true;
5392
    }
5393
5394
    /**
5395
     * Sets and saves the expired_on date.
5396
     *
5397
     * @return bool Returns true if author's name is not empty
5398
     */
5399
    public function set_modified_on()
5400
    {
5401
        $this->modified_on = api_get_utc_datetime();
5402
        $table = Database::get_course_table(TABLE_LP_MAIN);
5403
        $lp_id = $this->get_id();
5404
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5405
                WHERE iid = $lp_id";
5406
        Database::query($sql);
5407
5408
        return true;
5409
    }
5410
5411
    /**
5412
     * Sets the object's error message.
5413
     *
5414
     * @param string $error Error message. If empty, reinits the error string
5415
     */
5416
    public function set_error_msg($error = '')
5417
    {
5418
        if ($this->debug > 0) {
5419
            error_log('In learnpath::set_error_msg()', 0);
5420
        }
5421
        if (empty($error)) {
5422
            $this->error = '';
5423
        } else {
5424
            $this->error .= $error;
5425
        }
5426
    }
5427
5428
    /**
5429
     * Launches the current item if not 'sco'
5430
     * (starts timer and make sure there is a record ready in the DB).
5431
     *
5432
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5433
     *
5434
     * @return bool
5435
     */
5436
    public function start_current_item($allow_new_attempt = false)
5437
    {
5438
        $debug = $this->debug;
5439
        if ($debug) {
5440
            error_log('In learnpath::start_current_item()');
5441
            error_log('current: '.$this->current);
5442
        }
5443
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5444
            $type = $this->get_type();
5445
            $item_type = $this->items[$this->current]->get_type();
5446
            if ((2 == $type && 'sco' != $item_type) ||
5447
                (3 == $type && 'au' != $item_type) ||
5448
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5449
            ) {
5450
                if ($debug) {
5451
                    error_log('item type: '.$item_type);
5452
                    error_log('lp type: '.$type);
5453
                }
5454
                $this->items[$this->current]->open($allow_new_attempt);
5455
                $this->autocomplete_parents($this->current);
5456
                $prereq_check = $this->prerequisites_match($this->current);
5457
                if ($debug) {
5458
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5459
                }
5460
                $this->items[$this->current]->save(false, $prereq_check);
5461
            }
5462
            // If sco, then it is supposed to have been updated by some other call.
5463
            if ('sco' == $item_type) {
5464
                $this->items[$this->current]->restart();
5465
            }
5466
        }
5467
        if ($debug) {
5468
            error_log('lp_view_session_id');
5469
            error_log($this->lp_view_session_id);
5470
            error_log('api session id');
5471
            error_log(api_get_session_id());
5472
            error_log('End of learnpath::start_current_item()');
5473
        }
5474
5475
        return true;
5476
    }
5477
5478
    /**
5479
     * Stops the processing and counters for the old item (as held in $this->last).
5480
     *
5481
     * @return bool True/False
5482
     */
5483
    public function stop_previous_item()
5484
    {
5485
        $debug = $this->debug;
5486
        if ($debug) {
5487
            error_log('In learnpath::stop_previous_item()', 0);
5488
        }
5489
5490
        if (0 != $this->last && $this->last != $this->current &&
5491
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5492
        ) {
5493
            if ($debug) {
5494
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5495
            }
5496
            switch ($this->get_type()) {
5497
                case '3':
5498
                    if ('au' != $this->items[$this->last]->get_type()) {
5499
                        if ($debug) {
5500
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5501
                        }
5502
                        $this->items[$this->last]->close();
5503
                    } else {
5504
                        if ($debug) {
5505
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5506
                        }
5507
                    }
5508
                    break;
5509
                case '2':
5510
                    if ('sco' != $this->items[$this->last]->get_type()) {
5511
                        if ($debug) {
5512
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5513
                        }
5514
                        $this->items[$this->last]->close();
5515
                    } else {
5516
                        if ($debug) {
5517
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5518
                        }
5519
                    }
5520
                    break;
5521
                case '1':
5522
                default:
5523
                    if ($debug) {
5524
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5525
                    }
5526
                    $this->items[$this->last]->close();
5527
                    break;
5528
            }
5529
        } else {
5530
            if ($debug) {
5531
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5532
            }
5533
5534
            return false;
5535
        }
5536
5537
        return true;
5538
    }
5539
5540
    /**
5541
     * Updates the default view mode from fullscreen to embedded and inversely.
5542
     *
5543
     * @return string The current default view mode ('fullscreen' or 'embedded')
5544
     */
5545
    public function update_default_view_mode()
5546
    {
5547
        $table = Database::get_course_table(TABLE_LP_MAIN);
5548
        $sql = "SELECT * FROM $table
5549
                WHERE iid = ".$this->get_id();
5550
        $res = Database::query($sql);
5551
        if (Database::num_rows($res) > 0) {
5552
            $row = Database::fetch_array($res);
5553
            $default_view_mode = $row['default_view_mod'];
5554
            $view_mode = $default_view_mode;
5555
            switch ($default_view_mode) {
5556
                case 'fullscreen': // default with popup
5557
                    $view_mode = 'embedded';
5558
                    break;
5559
                case 'embedded': // default view with left menu
5560
                    $view_mode = 'embedframe';
5561
                    break;
5562
                case 'embedframe': //folded menu
5563
                    $view_mode = 'impress';
5564
                    break;
5565
                case 'impress':
5566
                    $view_mode = 'fullscreen';
5567
                    break;
5568
            }
5569
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5570
                    WHERE iid = ".$this->get_id();
5571
            Database::query($sql);
5572
            $this->mode = $view_mode;
5573
5574
            return $view_mode;
5575
        }
5576
5577
        return -1;
5578
    }
5579
5580
    /**
5581
     * Updates the default behaviour about auto-commiting SCORM updates.
5582
     *
5583
     * @return bool True if auto-commit has been set to 'on', false otherwise
5584
     */
5585
    public function update_default_scorm_commit()
5586
    {
5587
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5588
        $sql = "SELECT * FROM $lp_table
5589
                WHERE iid = ".$this->get_id();
5590
        $res = Database::query($sql);
5591
        if (Database::num_rows($res) > 0) {
5592
            $row = Database::fetch_array($res);
5593
            $force = $row['force_commit'];
5594
            if (1 == $force) {
5595
                $force = 0;
5596
                $force_return = false;
5597
            } elseif (0 == $force) {
5598
                $force = 1;
5599
                $force_return = true;
5600
            }
5601
            $sql = "UPDATE $lp_table SET force_commit = $force
5602
                    WHERE iid = ".$this->get_id();
5603
            Database::query($sql);
5604
            $this->force_commit = $force_return;
5605
5606
            return $force_return;
5607
        }
5608
5609
        return -1;
5610
    }
5611
5612
    /**
5613
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5614
     *
5615
     * @return bool True on success, false on failure
5616
     */
5617
    public function update_display_order()
5618
    {
5619
        $course_id = api_get_course_int_id();
5620
        $table = Database::get_course_table(TABLE_LP_MAIN);
5621
        $sql = "SELECT * FROM $table
5622
                WHERE c_id = $course_id
5623
                ORDER BY display_order";
5624
        $res = Database::query($sql);
5625
        if (false === $res) {
5626
            return false;
5627
        }
5628
5629
        $num = Database::num_rows($res);
5630
        // First check the order is correct, globally (might be wrong because
5631
        // of versions < 1.8.4).
5632
        if ($num > 0) {
5633
            $i = 1;
5634
            while ($row = Database::fetch_array($res)) {
5635
                if ($row['display_order'] != $i) {
5636
                    // If we find a gap in the order, we need to fix it.
5637
                    $sql = "UPDATE $table SET display_order = $i
5638
                            WHERE iid = ".$row['iid'];
5639
                    Database::query($sql);
5640
                }
5641
                $i++;
5642
            }
5643
        }
5644
5645
        return true;
5646
    }
5647
5648
    /**
5649
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5650
     *
5651
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5652
     */
5653
    public function update_reinit()
5654
    {
5655
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5656
        $sql = "SELECT * FROM $lp_table
5657
                WHERE iid = ".$this->get_id();
5658
        $res = Database::query($sql);
5659
        if (Database::num_rows($res) > 0) {
5660
            $row = Database::fetch_array($res);
5661
            $force = $row['prevent_reinit'];
5662
            if (1 == $force) {
5663
                $force = 0;
5664
            } elseif (0 == $force) {
5665
                $force = 1;
5666
            }
5667
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5668
                    WHERE iid = ".$this->get_id();
5669
            Database::query($sql);
5670
            $this->prevent_reinit = $force;
5671
5672
            return $force;
5673
        }
5674
5675
        return -1;
5676
    }
5677
5678
    /**
5679
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5680
     *
5681
     * @return string 'single', 'multi' or 'seriousgame'
5682
     *
5683
     * @author ndiechburg <[email protected]>
5684
     */
5685
    public function get_attempt_mode()
5686
    {
5687
        //Set default value for seriousgame_mode
5688
        if (!isset($this->seriousgame_mode)) {
5689
            $this->seriousgame_mode = 0;
5690
        }
5691
        // Set default value for prevent_reinit
5692
        if (!isset($this->prevent_reinit)) {
5693
            $this->prevent_reinit = 1;
5694
        }
5695
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5696
            return 'seriousgame';
5697
        }
5698
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5699
            return 'single';
5700
        }
5701
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5702
            return 'multiple';
5703
        }
5704
5705
        return 'single';
5706
    }
5707
5708
    /**
5709
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5710
     *
5711
     * @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...
5712
     *
5713
     * @return bool
5714
     *
5715
     * @author ndiechburg <[email protected]>
5716
     */
5717
    public function set_attempt_mode($mode)
5718
    {
5719
        switch ($mode) {
5720
            case 'seriousgame':
5721
                $sg_mode = 1;
5722
                $prevent_reinit = 1;
5723
                break;
5724
            case 'single':
5725
                $sg_mode = 0;
5726
                $prevent_reinit = 1;
5727
                break;
5728
            case 'multiple':
5729
                $sg_mode = 0;
5730
                $prevent_reinit = 0;
5731
                break;
5732
            default:
5733
                $sg_mode = 0;
5734
                $prevent_reinit = 0;
5735
                break;
5736
        }
5737
        $this->prevent_reinit = $prevent_reinit;
5738
        $this->seriousgame_mode = $sg_mode;
5739
        $table = Database::get_course_table(TABLE_LP_MAIN);
5740
        $sql = "UPDATE $table SET
5741
                prevent_reinit = $prevent_reinit ,
5742
                seriousgame_mode = $sg_mode
5743
                WHERE iid = ".$this->get_id();
5744
        $res = Database::query($sql);
5745
        if ($res) {
5746
            return true;
5747
        } else {
5748
            return false;
5749
        }
5750
    }
5751
5752
    /**
5753
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5754
     *
5755
     * @author ndiechburg <[email protected]>
5756
     */
5757
    public function switch_attempt_mode()
5758
    {
5759
        $mode = $this->get_attempt_mode();
5760
        switch ($mode) {
5761
            case 'single':
5762
                $next_mode = 'multiple';
5763
                break;
5764
            case 'multiple':
5765
                $next_mode = 'seriousgame';
5766
                break;
5767
            case 'seriousgame':
5768
            default:
5769
                $next_mode = 'single';
5770
                break;
5771
        }
5772
        $this->set_attempt_mode($next_mode);
5773
    }
5774
5775
    /**
5776
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5777
     * but possibility to do again a completed item.
5778
     *
5779
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5780
     *
5781
     * @author ndiechburg <[email protected]>
5782
     */
5783
    public function set_seriousgame_mode()
5784
    {
5785
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5786
        $sql = "SELECT * FROM $lp_table
5787
                WHERE iid = ".$this->get_id();
5788
        $res = Database::query($sql);
5789
        if (Database::num_rows($res) > 0) {
5790
            $row = Database::fetch_array($res);
5791
            $force = $row['seriousgame_mode'];
5792
            if (1 == $force) {
5793
                $force = 0;
5794
            } elseif (0 == $force) {
5795
                $force = 1;
5796
            }
5797
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5798
			        WHERE iid = ".$this->get_id();
5799
            Database::query($sql);
5800
            $this->seriousgame_mode = $force;
5801
5802
            return $force;
5803
        }
5804
5805
        return -1;
5806
    }
5807
5808
    /**
5809
     * Updates the "scorm_debug" value that shows or hide the debug window.
5810
     *
5811
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5812
     */
5813
    public function update_scorm_debug()
5814
    {
5815
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5816
        $sql = "SELECT * FROM $lp_table
5817
                WHERE iid = ".$this->get_id();
5818
        $res = Database::query($sql);
5819
        if (Database::num_rows($res) > 0) {
5820
            $row = Database::fetch_array($res);
5821
            $force = $row['debug'];
5822
            if (1 == $force) {
5823
                $force = 0;
5824
            } elseif (0 == $force) {
5825
                $force = 1;
5826
            }
5827
            $sql = "UPDATE $lp_table SET debug = $force
5828
                    WHERE iid = ".$this->get_id();
5829
            Database::query($sql);
5830
            $this->scorm_debug = $force;
5831
5832
            return $force;
5833
        }
5834
5835
        return -1;
5836
    }
5837
5838
    /**
5839
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5840
     *
5841
     * @author Kevin Van Den Haute
5842
     *
5843
     * @param  array
5844
     */
5845
    public function tree_array($array)
5846
    {
5847
        $array = $this->sort_tree_array($array);
5848
        $this->create_tree_array($array);
5849
    }
5850
5851
    /**
5852
     * Creates an array with the elements of the learning path tree in it.
5853
     *
5854
     * @author Kevin Van Den Haute
5855
     *
5856
     * @param array $array
5857
     * @param int   $parent
5858
     * @param int   $depth
5859
     * @param array $tmp
5860
     */
5861
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5862
    {
5863
        if (is_array($array)) {
5864
            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...
5865
                if ($array[$i]['parent_item_id'] == $parent) {
5866
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5867
                        $tmp[] = $array[$i]['parent_item_id'];
5868
                        $depth++;
5869
                    }
5870
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5871
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5872
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5873
5874
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5875
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5876
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5877
                    $this->arrMenu[] = [
5878
                        'id' => $array[$i]['id'],
5879
                        'ref' => $ref,
5880
                        'item_type' => $array[$i]['item_type'],
5881
                        'title' => $array[$i]['title'],
5882
                        'title_raw' => $array[$i]['title_raw'],
5883
                        'path' => $path,
5884
                        'description' => $array[$i]['description'],
5885
                        'parent_item_id' => $array[$i]['parent_item_id'],
5886
                        'previous_item_id' => $array[$i]['previous_item_id'],
5887
                        'next_item_id' => $array[$i]['next_item_id'],
5888
                        'min_score' => $array[$i]['min_score'],
5889
                        'max_score' => $array[$i]['max_score'],
5890
                        'mastery_score' => $array[$i]['mastery_score'],
5891
                        'display_order' => $array[$i]['display_order'],
5892
                        'prerequisite' => $preq,
5893
                        'depth' => $depth,
5894
                        'audio' => $audio,
5895
                        'prerequisite_min_score' => $prerequisiteMinScore,
5896
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5897
                    ];
5898
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5899
                }
5900
            }
5901
        }
5902
    }
5903
5904
    /**
5905
     * Sorts a multi dimensional array by parent id and display order.
5906
     *
5907
     * @author Kevin Van Den Haute
5908
     *
5909
     * @param array $array (array with al the learning path items in it)
5910
     *
5911
     * @return array
5912
     */
5913
    public function sort_tree_array($array)
5914
    {
5915
        foreach ($array as $key => $row) {
5916
            $parent[$key] = $row['parent_item_id'];
5917
            $position[$key] = $row['display_order'];
5918
        }
5919
5920
        if (count($array) > 0) {
5921
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5922
        }
5923
5924
        return $array;
5925
    }
5926
5927
    /**
5928
     * Function that creates a html list of learning path items so that we can add audio files to them.
5929
     *
5930
     * @author Kevin Van Den Haute
5931
     *
5932
     * @return string
5933
     */
5934
    public function overview()
5935
    {
5936
        $return = '';
5937
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5938
5939
        // we need to start a form when we want to update all the mp3 files
5940
        if ('true' == $update_audio) {
5941
            $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">';
5942
        }
5943
        $return .= '<div id="message"></div>';
5944
        if (0 == count($this->items)) {
5945
            $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');
5946
        } else {
5947
            $return_audio = '<table class="data_table">';
5948
            $return_audio .= '<tr>';
5949
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5950
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5951
            $return_audio .= '</tr>';
5952
5953
            if ('true' != $update_audio) {
5954
                $return .= '<div class="col-md-12">';
5955
                $return .= self::return_new_tree($update_audio);
5956
                $return .= '</div>';
5957
                $return .= Display::div(
5958
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5959
                    ['style' => 'float:left; margin-top:15px;width:100%']
5960
                );
5961
            } else {
5962
                $return_audio .= self::return_new_tree($update_audio);
5963
                $return .= $return_audio.'</table>';
5964
            }
5965
5966
            // We need to close the form when we are updating the mp3 files.
5967
            if ('true' == $update_audio) {
5968
                $return .= '<div class="footer-audio">';
5969
                $return .= Display::button(
5970
                    'save_audio',
5971
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5972
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5973
                );
5974
                $return .= '</div>';
5975
            }
5976
        }
5977
5978
        // We need to close the form when we are updating the mp3 files.
5979
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5980
            $return .= '</form>';
5981
        }
5982
5983
        return $return;
5984
    }
5985
5986
    /**
5987
     * @param string $update_audio
5988
     *
5989
     * @return array
5990
     */
5991
    public function processBuildMenuElements($update_audio = 'false')
5992
    {
5993
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5994
        $arrLP = $this->getItemsForForm();
5995
5996
        $this->tree_array($arrLP);
5997
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5998
        unset($this->arrMenu);
5999
        $default_data = null;
6000
        $default_content = null;
6001
        $elements = [];
6002
        $return_audio = null;
6003
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
6004
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6005
        $countItems = count($arrLP);
6006
6007
        $upIcon = Display::return_icon(
6008
            'up.png',
6009
            get_lang('Up'),
6010
            [],
6011
            ICON_SIZE_TINY
6012
        );
6013
6014
        $disableUpIcon = Display::return_icon(
6015
            'up_na.png',
6016
            get_lang('Up'),
6017
            [],
6018
            ICON_SIZE_TINY
6019
        );
6020
6021
        $downIcon = Display::return_icon(
6022
            'down.png',
6023
            get_lang('Down'),
6024
            [],
6025
            ICON_SIZE_TINY
6026
        );
6027
6028
        $disableDownIcon = Display::return_icon(
6029
            'down_na.png',
6030
            get_lang('Down'),
6031
            [],
6032
            ICON_SIZE_TINY
6033
        );
6034
6035
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6036
6037
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
6038
        $plugin = null;
6039
        if ($pluginCalendar) {
6040
            $plugin = LearningCalendarPlugin::create();
6041
        }
6042
6043
        for ($i = 0; $i < $countItems; $i++) {
6044
            $parent_id = $arrLP[$i]['parent_item_id'];
6045
            $title = $arrLP[$i]['title'];
6046
            $title_cut = $arrLP[$i]['title_raw'];
6047
            if (false === $show) {
6048
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6049
            }
6050
            // Link for the documents
6051
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
6052
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6053
                $title_cut = Display::url(
6054
                    $title_cut,
6055
                    $url,
6056
                    [
6057
                        'class' => 'ajax moved',
6058
                        'data-title' => $title,
6059
                        'title' => $title,
6060
                    ]
6061
                );
6062
            }
6063
6064
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6065
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6066
                Session::write('pathItem', $arrLP[$i]['path']);
6067
            }
6068
6069
            $oddClass = 'row_even';
6070
            if (0 == ($i % 2)) {
6071
                $oddClass = 'row_odd';
6072
            }
6073
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6074
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6075
6076
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6077
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6078
            } else {
6079
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6080
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6081
                } else {
6082
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6083
                        $icon = Display::return_icon('certificate.png');
6084
                    } else {
6085
                        $icon = Display::return_icon('folder_document.png');
6086
                    }
6087
                }
6088
            }
6089
6090
            // The audio column.
6091
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6092
            $audio = '';
6093
            if (!$update_audio || 'true' != $update_audio) {
6094
                if (empty($arrLP[$i]['audio'])) {
6095
                    $audio .= '';
6096
                }
6097
            } else {
6098
                $types = self::getChapterTypes();
6099
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6100
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6101
                    if (!empty($arrLP[$i]['audio'])) {
6102
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6103
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6104
                    }
6105
                }
6106
            }
6107
6108
            $return_audio .= Display::span($icon.' '.$title).
6109
                Display::tag(
6110
                    'td',
6111
                    $audio,
6112
                    ['style' => '']
6113
                );
6114
            $return_audio .= '</td>';
6115
            $move_icon = '';
6116
            $move_item_icon = '';
6117
            $edit_icon = '';
6118
            $delete_icon = '';
6119
            $audio_icon = '';
6120
            $prerequisities_icon = '';
6121
            $forumIcon = '';
6122
            $previewIcon = '';
6123
            $pluginCalendarIcon = '';
6124
            $orderIcons = '';
6125
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6126
6127
            if ($is_allowed_to_edit) {
6128
                if (!$update_audio || 'true' != $update_audio) {
6129
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
6130
                        $move_icon .= '<a class="moved" href="#">';
6131
                        $move_icon .= Display::return_icon(
6132
                            'move_everywhere.png',
6133
                            get_lang('Move'),
6134
                            [],
6135
                            ICON_SIZE_TINY
6136
                        );
6137
                        $move_icon .= '</a>';
6138
                    }
6139
                }
6140
6141
                // No edit for this item types
6142
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6143
                    if ('dir' != $arrLP[$i]['item_type']) {
6144
                        $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">';
6145
                        $edit_icon .= Display::return_icon(
6146
                            'edit.png',
6147
                            get_lang('Edit section description/name'),
6148
                            [],
6149
                            ICON_SIZE_TINY
6150
                        );
6151
                        $edit_icon .= '</a>';
6152
6153
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6154
                            $forumThread = null;
6155
                            if (isset($this->items[$arrLP[$i]['id']])) {
6156
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6157
                                    $this->course_int_id,
6158
                                    $this->lp_session_id
6159
                                );
6160
                            }
6161
                            if ($forumThread) {
6162
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6163
                                        'action' => 'dissociate_forum',
6164
                                        'id' => $arrLP[$i]['id'],
6165
                                        'lp_id' => $this->lp_id,
6166
                                    ]);
6167
                                $forumIcon = Display::url(
6168
                                    Display::return_icon(
6169
                                        'forum.png',
6170
                                        get_lang('Dissociate the forum of this learning path item'),
6171
                                        [],
6172
                                        ICON_SIZE_TINY
6173
                                    ),
6174
                                    $forumIconUrl,
6175
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6176
                                );
6177
                            } else {
6178
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6179
                                        'action' => 'create_forum',
6180
                                        'id' => $arrLP[$i]['id'],
6181
                                        'lp_id' => $this->lp_id,
6182
                                    ]);
6183
                                $forumIcon = Display::url(
6184
                                    Display::return_icon(
6185
                                        'forum.png',
6186
                                        get_lang('Associate a forum to this learning path item'),
6187
                                        [],
6188
                                        ICON_SIZE_TINY
6189
                                    ),
6190
                                    $forumIconUrl,
6191
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6192
                                );
6193
                            }
6194
                        }
6195
                    } else {
6196
                        $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">';
6197
                        $edit_icon .= Display::return_icon(
6198
                            'edit.png',
6199
                            get_lang('Edit section description/name'),
6200
                            [],
6201
                            ICON_SIZE_TINY
6202
                        );
6203
                        $edit_icon .= '</a>';
6204
                    }
6205
                } else {
6206
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6207
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6208
                        $edit_icon .= Display::return_icon(
6209
                            'edit.png',
6210
                            get_lang('Edit'),
6211
                            [],
6212
                            ICON_SIZE_TINY
6213
                        );
6214
                        $edit_icon .= '</a>';
6215
                    }
6216
                }
6217
6218
                if ($pluginCalendar) {
6219
                    $pluginLink = $pluginUrl.
6220
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6221
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6222
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6223
                    if ($itemInfo && 1 == $itemInfo['value']) {
6224
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6225
                    }
6226
                    $pluginCalendarIcon = Display::url(
6227
                        $iconCalendar,
6228
                        $pluginLink,
6229
                        ['class' => 'btn btn-default']
6230
                    );
6231
                }
6232
6233
                if ('final_item' != $arrLP[$i]['item_type']) {
6234
                    $orderIcons = Display::url(
6235
                        $upIcon,
6236
                        'javascript:void(0)',
6237
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6238
                    );
6239
                    $orderIcons .= Display::url(
6240
                        $downIcon,
6241
                        'javascript:void(0)',
6242
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6243
                    );
6244
                }
6245
6246
                $delete_icon .= ' <a
6247
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6248
                    onclick="return confirmation(\''.addslashes($title).'\');"
6249
                    class="btn btn-default">';
6250
                $delete_icon .= Display::return_icon(
6251
                    'delete.png',
6252
                    get_lang('Delete section'),
6253
                    [],
6254
                    ICON_SIZE_TINY
6255
                );
6256
                $delete_icon .= '</a>';
6257
6258
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6259
                $previewImage = Display::return_icon(
6260
                    'preview_view.png',
6261
                    get_lang('Preview'),
6262
                    [],
6263
                    ICON_SIZE_TINY
6264
                );
6265
6266
                switch ($arrLP[$i]['item_type']) {
6267
                    case TOOL_DOCUMENT:
6268
                    case TOOL_LP_FINAL_ITEM:
6269
                    case TOOL_READOUT_TEXT:
6270
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6271
                        $previewIcon = Display::url(
6272
                            $previewImage,
6273
                            $urlPreviewLink,
6274
                            [
6275
                                'target' => '_blank',
6276
                                'class' => 'btn btn-default',
6277
                                'data-title' => $arrLP[$i]['title'],
6278
                                'title' => $arrLP[$i]['title'],
6279
                            ]
6280
                        );
6281
                        break;
6282
                    case TOOL_THREAD:
6283
                    case TOOL_FORUM:
6284
                    case TOOL_QUIZ:
6285
                    case TOOL_STUDENTPUBLICATION:
6286
                    case TOOL_LP_FINAL_ITEM:
6287
                    case TOOL_LINK:
6288
                        $class = 'btn btn-default';
6289
                        $target = '_blank';
6290
                        $link = self::rl_get_resource_link_for_learnpath(
6291
                            $this->course_int_id,
6292
                            $this->lp_id,
6293
                            $arrLP[$i]['id'],
6294
                            0
6295
                        );
6296
                        $previewIcon = Display::url(
6297
                            $previewImage,
6298
                            $link,
6299
                            [
6300
                                'class' => $class,
6301
                                'data-title' => $arrLP[$i]['title'],
6302
                                'title' => $arrLP[$i]['title'],
6303
                                'target' => $target,
6304
                            ]
6305
                        );
6306
                        break;
6307
                    default:
6308
                        $previewIcon = Display::url(
6309
                            $previewImage,
6310
                            $url.'&action=view_item',
6311
                            ['class' => 'btn btn-default', 'target' => '_blank']
6312
                        );
6313
                        break;
6314
                }
6315
6316
                if ('dir' != $arrLP[$i]['item_type']) {
6317
                    $prerequisities_icon = Display::url(
6318
                        Display::return_icon(
6319
                            'accept.png',
6320
                            get_lang('Prerequisites'),
6321
                            [],
6322
                            ICON_SIZE_TINY
6323
                        ),
6324
                        $url.'&action=edit_item_prereq',
6325
                        ['class' => 'btn btn-default']
6326
                    );
6327
                    if ('final_item' != $arrLP[$i]['item_type']) {
6328
                        /*$move_item_icon = Display::url(
6329
                            Display::return_icon(
6330
                                'move.png',
6331
                                get_lang('Move'),
6332
                                [],
6333
                                ICON_SIZE_TINY
6334
                            ),
6335
                            $url.'&action=move_item',
6336
                            ['class' => 'btn btn-default']
6337
                        );*/
6338
                    }
6339
                    $audio_icon = Display::url(
6340
                        Display::return_icon(
6341
                            'audio.png',
6342
                            get_lang('Upload'),
6343
                            [],
6344
                            ICON_SIZE_TINY
6345
                        ),
6346
                        $url.'&action=add_audio',
6347
                        ['class' => 'btn btn-default']
6348
                    );
6349
                }
6350
            }
6351
            if ('true' != $update_audio) {
6352
                $row = $move_icon.' '.$icon.
6353
                    Display::span($title_cut).
6354
                    Display::tag(
6355
                        'div',
6356
                        "<div class=\"btn-group btn-group-xs\">
6357
                                    $previewIcon
6358
                                    $audio
6359
                                    $edit_icon
6360
                                    $pluginCalendarIcon
6361
                                    $forumIcon
6362
                                    $prerequisities_icon
6363
                                    $move_item_icon
6364
                                    $audio_icon
6365
                                    $orderIcons
6366
                                    $delete_icon
6367
                                </div>",
6368
                        ['class' => 'btn-toolbar button_actions']
6369
                    );
6370
            } else {
6371
                $row =
6372
                    Display::span($title.$icon).
6373
                    Display::span($audio, ['class' => 'button_actions']);
6374
            }
6375
6376
            $default_data[$arrLP[$i]['id']] = $row;
6377
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6378
6379
            if (empty($parent_id)) {
6380
                $elements[$arrLP[$i]['id']]['data'] = $row;
6381
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6382
            } else {
6383
                $parent_arrays = [];
6384
                if ($arrLP[$i]['depth'] > 1) {
6385
                    // Getting list of parents
6386
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6387
                        foreach ($arrLP as $item) {
6388
                            if ($item['id'] == $parent_id) {
6389
                                if (0 == $item['parent_item_id']) {
6390
                                    $parent_id = $item['id'];
6391
                                    break;
6392
                                } else {
6393
                                    $parent_id = $item['parent_item_id'];
6394
                                    if (empty($parent_arrays)) {
6395
                                        $parent_arrays[] = intval($item['id']);
6396
                                    }
6397
                                    $parent_arrays[] = $parent_id;
6398
                                    break;
6399
                                }
6400
                            }
6401
                        }
6402
                    }
6403
                }
6404
6405
                if (!empty($parent_arrays)) {
6406
                    $parent_arrays = array_reverse($parent_arrays);
6407
                    $val = '$elements';
6408
                    $x = 0;
6409
                    foreach ($parent_arrays as $item) {
6410
                        if ($x != count($parent_arrays) - 1) {
6411
                            $val .= '["'.$item.'"]["children"]';
6412
                        } else {
6413
                            $val .= '["'.$item.'"]["children"]';
6414
                        }
6415
                        $x++;
6416
                    }
6417
                    $val .= "";
6418
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6419
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6420
                } else {
6421
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6422
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6423
                }
6424
            }
6425
        }
6426
6427
        return [
6428
            'elements' => $elements,
6429
            'default_data' => $default_data,
6430
            'default_content' => $default_content,
6431
            'return_audio' => $return_audio,
6432
        ];
6433
    }
6434
6435
    /**
6436
     * @param string $updateAudio true/false strings
6437
     *
6438
     * @return string
6439
     */
6440
    public function returnLpItemList($updateAudio)
6441
    {
6442
        $result = $this->processBuildMenuElements($updateAudio);
6443
6444
        $html = self::print_recursive(
6445
            $result['elements'],
6446
            $result['default_data'],
6447
            $result['default_content']
6448
        );
6449
6450
        if (!empty($html)) {
6451
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6452
        }
6453
6454
        return $html;
6455
    }
6456
6457
    /**
6458
     * @param string $update_audio
6459
     * @param bool   $drop_element_here
6460
     *
6461
     * @return string
6462
     */
6463
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6464
    {
6465
        $result = $this->processBuildMenuElements($update_audio);
6466
6467
        $list = '<ul id="lp_item_list">';
6468
        $tree = $this->print_recursive(
6469
            $result['elements'],
6470
            $result['default_data'],
6471
            $result['default_content']
6472
        );
6473
6474
        if (!empty($tree)) {
6475
            $list .= $tree;
6476
        } else {
6477
            if ($drop_element_here) {
6478
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6479
            }
6480
        }
6481
        $list .= '</ul>';
6482
6483
        $return = Display::panelCollapse(
6484
            $this->name,
6485
            $list,
6486
            'scorm-list',
6487
            null,
6488
            'scorm-list-accordion',
6489
            'scorm-list-collapse'
6490
        );
6491
6492
        if ('true' === $update_audio) {
6493
            $return = $result['return_audio'];
6494
        }
6495
6496
        return $return;
6497
    }
6498
6499
    /**
6500
     * @param array $elements
6501
     * @param array $default_data
6502
     * @param array $default_content
6503
     *
6504
     * @return string
6505
     */
6506
    public function print_recursive($elements, $default_data, $default_content)
6507
    {
6508
        $return = '';
6509
        foreach ($elements as $key => $item) {
6510
            if (isset($item['load_data']) || empty($item['data'])) {
6511
                $item['data'] = $default_data[$item['load_data']];
6512
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6513
            }
6514
            $sub_list = '';
6515
            if (isset($item['type']) && 'dir' === $item['type']) {
6516
                // empty value
6517
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6518
            }
6519
            if (empty($item['children'])) {
6520
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6521
                $active = null;
6522
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6523
                    $active = 'active';
6524
                }
6525
                $return .= Display::tag(
6526
                    'li',
6527
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6528
                    ['id' => $key, 'class' => 'record li_container']
6529
                );
6530
            } else {
6531
                // Sections
6532
                $data = '';
6533
                if (isset($item['children'])) {
6534
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6535
                }
6536
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6537
                $return .= Display::tag(
6538
                    'li',
6539
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6540
                    ['id' => $key, 'class' => 'record li_container']
6541
                );
6542
            }
6543
        }
6544
6545
        return $return;
6546
    }
6547
6548
    /**
6549
     * This function builds the action menu.
6550
     *
6551
     * @param bool   $returnString           Optional
6552
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6553
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6554
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6555
     * @param string $action
6556
     * @param array  $extraField
6557
     *
6558
     * @return string
6559
     */
6560
    public function build_action_menu(
6561
        $returnString = false,
6562
        $showRequirementButtons = true,
6563
        $isConfigPage = false,
6564
        $allowExpand = true,
6565
        $action = '',
6566
        $extraField = []
6567
    ) {
6568
        $actionsRight = '';
6569
        $lpId = $this->lp_id;
6570
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6571
        $back = Display::url(
6572
            Display::return_icon(
6573
                'back.png',
6574
                get_lang('Back to learning paths'),
6575
                '',
6576
                ICON_SIZE_MEDIUM
6577
            ),
6578
            'lp_controller.php?'.api_get_cidreq()
6579
        );
6580
        } else {
6581
            $back = Display::url(
6582
                Display::return_icon(
6583
                    'back.png',
6584
                    get_lang('Back'),
6585
                    '',
6586
                    ICON_SIZE_MEDIUM
6587
                ),
6588
                $extraField['backTo']
6589
            );
6590
        }
6591
6592
        /*if ($backToBuild) {
6593
            $back = Display::url(
6594
                Display::return_icon(
6595
                    'back.png',
6596
                    get_lang('GoBack'),
6597
                    '',
6598
                    ICON_SIZE_MEDIUM
6599
                ),
6600
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6601
            );
6602
        }*/
6603
6604
        $actionsLeft = $back;
6605
6606
        $actionsLeft .= Display::url(
6607
            Display::return_icon(
6608
                'preview_view.png',
6609
                get_lang('Preview'),
6610
                '',
6611
                ICON_SIZE_MEDIUM
6612
            ),
6613
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6614
                'action' => 'view',
6615
                'lp_id' => $lpId,
6616
                'isStudentView' => 'true',
6617
            ])
6618
        );
6619
6620
        $actionsLeft .= Display::url(
6621
            Display::return_icon(
6622
                'upload_audio.png',
6623
                get_lang('Add audio'),
6624
                '',
6625
                ICON_SIZE_MEDIUM
6626
            ),
6627
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6628
                'action' => 'admin_view',
6629
                'lp_id' => $lpId,
6630
                'updateaudio' => 'true',
6631
            ])
6632
        );
6633
6634
        $subscriptionSettings = self::getSubscriptionSettings();
6635
6636
        $request = api_request_uri();
6637
        if (false === strpos($request, 'edit')) {
6638
            $actionsLeft .= Display::url(
6639
                Display::return_icon(
6640
                    'settings.png',
6641
                    get_lang('Course settings'),
6642
                    '',
6643
                    ICON_SIZE_MEDIUM
6644
                ),
6645
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6646
                    'action' => 'edit',
6647
                    'lp_id' => $lpId,
6648
                ])
6649
            );
6650
        }
6651
6652
        if ((false === strpos($request, 'build') &&
6653
            false === strpos($request, 'add_item')) ||
6654
            in_array($action, ['add_audio'])
6655
        ) {
6656
            $actionsLeft .= Display::url(
6657
                Display::return_icon(
6658
                    'edit.png',
6659
                    get_lang('Edit'),
6660
                    '',
6661
                    ICON_SIZE_MEDIUM
6662
                ),
6663
                'lp_controller.php?'.http_build_query([
6664
                    'action' => 'build',
6665
                    'lp_id' => $lpId,
6666
                ]).'&'.api_get_cidreq()
6667
            );
6668
        }
6669
6670
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6671
            if (1 == $this->subscribeUsers &&
6672
                $subscriptionSettings['allow_add_users_to_lp']) {
6673
                $actionsLeft .= Display::url(
6674
                    Display::return_icon(
6675
                        'user.png',
6676
                        get_lang('Subscribe users to learning path'),
6677
                        '',
6678
                        ICON_SIZE_MEDIUM
6679
                    ),
6680
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6681
                );
6682
            }
6683
        }
6684
6685
        if ($allowExpand) {
6686
            $actionsLeft .= Display::url(
6687
                Display::return_icon(
6688
                    'expand.png',
6689
                    get_lang('Expand'),
6690
                    ['id' => 'expand'],
6691
                    ICON_SIZE_MEDIUM
6692
                ).
6693
                Display::return_icon(
6694
                    'contract.png',
6695
                    get_lang('Collapse'),
6696
                    ['id' => 'contract', 'class' => 'hide'],
6697
                    ICON_SIZE_MEDIUM
6698
                ),
6699
                '#',
6700
                ['role' => 'button', 'id' => 'hide_bar_template']
6701
            );
6702
        }
6703
6704
        if ($showRequirementButtons) {
6705
            $buttons = [
6706
                [
6707
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6708
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6709
                        'action' => 'set_previous_step_as_prerequisite',
6710
                        'lp_id' => $lpId,
6711
                    ]),
6712
                ],
6713
                [
6714
                    'title' => get_lang('Clear all prerequisites'),
6715
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6716
                        'action' => 'clear_prerequisites',
6717
                        'lp_id' => $lpId,
6718
                    ]),
6719
                ],
6720
            ];
6721
            $actionsRight = Display::groupButtonWithDropDown(
6722
                get_lang('Prerequisites options'),
6723
                $buttons,
6724
                true
6725
            );
6726
        }
6727
6728
        $toolbar = Display::toolbarAction(
6729
            'actions-lp-controller',
6730
            [$actionsLeft, $actionsRight]
6731
        );
6732
6733
        if ($returnString) {
6734
            return $toolbar;
6735
        }
6736
6737
        echo $toolbar;
6738
    }
6739
6740
    /**
6741
     * Creates the default learning path folder.
6742
     *
6743
     * @param array $course
6744
     * @param int   $creatorId
6745
     *
6746
     * @return bool
6747
     */
6748
    public static function generate_learning_path_folder($course, $creatorId = 0)
6749
    {
6750
        // Creating learning_path folder
6751
        $dir = 'learning_path';
6752
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6753
        $folder = false;
6754
        $folderData = create_unexisting_directory(
6755
            $course,
6756
            $creatorId,
6757
            0,
6758
            null,
6759
            0,
6760
            '',
6761
            $dir,
6762
            get_lang('Learning paths'),
6763
            0
6764
        );
6765
6766
        if (!empty($folderData)) {
6767
            $folder = true;
6768
        }
6769
6770
        return $folder;
6771
    }
6772
6773
    /**
6774
     * @param array  $course
6775
     * @param string $lp_name
6776
     * @param int    $creatorId
6777
     *
6778
     * @return array
6779
     */
6780
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6781
    {
6782
        $filepath = '';
6783
        $dir = '/learning_path/';
6784
6785
        if (empty($lp_name)) {
6786
            $lp_name = $this->name;
6787
        }
6788
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6789
        $folder = self::generate_learning_path_folder($course, $creatorId);
6790
6791
        // Limits title size
6792
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6793
        $dir = $dir.$title;
6794
6795
        // Creating LP folder
6796
        $documentId = null;
6797
        if ($folder) {
6798
            $folderData = create_unexisting_directory(
6799
                $course,
6800
                $creatorId,
6801
                0,
6802
                0,
6803
                0,
6804
                $filepath,
6805
                $dir,
6806
                $lp_name
6807
            );
6808
            if (!empty($folderData)) {
6809
                $folder = true;
6810
            }
6811
6812
            $documentId = $folderData->getIid();
6813
            $dir = $dir.'/';
6814
            if ($folder) {
6815
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6816
            }
6817
        }
6818
6819
        if (empty($documentId)) {
6820
            $dir = api_remove_trailing_slash($dir);
6821
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6822
        }
6823
6824
        $array = [
6825
            'dir' => $dir,
6826
            'filepath' => $filepath,
6827
            'folder' => $folder,
6828
            'id' => $documentId,
6829
        ];
6830
6831
        return $array;
6832
    }
6833
6834
    /**
6835
     * Create a new document //still needs some finetuning.
6836
     *
6837
     * @param array  $courseInfo
6838
     * @param string $content
6839
     * @param string $title
6840
     * @param string $extension
6841
     * @param int    $parentId
6842
     * @param int    $creatorId  creator id
6843
     *
6844
     * @return int
6845
     */
6846
    public function create_document(
6847
        $courseInfo,
6848
        $content = '',
6849
        $title = '',
6850
        $extension = 'html',
6851
        $parentId = 0,
6852
        $creatorId = 0
6853
    ) {
6854
        if (!empty($courseInfo)) {
6855
            $course_id = $courseInfo['real_id'];
6856
        } else {
6857
            $course_id = api_get_course_int_id();
6858
        }
6859
6860
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6861
        $sessionId = api_get_session_id();
6862
6863
        // Generates folder
6864
        $result = $this->generate_lp_folder($courseInfo);
6865
        $dir = $result['dir'];
6866
6867
        if (empty($parentId) || '/' == $parentId) {
6868
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6869
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6870
6871
            if ('/' === $parentId) {
6872
                $dir = '/';
6873
            }
6874
6875
            // Please, do not modify this dirname formatting.
6876
            if (strstr($dir, '..')) {
6877
                $dir = '/';
6878
            }
6879
6880
            if (!empty($dir[0]) && '.' == $dir[0]) {
6881
                $dir = substr($dir, 1);
6882
            }
6883
            if (!empty($dir[0]) && '/' != $dir[0]) {
6884
                $dir = '/'.$dir;
6885
            }
6886
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6887
                $dir .= '/';
6888
            }
6889
        } else {
6890
            $parentInfo = DocumentManager::get_document_data_by_id(
6891
                $parentId,
6892
                $courseInfo['code']
6893
            );
6894
            if (!empty($parentInfo)) {
6895
                $dir = $parentInfo['path'].'/';
6896
            }
6897
        }
6898
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6899
        // is already escaped twice when it gets here.
6900
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6901
        if (!empty($title)) {
6902
            $title = api_replace_dangerous_char(stripslashes($title));
6903
        } else {
6904
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6905
        }
6906
6907
        $title = disable_dangerous_file($title);
6908
        $filename = $title;
6909
        $content = !empty($content) ? $content : $_POST['content_lp'];
6910
        $tmp_filename = $filename;
6911
        $i = 0;
6912
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6913
            $tmp_filename = $filename.'_'.++$i;
6914
        }
6915
        $filename = $tmp_filename.'.'.$extension;
6916
6917
        if ('html' === $extension) {
6918
            $content = stripslashes($content);
6919
            $content = str_replace(
6920
                api_get_path(WEB_COURSE_PATH),
6921
                api_get_path(REL_PATH).'courses/',
6922
                $content
6923
            );
6924
6925
            // Change the path of mp3 to absolute.
6926
            // The first regexp deals with :// urls.
6927
            $content = preg_replace(
6928
                "|(flashvars=\"file=)([^:/]+)/|",
6929
                "$1".api_get_path(
6930
                    REL_COURSE_PATH
6931
                ).$courseInfo['path'].'/document/',
6932
                $content
6933
            );
6934
            // The second regexp deals with audio/ urls.
6935
            $content = preg_replace(
6936
                "|(flashvars=\"file=)([^/]+)/|",
6937
                "$1".api_get_path(
6938
                    REL_COURSE_PATH
6939
                ).$courseInfo['path'].'/document/$2/',
6940
                $content
6941
            );
6942
            // For flv player: To prevent edition problem with firefox,
6943
            // we have to use a strange tip (don't blame me please).
6944
            $content = str_replace(
6945
                '</body>',
6946
                '<style type="text/css">body{}</style></body>',
6947
                $content
6948
            );
6949
        }
6950
6951
        $save_file_path = $dir.$filename;
6952
6953
        $document = DocumentManager::addDocument(
6954
            $courseInfo,
6955
            $save_file_path,
6956
            'file',
6957
            '',
6958
            $tmp_filename,
6959
            '',
6960
            0, //readonly
6961
            true,
6962
            null,
6963
            $sessionId,
6964
            $creatorId,
6965
            false,
6966
            $content,
6967
            $parentId
6968
        );
6969
6970
        $document_id = $document->getIid();
6971
        if ($document_id) {
6972
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6973
            $new_title = $originalTitle;
6974
6975
            if ($new_comment || $new_title) {
6976
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6977
                $ct = '';
6978
                if ($new_comment) {
6979
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6980
                }
6981
                if ($new_title) {
6982
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6983
                }
6984
6985
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6986
                        WHERE iid = $document_id ";
6987
                Database::query($sql);
6988
            }
6989
        }
6990
6991
        return $document_id;
6992
    }
6993
6994
    /**
6995
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6996
     */
6997
    public function edit_document()
6998
    {
6999
        $repo = Container::getDocumentRepository();
7000
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
7001
            $id = (int) $_REQUEST['document_id'];
7002
            /** @var CDocument $document */
7003
            $document = $repo->find($id);
7004
7005
            if ($document->getResourceNode()->hasEditableTextContent()) {
7006
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
7007
            }
7008
7009
            $document->setTitle($_REQUEST['title']);
7010
            $repo->getEntityManager()->persist($document);
7011
            $repo->getEntityManager()->flush();
7012
        }
7013
    }
7014
7015
    /**
7016
     * Displays the selected item, with a panel for manipulating the item.
7017
     *
7018
     * @param CLpItem $lpItem
7019
     * @param string  $msg
7020
     * @param bool    $show_actions
7021
     *
7022
     * @return string
7023
     */
7024
    public function display_item($lpItem, $msg = null, $show_actions = true)
7025
    {
7026
        $course_id = api_get_course_int_id();
7027
        $return = '';
7028
7029
        if (empty($lpItem)) {
7030
            return '';
7031
        }
7032
        $item_id = $lpItem->getIid();
7033
        $itemType = $lpItem->getItemType();
7034
        $lpId = $lpItem->getLpId();
7035
        $path = $lpItem->getPath();
7036
7037
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
7038
7039
        // Prevents wrong parent selection for document, see Bug#1251.
7040
        if ('dir' !== $itemType) {
7041
            Session::write('parent_item_id', $lpItem->getParentItemId());
7042
        }
7043
7044
        if ($show_actions) {
7045
            $return .= $this->displayItemMenu($lpItem);
7046
        }
7047
        $return .= '<div style="padding:10px;">';
7048
7049
        if ('' != $msg) {
7050
            $return .= $msg;
7051
        }
7052
7053
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
7054
7055
        switch ($itemType) {
7056
            case TOOL_THREAD:
7057
                $link = $this->rl_get_resource_link_for_learnpath(
7058
                    $course_id,
7059
                    $lpId,
7060
                    $item_id,
7061
                    0
7062
                );
7063
                $return .= Display::url(
7064
                    get_lang('Go to thread'),
7065
                    $link,
7066
                    ['class' => 'btn btn-primary']
7067
                );
7068
                break;
7069
            case TOOL_FORUM:
7070
                $return .= Display::url(
7071
                    get_lang('Go to the forum'),
7072
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
7073
                    ['class' => 'btn btn-primary']
7074
                );
7075
                break;
7076
            case TOOL_QUIZ:
7077
                if (!empty($path)) {
7078
                    $exercise = new Exercise();
7079
                    $exercise->read($path);
7080
                    $return .= $exercise->description.'<br />';
7081
                    $return .= Display::url(
7082
                        get_lang('Go to exercise'),
7083
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7084
                        ['class' => 'btn btn-primary']
7085
                    );
7086
                }
7087
                break;
7088
            case TOOL_LP_FINAL_ITEM:
7089
                $return .= $this->getSavedFinalItem();
7090
                break;
7091
            case TOOL_DOCUMENT:
7092
            case TOOL_READOUT_TEXT:
7093
                $repo = Container::getDocumentRepository();
7094
                /** @var CDocument $document */
7095
                $document = $repo->find($lpItem->getPath());
7096
                $return .= $this->display_document($document, true, true);
7097
                break;
7098
            case TOOL_HOTPOTATOES:
7099
                $return .= $this->display_document($document, false, true);
7100
                break;
7101
        }
7102
        $return .= '</div>';
7103
7104
        return $return;
7105
    }
7106
7107
    /**
7108
     * Shows the needed forms for editing a specific item.
7109
     *
7110
     * @param CLpItem $lpItem
7111
     *
7112
     * @throws Exception
7113
     * @throws HTML_QuickForm_Error
7114
     *
7115
     * @return string
7116
     */
7117
    public function display_edit_item($lpItem)
7118
    {
7119
        $course_id = api_get_course_int_id();
7120
        $return = '';
7121
7122
        if (empty($lpItem)) {
7123
            return '';
7124
        }
7125
        $item_id = $lpItem->getIid();
7126
        $itemType = $lpItem->getItemType();
7127
        $path = $lpItem->getPath();
7128
7129
        switch ($itemType) {
7130
            case 'dir':
7131
            case 'asset':
7132
            case 'sco':
7133
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
7134
                    $return .= $this->displayItemMenu($lpItem);
7135
                    $return .= $this->display_item_form(
7136
                        $lpItem,
7137
                        'edit'
7138
                    );
7139
                } else {
7140
                    $return .= $this->display_item_form(
7141
                        $lpItem,
7142
                        'edit_item'
7143
                    );
7144
                }
7145
                break;
7146
            case TOOL_LP_FINAL_ITEM:
7147
            case TOOL_DOCUMENT:
7148
            case TOOL_READOUT_TEXT:
7149
                $return .= $this->displayItemMenu($lpItem);
7150
                $return .= $this->displayDocumentForm('edit', $lpItem);
7151
                break;
7152
            case TOOL_LINK:
7153
                $link = null;
7154
                if (!empty($path)) {
7155
                    $repo = Container::getLinkRepository();
7156
                    $link = $repo->find($path);
7157
                }
7158
                $return .= $this->displayItemMenu($lpItem);
7159
                $return .= $this->display_link_form('edit', $lpItem, $link);
7160
7161
                break;
7162
            case TOOL_QUIZ:
7163
                if (!empty($path)) {
7164
                    $repo = Container::getQuizRepository();
7165
                    $resource = $repo->find($path);
7166
                }
7167
                $return .= $this->displayItemMenu($lpItem);
7168
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7169
                break;
7170
            /*case TOOL_HOTPOTATOES:
7171
                $return .= $this->displayItemMenu($lpItem);
7172
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7173
                break;*/
7174
            case TOOL_STUDENTPUBLICATION:
7175
                if (!empty($path)) {
7176
                    $repo = Container::getStudentPublicationRepository();
7177
                    $resource = $repo->find($path);
7178
                }
7179
                $return .= $this->displayItemMenu($lpItem);
7180
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7181
                break;
7182
            case TOOL_FORUM:
7183
                if (!empty($path)) {
7184
                    $repo = Container::getForumRepository();
7185
                    $resource = $repo->find($path);
7186
                }
7187
                $return .= $this->displayItemMenu($lpItem);
7188
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7189
                break;
7190
            case TOOL_THREAD:
7191
                if (!empty($path)) {
7192
                    $repo = Container::getForumPostRepository();
7193
                    $resource = $repo->find($path);
7194
                }
7195
                $return .= $this->displayItemMenu($lpItem);
7196
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7197
                break;
7198
        }
7199
7200
        return $return;
7201
    }
7202
7203
    /**
7204
     * Function that displays a list with al the resources that
7205
     * could be added to the learning path.
7206
     *
7207
     * @throws Exception
7208
     * @throws HTML_QuickForm_Error
7209
     *
7210
     * @return bool
7211
     */
7212
    public function displayResources()
7213
    {
7214
        // Get all the docs.
7215
        $documents = $this->get_documents(true);
7216
7217
        // Get all the exercises.
7218
        $exercises = $this->get_exercises();
7219
7220
        // Get all the links.
7221
        $links = $this->get_links();
7222
7223
        // Get all the student publications.
7224
        $works = $this->get_student_publications();
7225
7226
        // Get all the forums.
7227
        $forums = $this->get_forums();
7228
7229
        // Get the final item form (see BT#11048) .
7230
        $finish = $this->getFinalItemForm();
7231
7232
        $headers = [
7233
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7234
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7235
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7236
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7237
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7238
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7239
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7240
        ];
7241
7242
        echo Display::return_message(
7243
            get_lang('Click on the [Learner view] button to see your learning path'),
7244
            'normal'
7245
        );
7246
        $section = $this->displayNewSectionForm();
7247
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7248
7249
        echo Display::tabs(
7250
            $headers,
7251
            [
7252
                $documents,
7253
                $exercises,
7254
                $links,
7255
                $works,
7256
                $forums,
7257
                $section,
7258
                $finish,
7259
            ],
7260
            'resource_tab',
7261
            [],
7262
            [],
7263
            $selected
7264
        );
7265
7266
        return true;
7267
    }
7268
7269
    /**
7270
     * Returns the extension of a document.
7271
     *
7272
     * @param string $filename
7273
     *
7274
     * @return string Extension (part after the last dot)
7275
     */
7276
    public function get_extension($filename)
7277
    {
7278
        $explode = explode('.', $filename);
7279
7280
        return $explode[count($explode) - 1];
7281
    }
7282
7283
    /**
7284
     * @return string
7285
     */
7286
    public function getCurrentBuildingModeURL()
7287
    {
7288
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7289
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7290
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7291
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7292
7293
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7294
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7295
7296
        return $currentUrl;
7297
    }
7298
7299
    /**
7300
     * Displays a document by id.
7301
     *
7302
     * @param CDocument $document
7303
     * @param bool      $show_title
7304
     * @param bool      $iframe
7305
     * @param bool      $edit_link
7306
     *
7307
     * @return string
7308
     */
7309
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7310
    {
7311
        $return = '';
7312
        if (!$document) {
7313
            return '';
7314
        }
7315
7316
        $repo = Container::getDocumentRepository();
7317
7318
        // TODO: Add a path filter.
7319
        if ($iframe) {
7320
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7321
            $url = $repo->getResourceFileUrl($document);
7322
7323
            $return .= '<iframe
7324
                id="learnpath_preview_frame"
7325
                frameborder="0"
7326
                height="400"
7327
                width="100%"
7328
                scrolling="auto"
7329
                src="'.$url.'"></iframe>';
7330
        } else {
7331
            $return = $repo->getResourceFileContent($document);
7332
        }
7333
7334
        return $return;
7335
    }
7336
7337
    /**
7338
     * Return HTML form to add/edit a link item.
7339
     *
7340
     * @param string  $action (add/edit)
7341
     * @param CLpItem $lpItem
7342
     * @param CLink   $link
7343
     *
7344
     * @throws Exception
7345
     * @throws HTML_QuickForm_Error
7346
     *
7347
     * @return string HTML form
7348
     */
7349
    public function display_link_form($action = 'add', $lpItem, $link)
7350
    {
7351
        $item_url = '';
7352
        if ($link) {
7353
            $item_url = stripslashes($link->getUrl());
7354
        }
7355
        $form = new FormValidator(
7356
            'edit_link',
7357
            'POST',
7358
            $this->getCurrentBuildingModeURL()
7359
        );
7360
7361
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7362
7363
        $urlAttributes = ['class' => 'learnpath_item_form'];
7364
        $urlAttributes['disabled'] = 'disabled';
7365
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7366
        $form->setDefault('url', $item_url);
7367
7368
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7369
7370
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7371
    }
7372
7373
    /**
7374
     * Return HTML form to add/edit a quiz.
7375
     *
7376
     * @param string  $action   Action (add/edit)
7377
     * @param CLpItem $lpItem   Item ID if already exists
7378
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7379
     *
7380
     * @throws Exception
7381
     *
7382
     * @return string HTML form
7383
     */
7384
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7385
    {
7386
        $form = new FormValidator(
7387
            'quiz_form',
7388
            'POST',
7389
            $this->getCurrentBuildingModeURL()
7390
        );
7391
7392
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7393
7394
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7395
7396
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7397
    }
7398
7399
    /**
7400
     * Return the form to display the forum edit/add option.
7401
     *
7402
     * @param CLpItem $lpItem
7403
     *
7404
     * @throws Exception
7405
     *
7406
     * @return string HTML form
7407
     */
7408
    public function display_forum_form($action = 'add', $lpItem, $resource)
7409
    {
7410
        $form = new FormValidator(
7411
            'forum_form',
7412
            'POST',
7413
            $this->getCurrentBuildingModeURL()
7414
        );
7415
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7416
7417
        if ('add' === $action) {
7418
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7419
        } else {
7420
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7421
        }
7422
7423
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7424
    }
7425
7426
    /**
7427
     * Return HTML form to add/edit forum threads.
7428
     *
7429
     * @param string  $action
7430
     * @param CLpItem $lpItem
7431
     * @param string  $resource
7432
     *
7433
     * @throws Exception
7434
     *
7435
     * @return string HTML form
7436
     */
7437
    public function display_thread_form($action = 'add', $lpItem, $resource)
7438
    {
7439
        $form = new FormValidator(
7440
            'thread_form',
7441
            'POST',
7442
            $this->getCurrentBuildingModeURL()
7443
        );
7444
7445
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7446
7447
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7448
7449
        return $form->returnForm();
7450
    }
7451
7452
    /**
7453
     * Return the HTML form to display an item (generally a dir item).
7454
     *
7455
     * @param CLpItem $lpItem
7456
     * @param string  $action
7457
     *
7458
     * @throws Exception
7459
     * @throws HTML_QuickForm_Error
7460
     *
7461
     * @return string HTML form
7462
     */
7463
    public function display_item_form(
7464
        $lpItem,
7465
        $action = 'add_item'
7466
    ) {
7467
        $item_type = $lpItem->getItemType();
7468
7469
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7470
7471
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7472
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7473
7474
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7475
7476
        return $form->returnForm();
7477
    }
7478
7479
    /**
7480
     * Return HTML form to add/edit a student publication (work).
7481
     *
7482
     * @param string              $action
7483
     * @param CStudentPublication $resource
7484
     *
7485
     * @throws Exception
7486
     *
7487
     * @return string HTML form
7488
     */
7489
    public function display_student_publication_form(
7490
        $action = 'add',
7491
        CLpItem $lpItem,
7492
        $resource
7493
    ) {
7494
        $form = new FormValidator('frm_student_publication', 'post', '#');
7495
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7496
7497
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7498
7499
        $return = '<div class="sectioncomment">';
7500
        $return .= $form->returnForm();
7501
        $return .= '</div>';
7502
7503
        return $return;
7504
    }
7505
7506
    public function displayNewSectionForm()
7507
    {
7508
        $action = 'add_item';
7509
        $item_type = 'dir';
7510
7511
        $lpItem = new CLpItem();
7512
        $lpItem->setItemType('dir');
7513
7514
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7515
7516
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7517
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7518
7519
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7520
        $form->addElement('hidden', 'type', 'dir');
7521
7522
        return $form->returnForm();
7523
    }
7524
7525
    /**
7526
     * Returns the form to update or create a document.
7527
     *
7528
     * @param string  $action (add/edit)
7529
     * @param CLpItem $lpItem
7530
     *
7531
     * @throws HTML_QuickForm_Error
7532
     * @throws Exception
7533
     *
7534
     * @return string HTML form
7535
     */
7536
    public function displayDocumentForm($action = 'add', $lpItem = null)
7537
    {
7538
        if (empty($lpItem)) {
7539
            return '';
7540
        }
7541
7542
        $courseInfo = api_get_course_info();
7543
7544
        $form = new FormValidator(
7545
            'form',
7546
            'POST',
7547
            $this->getCurrentBuildingModeURL(),
7548
            '',
7549
            ['enctype' => 'multipart/form-data']
7550
        );
7551
7552
        $data = $this->generate_lp_folder($courseInfo);
7553
7554
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7555
7556
        switch ($action) {
7557
            case 'add':
7558
                $folders = DocumentManager::get_all_document_folders(
7559
                    $courseInfo,
7560
                    0,
7561
                    true
7562
                );
7563
                DocumentManager::build_directory_selector(
7564
                    $folders,
7565
                    '',
7566
                    [],
7567
                    true,
7568
                    $form,
7569
                    'directory_parent_id'
7570
                );
7571
7572
                if (isset($data['id'])) {
7573
                    $defaults['directory_parent_id'] = $data['id'];
7574
                }
7575
7576
                break;
7577
        }
7578
7579
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7580
7581
        return $form->returnForm();
7582
    }
7583
7584
    /**
7585
     * @param array  $courseInfo
7586
     * @param string $content
7587
     * @param string $title
7588
     * @param int    $parentId
7589
     *
7590
     * @throws \Doctrine\ORM\ORMException
7591
     * @throws \Doctrine\ORM\OptimisticLockException
7592
     * @throws \Doctrine\ORM\TransactionRequiredException
7593
     *
7594
     * @return int
7595
     */
7596
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7597
    {
7598
        $creatorId = api_get_user_id();
7599
        $sessionId = api_get_session_id();
7600
7601
        // Generates folder
7602
        $result = $this->generate_lp_folder($courseInfo);
7603
        $dir = $result['dir'];
7604
7605
        if (empty($parentId) || '/' == $parentId) {
7606
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7607
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7608
7609
            if ('/' === $parentId) {
7610
                $dir = '/';
7611
            }
7612
7613
            // Please, do not modify this dirname formatting.
7614
            if (strstr($dir, '..')) {
7615
                $dir = '/';
7616
            }
7617
7618
            if (!empty($dir[0]) && '.' == $dir[0]) {
7619
                $dir = substr($dir, 1);
7620
            }
7621
            if (!empty($dir[0]) && '/' != $dir[0]) {
7622
                $dir = '/'.$dir;
7623
            }
7624
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7625
                $dir .= '/';
7626
            }
7627
        } else {
7628
            $parentInfo = DocumentManager::get_document_data_by_id(
7629
                $parentId,
7630
                $courseInfo['code']
7631
            );
7632
            if (!empty($parentInfo)) {
7633
                $dir = $parentInfo['path'].'/';
7634
            }
7635
        }
7636
7637
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7638
7639
        if (!is_dir($filepath)) {
7640
            $dir = '/';
7641
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7642
        }
7643
7644
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7645
7646
        if (!empty($title)) {
7647
            $title = api_replace_dangerous_char(stripslashes($title));
7648
        } else {
7649
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7650
        }
7651
7652
        $title = disable_dangerous_file($title);
7653
        $filename = $title;
7654
        $content = !empty($content) ? $content : $_POST['content_lp'];
7655
        $tmpFileName = $filename;
7656
7657
        $i = 0;
7658
        while (file_exists($filepath.$tmpFileName.'.html')) {
7659
            $tmpFileName = $filename.'_'.++$i;
7660
        }
7661
7662
        $filename = $tmpFileName.'.html';
7663
        $content = stripslashes($content);
7664
7665
        if (file_exists($filepath.$filename)) {
7666
            return 0;
7667
        }
7668
7669
        $putContent = file_put_contents($filepath.$filename, $content);
7670
7671
        if (false === $putContent) {
7672
            return 0;
7673
        }
7674
7675
        $fileSize = filesize($filepath.$filename);
7676
        $saveFilePath = $dir.$filename;
7677
7678
        $document = DocumentManager::addDocument(
7679
            $courseInfo,
7680
            $saveFilePath,
7681
            'file',
7682
            $fileSize,
7683
            $tmpFileName,
7684
            '',
7685
            0, //readonly
7686
            true,
7687
            null,
7688
            $sessionId,
7689
            $creatorId
7690
        );
7691
7692
        $documentId = $document->getId();
7693
7694
        if (!$document) {
7695
            return 0;
7696
        }
7697
7698
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7699
        $newTitle = $originalTitle;
7700
7701
        if ($newComment || $newTitle) {
7702
            $em = Database::getManager();
7703
7704
            if ($newComment) {
7705
                $document->setComment($newComment);
7706
            }
7707
7708
            if ($newTitle) {
7709
                $document->setTitle($newTitle);
7710
            }
7711
7712
            $em->persist($document);
7713
            $em->flush();
7714
        }
7715
7716
        return $documentId;
7717
    }
7718
7719
    /**
7720
     * Displays the menu for manipulating a step.
7721
     *
7722
     * @return string
7723
     */
7724
    public function displayItemMenu(CLpItem $lpItem)
7725
    {
7726
        $item_id = $lpItem->getIid();
7727
        $audio = $lpItem->getAudio();
7728
        $itemType = $lpItem->getItemType();
7729
        $path = $lpItem->getPath();
7730
7731
        $return = '<div class="actions">';
7732
        $audio_player = null;
7733
        // We display an audio player if needed.
7734
        if (!empty($audio)) {
7735
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7736
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7737
                .'<audio src="'.$webAudioPath.'" controls>'
7738
                .'</div><br>';*/
7739
        }
7740
7741
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7742
7743
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7744
            $return .= Display::url(
7745
                Display::return_icon(
7746
                    'edit.png',
7747
                    get_lang('Edit'),
7748
                    [],
7749
                    ICON_SIZE_SMALL
7750
                ),
7751
                $url.'&action=edit_item&path_item='.$path
7752
            );
7753
7754
            /*$return .= Display::url(
7755
                Display::return_icon(
7756
                    'move.png',
7757
                    get_lang('Move'),
7758
                    [],
7759
                    ICON_SIZE_SMALL
7760
                ),
7761
                $url.'&action=move_item'
7762
            );*/
7763
        }
7764
7765
        // Commented for now as prerequisites cannot be added to chapters.
7766
        if ('dir' !== $itemType) {
7767
            $return .= Display::url(
7768
                Display::return_icon(
7769
                    'accept.png',
7770
                    get_lang('Prerequisites'),
7771
                    [],
7772
                    ICON_SIZE_SMALL
7773
                ),
7774
                $url.'&action=edit_item_prereq'
7775
            );
7776
        }
7777
        $return .= Display::url(
7778
            Display::return_icon(
7779
                'delete.png',
7780
                get_lang('Delete'),
7781
                [],
7782
                ICON_SIZE_SMALL
7783
            ),
7784
            $url.'&action=delete_item'
7785
        );
7786
7787
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7788
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7789
            if (empty($documentData)) {
7790
                // Try with iid
7791
                $table = Database::get_course_table(TABLE_DOCUMENT);
7792
                $sql = "SELECT path FROM $table
7793
                        WHERE
7794
                              c_id = ".api_get_course_int_id()." AND
7795
                              iid = ".$path." AND
7796
                              path NOT LIKE '%_DELETED_%'";
7797
                $result = Database::query($sql);
7798
                $documentData = Database::fetch_array($result);
7799
                if ($documentData) {
7800
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7801
                }
7802
            }
7803
            if (isset($documentData['absolute_path_from_document'])) {
7804
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7805
            }
7806
        }*/
7807
7808
        $return .= '</div>';
7809
7810
        if (!empty($audio_player)) {
7811
            $return .= $audio_player;
7812
        }
7813
7814
        return $return;
7815
    }
7816
7817
    /**
7818
     * Creates the javascript needed for filling up the checkboxes without page reload.
7819
     *
7820
     * @return string
7821
     */
7822
    public function get_js_dropdown_array()
7823
    {
7824
        $course_id = api_get_course_int_id();
7825
        $return = 'var child_name = new Array();'."\n";
7826
        $return .= 'var child_value = new Array();'."\n\n";
7827
        $return .= 'child_name[0] = new Array();'."\n";
7828
        $return .= 'child_value[0] = new Array();'."\n\n";
7829
7830
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7831
        $sql = "SELECT * FROM ".$tbl_lp_item."
7832
                WHERE
7833
                    c_id = $course_id AND
7834
                    lp_id = ".$this->lp_id." AND
7835
                    parent_item_id = 0
7836
                ORDER BY display_order ASC";
7837
        Database::query($sql);
7838
        $i = 0;
7839
7840
        $list = $this->getItemsForForm(true);
7841
7842
        foreach ($list as $row_zero) {
7843
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7844
                if (TOOL_QUIZ == $row_zero['item_type']) {
7845
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7846
                }
7847
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7848
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7849
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7850
            }
7851
        }
7852
7853
        $return .= "\n";
7854
        $sql = "SELECT * FROM $tbl_lp_item
7855
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7856
        $res = Database::query($sql);
7857
        while ($row = Database::fetch_array($res)) {
7858
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7859
                           WHERE
7860
                                c_id = ".$course_id." AND
7861
                                parent_item_id = ".$row['iid']."
7862
                           ORDER BY display_order ASC";
7863
            $res_parent = Database::query($sql_parent);
7864
            $i = 0;
7865
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7866
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7867
7868
            while ($row_parent = Database::fetch_array($res_parent)) {
7869
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7870
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7871
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7872
            }
7873
            $return .= "\n";
7874
        }
7875
7876
        $return .= "
7877
            function load_cbo(id) {
7878
                if (!id) {
7879
                    return false;
7880
                }
7881
7882
                var cbo = document.getElementById('previous');
7883
                for(var i = cbo.length - 1; i > 0; i--) {
7884
                    cbo.options[i] = null;
7885
                }
7886
7887
                var k=0;
7888
                for(var i = 1; i <= child_name[id].length; i++){
7889
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7890
                    option.style.paddingLeft = '40px';
7891
                    cbo.options[i] = option;
7892
                    k = i;
7893
                }
7894
7895
                cbo.options[k].selected = true;
7896
                //$('#previous').selectpicker('refresh');
7897
            }";
7898
7899
        return $return;
7900
    }
7901
7902
    /**
7903
     * Display the form to allow moving an item.
7904
     *
7905
     * @param CLpItem $lpItem
7906
     *
7907
     * @throws Exception
7908
     * @throws HTML_QuickForm_Error
7909
     *
7910
     * @return string HTML form
7911
     */
7912
    public function display_move_item($lpItem)
7913
    {
7914
        $return = '';
7915
        $path = $lpItem->getPath();
7916
7917
        if ($lpItem) {
7918
            $itemType = $lpItem->getItemType();
7919
            switch ($itemType) {
7920
                case 'dir':
7921
                case 'asset':
7922
                    $return .= $this->displayItemMenu($lpItem);
7923
                    $return .= $this->display_item_form(
7924
                        $lpItem,
7925
                        get_lang('Move the current section'),
7926
                        'move',
7927
                        $row
7928
                    );
7929
                    break;
7930
                case TOOL_DOCUMENT:
7931
                    $return .= $this->displayItemMenu($lpItem);
7932
                    $return .= $this->displayDocumentForm('move', $lpItem);
7933
                    break;
7934
                case TOOL_LINK:
7935
                    $link = null;
7936
                    if (!empty($path)) {
7937
                        $repo = Container::getLinkRepository();
7938
                        $link = $repo->find($path);
7939
                    }
7940
                    $return .= $this->displayItemMenu($lpItem);
7941
                    $return .= $this->display_link_form('move', $lpItem, $link);
7942
                    break;
7943
                case TOOL_HOTPOTATOES:
7944
                    $return .= $this->displayItemMenu($lpItem);
7945
                    $return .= $this->display_link_form('move', $lpItem, $row);
7946
                    break;
7947
                case TOOL_QUIZ:
7948
                    $return .= $this->displayItemMenu($lpItem);
7949
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7950
                    break;
7951
                case TOOL_STUDENTPUBLICATION:
7952
                    $return .= $this->displayItemMenu($lpItem);
7953
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7954
                    break;
7955
                case TOOL_FORUM:
7956
                    $return .= $this->displayItemMenu($lpItem);
7957
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7958
                    break;
7959
                case TOOL_THREAD:
7960
                    $return .= $this->displayItemMenu($lpItem);
7961
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7962
                    break;
7963
            }
7964
        }
7965
7966
        return $return;
7967
    }
7968
7969
    /**
7970
     * Return HTML form to allow prerequisites selection.
7971
     *
7972
     * @todo use FormValidator
7973
     *
7974
     * @return string HTML form
7975
     */
7976
    public function display_item_prerequisites_form(CLpItem $lpItem)
7977
    {
7978
        $course_id = api_get_course_int_id();
7979
        $prerequisiteId = $lpItem->getPrerequisite();
7980
        $itemId = $lpItem->getIid();
7981
7982
        $return = '<legend>';
7983
        $return .= get_lang('Add/edit prerequisites');
7984
        $return .= '</legend>';
7985
        $return .= '<form method="POST">';
7986
        $return .= '<div class="table-responsive">';
7987
        $return .= '<table class="table table-hover">';
7988
        $return .= '<thead>';
7989
        $return .= '<tr>';
7990
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7991
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7992
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7993
        $return .= '</tr>';
7994
        $return .= '</thead>';
7995
7996
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7997
        $return .= '<tbody>';
7998
        $return .= '<tr>';
7999
        $return .= '<td colspan="3">';
8000
        $return .= '<div class="radio learnpath"><label for="idnone">';
8001
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
8002
        $return .= get_lang('none').'</label>';
8003
        $return .= '</div>';
8004
        $return .= '</tr>';
8005
8006
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8007
        $sql = "SELECT * FROM $tbl_lp_item
8008
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8009
        $result = Database::query($sql);
8010
8011
        $selectedMinScore = [];
8012
        $selectedMaxScore = [];
8013
        $masteryScore = [];
8014
        while ($row = Database::fetch_array($result)) {
8015
            if ($row['iid'] == $itemId) {
8016
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
8017
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
8018
            }
8019
            $masteryScore[$row['iid']] = $row['mastery_score'];
8020
        }
8021
8022
        $arrLP = $this->getItemsForForm();
8023
        $this->tree_array($arrLP);
8024
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8025
        unset($this->arrMenu);
8026
8027
        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...
8028
            $item = $arrLP[$i];
8029
8030
            if ($item['id'] == $itemId) {
8031
                break;
8032
            }
8033
8034
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
8035
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
8036
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
8037
8038
            $return .= '<tr>';
8039
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
8040
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
8041
            $return .= '<label for="id'.$item['id'].'">';
8042
8043
            $checked = '';
8044
            if (null !== $prerequisiteId) {
8045
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
8046
            }
8047
8048
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
8049
8050
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
8051
8052
            $icon_name = str_replace(' ', '', $item['item_type']);
8053
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
8054
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
8055
            } else {
8056
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
8057
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
8058
                } else {
8059
                    $return .= Display::return_icon('folder_document.png');
8060
                }
8061
            }
8062
8063
            $return .= $item['title'].'</label>';
8064
            $return .= '</div>';
8065
            $return .= '</td>';
8066
8067
            if (TOOL_QUIZ == $item['item_type']) {
8068
                // lets update max_score Tests information depending of the Tests Advanced properties
8069
                $lpItemObj = new LpItem($course_id, $item['id']);
8070
                $exercise = new Exercise($course_id);
8071
                $exercise->read($lpItemObj->path);
8072
                $lpItemObj->max_score = $exercise->get_max_score();
8073
                $lpItemObj->update();
8074
                $item['max_score'] = $lpItemObj->max_score;
8075
8076
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
8077
                    // Backwards compatibility with 1.9.x use mastery_score as min value
8078
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
8079
                }
8080
8081
                $return .= '<td>';
8082
                $return .= '<input
8083
                    class="form-control"
8084
                    size="4" maxlength="3"
8085
                    name="min_'.$item['id'].'"
8086
                    type="number"
8087
                    min="0"
8088
                    step="any"
8089
                    max="'.$item['max_score'].'"
8090
                    value="'.$selectedMinScoreValue.'"
8091
                />';
8092
                $return .= '</td>';
8093
                $return .= '<td>';
8094
                $return .= '<input
8095
                    class="form-control"
8096
                    size="4"
8097
                    maxlength="3"
8098
                    name="max_'.$item['id'].'"
8099
                    type="number"
8100
                    min="0"
8101
                    step="any"
8102
                    max="'.$item['max_score'].'"
8103
                    value="'.$selectedMaxScoreValue.'"
8104
                />';
8105
                $return .= '</td>';
8106
            }
8107
8108
            if (TOOL_HOTPOTATOES == $item['item_type']) {
8109
                $return .= '<td>';
8110
                $return .= '<input
8111
                    size="4"
8112
                    maxlength="3"
8113
                    name="min_'.$item['id'].'"
8114
                    type="number"
8115
                    min="0"
8116
                    step="any"
8117
                    max="'.$item['max_score'].'"
8118
                    value="'.$selectedMinScoreValue.'"
8119
                />';
8120
                $return .= '</td>';
8121
                $return .= '<td>';
8122
                $return .= '<input
8123
                    size="4"
8124
                    maxlength="3"
8125
                    name="max_'.$item['id'].'"
8126
                    type="number"
8127
                    min="0"
8128
                    step="any"
8129
                    max="'.$item['max_score'].'"
8130
                    value="'.$selectedMaxScoreValue.'"
8131
                />';
8132
                $return .= '</td>';
8133
            }
8134
            $return .= '</tr>';
8135
        }
8136
        $return .= '<tr>';
8137
        $return .= '</tr>';
8138
        $return .= '</tbody>';
8139
        $return .= '</table>';
8140
        $return .= '</div>';
8141
        $return .= '<div class="form-group">';
8142
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
8143
            get_lang('Save prerequisites settings').'</button>';
8144
        $return .= '</form>';
8145
8146
        return $return;
8147
    }
8148
8149
    /**
8150
     * Return HTML list to allow prerequisites selection for lp.
8151
     *
8152
     * @return string HTML form
8153
     */
8154
    public function display_lp_prerequisites_list()
8155
    {
8156
        $course_id = api_get_course_int_id();
8157
        $lp_id = $this->lp_id;
8158
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8159
8160
        // get current prerequisite
8161
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8162
        $result = Database::query($sql);
8163
        $row = Database::fetch_array($result);
8164
        $prerequisiteId = $row['prerequisite'];
8165
        $session_id = api_get_session_id();
8166
        $session_condition = api_get_session_condition($session_id, true, true);
8167
        $sql = "SELECT * FROM $tbl_lp
8168
                WHERE c_id = $course_id $session_condition
8169
                ORDER BY display_order ";
8170
        $rs = Database::query($sql);
8171
        $return = '';
8172
        $return .= '<select name="prerequisites" class="form-control">';
8173
        $return .= '<option value="0">'.get_lang('none').'</option>';
8174
        if (Database::num_rows($rs) > 0) {
8175
            while ($row = Database::fetch_array($rs)) {
8176
                if ($row['iid'] == $lp_id) {
8177
                    continue;
8178
                }
8179
                $return .= '<option
8180
                    value="'.$row['iid'].'" '.(($row['iid'] == $prerequisiteId) ? ' selected ' : '').'>'.
8181
                    $row['name'].
8182
                    '</option>';
8183
            }
8184
        }
8185
        $return .= '</select>';
8186
8187
        return $return;
8188
    }
8189
8190
    /**
8191
     * Creates a list with all the documents in it.
8192
     *
8193
     * @param bool $showInvisibleFiles
8194
     *
8195
     * @throws Exception
8196
     * @throws HTML_QuickForm_Error
8197
     *
8198
     * @return string
8199
     */
8200
    public function get_documents($showInvisibleFiles = false)
8201
    {
8202
        $course_info = api_get_course_info();
8203
        $sessionId = api_get_session_id();
8204
        $documentTree = DocumentManager::get_document_preview(
8205
            $course_info,
8206
            $this->lp_id,
8207
            null,
8208
            $sessionId,
8209
            true,
8210
            null,
8211
            null,
8212
            $showInvisibleFiles,
8213
            true
8214
        );
8215
8216
        $headers = [
8217
            get_lang('Files'),
8218
            get_lang('CreateTheDocument'),
8219
            get_lang('CreateReadOutText'),
8220
            get_lang('Upload'),
8221
        ];
8222
8223
        $form = new FormValidator(
8224
            'form_upload',
8225
            'POST',
8226
            $this->getCurrentBuildingModeURL(),
8227
            '',
8228
            ['enctype' => 'multipart/form-data']
8229
        );
8230
8231
        $folders = DocumentManager::get_all_document_folders(
8232
            api_get_course_info(),
8233
            0,
8234
            true
8235
        );
8236
8237
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8238
8239
        DocumentManager::build_directory_selector(
8240
            $folders,
8241
            $lpPathInfo['id'],
8242
            [],
8243
            true,
8244
            $form,
8245
            'directory_parent_id'
8246
        );
8247
8248
        $group = [
8249
            $form->createElement(
8250
                'radio',
8251
                'if_exists',
8252
                get_lang('If file exists:'),
8253
                get_lang('Do nothing'),
8254
                'nothing'
8255
            ),
8256
            $form->createElement(
8257
                'radio',
8258
                'if_exists',
8259
                null,
8260
                get_lang('Overwrite the existing file'),
8261
                'overwrite'
8262
            ),
8263
            $form->createElement(
8264
                'radio',
8265
                'if_exists',
8266
                null,
8267
                get_lang('Rename the uploaded file if it exists'),
8268
                'rename'
8269
            ),
8270
        ];
8271
        $form->addGroup($group, null, get_lang('If file exists:'));
8272
8273
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8274
        $defaultFileExistsOption = 'rename';
8275
        if (!empty($fileExistsOption)) {
8276
            $defaultFileExistsOption = $fileExistsOption;
8277
        }
8278
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8279
8280
        // Check box options
8281
        $form->addElement(
8282
            'checkbox',
8283
            'unzip',
8284
            get_lang('Options'),
8285
            get_lang('Uncompress zip')
8286
        );
8287
8288
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8289
        $form->addMultipleUpload($url);
8290
8291
        $lpItem = new CLpItem();
8292
        $lpItem->setItemType(TOOL_DOCUMENT);
8293
        $new = $this->displayDocumentForm('add', $lpItem);
8294
8295
        /*$lpItem = new CLpItem();
8296
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8297
        $frmReadOutText = $this->displayDocumentForm('add');*/
8298
8299
        $headers = [
8300
            get_lang('Files'),
8301
            get_lang('Create a new document'),
8302
            get_lang('Create read-out text'),
8303
            get_lang('Upload'),
8304
        ];
8305
8306
        return Display::tabs(
8307
            $headers,
8308
            [$documentTree, $new, $form->returnForm()],
8309
            'subtab'
8310
        );
8311
    }
8312
8313
    /**
8314
     * Creates a list with all the exercises (quiz) in it.
8315
     *
8316
     * @return string
8317
     */
8318
    public function get_exercises()
8319
    {
8320
        $course_id = api_get_course_int_id();
8321
        $session_id = api_get_session_id();
8322
        $userInfo = api_get_user_info();
8323
8324
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8325
        $condition_session = api_get_session_condition($session_id, true, true);
8326
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8327
8328
        $activeCondition = ' active <> -1 ';
8329
        if ($setting) {
8330
            $activeCondition = ' active = 1 ';
8331
        }
8332
8333
        $categoryCondition = '';
8334
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8335
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8336
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8337
        }
8338
8339
        $keywordCondition = '';
8340
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8341
8342
        if (!empty($keyword)) {
8343
            $keyword = Database::escape_string($keyword);
8344
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8345
        }
8346
8347
        $sql_quiz = "SELECT * FROM $tbl_quiz
8348
                     WHERE
8349
                            c_id = $course_id AND
8350
                            $activeCondition
8351
                            $condition_session
8352
                            $categoryCondition
8353
                            $keywordCondition
8354
                     ORDER BY title ASC";
8355
        $res_quiz = Database::query($sql_quiz);
8356
8357
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8358
8359
        // Create a search-box
8360
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8361
        $form->addHidden('action', 'add_item');
8362
        $form->addHidden('type', 'step');
8363
        $form->addHidden('lp_id', $this->lp_id);
8364
        $form->addHidden('lp_build_selected', '2');
8365
8366
        $form->addCourseHiddenParams();
8367
        $form->addText(
8368
            'keyword',
8369
            get_lang('Search'),
8370
            false,
8371
            [
8372
                'aria-label' => get_lang('Search'),
8373
            ]
8374
        );
8375
8376
        if (api_get_configuration_value('allow_exercise_categories')) {
8377
            $manager = new ExerciseCategoryManager();
8378
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8379
            if (!empty($options)) {
8380
                $form->addSelect(
8381
                    'category_id',
8382
                    get_lang('Category'),
8383
                    $options,
8384
                    ['placeholder' => get_lang('Please select an option')]
8385
                );
8386
            }
8387
        }
8388
8389
        $form->addButtonSearch(get_lang('Search'));
8390
        $return = $form->returnForm();
8391
8392
        $return .= '<ul class="lp_resource">';
8393
        $return .= '<li class="lp_resource_element">';
8394
        $return .= Display::return_icon('new_exercice.png');
8395
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8396
            get_lang('New test').'</a>';
8397
        $return .= '</li>';
8398
8399
        $previewIcon = Display::return_icon(
8400
            'preview_view.png',
8401
            get_lang('Preview')
8402
        );
8403
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8404
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8405
8406
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8407
        $repo = Container::getQuizRepository();
8408
        $courseEntity = api_get_course_entity();
8409
        $sessionEntity = api_get_session_entity();
8410
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8411
            $exerciseId = $row_quiz['iid'];
8412
            /** @var CQuiz $exercise */
8413
            $exercise = $repo->find($exerciseId);
8414
            $title = strip_tags(api_html_entity_decode($row_quiz['title']));
8415
8416
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8417
            /*$visibility = api_get_item_visibility(
8418
                ['real_id' => $course_id],
8419
                TOOL_QUIZ,
8420
                $row_quiz['iid'],
8421
                $session_id
8422
            );*/
8423
8424
            $link = Display::url(
8425
                $previewIcon,
8426
                $exerciseUrl.'&exerciseId='.$exerciseId,
8427
                ['target' => '_blank']
8428
            );
8429
            $return .= '<li class="lp_resource_element" data_id="'.$exerciseId.'" data_type="quiz" title="'.$title.'" >';
8430
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8431
            $return .= $quizIcon;
8432
            $sessionStar = api_get_session_image(
8433
                $row_quiz['session_id'],
8434
                $userInfo['status']
8435
            );
8436
            $return .= Display::url(
8437
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8438
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
8439
                [
8440
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8441
                ]
8442
            );
8443
            $return .= '</li>';
8444
        }
8445
8446
        $return .= '</ul>';
8447
8448
        return $return;
8449
    }
8450
8451
    /**
8452
     * Creates a list with all the links in it.
8453
     *
8454
     * @return string
8455
     */
8456
    public function get_links()
8457
    {
8458
        $sessionId = api_get_session_id();
8459
        $repo = Container::getLinkRepository();
8460
8461
        $course = api_get_course_entity();
8462
        $session = api_get_session_entity($sessionId);
8463
        $qb = $repo->getResourcesByCourse($course, $session);
8464
        /** @var CLink[] $links */
8465
        $links = $qb->getQuery()->getResult();
8466
8467
        $selfUrl = api_get_self();
8468
        $courseIdReq = api_get_cidreq();
8469
        $userInfo = api_get_user_info();
8470
8471
        $moveEverywhereIcon = Display::return_icon(
8472
            'move_everywhere.png',
8473
            get_lang('Move'),
8474
            [],
8475
            ICON_SIZE_TINY
8476
        );
8477
8478
        /*$condition_session = api_get_session_condition(
8479
            $session_id,
8480
            true,
8481
            true,
8482
            'link.session_id'
8483
        );
8484
8485
        $sql = "SELECT
8486
                    link.id as link_id,
8487
                    link.title as link_title,
8488
                    link.session_id as link_session_id,
8489
                    link.category_id as category_id,
8490
                    link_category.category_title as category_title
8491
                FROM $tbl_link as link
8492
                LEFT JOIN $linkCategoryTable as link_category
8493
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8494
                WHERE link.c_id = $course_id $condition_session
8495
                ORDER BY link_category.category_title ASC, link.title ASC";
8496
        $result = Database::query($sql);*/
8497
        $categorizedLinks = [];
8498
        $categories = [];
8499
8500
        foreach ($links as $link) {
8501
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8502
8503
            if (empty($categoryId)) {
8504
                $categories[0] = get_lang('Uncategorized');
8505
            } else {
8506
                $category = $link->getCategory();
8507
                $categories[$categoryId] = $category->getCategoryTitle();
8508
            }
8509
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8510
        }
8511
8512
        $linksHtmlCode =
8513
            '<script>
8514
            function toggle_tool(tool, id) {
8515
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8516
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8517
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8518
                } else {
8519
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8520
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8521
                }
8522
            }
8523
        </script>
8524
8525
        <ul class="lp_resource">
8526
            <li class="lp_resource_element">
8527
                '.Display::return_icon('linksnew.gif').'
8528
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('Add a link').'">'.
8529
                get_lang('Add a link').'
8530
                </a>
8531
            </li>';
8532
8533
        foreach ($categorizedLinks as $categoryId => $links) {
8534
            $linkNodes = null;
8535
            /** @var CLink $link */
8536
            foreach ($links as $key => $link) {
8537
                $title = $link->getTitle();
8538
8539
                $linkUrl = Display::url(
8540
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8541
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8542
                    ['target' => '_blank']
8543
                );
8544
8545
                if ($link->isVisible($course, $session)) {
8546
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8547
                    $sessionStar = '';
8548
                    $linkNodes .=
8549
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8550
                        <a class="moved" href="#">'.
8551
                            $moveEverywhereIcon.
8552
                        '</a>
8553
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8554
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8555
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8556
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8557
                        '</a>
8558
                    </li>';
8559
                }
8560
            }
8561
            $linksHtmlCode .=
8562
                '<li>
8563
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8564
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8565
                    align="absbottom" />
8566
                </a>
8567
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8568
            </li>
8569
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8570
        }
8571
        $linksHtmlCode .= '</ul>';
8572
8573
        return $linksHtmlCode;
8574
    }
8575
8576
    /**
8577
     * Creates a list with all the student publications in it.
8578
     *
8579
     * @return string
8580
     */
8581
    public function get_student_publications()
8582
    {
8583
        $return = '<ul class="lp_resource">';
8584
        $return .= '<li class="lp_resource_element">';
8585
        /*
8586
        $return .= Display::return_icon('works_new.gif');
8587
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8588
            get_lang('Add the Assignments tool to the course').'</a>';
8589
        $return .= '</li>';*/
8590
8591
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8592
        $works = getWorkListTeacher(0, 100, null, null, null);
8593
        if (!empty($works)) {
8594
            foreach ($works as $work) {
8595
                $link = Display::url(
8596
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8597
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8598
                    ['target' => '_blank']
8599
                );
8600
8601
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8602
                $return .= '<a class="moved" href="#">';
8603
                $return .= Display::return_icon(
8604
                    'move_everywhere.png',
8605
                    get_lang('Move'),
8606
                    [],
8607
                    ICON_SIZE_TINY
8608
                );
8609
                $return .= '</a> ';
8610
8611
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8612
                $return .= ' <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id.'">'.
8613
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8614
                </a>';
8615
8616
                $return .= '</li>';
8617
            }
8618
        }
8619
8620
        $return .= '</ul>';
8621
8622
        return $return;
8623
    }
8624
8625
    /**
8626
     * Creates a list with all the forums in it.
8627
     *
8628
     * @return string
8629
     */
8630
    public function get_forums()
8631
    {
8632
        require_once '../forum/forumfunction.inc.php';
8633
8634
        $forumCategories = get_forum_categories();
8635
        $forumsInNoCategory = get_forums_in_category(0);
8636
        if (!empty($forumsInNoCategory)) {
8637
            $forumCategories = array_merge(
8638
                $forumCategories,
8639
                [
8640
                    [
8641
                        'cat_id' => 0,
8642
                        'session_id' => 0,
8643
                        'visibility' => 1,
8644
                        'cat_comment' => null,
8645
                    ],
8646
                ]
8647
            );
8648
        }
8649
8650
        $a_forums = [];
8651
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8652
        $sessionEntity = api_get_session_entity(api_get_session_id());
8653
8654
        foreach ($forumCategories as $forumCategory) {
8655
            // The forums in this category.
8656
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8657
            if (!empty($forumsInCategory)) {
8658
                foreach ($forumsInCategory as $forum) {
8659
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8660
                        $a_forums[] = $forum;
8661
                    }
8662
                }
8663
            }
8664
        }
8665
8666
        $return = '<ul class="lp_resource">';
8667
8668
        // First add link
8669
        $return .= '<li class="lp_resource_element">';
8670
        $return .= Display::return_icon('new_forum.png');
8671
        $return .= Display::url(
8672
            get_lang('Create a new forum'),
8673
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8674
                'action' => 'add',
8675
                'content' => 'forum',
8676
                'lp_id' => $this->lp_id,
8677
            ]),
8678
            ['title' => get_lang('Create a new forum')]
8679
        );
8680
        $return .= '</li>';
8681
8682
        $return .= '<script>
8683
            function toggle_forum(forum_id) {
8684
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8685
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8686
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8687
                } else {
8688
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8689
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8690
                }
8691
            }
8692
        </script>';
8693
8694
        foreach ($a_forums as $forum) {
8695
            $forumId = $forum->getIid();
8696
            $title = Security::remove_XSS($forum->getForumTitle());
8697
            $link = Display::url(
8698
                Display::return_icon('preview_view.png', get_lang('Preview')),
8699
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8700
                ['target' => '_blank']
8701
            );
8702
8703
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8704
            $return .= '<a class="moved" href="#">';
8705
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8706
            $return .= ' </a>';
8707
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8708
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8709
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8710
                        </a>
8711
                        <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
8712
                $title.' '.$link.'</a>';
8713
8714
            $return .= '</li>';
8715
8716
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8717
            $threads = get_threads($forumId);
8718
            if (is_array($threads)) {
8719
                foreach ($threads as $thread) {
8720
                    $threadId = $thread->getIid();
8721
                    $link = Display::url(
8722
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8723
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8724
                        ['target' => '_blank']
8725
                    );
8726
8727
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8728
                    $return .= '&nbsp;<a class="moved" href="#">';
8729
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8730
                    $return .= ' </a>';
8731
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8732
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'">'.
8733
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8734
                    $return .= '</li>';
8735
                }
8736
            }
8737
            $return .= '</div>';
8738
        }
8739
        $return .= '</ul>';
8740
8741
        return $return;
8742
    }
8743
8744
    /**
8745
     * // TODO: The output encoding should be equal to the system encoding.
8746
     *
8747
     * Exports the learning path as a SCORM package. This is the main function that
8748
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8749
     * whole thing and returns the zip.
8750
     *
8751
     * This method needs to be called in PHP5, as it will fail with non-adequate
8752
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8753
     * you need to call it on a learnpath object.
8754
     *
8755
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8756
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8757
     * path has been modified, it should use the generic method here below.
8758
     *
8759
     * @return string Returns the zip package string, or null if error
8760
     */
8761
    public function scormExport()
8762
    {
8763
        api_set_more_memory_and_time_limits();
8764
8765
        $_course = api_get_course_info();
8766
        $course_id = $_course['real_id'];
8767
        // Create the zip handler (this will remain available throughout the method).
8768
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8769
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8770
        $temp_dir_short = uniqid('scorm_export', true);
8771
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8772
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8773
        $zip_folder = new PclZip($temp_zip_file);
8774
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8775
        $root_path = $main_path = api_get_path(SYS_PATH);
8776
        $files_cleanup = [];
8777
8778
        // Place to temporarily stash the zip file.
8779
        // create the temp dir if it doesn't exist
8780
        // or do a cleanup before creating the zip file.
8781
        if (!is_dir($temp_zip_dir)) {
8782
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8783
        } else {
8784
            // Cleanup: Check the temp dir for old files and delete them.
8785
            $handle = opendir($temp_zip_dir);
8786
            while (false !== ($file = readdir($handle))) {
8787
                if ('.' != $file && '..' != $file) {
8788
                    unlink("$temp_zip_dir/$file");
8789
                }
8790
            }
8791
            closedir($handle);
8792
        }
8793
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8794
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8795
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8796
        ) {
8797
            // Remove the possible . at the end of the path.
8798
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8799
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8800
            mkdir(
8801
                $dest_path_to_scorm_folder,
8802
                api_get_permissions_for_new_directories(),
8803
                true
8804
            );
8805
            copyr(
8806
                $current_course_path.'/scorm/'.$this->path,
8807
                $dest_path_to_scorm_folder,
8808
                ['imsmanifest'],
8809
                $zip_files
8810
            );
8811
        }
8812
8813
        // Build a dummy imsmanifest structure.
8814
        // Do not add to the zip yet (we still need it).
8815
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8816
        // Aggregation Model official document, section "2.3 Content Packaging".
8817
        // We are going to build a UTF-8 encoded manifest.
8818
        // Later we will recode it to the desired (and supported) encoding.
8819
        $xmldoc = new DOMDocument('1.0');
8820
        $root = $xmldoc->createElement('manifest');
8821
        $root->setAttribute('identifier', 'SingleCourseManifest');
8822
        $root->setAttribute('version', '1.1');
8823
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8824
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8825
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8826
        $root->setAttribute(
8827
            'xsi:schemaLocation',
8828
            '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'
8829
        );
8830
        // Build mandatory sub-root container elements.
8831
        $metadata = $xmldoc->createElement('metadata');
8832
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8833
        $metadata->appendChild($md_schema);
8834
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8835
        $metadata->appendChild($md_schemaversion);
8836
        $root->appendChild($metadata);
8837
8838
        $organizations = $xmldoc->createElement('organizations');
8839
        $resources = $xmldoc->createElement('resources');
8840
8841
        // Build the only organization we will use in building our learnpaths.
8842
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8843
        $organization = $xmldoc->createElement('organization');
8844
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8845
        // To set the title of the SCORM entity (=organization), we take the name given
8846
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8847
        // learning path charset) as it is the encoding that defines how it is stored
8848
        // in the database. Then we convert it to HTML entities again as the "&" character
8849
        // alone is not authorized in XML (must be &amp;).
8850
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8851
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8852
        $organization->appendChild($org_title);
8853
        $folder_name = 'document';
8854
8855
        // Removes the learning_path/scorm_folder path when exporting see #4841
8856
        $path_to_remove = '';
8857
        $path_to_replace = '';
8858
        $result = $this->generate_lp_folder($_course);
8859
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8860
            $path_to_remove = 'document'.$result['dir'];
8861
            $path_to_replace = $folder_name.'/';
8862
        }
8863
8864
        // Fixes chamilo scorm exports
8865
        if ('chamilo_scorm_export' === $this->ref) {
8866
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8867
        }
8868
8869
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8870
        $link_updates = [];
8871
        $links_to_create = [];
8872
        foreach ($this->ordered_items as $index => $itemId) {
8873
            /** @var learnpathItem $item */
8874
            $item = $this->items[$itemId];
8875
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8876
                // Get included documents from this item.
8877
                if ('sco' === $item->type) {
8878
                    $inc_docs = $item->get_resources_from_source(
8879
                        null,
8880
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8881
                    );
8882
                } else {
8883
                    $inc_docs = $item->get_resources_from_source();
8884
                }
8885
8886
                // Give a child element <item> to the <organization> element.
8887
                $my_item_id = $item->get_id();
8888
                $my_item = $xmldoc->createElement('item');
8889
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8890
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8891
                $my_item->setAttribute('isvisible', 'true');
8892
                // Give a child element <title> to the <item> element.
8893
                $my_title = $xmldoc->createElement(
8894
                    'title',
8895
                    htmlspecialchars(
8896
                        api_utf8_encode($item->get_title()),
8897
                        ENT_QUOTES,
8898
                        'UTF-8'
8899
                    )
8900
                );
8901
                $my_item->appendChild($my_title);
8902
                // Give a child element <adlcp:prerequisites> to the <item> element.
8903
                $my_prereqs = $xmldoc->createElement(
8904
                    'adlcp:prerequisites',
8905
                    $this->get_scorm_prereq_string($my_item_id)
8906
                );
8907
                $my_prereqs->setAttribute('type', 'aicc_script');
8908
                $my_item->appendChild($my_prereqs);
8909
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8910
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8911
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8912
                //$xmldoc->createElement('adlcp:timelimitaction','');
8913
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8914
                //$xmldoc->createElement('adlcp:datafromlms','');
8915
                // Give a child element <adlcp:masteryscore> to the <item> element.
8916
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8917
                $my_item->appendChild($my_masteryscore);
8918
8919
                // Attach this item to the organization element or hits parent if there is one.
8920
                if (!empty($item->parent) && 0 != $item->parent) {
8921
                    $children = $organization->childNodes;
8922
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8923
                    if (is_object($possible_parent)) {
8924
                        $possible_parent->appendChild($my_item);
8925
                    } else {
8926
                        if ($this->debug > 0) {
8927
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8928
                        }
8929
                    }
8930
                } else {
8931
                    if ($this->debug > 0) {
8932
                        error_log('No parent');
8933
                    }
8934
                    $organization->appendChild($my_item);
8935
                }
8936
8937
                // Get the path of the file(s) from the course directory root.
8938
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8939
                $my_xml_file_path = $my_file_path;
8940
                if (!empty($path_to_remove)) {
8941
                    // From docs
8942
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8943
8944
                    // From quiz
8945
                    if ('chamilo_scorm_export' === $this->ref) {
8946
                        $path_to_remove = 'scorm/'.$this->path.'/';
8947
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8948
                    }
8949
                }
8950
8951
                $my_sub_dir = dirname($my_file_path);
8952
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8953
                $my_xml_sub_dir = $my_sub_dir;
8954
                // Give a <resource> child to the <resources> element
8955
                $my_resource = $xmldoc->createElement('resource');
8956
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8957
                $my_resource->setAttribute('type', 'webcontent');
8958
                $my_resource->setAttribute('href', $my_xml_file_path);
8959
                // adlcp:scormtype can be either 'sco' or 'asset'.
8960
                if ('sco' === $item->type) {
8961
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8962
                } else {
8963
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8964
                }
8965
                // xml:base is the base directory to find the files declared in this resource.
8966
                $my_resource->setAttribute('xml:base', '');
8967
                // Give a <file> child to the <resource> element.
8968
                $my_file = $xmldoc->createElement('file');
8969
                $my_file->setAttribute('href', $my_xml_file_path);
8970
                $my_resource->appendChild($my_file);
8971
8972
                // Dependency to other files - not yet supported.
8973
                $i = 1;
8974
                if ($inc_docs) {
8975
                    foreach ($inc_docs as $doc_info) {
8976
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8977
                            continue;
8978
                        }
8979
                        $my_dep = $xmldoc->createElement('resource');
8980
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8981
                        $my_dep->setAttribute('identifier', $res_id);
8982
                        $my_dep->setAttribute('type', 'webcontent');
8983
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8984
                        $my_dep_file = $xmldoc->createElement('file');
8985
                        // Check type of URL.
8986
                        if ('remote' == $doc_info[1]) {
8987
                            // Remote file. Save url as is.
8988
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8989
                            $my_dep->setAttribute('xml:base', '');
8990
                        } elseif ('local' === $doc_info[1]) {
8991
                            switch ($doc_info[2]) {
8992
                                case 'url':
8993
                                    // Local URL - save path as url for now, don't zip file.
8994
                                    $abs_path = api_get_path(SYS_PATH).
8995
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8996
                                    $current_dir = dirname($abs_path);
8997
                                    $current_dir = str_replace('\\', '/', $current_dir);
8998
                                    $file_path = realpath($abs_path);
8999
                                    $file_path = str_replace('\\', '/', $file_path);
9000
                                    $my_dep_file->setAttribute('href', $file_path);
9001
                                    $my_dep->setAttribute('xml:base', '');
9002
                                    if (false !== strstr($file_path, $main_path)) {
9003
                                        // The calculated real path is really inside Chamilo's root path.
9004
                                        // Reduce file path to what's under the DocumentRoot.
9005
                                        $replace = $file_path;
9006
                                        $file_path = substr($file_path, strlen($root_path) - 1);
9007
                                        $destinationFile = $file_path;
9008
9009
                                        if (false !== strstr($file_path, 'upload/users')) {
9010
                                            $pos = strpos($file_path, 'my_files/');
9011
                                            if (false !== $pos) {
9012
                                                $onlyDirectory = str_replace(
9013
                                                    'upload/users/',
9014
                                                    '',
9015
                                                    substr($file_path, $pos, strlen($file_path))
9016
                                                );
9017
                                            }
9018
                                            $replace = $onlyDirectory;
9019
                                            $destinationFile = $replace;
9020
                                        }
9021
                                        $zip_files_abs[] = $file_path;
9022
                                        $link_updates[$my_file_path][] = [
9023
                                            'orig' => $doc_info[0],
9024
                                            'dest' => $destinationFile,
9025
                                            'replace' => $replace,
9026
                                        ];
9027
                                        $my_dep_file->setAttribute('href', $file_path);
9028
                                        $my_dep->setAttribute('xml:base', '');
9029
                                    } elseif (empty($file_path)) {
9030
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9031
                                        $file_path = str_replace('//', '/', $file_path);
9032
                                        if (file_exists($file_path)) {
9033
                                            // We get the relative path.
9034
                                            $file_path = substr($file_path, strlen($current_dir));
9035
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
9036
                                            $link_updates[$my_file_path][] = [
9037
                                                'orig' => $doc_info[0],
9038
                                                'dest' => $file_path,
9039
                                            ];
9040
                                            $my_dep_file->setAttribute('href', $file_path);
9041
                                            $my_dep->setAttribute('xml:base', '');
9042
                                        }
9043
                                    }
9044
                                    break;
9045
                                case 'abs':
9046
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9047
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9048
                                    $my_dep->setAttribute('xml:base', '');
9049
9050
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
9051
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
9052
                                    $abs_img_path_without_subdir = $doc_info[0];
9053
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
9054
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
9055
                                    if (0 === $pos) {
9056
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
9057
                                    }
9058
9059
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
9060
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
9061
9062
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
9063
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
9064
                                    // Check if the current document is in that path.
9065
                                    if (false !== strstr($file_path, $cur_path)) {
9066
                                        $destinationFile = substr($file_path, strlen($cur_path));
9067
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
9068
9069
                                        $fileToTest = $cur_path.$my_file_path;
9070
                                        if (!empty($path_to_remove)) {
9071
                                            $fileToTest = str_replace(
9072
                                                $path_to_remove.'/',
9073
                                                $path_to_replace,
9074
                                                $cur_path.$my_file_path
9075
                                            );
9076
                                        }
9077
9078
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
9079
9080
                                        // Put the current document in the zip (this array is the array
9081
                                        // that will manage documents already in the course folder - relative).
9082
                                        $zip_files[] = $filePathNoCoursePart;
9083
                                        // Update the links to the current document in the
9084
                                        // containing document (make them relative).
9085
                                        $link_updates[$my_file_path][] = [
9086
                                            'orig' => $doc_info[0],
9087
                                            'dest' => $destinationFile,
9088
                                            'replace' => $relative_path,
9089
                                        ];
9090
9091
                                        $my_dep_file->setAttribute('href', $file_path);
9092
                                        $my_dep->setAttribute('xml:base', '');
9093
                                    } elseif (false !== strstr($file_path, $main_path)) {
9094
                                        // The calculated real path is really inside Chamilo's root path.
9095
                                        // Reduce file path to what's under the DocumentRoot.
9096
                                        $file_path = substr($file_path, strlen($root_path));
9097
                                        $zip_files_abs[] = $file_path;
9098
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9099
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9100
                                        $my_dep->setAttribute('xml:base', '');
9101
                                    } elseif (empty($file_path)) {
9102
                                        // Probably this is an image inside "/main" directory
9103
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
9104
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9105
9106
                                        if (file_exists($file_path)) {
9107
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
9108
                                                // We get the relative path.
9109
                                                $pos = strpos($file_path, 'main/default_course_document/');
9110
                                                if (false !== $pos) {
9111
                                                    $onlyDirectory = str_replace(
9112
                                                        'main/default_course_document/',
9113
                                                        '',
9114
                                                        substr($file_path, $pos, strlen($file_path))
9115
                                                    );
9116
                                                }
9117
9118
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
9119
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
9120
                                                $zip_files_abs[] = $fileAbs;
9121
                                                $link_updates[$my_file_path][] = [
9122
                                                    'orig' => $doc_info[0],
9123
                                                    'dest' => $destinationFile,
9124
                                                ];
9125
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9126
                                                $my_dep->setAttribute('xml:base', '');
9127
                                            }
9128
                                        }
9129
                                    }
9130
                                    break;
9131
                                case 'rel':
9132
                                    // Path relative to the current document.
9133
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
9134
                                    if ('..' === substr($doc_info[0], 0, 2)) {
9135
                                        // Relative path going up.
9136
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9137
                                        $current_dir = str_replace('\\', '/', $current_dir);
9138
                                        $file_path = realpath($current_dir.$doc_info[0]);
9139
                                        $file_path = str_replace('\\', '/', $file_path);
9140
                                        if (false !== strstr($file_path, $main_path)) {
9141
                                            // The calculated real path is really inside Chamilo's root path.
9142
                                            // Reduce file path to what's under the DocumentRoot.
9143
                                            $file_path = substr($file_path, strlen($root_path));
9144
                                            $zip_files_abs[] = $file_path;
9145
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9146
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9147
                                            $my_dep->setAttribute('xml:base', '');
9148
                                        }
9149
                                    } else {
9150
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9151
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
9152
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9153
                                    }
9154
                                    break;
9155
                                default:
9156
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9157
                                    $my_dep->setAttribute('xml:base', '');
9158
                                    break;
9159
                            }
9160
                        }
9161
                        $my_dep->appendChild($my_dep_file);
9162
                        $resources->appendChild($my_dep);
9163
                        $dependency = $xmldoc->createElement('dependency');
9164
                        $dependency->setAttribute('identifierref', $res_id);
9165
                        $my_resource->appendChild($dependency);
9166
                        $i++;
9167
                    }
9168
                }
9169
                $resources->appendChild($my_resource);
9170
                $zip_files[] = $my_file_path;
9171
            } else {
9172
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
9173
                switch ($item->type) {
9174
                    case TOOL_LINK:
9175
                        $my_item = $xmldoc->createElement('item');
9176
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9177
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9178
                        $my_item->setAttribute('isvisible', 'true');
9179
                        // Give a child element <title> to the <item> element.
9180
                        $my_title = $xmldoc->createElement(
9181
                            'title',
9182
                            htmlspecialchars(
9183
                                api_utf8_encode($item->get_title()),
9184
                                ENT_QUOTES,
9185
                                'UTF-8'
9186
                            )
9187
                        );
9188
                        $my_item->appendChild($my_title);
9189
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9190
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9191
                        $my_prereqs->setAttribute('type', 'aicc_script');
9192
                        $my_item->appendChild($my_prereqs);
9193
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9194
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9195
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9196
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9197
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9198
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9199
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9200
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9201
                        $my_item->appendChild($my_masteryscore);
9202
9203
                        // Attach this item to the organization element or its parent if there is one.
9204
                        if (!empty($item->parent) && 0 != $item->parent) {
9205
                            $children = $organization->childNodes;
9206
                            for ($i = 0; $i < $children->length; $i++) {
9207
                                $item_temp = $children->item($i);
9208
                                if ('item' == $item_temp->nodeName) {
9209
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9210
                                        $item_temp->appendChild($my_item);
9211
                                    }
9212
                                }
9213
                            }
9214
                        } else {
9215
                            $organization->appendChild($my_item);
9216
                        }
9217
9218
                        $my_file_path = 'link_'.$item->get_id().'.html';
9219
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9220
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9221
                        $rs = Database::query($sql);
9222
                        if ($link = Database::fetch_array($rs)) {
9223
                            $url = $link['url'];
9224
                            $title = stripslashes($link['title']);
9225
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9226
                            $my_xml_file_path = $my_file_path;
9227
                            $my_sub_dir = dirname($my_file_path);
9228
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9229
                            $my_xml_sub_dir = $my_sub_dir;
9230
                            // Give a <resource> child to the <resources> element.
9231
                            $my_resource = $xmldoc->createElement('resource');
9232
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9233
                            $my_resource->setAttribute('type', 'webcontent');
9234
                            $my_resource->setAttribute('href', $my_xml_file_path);
9235
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9236
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9237
                            // xml:base is the base directory to find the files declared in this resource.
9238
                            $my_resource->setAttribute('xml:base', '');
9239
                            // give a <file> child to the <resource> element.
9240
                            $my_file = $xmldoc->createElement('file');
9241
                            $my_file->setAttribute('href', $my_xml_file_path);
9242
                            $my_resource->appendChild($my_file);
9243
                            $resources->appendChild($my_resource);
9244
                        }
9245
                        break;
9246
                    case TOOL_QUIZ:
9247
                        $exe_id = $item->path;
9248
                        // Should be using ref when everything will be cleaned up in this regard.
9249
                        $exe = new Exercise();
9250
                        $exe->read($exe_id);
9251
                        $my_item = $xmldoc->createElement('item');
9252
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9253
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9254
                        $my_item->setAttribute('isvisible', 'true');
9255
                        // Give a child element <title> to the <item> element.
9256
                        $my_title = $xmldoc->createElement(
9257
                            'title',
9258
                            htmlspecialchars(
9259
                                api_utf8_encode($item->get_title()),
9260
                                ENT_QUOTES,
9261
                                'UTF-8'
9262
                            )
9263
                        );
9264
                        $my_item->appendChild($my_title);
9265
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9266
                        $my_item->appendChild($my_max_score);
9267
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9268
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9269
                        $my_prereqs->setAttribute('type', 'aicc_script');
9270
                        $my_item->appendChild($my_prereqs);
9271
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9272
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9273
                        $my_item->appendChild($my_masteryscore);
9274
9275
                        // Attach this item to the organization element or hits parent if there is one.
9276
                        if (!empty($item->parent) && 0 != $item->parent) {
9277
                            $children = $organization->childNodes;
9278
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9279
                            if ($possible_parent) {
9280
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9281
                                    $possible_parent->appendChild($my_item);
9282
                                }
9283
                            }
9284
                        } else {
9285
                            $organization->appendChild($my_item);
9286
                        }
9287
9288
                        // Get the path of the file(s) from the course directory root
9289
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9290
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9291
                        // Write the contents of the exported exercise into a (big) html file
9292
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9293
                        $scormExercise = new ScormExercise($exe, true);
9294
                        $contents = $scormExercise->export();
9295
9296
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9297
                        $res = file_put_contents($tmp_file_path, $contents);
9298
                        if (false === $res) {
9299
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9300
                        }
9301
                        $files_cleanup[] = $tmp_file_path;
9302
                        $my_xml_file_path = $my_file_path;
9303
                        $my_sub_dir = dirname($my_file_path);
9304
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9305
                        $my_xml_sub_dir = $my_sub_dir;
9306
                        // Give a <resource> child to the <resources> element.
9307
                        $my_resource = $xmldoc->createElement('resource');
9308
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9309
                        $my_resource->setAttribute('type', 'webcontent');
9310
                        $my_resource->setAttribute('href', $my_xml_file_path);
9311
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9312
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9313
                        // xml:base is the base directory to find the files declared in this resource.
9314
                        $my_resource->setAttribute('xml:base', '');
9315
                        // Give a <file> child to the <resource> element.
9316
                        $my_file = $xmldoc->createElement('file');
9317
                        $my_file->setAttribute('href', $my_xml_file_path);
9318
                        $my_resource->appendChild($my_file);
9319
9320
                        // Get included docs.
9321
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9322
9323
                        // Dependency to other files - not yet supported.
9324
                        $i = 1;
9325
                        foreach ($inc_docs as $doc_info) {
9326
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9327
                                continue;
9328
                            }
9329
                            $my_dep = $xmldoc->createElement('resource');
9330
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9331
                            $my_dep->setAttribute('identifier', $res_id);
9332
                            $my_dep->setAttribute('type', 'webcontent');
9333
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9334
                            $my_dep_file = $xmldoc->createElement('file');
9335
                            // Check type of URL.
9336
                            if ('remote' == $doc_info[1]) {
9337
                                // Remote file. Save url as is.
9338
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9339
                                $my_dep->setAttribute('xml:base', '');
9340
                            } elseif ('local' == $doc_info[1]) {
9341
                                switch ($doc_info[2]) {
9342
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9343
                                        // Save file but as local file (retrieve from URL).
9344
                                        $abs_path = api_get_path(SYS_PATH).
9345
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9346
                                        $current_dir = dirname($abs_path);
9347
                                        $current_dir = str_replace('\\', '/', $current_dir);
9348
                                        $file_path = realpath($abs_path);
9349
                                        $file_path = str_replace('\\', '/', $file_path);
9350
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9351
                                        $my_dep->setAttribute('xml:base', '');
9352
                                        if (false !== strstr($file_path, $main_path)) {
9353
                                            // The calculated real path is really inside the chamilo root path.
9354
                                            // Reduce file path to what's under the DocumentRoot.
9355
                                            $file_path = substr($file_path, strlen($root_path));
9356
                                            $zip_files_abs[] = $file_path;
9357
                                            $link_updates[$my_file_path][] = [
9358
                                                'orig' => $doc_info[0],
9359
                                                'dest' => 'document/'.$file_path,
9360
                                            ];
9361
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9362
                                            $my_dep->setAttribute('xml:base', '');
9363
                                        } elseif (empty($file_path)) {
9364
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9365
                                            $file_path = str_replace('//', '/', $file_path);
9366
                                            if (file_exists($file_path)) {
9367
                                                $file_path = substr($file_path, strlen($current_dir));
9368
                                                // We get the relative path.
9369
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9370
                                                $link_updates[$my_file_path][] = [
9371
                                                    'orig' => $doc_info[0],
9372
                                                    'dest' => 'document/'.$file_path,
9373
                                                ];
9374
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9375
                                                $my_dep->setAttribute('xml:base', '');
9376
                                            }
9377
                                        }
9378
                                        break;
9379
                                    case 'abs':
9380
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9381
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9382
                                        $current_dir = str_replace('\\', '/', $current_dir);
9383
                                        $file_path = realpath($doc_info[0]);
9384
                                        $file_path = str_replace('\\', '/', $file_path);
9385
                                        $my_dep_file->setAttribute('href', $file_path);
9386
                                        $my_dep->setAttribute('xml:base', '');
9387
9388
                                        if (false !== strstr($file_path, $main_path)) {
9389
                                            // The calculated real path is really inside the chamilo root path.
9390
                                            // Reduce file path to what's under the DocumentRoot.
9391
                                            $file_path = substr($file_path, strlen($root_path));
9392
                                            $zip_files_abs[] = $file_path;
9393
                                            $link_updates[$my_file_path][] = [
9394
                                                'orig' => $doc_info[0],
9395
                                                'dest' => $file_path,
9396
                                            ];
9397
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9398
                                            $my_dep->setAttribute('xml:base', '');
9399
                                        } elseif (empty($file_path)) {
9400
                                            $docSysPartPath = str_replace(
9401
                                                api_get_path(REL_COURSE_PATH),
9402
                                                '',
9403
                                                $doc_info[0]
9404
                                            );
9405
9406
                                            $docSysPartPathNoCourseCode = str_replace(
9407
                                                $_course['directory'].'/',
9408
                                                '',
9409
                                                $docSysPartPath
9410
                                            );
9411
9412
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9413
                                            if (file_exists($docSysPath)) {
9414
                                                $file_path = $docSysPartPathNoCourseCode;
9415
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9416
                                                $link_updates[$my_file_path][] = [
9417
                                                    'orig' => $doc_info[0],
9418
                                                    'dest' => $file_path,
9419
                                                ];
9420
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9421
                                                $my_dep->setAttribute('xml:base', '');
9422
                                            }
9423
                                        }
9424
                                        break;
9425
                                    case 'rel':
9426
                                        // Path relative to the current document. Save xml:base as current document's
9427
                                        // directory and save file in zip as subdir.file_path
9428
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9429
                                            // Relative path going up.
9430
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9431
                                            $current_dir = str_replace('\\', '/', $current_dir);
9432
                                            $file_path = realpath($current_dir.$doc_info[0]);
9433
                                            $file_path = str_replace('\\', '/', $file_path);
9434
                                            if (false !== strstr($file_path, $main_path)) {
9435
                                                // The calculated real path is really inside Chamilo's root path.
9436
                                                // Reduce file path to what's under the DocumentRoot.
9437
9438
                                                $file_path = substr($file_path, strlen($root_path));
9439
                                                $file_path_dest = $file_path;
9440
9441
                                                // File path is courses/CHAMILO/document/....
9442
                                                $info_file_path = explode('/', $file_path);
9443
                                                if ('courses' == $info_file_path[0]) {
9444
                                                    // Add character "/" in file path.
9445
                                                    $file_path_dest = 'document/'.$file_path;
9446
                                                }
9447
                                                $zip_files_abs[] = $file_path;
9448
9449
                                                $link_updates[$my_file_path][] = [
9450
                                                    'orig' => $doc_info[0],
9451
                                                    'dest' => $file_path_dest,
9452
                                                ];
9453
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9454
                                                $my_dep->setAttribute('xml:base', '');
9455
                                            }
9456
                                        } else {
9457
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9458
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9459
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9460
                                        }
9461
                                        break;
9462
                                    default:
9463
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9464
                                        $my_dep->setAttribute('xml:base', '');
9465
                                        break;
9466
                                }
9467
                            }
9468
                            $my_dep->appendChild($my_dep_file);
9469
                            $resources->appendChild($my_dep);
9470
                            $dependency = $xmldoc->createElement('dependency');
9471
                            $dependency->setAttribute('identifierref', $res_id);
9472
                            $my_resource->appendChild($dependency);
9473
                            $i++;
9474
                        }
9475
                        $resources->appendChild($my_resource);
9476
                        $zip_files[] = $my_file_path;
9477
                        break;
9478
                    default:
9479
                        // Get the path of the file(s) from the course directory root
9480
                        $my_file_path = 'non_exportable.html';
9481
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9482
                        $my_xml_file_path = $my_file_path;
9483
                        $my_sub_dir = dirname($my_file_path);
9484
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9485
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9486
                        $my_xml_sub_dir = $my_sub_dir;
9487
                        // Give a <resource> child to the <resources> element.
9488
                        $my_resource = $xmldoc->createElement('resource');
9489
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9490
                        $my_resource->setAttribute('type', 'webcontent');
9491
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9492
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9493
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9494
                        // xml:base is the base directory to find the files declared in this resource.
9495
                        $my_resource->setAttribute('xml:base', '');
9496
                        // Give a <file> child to the <resource> element.
9497
                        $my_file = $xmldoc->createElement('file');
9498
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9499
                        $my_resource->appendChild($my_file);
9500
                        $resources->appendChild($my_resource);
9501
                        break;
9502
                }
9503
            }
9504
        }
9505
        $organizations->appendChild($organization);
9506
        $root->appendChild($organizations);
9507
        $root->appendChild($resources);
9508
        $xmldoc->appendChild($root);
9509
9510
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9511
9512
        // then add the file to the zip, then destroy the file (this is done automatically).
9513
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9514
        foreach ($zip_files as $file_path) {
9515
            if (empty($file_path)) {
9516
                continue;
9517
            }
9518
9519
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9520
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9521
9522
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9523
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9524
            }
9525
9526
            $this->create_path($dest_file);
9527
            @copy($filePath, $dest_file);
9528
9529
            // Check if the file needs a link update.
9530
            if (in_array($file_path, array_keys($link_updates))) {
9531
                $string = file_get_contents($dest_file);
9532
                unlink($dest_file);
9533
                foreach ($link_updates[$file_path] as $old_new) {
9534
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9535
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9536
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9537
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9538
                    if ('flv' === substr($old_new['dest'], -3) &&
9539
                        'main/' === substr($old_new['dest'], 0, 5)
9540
                    ) {
9541
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9542
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9543
                        'video/' === substr($old_new['dest'], 0, 6)
9544
                    ) {
9545
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9546
                    }
9547
9548
                    // Fix to avoid problems with default_course_document
9549
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9550
                        $newDestination = $old_new['dest'];
9551
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9552
                            $newDestination = $old_new['replace'];
9553
                        }
9554
                    } else {
9555
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9556
                    }
9557
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9558
9559
                    // Add files inside the HTMLs
9560
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9561
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9562
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9563
                        copy(
9564
                            $sys_course_path.$new_path,
9565
                            $destinationFile
9566
                        );
9567
                    }
9568
                }
9569
                file_put_contents($dest_file, $string);
9570
            }
9571
9572
            if (file_exists($filePath) && $copyAll) {
9573
                $extension = $this->get_extension($filePath);
9574
                if (in_array($extension, ['html', 'html'])) {
9575
                    $containerOrigin = dirname($filePath);
9576
                    $containerDestination = dirname($dest_file);
9577
9578
                    $finder = new Finder();
9579
                    $finder->files()->in($containerOrigin)
9580
                        ->notName('*_DELETED_*')
9581
                        ->exclude('share_folder')
9582
                        ->exclude('chat_files')
9583
                        ->exclude('certificates')
9584
                    ;
9585
9586
                    if (is_dir($containerOrigin) &&
9587
                        is_dir($containerDestination)
9588
                    ) {
9589
                        $fs = new Filesystem();
9590
                        $fs->mirror(
9591
                            $containerOrigin,
9592
                            $containerDestination,
9593
                            $finder
9594
                        );
9595
                    }
9596
                }
9597
            }
9598
        }
9599
9600
        foreach ($zip_files_abs as $file_path) {
9601
            if (empty($file_path)) {
9602
                continue;
9603
            }
9604
9605
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9606
                continue;
9607
            }
9608
9609
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9610
            if (false !== strstr($file_path, 'upload/users')) {
9611
                $pos = strpos($file_path, 'my_files/');
9612
                if (false !== $pos) {
9613
                    $onlyDirectory = str_replace(
9614
                        'upload/users/',
9615
                        '',
9616
                        substr($file_path, $pos, strlen($file_path))
9617
                    );
9618
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9619
                }
9620
            }
9621
9622
            if (false !== strstr($file_path, 'default_course_document/')) {
9623
                $replace = str_replace('/main', '', $file_path);
9624
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9625
            }
9626
9627
            if (empty($dest_file)) {
9628
                continue;
9629
            }
9630
9631
            $this->create_path($dest_file);
9632
            copy($main_path.$file_path, $dest_file);
9633
            // Check if the file needs a link update.
9634
            if (in_array($file_path, array_keys($link_updates))) {
9635
                $string = file_get_contents($dest_file);
9636
                unlink($dest_file);
9637
                foreach ($link_updates[$file_path] as $old_new) {
9638
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9639
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9640
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9641
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9642
                    if ('flv' == substr($old_new['dest'], -3) &&
9643
                        'main/' == substr($old_new['dest'], 0, 5)
9644
                    ) {
9645
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9646
                    }
9647
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9648
                }
9649
                file_put_contents($dest_file, $string);
9650
            }
9651
        }
9652
9653
        if (is_array($links_to_create)) {
9654
            foreach ($links_to_create as $file => $link) {
9655
                $content = '<!DOCTYPE html><head>
9656
                            <meta charset="'.api_get_language_isocode().'" />
9657
                            <title>'.$link['title'].'</title>
9658
                            </head>
9659
                            <body dir="'.api_get_text_direction().'">
9660
                            <div style="text-align:center">
9661
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9662
                            </body>
9663
                            </html>';
9664
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9665
            }
9666
        }
9667
9668
        // Add non exportable message explanation.
9669
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9670
        $file_content = '<!DOCTYPE html><head>
9671
                        <meta charset="'.api_get_language_isocode().'" />
9672
                        <title>'.$lang_not_exportable.'</title>
9673
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9674
                        </head>
9675
                        <body dir="'.api_get_text_direction().'">';
9676
        $file_content .=
9677
            <<<EOD
9678
                    <style>
9679
            .error-message {
9680
                font-family: arial, verdana, helvetica, sans-serif;
9681
                border-width: 1px;
9682
                border-style: solid;
9683
                left: 50%;
9684
                margin: 10px auto;
9685
                min-height: 30px;
9686
                padding: 5px;
9687
                right: 50%;
9688
                width: 500px;
9689
                background-color: #FFD1D1;
9690
                border-color: #FF0000;
9691
                color: #000;
9692
            }
9693
        </style>
9694
    <body>
9695
        <div class="error-message">
9696
            $lang_not_exportable
9697
        </div>
9698
    </body>
9699
</html>
9700
EOD;
9701
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9702
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9703
        }
9704
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9705
9706
        // Add the extra files that go along with a SCORM package.
9707
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9708
9709
        $fs = new Filesystem();
9710
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9711
9712
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9713
        $manifest = @$xmldoc->saveXML();
9714
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9715
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9716
        $zip_folder->add(
9717
            $archivePath.'/'.$temp_dir_short,
9718
            PCLZIP_OPT_REMOVE_PATH,
9719
            $archivePath.'/'.$temp_dir_short.'/'
9720
        );
9721
9722
        // Clean possible temporary files.
9723
        foreach ($files_cleanup as $file) {
9724
            $res = unlink($file);
9725
            if (false === $res) {
9726
                error_log(
9727
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9728
                    0
9729
                );
9730
            }
9731
        }
9732
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9733
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9734
    }
9735
9736
    /**
9737
     * @param int $lp_id
9738
     *
9739
     * @return bool
9740
     */
9741
    public function scorm_export_to_pdf($lp_id)
9742
    {
9743
        $lp_id = (int) $lp_id;
9744
        $files_to_export = [];
9745
9746
        $sessionId = api_get_session_id();
9747
        $course_data = api_get_course_info($this->cc);
9748
9749
        if (!empty($course_data)) {
9750
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9751
            $list = self::get_flat_ordered_items_list($lp_id);
9752
            if (!empty($list)) {
9753
                foreach ($list as $item_id) {
9754
                    $item = $this->items[$item_id];
9755
                    switch ($item->type) {
9756
                        case 'document':
9757
                            // Getting documents from a LP with chamilo documents
9758
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9759
                            // Try loading document from the base course.
9760
                            if (empty($file_data) && !empty($sessionId)) {
9761
                                $file_data = DocumentManager::get_document_data_by_id(
9762
                                    $item->path,
9763
                                    $this->cc,
9764
                                    false,
9765
                                    0
9766
                                );
9767
                            }
9768
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9769
                            if (file_exists($file_path)) {
9770
                                $files_to_export[] = [
9771
                                    'title' => $item->get_title(),
9772
                                    'path' => $file_path,
9773
                                ];
9774
                            }
9775
                            break;
9776
                        case 'asset': //commes from a scorm package generated by chamilo
9777
                        case 'sco':
9778
                            $file_path = $scorm_path.'/'.$item->path;
9779
                            if (file_exists($file_path)) {
9780
                                $files_to_export[] = [
9781
                                    'title' => $item->get_title(),
9782
                                    'path' => $file_path,
9783
                                ];
9784
                            }
9785
                            break;
9786
                        case 'dir':
9787
                            $files_to_export[] = [
9788
                                'title' => $item->get_title(),
9789
                                'path' => null,
9790
                            ];
9791
                            break;
9792
                    }
9793
                }
9794
            }
9795
9796
            $pdf = new PDF();
9797
            $result = $pdf->html_to_pdf(
9798
                $files_to_export,
9799
                $this->name,
9800
                $this->cc,
9801
                true,
9802
                true,
9803
                true,
9804
                $this->get_name()
9805
            );
9806
9807
            return $result;
9808
        }
9809
9810
        return false;
9811
    }
9812
9813
    /**
9814
     * Temp function to be moved in main_api or the best place around for this.
9815
     * Creates a file path if it doesn't exist.
9816
     *
9817
     * @param string $path
9818
     */
9819
    public function create_path($path)
9820
    {
9821
        $path_bits = explode('/', dirname($path));
9822
9823
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9824
        $path_built = IS_WINDOWS_OS ? '' : '/';
9825
        foreach ($path_bits as $bit) {
9826
            if (!empty($bit)) {
9827
                $new_path = $path_built.$bit;
9828
                if (is_dir($new_path)) {
9829
                    $path_built = $new_path.'/';
9830
                } else {
9831
                    mkdir($new_path, api_get_permissions_for_new_directories());
9832
                    $path_built = $new_path.'/';
9833
                }
9834
            }
9835
        }
9836
    }
9837
9838
    /**
9839
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
9840
     *
9841
     * @return bool The results of the unlink function, or false if there was no image to start with
9842
     */
9843
    public function delete_lp_image()
9844
    {
9845
        $img = $this->get_preview_image();
9846
        if ('' != $img) {
9847
            $del_file = $this->get_preview_image_path(null, 'sys');
9848
            if (isset($del_file) && file_exists($del_file)) {
9849
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
9850
                if (file_exists($del_file_2)) {
9851
                    unlink($del_file_2);
9852
                }
9853
                $this->set_preview_image('');
9854
9855
                return @unlink($del_file);
9856
            }
9857
        }
9858
9859
        return false;
9860
    }
9861
9862
    /**
9863
     * Uploads an author image to the upload/learning_path/images directory.
9864
     *
9865
     * @param array    The image array, coming from the $_FILES superglobal
9866
     *
9867
     * @return bool True on success, false on error
9868
     */
9869
    public function upload_image($image_array)
9870
    {
9871
        if (!empty($image_array['name'])) {
9872
            $upload_ok = process_uploaded_file($image_array);
9873
            $has_attachment = true;
9874
        }
9875
9876
        if ($upload_ok && $has_attachment) {
9877
            $courseDir = api_get_course_path().'/upload/learning_path/images';
9878
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
9879
            $updir = $sys_course_path.$courseDir;
9880
            // Try to add an extension to the file if it hasn't one.
9881
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
9882
9883
            if (filter_extension($new_file_name)) {
9884
                $file_extension = explode('.', $image_array['name']);
9885
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
9886
                $filename = uniqid('');
9887
                $new_file_name = $filename.'.'.$file_extension;
9888
                $new_path = $updir.'/'.$new_file_name;
9889
9890
                // Resize the image.
9891
                $temp = new Image($image_array['tmp_name']);
9892
                $temp->resize(104);
9893
                $result = $temp->send_image($new_path);
9894
9895
                // Storing the image filename.
9896
                if ($result) {
9897
                    $this->set_preview_image($new_file_name);
9898
9899
                    //Resize to 64px to use on course homepage
9900
                    $temp->resize(64);
9901
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
9902
9903
                    return true;
9904
                }
9905
            }
9906
        }
9907
9908
        return false;
9909
    }
9910
9911
    /**
9912
     * @param int    $lp_id
9913
     * @param string $status
9914
     */
9915
    public function set_autolaunch($lp_id, $status)
9916
    {
9917
        $course_id = api_get_course_int_id();
9918
        $lp_id = (int) $lp_id;
9919
        $status = (int) $status;
9920
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9921
9922
        // Setting everything to autolaunch = 0
9923
        $attributes['autolaunch'] = 0;
9924
        $where = [
9925
            'session_id = ? AND c_id = ? ' => [
9926
                api_get_session_id(),
9927
                $course_id,
9928
            ],
9929
        ];
9930
        Database::update($lp_table, $attributes, $where);
9931
        if (1 == $status) {
9932
            //Setting my lp_id to autolaunch = 1
9933
            $attributes['autolaunch'] = 1;
9934
            $where = [
9935
                'iid = ? AND session_id = ? AND c_id = ?' => [
9936
                    $lp_id,
9937
                    api_get_session_id(),
9938
                    $course_id,
9939
                ],
9940
            ];
9941
            Database::update($lp_table, $attributes, $where);
9942
        }
9943
    }
9944
9945
    /**
9946
     * Gets previous_item_id for the next element of the lp_item table.
9947
     *
9948
     * @author Isaac flores paz
9949
     *
9950
     * @return int Previous item ID
9951
     */
9952
    public function select_previous_item_id()
9953
    {
9954
        $course_id = api_get_course_int_id();
9955
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9956
9957
        // Get the max order of the items
9958
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9959
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9960
        $rs_max_order = Database::query($sql);
9961
        $row_max_order = Database::fetch_object($rs_max_order);
9962
        $max_order = $row_max_order->display_order;
9963
        // Get the previous item ID
9964
        $sql = "SELECT iid as previous FROM $table_lp_item
9965
                WHERE
9966
                    c_id = $course_id AND
9967
                    lp_id = ".$this->lp_id." AND
9968
                    display_order = '$max_order' ";
9969
        $rs_max = Database::query($sql);
9970
        $row_max = Database::fetch_object($rs_max);
9971
9972
        // Return the previous item ID
9973
        return $row_max->previous;
9974
    }
9975
9976
    /**
9977
     * Copies an LP.
9978
     */
9979
    public function copy()
9980
    {
9981
        // Course builder
9982
        $cb = new CourseBuilder();
9983
9984
        //Setting tools that will be copied
9985
        $cb->set_tools_to_build(['learnpaths']);
9986
9987
        //Setting elements that will be copied
9988
        $cb->set_tools_specific_id_list(
9989
            ['learnpaths' => [$this->lp_id]]
9990
        );
9991
9992
        $course = $cb->build();
9993
9994
        //Course restorer
9995
        $course_restorer = new CourseRestorer($course);
9996
        $course_restorer->set_add_text_in_items(true);
9997
        $course_restorer->set_tool_copy_settings(
9998
            ['learnpaths' => ['reset_dates' => true]]
9999
        );
10000
        $course_restorer->restore(
10001
            api_get_course_id(),
10002
            api_get_session_id(),
10003
            false,
10004
            false
10005
        );
10006
    }
10007
10008
    /**
10009
     * Verify document size.
10010
     *
10011
     * @param string $s
10012
     *
10013
     * @return bool
10014
     */
10015
    public static function verify_document_size($s)
10016
    {
10017
        $post_max = ini_get('post_max_size');
10018
        if ('M' == substr($post_max, -1, 1)) {
10019
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
10020
        } elseif ('G' == substr($post_max, -1, 1)) {
10021
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
10022
        }
10023
        $upl_max = ini_get('upload_max_filesize');
10024
        if ('M' == substr($upl_max, -1, 1)) {
10025
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
10026
        } elseif ('G' == substr($upl_max, -1, 1)) {
10027
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
10028
        }
10029
10030
        $repo = Container::getDocumentRepository();
10031
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
10032
10033
        $course_max_space = DocumentManager::get_course_quota();
10034
        $total_size = filesize($s) + $documents_total_space;
10035
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
10036
            return true;
10037
        }
10038
10039
        return false;
10040
    }
10041
10042
    /**
10043
     * Clear LP prerequisites.
10044
     */
10045
    public function clear_prerequisites()
10046
    {
10047
        $course_id = $this->get_course_int_id();
10048
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10049
        $lp_id = $this->get_id();
10050
        // Cleaning prerequisites
10051
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
10052
                WHERE c_id = $course_id AND lp_id = $lp_id";
10053
        Database::query($sql);
10054
10055
        // Cleaning mastery score for exercises
10056
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
10057
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
10058
        Database::query($sql);
10059
    }
10060
10061
    public function set_previous_step_as_prerequisite_for_all_items()
10062
    {
10063
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10064
        $course_id = $this->get_course_int_id();
10065
        $lp_id = $this->get_id();
10066
10067
        if (!empty($this->items)) {
10068
            $previous_item_id = null;
10069
            $previous_item_max = 0;
10070
            $previous_item_type = null;
10071
            $last_item_not_dir = null;
10072
            $last_item_not_dir_type = null;
10073
            $last_item_not_dir_max = null;
10074
10075
            foreach ($this->ordered_items as $itemId) {
10076
                $item = $this->getItem($itemId);
10077
                // if there was a previous item... (otherwise jump to set it)
10078
                if (!empty($previous_item_id)) {
10079
                    $current_item_id = $item->get_id(); //save current id
10080
                    if ('dir' != $item->get_type()) {
10081
                        // Current item is not a folder, so it qualifies to get a prerequisites
10082
                        if ('quiz' == $last_item_not_dir_type) {
10083
                            // if previous is quiz, mark its max score as default score to be achieved
10084
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
10085
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
10086
                            Database::query($sql);
10087
                        }
10088
                        // now simply update the prerequisite to set it to the last non-chapter item
10089
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
10090
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
10091
                        Database::query($sql);
10092
                        // record item as 'non-chapter' reference
10093
                        $last_item_not_dir = $item->get_id();
10094
                        $last_item_not_dir_type = $item->get_type();
10095
                        $last_item_not_dir_max = $item->get_max();
10096
                    }
10097
                } else {
10098
                    if ('dir' != $item->get_type()) {
10099
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
10100
                        $last_item_not_dir = $item->get_id();
10101
                        $last_item_not_dir_type = $item->get_type();
10102
                        $last_item_not_dir_max = $item->get_max();
10103
                    }
10104
                }
10105
                // Saving the item as "previous item" for the next loop
10106
                $previous_item_id = $item->get_id();
10107
                $previous_item_max = $item->get_max();
10108
                $previous_item_type = $item->get_type();
10109
            }
10110
        }
10111
    }
10112
10113
    /**
10114
     * @param array $params
10115
     *
10116
     * @return int
10117
     */
10118
    public static function createCategory($params)
10119
    {
10120
        $courseEntity = api_get_course_entity(api_get_course_int_id());
10121
10122
        $item = new CLpCategory();
10123
        $item
10124
            ->setName($params['name'])
10125
            ->setCId($params['c_id'])
10126
            ->setParent($courseEntity)
10127
            ->addCourseLink($courseEntity, api_get_session_entity())
10128
        ;
10129
10130
        $repo = Container::getLpCategoryRepository();
10131
        $em = $repo->getEntityManager();
10132
        $em->persist($item);
10133
        $em->flush();
10134
10135
        /*api_item_property_update(
10136
            api_get_course_info(),
10137
            TOOL_LEARNPATH_CATEGORY,
10138
            $item->getId(),
10139
            'visible',
10140
            api_get_user_id()
10141
        );*/
10142
10143
        return $item->getIid();
10144
    }
10145
10146
    /**
10147
     * @param array $params
10148
     */
10149
    public static function updateCategory($params)
10150
    {
10151
        $em = Database::getManager();
10152
        /** @var CLpCategory $item */
10153
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
10154
        if ($item) {
10155
            $item->setName($params['name']);
10156
            $em->persist($item);
10157
            $em->flush();
10158
        }
10159
    }
10160
10161
    /**
10162
     * @param int $id
10163
     */
10164
    public static function moveUpCategory($id)
10165
    {
10166
        $id = (int) $id;
10167
        $em = Database::getManager();
10168
        /** @var CLpCategory $item */
10169
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10170
        if ($item) {
10171
            $position = $item->getPosition() - 1;
10172
            $item->setPosition($position);
10173
            $em->persist($item);
10174
            $em->flush();
10175
        }
10176
    }
10177
10178
    /**
10179
     * @param int $id
10180
     */
10181
    public static function moveDownCategory($id)
10182
    {
10183
        $id = (int) $id;
10184
        $em = Database::getManager();
10185
        /** @var CLpCategory $item */
10186
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10187
        if ($item) {
10188
            $position = $item->getPosition() + 1;
10189
            $item->setPosition($position);
10190
            $em->persist($item);
10191
            $em->flush();
10192
        }
10193
    }
10194
10195
    public static function getLpList($courseId)
10196
    {
10197
        $table = Database::get_course_table(TABLE_LP_MAIN);
10198
        $courseId = (int) $courseId;
10199
10200
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
10201
        $result = Database::query($sql);
10202
10203
        return Database::store_result($result, 'ASSOC');
10204
    }
10205
10206
    /**
10207
     * @param int $courseId
10208
     *
10209
     * @throws \Doctrine\ORM\Query\QueryException
10210
     *
10211
     * @return int|mixed
10212
     */
10213
    public static function getCountCategories($courseId)
10214
    {
10215
        if (empty($courseId)) {
10216
            return 0;
10217
        }
10218
        $em = Database::getManager();
10219
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10220
        $query->setParameter('id', $courseId);
10221
10222
        return $query->getSingleScalarResult();
10223
    }
10224
10225
    /**
10226
     * @param int $courseId
10227
     *
10228
     * @return CLpCategory[]
10229
     */
10230
    public static function getCategories($courseId)
10231
    {
10232
        $em = Database::getManager();
10233
10234
        // Using doctrine extensions
10235
        /** @var SortableRepository $repo */
10236
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10237
10238
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
10239
    }
10240
10241
    public static function getCategorySessionId($id)
10242
    {
10243
        if (false === api_get_configuration_value('allow_session_lp_category')) {
10244
            return 0;
10245
        }
10246
10247
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
10248
        $id = (int) $id;
10249
10250
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
10251
        $result = Database::query($sql);
10252
        $result = Database::fetch_array($result, 'ASSOC');
10253
10254
        if ($result) {
10255
            return (int) $result['session_id'];
10256
        }
10257
10258
        return 0;
10259
    }
10260
10261
    /**
10262
     * @param int $id
10263
     *
10264
     * @return CLpCategory
10265
     */
10266
    public static function getCategory($id)
10267
    {
10268
        $id = (int) $id;
10269
        $em = Database::getManager();
10270
10271
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
10272
    }
10273
10274
    /**
10275
     * @param int $courseId
10276
     *
10277
     * @return array
10278
     */
10279
    public static function getCategoryByCourse($courseId)
10280
    {
10281
        $em = Database::getManager();
10282
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10283
            ['cId' => $courseId]
10284
        );
10285
10286
        return $items;
10287
    }
10288
10289
    /**
10290
     * @param int $id
10291
     */
10292
    public static function deleteCategory($id): bool
10293
    {
10294
        $repo = Container::getLpCategoryRepository();
10295
        /** @var CLpCategory $category */
10296
        $category = $repo->find($id);
10297
        if ($category) {
10298
            $em = $repo->getEntityManager();
10299
            $lps = $category->getLps();
10300
10301
            foreach ($lps as $lp) {
10302
                $lp->setCategory(null);
10303
                $em->persist($lp);
10304
            }
10305
10306
            // Removing category.
10307
            $em->remove($category);
10308
            $em->flush();
10309
10310
            return true;
10311
        }
10312
10313
        return false;
10314
    }
10315
10316
    /**
10317
     * @param int  $courseId
10318
     * @param bool $addSelectOption
10319
     *
10320
     * @return mixed
10321
     */
10322
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10323
    {
10324
        $items = self::getCategoryByCourse($courseId);
10325
        $cats = [];
10326
        if ($addSelectOption) {
10327
            $cats = [get_lang('Select a category')];
10328
        }
10329
10330
        if (!empty($items)) {
10331
            foreach ($items as $cat) {
10332
                $cats[$cat->getIid()] = $cat->getName();
10333
            }
10334
        }
10335
10336
        return $cats;
10337
    }
10338
10339
    /**
10340
     * @param string $courseCode
10341
     * @param int    $lpId
10342
     * @param int    $user_id
10343
     *
10344
     * @return learnpath
10345
     */
10346
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10347
    {
10348
        $debug = 0;
10349
        $learnPath = null;
10350
        $lpObject = Session::read('lpobject');
10351
        if (null !== $lpObject) {
10352
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10353
            if ($debug) {
10354
                error_log('getLpFromSession: unserialize');
10355
                error_log('------getLpFromSession------');
10356
                error_log('------unserialize------');
10357
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10358
                error_log("api_get_sessionid: ".api_get_session_id());
10359
            }
10360
        }
10361
10362
        if (!is_object($learnPath)) {
10363
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10364
            if ($debug) {
10365
                error_log('------getLpFromSession------');
10366
                error_log('getLpFromSession: create new learnpath');
10367
                error_log("create new LP with $courseCode - $lpId - $user_id");
10368
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10369
                error_log("api_get_sessionid: ".api_get_session_id());
10370
            }
10371
        }
10372
10373
        return $learnPath;
10374
    }
10375
10376
    /**
10377
     * @param int $itemId
10378
     *
10379
     * @return learnpathItem|false
10380
     */
10381
    public function getItem($itemId)
10382
    {
10383
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10384
            return $this->items[$itemId];
10385
        }
10386
10387
        return false;
10388
    }
10389
10390
    /**
10391
     * @return int
10392
     */
10393
    public function getCurrentAttempt()
10394
    {
10395
        $attempt = $this->getItem($this->get_current_item_id());
10396
        if ($attempt) {
10397
            return $attempt->get_attempt_id();
10398
        }
10399
10400
        return 0;
10401
    }
10402
10403
    /**
10404
     * @return int
10405
     */
10406
    public function getCategoryId()
10407
    {
10408
        return (int) $this->categoryId;
10409
    }
10410
10411
    /**
10412
     * @param int $categoryId
10413
     *
10414
     * @return bool
10415
     */
10416
    public function setCategoryId($categoryId)
10417
    {
10418
        $this->categoryId = (int) $categoryId;
10419
        $table = Database::get_course_table(TABLE_LP_MAIN);
10420
        $lp_id = $this->get_id();
10421
        if (empty($categoryId)) {
10422
            $this->categoryId = null;
10423
            $sql = "UPDATE $table SET category_id = NULL WHERE iid = $lp_id";
10424
            Database::query($sql);
10425
10426
            return true;
10427
        }
10428
10429
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10430
                WHERE iid = $lp_id";
10431
        Database::query($sql);
10432
10433
        return true;
10434
    }
10435
10436
    /**
10437
     * Get whether this is a learning path with the possibility to subscribe
10438
     * users or not.
10439
     *
10440
     * @return int
10441
     */
10442
    public function getSubscribeUsers()
10443
    {
10444
        return $this->subscribeUsers;
10445
    }
10446
10447
    /**
10448
     * Set whether this is a learning path with the possibility to subscribe
10449
     * users or not.
10450
     *
10451
     * @param int $value (0 = false, 1 = true)
10452
     *
10453
     * @return bool
10454
     */
10455
    public function setSubscribeUsers($value)
10456
    {
10457
        $this->subscribeUsers = (int) $value;
10458
        $table = Database::get_course_table(TABLE_LP_MAIN);
10459
        $lp_id = $this->get_id();
10460
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10461
                WHERE iid = $lp_id";
10462
        Database::query($sql);
10463
10464
        return true;
10465
    }
10466
10467
    /**
10468
     * Calculate the count of stars for a user in this LP
10469
     * This calculation is based on the following rules:
10470
     * - the student gets one star when he gets to 50% of the learning path
10471
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10472
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10473
     * - the student gets the final star when the score for the *last* test is >= 80%.
10474
     *
10475
     * @param int $sessionId Optional. The session ID
10476
     *
10477
     * @return int The count of stars
10478
     */
10479
    public function getCalculateStars($sessionId = 0)
10480
    {
10481
        $stars = 0;
10482
        $progress = self::getProgress(
10483
            $this->lp_id,
10484
            $this->user_id,
10485
            $this->course_int_id,
10486
            $sessionId
10487
        );
10488
10489
        if ($progress >= 50) {
10490
            $stars++;
10491
        }
10492
10493
        // Calculate stars chapters evaluation
10494
        $exercisesItems = $this->getExercisesItems();
10495
10496
        if (!empty($exercisesItems)) {
10497
            $totalResult = 0;
10498
10499
            foreach ($exercisesItems as $exerciseItem) {
10500
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10501
                    $this->user_id,
10502
                    $exerciseItem->path,
10503
                    $this->course_int_id,
10504
                    $sessionId,
10505
                    $this->lp_id,
10506
                    $exerciseItem->db_id
10507
                );
10508
10509
                $exerciseResultInfo = end($exerciseResultInfo);
10510
10511
                if (!$exerciseResultInfo) {
10512
                    continue;
10513
                }
10514
10515
                if (!empty($exerciseResultInfo['max_score'])) {
10516
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10517
                } else {
10518
                    $exerciseResult = 0;
10519
                }
10520
                $totalResult += $exerciseResult;
10521
            }
10522
10523
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10524
10525
            if ($totalExerciseAverage >= 50) {
10526
                $stars++;
10527
            }
10528
10529
            if ($totalExerciseAverage >= 80) {
10530
                $stars++;
10531
            }
10532
        }
10533
10534
        // Calculate star for final evaluation
10535
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10536
10537
        if (!empty($finalEvaluationItem)) {
10538
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10539
                $this->user_id,
10540
                $finalEvaluationItem->path,
10541
                $this->course_int_id,
10542
                $sessionId,
10543
                $this->lp_id,
10544
                $finalEvaluationItem->db_id
10545
            );
10546
10547
            $evaluationResultInfo = end($evaluationResultInfo);
10548
10549
            if ($evaluationResultInfo) {
10550
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10551
10552
                if ($evaluationResult >= 80) {
10553
                    $stars++;
10554
                }
10555
            }
10556
        }
10557
10558
        return $stars;
10559
    }
10560
10561
    /**
10562
     * Get the items of exercise type.
10563
     *
10564
     * @return array The items. Otherwise return false
10565
     */
10566
    public function getExercisesItems()
10567
    {
10568
        $exercises = [];
10569
        foreach ($this->items as $item) {
10570
            if ('quiz' != $item->type) {
10571
                continue;
10572
            }
10573
            $exercises[] = $item;
10574
        }
10575
10576
        array_pop($exercises);
10577
10578
        return $exercises;
10579
    }
10580
10581
    /**
10582
     * Get the item of exercise type (evaluation type).
10583
     *
10584
     * @return array The final evaluation. Otherwise return false
10585
     */
10586
    public function getFinalEvaluationItem()
10587
    {
10588
        $exercises = [];
10589
        foreach ($this->items as $item) {
10590
            if (TOOL_QUIZ !== $item->type) {
10591
                continue;
10592
            }
10593
10594
            $exercises[] = $item;
10595
        }
10596
10597
        return array_pop($exercises);
10598
    }
10599
10600
    /**
10601
     * Calculate the total points achieved for the current user in this learning path.
10602
     *
10603
     * @param int $sessionId Optional. The session Id
10604
     *
10605
     * @return int
10606
     */
10607
    public function getCalculateScore($sessionId = 0)
10608
    {
10609
        // Calculate stars chapters evaluation
10610
        $exercisesItems = $this->getExercisesItems();
10611
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10612
        $totalExercisesResult = 0;
10613
        $totalEvaluationResult = 0;
10614
10615
        if (false !== $exercisesItems) {
10616
            foreach ($exercisesItems as $exerciseItem) {
10617
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10618
                    $this->user_id,
10619
                    $exerciseItem->path,
10620
                    $this->course_int_id,
10621
                    $sessionId,
10622
                    $this->lp_id,
10623
                    $exerciseItem->db_id
10624
                );
10625
10626
                $exerciseResultInfo = end($exerciseResultInfo);
10627
10628
                if (!$exerciseResultInfo) {
10629
                    continue;
10630
                }
10631
10632
                $totalExercisesResult += $exerciseResultInfo['score'];
10633
            }
10634
        }
10635
10636
        if (!empty($finalEvaluationItem)) {
10637
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10638
                $this->user_id,
10639
                $finalEvaluationItem->path,
10640
                $this->course_int_id,
10641
                $sessionId,
10642
                $this->lp_id,
10643
                $finalEvaluationItem->db_id
10644
            );
10645
10646
            $evaluationResultInfo = end($evaluationResultInfo);
10647
10648
            if ($evaluationResultInfo) {
10649
                $totalEvaluationResult += $evaluationResultInfo['score'];
10650
            }
10651
        }
10652
10653
        return $totalExercisesResult + $totalEvaluationResult;
10654
    }
10655
10656
    /**
10657
     * Check if URL is not allowed to be show in a iframe.
10658
     *
10659
     * @param string $src
10660
     *
10661
     * @return string
10662
     */
10663
    public function fixBlockedLinks($src)
10664
    {
10665
        $urlInfo = parse_url($src);
10666
10667
        $platformProtocol = 'https';
10668
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10669
            $platformProtocol = 'http';
10670
        }
10671
10672
        $protocolFixApplied = false;
10673
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10674
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10675
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10676
10677
        if ($platformProtocol != $scheme) {
10678
            Session::write('x_frame_source', $src);
10679
            $src = 'blank.php?error=x_frames_options';
10680
            $protocolFixApplied = true;
10681
        }
10682
10683
        if (false == $protocolFixApplied) {
10684
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10685
                // Check X-Frame-Options
10686
                $ch = curl_init();
10687
                $options = [
10688
                    CURLOPT_URL => $src,
10689
                    CURLOPT_RETURNTRANSFER => true,
10690
                    CURLOPT_HEADER => true,
10691
                    CURLOPT_FOLLOWLOCATION => true,
10692
                    CURLOPT_ENCODING => "",
10693
                    CURLOPT_AUTOREFERER => true,
10694
                    CURLOPT_CONNECTTIMEOUT => 120,
10695
                    CURLOPT_TIMEOUT => 120,
10696
                    CURLOPT_MAXREDIRS => 10,
10697
                ];
10698
10699
                $proxySettings = api_get_configuration_value('proxy_settings');
10700
                if (!empty($proxySettings) &&
10701
                    isset($proxySettings['curl_setopt_array'])
10702
                ) {
10703
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10704
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10705
                }
10706
10707
                curl_setopt_array($ch, $options);
10708
                $response = curl_exec($ch);
10709
                $httpCode = curl_getinfo($ch);
10710
                $headers = substr($response, 0, $httpCode['header_size']);
10711
10712
                $error = false;
10713
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10714
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10715
                ) {
10716
                    $error = true;
10717
                }
10718
10719
                if ($error) {
10720
                    Session::write('x_frame_source', $src);
10721
                    $src = 'blank.php?error=x_frames_options';
10722
                }
10723
            }
10724
        }
10725
10726
        return $src;
10727
    }
10728
10729
    /**
10730
     * Check if this LP has a created forum in the basis course.
10731
     *
10732
     * @return bool
10733
     */
10734
    public function lpHasForum()
10735
    {
10736
        $forumTable = Database::get_course_table(TABLE_FORUM);
10737
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10738
10739
        $fakeFrom = "
10740
            $forumTable f
10741
            INNER JOIN $itemProperty ip
10742
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10743
        ";
10744
10745
        $resultData = Database::select(
10746
            'COUNT(f.iid) AS qty',
10747
            $fakeFrom,
10748
            [
10749
                'where' => [
10750
                    'ip.visibility != ? AND ' => 2,
10751
                    'ip.tool = ? AND ' => TOOL_FORUM,
10752
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10753
                    'f.lp_id = ?' => intval($this->lp_id),
10754
                ],
10755
            ],
10756
            'first'
10757
        );
10758
10759
        return $resultData['qty'] > 0;
10760
    }
10761
10762
    /**
10763
     * Get the forum for this learning path.
10764
     *
10765
     * @param int $sessionId
10766
     *
10767
     * @return array
10768
     */
10769
    public function getForum($sessionId = 0)
10770
    {
10771
        $repo = Container::getForumRepository();
10772
10773
        $course = api_get_course_entity();
10774
        $session = api_get_session_entity($sessionId);
10775
        $qb = $repo->getResourcesByCourse($course, $session);
10776
10777
        return $qb->getQuery()->getResult();
10778
    }
10779
10780
    /**
10781
     * Create a forum for this learning path.
10782
     *
10783
     * @param int $forumCategoryId
10784
     *
10785
     * @return int The forum ID if was created. Otherwise return false
10786
     */
10787
    public function createForum($forumCategoryId)
10788
    {
10789
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10790
10791
        return store_forum(
10792
            [
10793
                'lp_id' => $this->lp_id,
10794
                'forum_title' => $this->name,
10795
                'forum_comment' => null,
10796
                'forum_category' => (int) $forumCategoryId,
10797
                'students_can_edit_group' => ['students_can_edit' => 0],
10798
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10799
                'default_view_type_group' => ['default_view_type' => 'flat'],
10800
                'group_forum' => 0,
10801
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10802
            ],
10803
            [],
10804
            true
10805
        );
10806
    }
10807
10808
    /**
10809
     * Get the LP Final Item form.
10810
     *
10811
     * @throws Exception
10812
     * @throws HTML_QuickForm_Error
10813
     *
10814
     * @return string
10815
     */
10816
    public function getFinalItemForm()
10817
    {
10818
        $finalItem = $this->getFinalItem();
10819
        $title = '';
10820
10821
        if ($finalItem) {
10822
            $title = $finalItem->get_title();
10823
            $buttonText = get_lang('Save');
10824
            $content = $this->getSavedFinalItem();
10825
        } else {
10826
            $buttonText = get_lang('Add this document to the course');
10827
            $content = $this->getFinalItemTemplate();
10828
        }
10829
10830
        $editorConfig = [
10831
            'ToolbarSet' => 'LearningPathDocuments',
10832
            'Width' => '100%',
10833
            'Height' => '500',
10834
            'FullPage' => true,
10835
//            'CreateDocumentDir' => $relative_prefix,
10836
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10837
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10838
        ];
10839
10840
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10841
            'type' => 'document',
10842
            'lp_id' => $this->lp_id,
10843
        ]);
10844
10845
        $form = new FormValidator('final_item', 'POST', $url);
10846
        $form->addText('title', get_lang('Title'));
10847
        $form->addButtonSave($buttonText);
10848
        $form->addHtml(
10849
            Display::return_message(
10850
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10851
                'normal',
10852
                false
10853
            )
10854
        );
10855
10856
        $renderer = $form->defaultRenderer();
10857
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10858
10859
        $form->addHtmlEditor(
10860
            'content_lp_certificate',
10861
            null,
10862
            true,
10863
            false,
10864
            $editorConfig,
10865
            true
10866
        );
10867
        $form->addHidden('action', 'add_final_item');
10868
        $form->addHidden('path', Session::read('pathItem'));
10869
        $form->addHidden('previous', $this->get_last());
10870
        $form->setDefaults(
10871
            ['title' => $title, 'content_lp_certificate' => $content]
10872
        );
10873
10874
        if ($form->validate()) {
10875
            $values = $form->exportValues();
10876
            $lastItemId = $this->getLastInFirstLevel();
10877
10878
            if (!$finalItem) {
10879
                $documentId = $this->create_document(
10880
                    $this->course_info,
10881
                    $values['content_lp_certificate'],
10882
                    $values['title']
10883
                );
10884
                $this->add_item(
10885
                    0,
10886
                    $lastItemId,
10887
                    'final_item',
10888
                    $documentId,
10889
                    $values['title'],
10890
                    ''
10891
                );
10892
10893
                Display::addFlash(
10894
                    Display::return_message(get_lang('Added'))
10895
                );
10896
            } else {
10897
                $this->edit_document($this->course_info);
10898
            }
10899
        }
10900
10901
        return $form->returnForm();
10902
    }
10903
10904
    /**
10905
     * Check if the current lp item is first, both, last or none from lp list.
10906
     *
10907
     * @param int $currentItemId
10908
     *
10909
     * @return string
10910
     */
10911
    public function isFirstOrLastItem($currentItemId)
10912
    {
10913
        $lpItemId = [];
10914
        $typeListNotToVerify = self::getChapterTypes();
10915
10916
        // Using get_toc() function instead $this->items because returns the correct order of the items
10917
        foreach ($this->get_toc() as $item) {
10918
            if (!in_array($item['type'], $typeListNotToVerify)) {
10919
                $lpItemId[] = $item['id'];
10920
            }
10921
        }
10922
10923
        $lastLpItemIndex = count($lpItemId) - 1;
10924
        $position = array_search($currentItemId, $lpItemId);
10925
10926
        switch ($position) {
10927
            case 0:
10928
                if (!$lastLpItemIndex) {
10929
                    $answer = 'both';
10930
                    break;
10931
                }
10932
10933
                $answer = 'first';
10934
                break;
10935
            case $lastLpItemIndex:
10936
                $answer = 'last';
10937
                break;
10938
            default:
10939
                $answer = 'none';
10940
        }
10941
10942
        return $answer;
10943
    }
10944
10945
    /**
10946
     * Get whether this is a learning path with the accumulated SCORM time or not.
10947
     *
10948
     * @return int
10949
     */
10950
    public function getAccumulateScormTime()
10951
    {
10952
        return $this->accumulateScormTime;
10953
    }
10954
10955
    /**
10956
     * Set whether this is a learning path with the accumulated SCORM time or not.
10957
     *
10958
     * @param int $value (0 = false, 1 = true)
10959
     *
10960
     * @return bool Always returns true
10961
     */
10962
    public function setAccumulateScormTime($value)
10963
    {
10964
        $this->accumulateScormTime = (int) $value;
10965
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
10966
        $lp_id = $this->get_id();
10967
        $sql = "UPDATE $lp_table
10968
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
10969
                WHERE iid = $lp_id";
10970
        Database::query($sql);
10971
10972
        return true;
10973
    }
10974
10975
    /**
10976
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10977
     * the new learning path tool.
10978
     *
10979
     * The function is a big switch on tool type.
10980
     * In each case, we query the corresponding table for information and build the link
10981
     * with that information.
10982
     *
10983
     * @author Yannick Warnier <[email protected]> - rebranding based on
10984
     * previous work (display_addedresource_link_in_learnpath())
10985
     *
10986
     * @param int $course_id      Course code
10987
     * @param int $learningPathId The learning path ID (in lp table)
10988
     * @param int $id_in_path     the unique index in the items table
10989
     * @param int $lpViewId
10990
     *
10991
     * @return string
10992
     */
10993
    public static function rl_get_resource_link_for_learnpath(
10994
        $course_id,
10995
        $learningPathId,
10996
        $id_in_path,
10997
        $lpViewId
10998
    ) {
10999
        $session_id = api_get_session_id();
11000
        $course_info = api_get_course_info_by_id($course_id);
11001
11002
        $learningPathId = (int) $learningPathId;
11003
        $id_in_path = (int) $id_in_path;
11004
        $lpViewId = (int) $lpViewId;
11005
11006
        $em = Database::getManager();
11007
        $lpItemRepo = $em->getRepository(CLpItem::class);
11008
11009
        /** @var CLpItem $rowItem */
11010
        $rowItem = $lpItemRepo->findOneBy([
11011
            'cId' => $course_id,
11012
            'lp' => $learningPathId,
11013
            'iid' => $id_in_path,
11014
        ]);
11015
11016
        if (!$rowItem) {
11017
            // Try one more time with "id"
11018
            /** @var CLpItem $rowItem */
11019
            $rowItem = $lpItemRepo->findOneBy([
11020
                'cId' => $course_id,
11021
                'lp' => $learningPathId,
11022
                'id' => $id_in_path,
11023
            ]);
11024
11025
            if (!$rowItem) {
11026
                return -1;
11027
            }
11028
        }
11029
11030
        $type = $rowItem->getItemType();
11031
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
11032
        $main_dir_path = api_get_path(WEB_CODE_PATH);
11033
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
11034
        $link = '';
11035
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
11036
11037
        switch ($type) {
11038
            case 'dir':
11039
                return $main_dir_path.'lp/blank.php';
11040
            case TOOL_CALENDAR_EVENT:
11041
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
11042
            case TOOL_ANNOUNCEMENT:
11043
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
11044
            case TOOL_LINK:
11045
                $linkInfo = Link::getLinkInfo($id);
11046
                if (isset($linkInfo['url'])) {
11047
                    return $linkInfo['url'];
11048
                }
11049
11050
                return '';
11051
            case TOOL_QUIZ:
11052
                if (empty($id)) {
11053
                    return '';
11054
                }
11055
11056
                // Get the lp_item_view with the highest view_count.
11057
                $learnpathItemViewResult = $em
11058
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
11059
                    ->findBy(
11060
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
11061
                        ['viewCount' => 'DESC'],
11062
                        1
11063
                    );
11064
                /** @var CLpItemView $learnpathItemViewData */
11065
                $learnpathItemViewData = current($learnpathItemViewResult);
11066
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
11067
11068
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
11069
                    .http_build_query([
11070
                        'lp_init' => 1,
11071
                        'learnpath_item_view_id' => $learnpathItemViewId,
11072
                        'learnpath_id' => $learningPathId,
11073
                        'learnpath_item_id' => $id_in_path,
11074
                        'exerciseId' => $id,
11075
                    ]);
11076
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
11077
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
11078
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
11079
                $myrow = Database::fetch_array($result);
11080
                $path = $myrow['path'];
11081
11082
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
11083
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
11084
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
11085
            case TOOL_FORUM:
11086
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
11087
            case TOOL_THREAD:
11088
                // forum post
11089
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
11090
                if (empty($id)) {
11091
                    return '';
11092
                }
11093
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
11094
                $result = Database::query($sql);
11095
                $myrow = Database::fetch_array($result);
11096
11097
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
11098
                    .$extraParams;
11099
            case TOOL_POST:
11100
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11101
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
11102
                $myrow = Database::fetch_array($result);
11103
11104
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
11105
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
11106
            case TOOL_READOUT_TEXT:
11107
                return api_get_path(WEB_CODE_PATH).
11108
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
11109
            case TOOL_DOCUMENT:
11110
                $repo = Container::getDocumentRepository();
11111
                $document = $repo->find($rowItem->getPath());
11112
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
11113
11114
                return $file;
11115
11116
                $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...
11117
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
11118
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
11119
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
11120
11121
                $openmethod = 2;
11122
                $officedoc = false;
11123
                Session::write('openmethod', $openmethod);
11124
                Session::write('officedoc', $officedoc);
11125
11126
                if ($showDirectUrl) {
11127
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
11128
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
11129
                        if (Link::isPdfLink($file)) {
11130
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
11131
11132
                            return $pdfUrl;
11133
                        }
11134
                    }
11135
11136
                    return $file;
11137
                }
11138
11139
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
11140
            case TOOL_LP_FINAL_ITEM:
11141
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
11142
                    .$extraParams;
11143
            case 'assignments':
11144
                return $main_dir_path.'work/work.php?'.$extraParams;
11145
            case TOOL_DROPBOX:
11146
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
11147
            case 'introduction_text': //DEPRECATED
11148
                return '';
11149
            case TOOL_COURSE_DESCRIPTION:
11150
                return $main_dir_path.'course_description?'.$extraParams;
11151
            case TOOL_GROUP:
11152
                return $main_dir_path.'group/group.php?'.$extraParams;
11153
            case TOOL_USER:
11154
                return $main_dir_path.'user/user.php?'.$extraParams;
11155
            case TOOL_STUDENTPUBLICATION:
11156
                if (!empty($rowItem->getPath())) {
11157
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
11158
                }
11159
11160
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
11161
        }
11162
11163
        return $link;
11164
    }
11165
11166
    /**
11167
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11168
     *
11169
     * @author Yannick Warnier <[email protected]>
11170
     *
11171
     * @param string $course_code    Course code
11172
     * @param int    $learningPathId
11173
     * @param int    $id_in_path     The resource ID
11174
     *
11175
     * @return string
11176
     */
11177
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11178
    {
11179
        $_course = api_get_course_info($course_code);
11180
        if (empty($_course)) {
11181
            return '';
11182
        }
11183
        $course_id = $_course['real_id'];
11184
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11185
        $learningPathId = (int) $learningPathId;
11186
        $id_in_path = (int) $id_in_path;
11187
11188
        $sql = "SELECT item_type, title, ref
11189
                FROM $tbl_lp_item
11190
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11191
        $res_item = Database::query($sql);
11192
11193
        if (Database::num_rows($res_item) < 1) {
11194
            return '';
11195
        }
11196
        $row_item = Database::fetch_array($res_item);
11197
        $type = strtolower($row_item['item_type']);
11198
        $id = $row_item['ref'];
11199
        $output = '';
11200
11201
        switch ($type) {
11202
            case TOOL_CALENDAR_EVENT:
11203
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11204
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11205
                $myrow = Database::fetch_array($result);
11206
                $output = $myrow['title'];
11207
                break;
11208
            case TOOL_ANNOUNCEMENT:
11209
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11210
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11211
                $myrow = Database::fetch_array($result);
11212
                $output = $myrow['title'];
11213
                break;
11214
            case TOOL_LINK:
11215
                // Doesn't take $target into account.
11216
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11217
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11218
                $myrow = Database::fetch_array($result);
11219
                $output = $myrow['title'];
11220
                break;
11221
            case TOOL_QUIZ:
11222
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11223
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11224
                $myrow = Database::fetch_array($result);
11225
                $output = $myrow['title'];
11226
                break;
11227
            case TOOL_FORUM:
11228
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11229
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11230
                $myrow = Database::fetch_array($result);
11231
                $output = $myrow['forum_name'];
11232
                break;
11233
            case TOOL_THREAD:
11234
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11235
                // Grabbing the title of the post.
11236
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11237
                $result_title = Database::query($sql_title);
11238
                $myrow_title = Database::fetch_array($result_title);
11239
                $output = $myrow_title['post_title'];
11240
                break;
11241
            case TOOL_POST:
11242
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11243
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11244
                $result = Database::query($sql);
11245
                $post = Database::fetch_array($result);
11246
                $output = $post['post_title'];
11247
                break;
11248
            case 'dir':
11249
            case TOOL_DOCUMENT:
11250
                $title = $row_item['title'];
11251
                $output = '-';
11252
                if (!empty($title)) {
11253
                    $output = $title;
11254
                }
11255
                break;
11256
            case 'hotpotatoes':
11257
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11258
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11259
                $myrow = Database::fetch_array($result);
11260
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11261
                $last = count($pathname) - 1; // Making a correct name for the link.
11262
                $filename = $pathname[$last]; // Making a correct name for the link.
11263
                $myrow['path'] = rawurlencode($myrow['path']);
11264
                $output = $filename;
11265
                break;
11266
        }
11267
11268
        return stripslashes($output);
11269
    }
11270
11271
    /**
11272
     * Get the parent names for the current item.
11273
     *
11274
     * @param int $newItemId Optional. The item ID
11275
     *
11276
     * @return array
11277
     */
11278
    public function getCurrentItemParentNames($newItemId = 0)
11279
    {
11280
        $newItemId = $newItemId ?: $this->get_current_item_id();
11281
        $return = [];
11282
        $item = $this->getItem($newItemId);
11283
        $parent = $this->getItem($item->get_parent());
11284
11285
        while ($parent) {
11286
            $return[] = $parent->get_title();
11287
            $parent = $this->getItem($parent->get_parent());
11288
        }
11289
11290
        return array_reverse($return);
11291
    }
11292
11293
    /**
11294
     * Reads and process "lp_subscription_settings" setting.
11295
     *
11296
     * @return array
11297
     */
11298
    public static function getSubscriptionSettings()
11299
    {
11300
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11301
        if (empty($subscriptionSettings)) {
11302
            // By default allow both settings
11303
            $subscriptionSettings = [
11304
                'allow_add_users_to_lp' => true,
11305
                'allow_add_users_to_lp_category' => true,
11306
            ];
11307
        } else {
11308
            $subscriptionSettings = $subscriptionSettings['options'];
11309
        }
11310
11311
        return $subscriptionSettings;
11312
    }
11313
11314
    /**
11315
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11316
     */
11317
    public function exportToCourseBuildFormat()
11318
    {
11319
        if (!api_is_allowed_to_edit()) {
11320
            return false;
11321
        }
11322
11323
        $courseBuilder = new CourseBuilder();
11324
        $itemList = [];
11325
        /** @var learnpathItem $item */
11326
        foreach ($this->items as $item) {
11327
            $itemList[$item->get_type()][] = $item->get_path();
11328
        }
11329
11330
        if (empty($itemList)) {
11331
            return false;
11332
        }
11333
11334
        if (isset($itemList['document'])) {
11335
            // Get parents
11336
            foreach ($itemList['document'] as $documentId) {
11337
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11338
                if (!empty($documentInfo['parents'])) {
11339
                    foreach ($documentInfo['parents'] as $parentInfo) {
11340
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11341
                            continue;
11342
                        }
11343
                        $itemList['document'][] = $parentInfo['iid'];
11344
                    }
11345
                }
11346
            }
11347
11348
            $courseInfo = api_get_course_info();
11349
            foreach ($itemList['document'] as $documentId) {
11350
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11351
                $items = DocumentManager::get_resources_from_source_html(
11352
                    $documentInfo['absolute_path'],
11353
                    true,
11354
                    TOOL_DOCUMENT
11355
                );
11356
11357
                if (!empty($items)) {
11358
                    foreach ($items as $item) {
11359
                        // Get information about source url
11360
                        $url = $item[0]; // url
11361
                        $scope = $item[1]; // scope (local, remote)
11362
                        $type = $item[2]; // type (rel, abs, url)
11363
11364
                        $origParseUrl = parse_url($url);
11365
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11366
11367
                        if ('local' == $scope) {
11368
                            if ('abs' == $type || 'rel' == $type) {
11369
                                $documentFile = strstr($realOrigPath, 'document');
11370
                                if (false !== strpos($realOrigPath, $documentFile)) {
11371
                                    $documentFile = str_replace('document', '', $documentFile);
11372
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11373
                                    // Document found! Add it to the list
11374
                                    if ($itemDocumentId) {
11375
                                        $itemList['document'][] = $itemDocumentId;
11376
                                    }
11377
                                }
11378
                            }
11379
                        }
11380
                    }
11381
                }
11382
            }
11383
11384
            $courseBuilder->build_documents(
11385
                api_get_session_id(),
11386
                $this->get_course_int_id(),
11387
                true,
11388
                $itemList['document']
11389
            );
11390
        }
11391
11392
        if (isset($itemList['quiz'])) {
11393
            $courseBuilder->build_quizzes(
11394
                api_get_session_id(),
11395
                $this->get_course_int_id(),
11396
                true,
11397
                $itemList['quiz']
11398
            );
11399
        }
11400
11401
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11402
11403
        /*if (!empty($itemList['thread'])) {
11404
            $postList = [];
11405
            foreach ($itemList['thread'] as $postId) {
11406
                $post = get_post_information($postId);
11407
                if ($post) {
11408
                    if (!isset($itemList['forum'])) {
11409
                        $itemList['forum'] = [];
11410
                    }
11411
                    $itemList['forum'][] = $post['forum_id'];
11412
                    $postList[] = $postId;
11413
                }
11414
            }
11415
11416
            if (!empty($postList)) {
11417
                $courseBuilder->build_forum_posts(
11418
                    $this->get_course_int_id(),
11419
                    null,
11420
                    null,
11421
                    $postList
11422
                );
11423
            }
11424
        }*/
11425
11426
        if (!empty($itemList['thread'])) {
11427
            $threadList = [];
11428
            $em = Database::getManager();
11429
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11430
            foreach ($itemList['thread'] as $threadId) {
11431
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11432
                $thread = $repo->find($threadId);
11433
                if ($thread) {
11434
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11435
                    $threadList[] = $thread->getIid();
11436
                }
11437
            }
11438
11439
            if (!empty($threadList)) {
11440
                $courseBuilder->build_forum_topics(
11441
                    api_get_session_id(),
11442
                    $this->get_course_int_id(),
11443
                    null,
11444
                    $threadList
11445
                );
11446
            }
11447
        }
11448
11449
        $forumCategoryList = [];
11450
        if (isset($itemList['forum'])) {
11451
            foreach ($itemList['forum'] as $forumId) {
11452
                $forumInfo = get_forums($forumId);
11453
                $forumCategoryList[] = $forumInfo['forum_category'];
11454
            }
11455
        }
11456
11457
        if (!empty($forumCategoryList)) {
11458
            $courseBuilder->build_forum_category(
11459
                api_get_session_id(),
11460
                $this->get_course_int_id(),
11461
                true,
11462
                $forumCategoryList
11463
            );
11464
        }
11465
11466
        if (!empty($itemList['forum'])) {
11467
            $courseBuilder->build_forums(
11468
                api_get_session_id(),
11469
                $this->get_course_int_id(),
11470
                true,
11471
                $itemList['forum']
11472
            );
11473
        }
11474
11475
        if (isset($itemList['link'])) {
11476
            $courseBuilder->build_links(
11477
                api_get_session_id(),
11478
                $this->get_course_int_id(),
11479
                true,
11480
                $itemList['link']
11481
            );
11482
        }
11483
11484
        if (!empty($itemList['student_publication'])) {
11485
            $courseBuilder->build_works(
11486
                api_get_session_id(),
11487
                $this->get_course_int_id(),
11488
                true,
11489
                $itemList['student_publication']
11490
            );
11491
        }
11492
11493
        $courseBuilder->build_learnpaths(
11494
            api_get_session_id(),
11495
            $this->get_course_int_id(),
11496
            true,
11497
            [$this->get_id()],
11498
            false
11499
        );
11500
11501
        $courseBuilder->restoreDocumentsFromList();
11502
11503
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11504
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11505
        $result = DocumentManager::file_send_for_download(
11506
            $zipPath,
11507
            true,
11508
            $this->get_name().'.zip'
11509
        );
11510
11511
        if ($result) {
11512
            api_not_allowed();
11513
        }
11514
11515
        return true;
11516
    }
11517
11518
    /**
11519
     * Get whether this is a learning path with the accumulated work time or not.
11520
     *
11521
     * @return int
11522
     */
11523
    public function getAccumulateWorkTime()
11524
    {
11525
        return (int) $this->accumulateWorkTime;
11526
    }
11527
11528
    /**
11529
     * Get whether this is a learning path with the accumulated work time or not.
11530
     *
11531
     * @return int
11532
     */
11533
    public function getAccumulateWorkTimeTotalCourse()
11534
    {
11535
        $table = Database::get_course_table(TABLE_LP_MAIN);
11536
        $sql = "SELECT SUM(accumulate_work_time) AS total
11537
                FROM $table
11538
                WHERE c_id = ".$this->course_int_id;
11539
        $result = Database::query($sql);
11540
        $row = Database::fetch_array($result);
11541
11542
        return (int) $row['total'];
11543
    }
11544
11545
    /**
11546
     * Set whether this is a learning path with the accumulated work time or not.
11547
     *
11548
     * @param int $value (0 = false, 1 = true)
11549
     *
11550
     * @return bool
11551
     */
11552
    public function setAccumulateWorkTime($value)
11553
    {
11554
        if (!api_get_configuration_value('lp_minimum_time')) {
11555
            return false;
11556
        }
11557
11558
        $this->accumulateWorkTime = (int) $value;
11559
        $table = Database::get_course_table(TABLE_LP_MAIN);
11560
        $lp_id = $this->get_id();
11561
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
11562
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
11563
        Database::query($sql);
11564
11565
        return true;
11566
    }
11567
11568
    /**
11569
     * @param int $lpId
11570
     * @param int $courseId
11571
     *
11572
     * @return mixed
11573
     */
11574
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11575
    {
11576
        $lpId = (int) $lpId;
11577
        $courseId = (int) $courseId;
11578
11579
        $table = Database::get_course_table(TABLE_LP_MAIN);
11580
        $sql = "SELECT accumulate_work_time
11581
                FROM $table
11582
                WHERE c_id = $courseId AND id = $lpId";
11583
        $result = Database::query($sql);
11584
        $row = Database::fetch_array($result);
11585
11586
        return $row['accumulate_work_time'];
11587
    }
11588
11589
    /**
11590
     * @param int $courseId
11591
     *
11592
     * @return int
11593
     */
11594
    public static function getAccumulateWorkTimeTotal($courseId)
11595
    {
11596
        $table = Database::get_course_table(TABLE_LP_MAIN);
11597
        $courseId = (int) $courseId;
11598
        $sql = "SELECT SUM(accumulate_work_time) AS total
11599
                FROM $table
11600
                WHERE c_id = $courseId";
11601
        $result = Database::query($sql);
11602
        $row = Database::fetch_array($result);
11603
11604
        return (int) $row['total'];
11605
    }
11606
11607
    /**
11608
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11609
     * and put the images in.
11610
     *
11611
     * @return array
11612
     */
11613
    public static function getIconSelect()
11614
    {
11615
        $theme = api_get_visual_theme();
11616
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11617
        $icons = ['' => get_lang('Please select an option')];
11618
11619
        if (is_dir($path)) {
11620
            $finder = new Finder();
11621
            $finder->files()->in($path);
11622
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11623
            /** @var SplFileInfo $file */
11624
            foreach ($finder as $file) {
11625
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11626
                    $icons[$file->getFilename()] = $file->getFilename();
11627
                }
11628
            }
11629
        }
11630
11631
        return $icons;
11632
    }
11633
11634
    /**
11635
     * @param int $lpId
11636
     *
11637
     * @return string
11638
     */
11639
    public static function getSelectedIcon($lpId)
11640
    {
11641
        $extraFieldValue = new ExtraFieldValue('lp');
11642
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11643
        $icon = '';
11644
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11645
            $icon = $lpIcon['value'];
11646
        }
11647
11648
        return $icon;
11649
    }
11650
11651
    /**
11652
     * @param int $lpId
11653
     *
11654
     * @return string
11655
     */
11656
    public static function getSelectedIconHtml($lpId)
11657
    {
11658
        $icon = self::getSelectedIcon($lpId);
11659
11660
        if (empty($icon)) {
11661
            return '';
11662
        }
11663
11664
        $theme = api_get_visual_theme();
11665
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11666
11667
        return Display::img($path);
11668
    }
11669
11670
    /**
11671
     * @param string $value
11672
     *
11673
     * @return string
11674
     */
11675
    public function cleanItemTitle($value)
11676
    {
11677
        $value = Security::remove_XSS(strip_tags($value));
11678
11679
        return $value;
11680
    }
11681
11682
    public function setItemTitle(FormValidator $form)
11683
    {
11684
        if (api_get_configuration_value('save_titles_as_html')) {
11685
            $form->addHtmlEditor(
11686
                'title',
11687
                get_lang('Title'),
11688
                true,
11689
                false,
11690
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11691
            );
11692
        } else {
11693
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11694
            $form->applyFilter('title', 'trim');
11695
            $form->applyFilter('title', 'html_filter');
11696
        }
11697
    }
11698
11699
    /**
11700
     * @return array
11701
     */
11702
    public function getItemsForForm($addParentCondition = false)
11703
    {
11704
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11705
        $course_id = api_get_course_int_id();
11706
11707
        $sql = "SELECT * FROM $tbl_lp_item
11708
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11709
11710
        if ($addParentCondition) {
11711
            $sql .= ' AND parent_item_id = 0 ';
11712
        }
11713
        $sql .= ' ORDER BY display_order ASC';
11714
11715
        $result = Database::query($sql);
11716
        $arrLP = [];
11717
        while ($row = Database::fetch_array($result)) {
11718
            $arrLP[] = [
11719
                'iid' => $row['iid'],
11720
                'id' => $row['iid'],
11721
                'item_type' => $row['item_type'],
11722
                'title' => $this->cleanItemTitle($row['title']),
11723
                'title_raw' => $row['title'],
11724
                'path' => $row['path'],
11725
                'description' => Security::remove_XSS($row['description']),
11726
                'parent_item_id' => $row['parent_item_id'],
11727
                'previous_item_id' => $row['previous_item_id'],
11728
                'next_item_id' => $row['next_item_id'],
11729
                'display_order' => $row['display_order'],
11730
                'max_score' => $row['max_score'],
11731
                'min_score' => $row['min_score'],
11732
                'mastery_score' => $row['mastery_score'],
11733
                'prerequisite' => $row['prerequisite'],
11734
                'max_time_allowed' => $row['max_time_allowed'],
11735
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11736
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11737
            ];
11738
        }
11739
11740
        return $arrLP;
11741
    }
11742
11743
    /**
11744
     * Gets whether this SCORM learning path has been marked to use the score
11745
     * as progress. Takes into account whether the learnpath matches (SCORM
11746
     * content + less than 2 items).
11747
     *
11748
     * @return bool True if the score should be used as progress, false otherwise
11749
     */
11750
    public function getUseScoreAsProgress()
11751
    {
11752
        // If not a SCORM, we don't care about the setting
11753
        if (2 != $this->get_type()) {
11754
            return false;
11755
        }
11756
        // If more than one step in the SCORM, we don't care about the setting
11757
        if ($this->get_total_items_count() > 1) {
11758
            return false;
11759
        }
11760
        $extraFieldValue = new ExtraFieldValue('lp');
11761
        $doUseScore = false;
11762
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
11763
        if (!empty($useScore) && isset($useScore['value'])) {
11764
            $doUseScore = $useScore['value'];
11765
        }
11766
11767
        return $doUseScore;
11768
    }
11769
11770
    /**
11771
     * Get the user identifier (user_id or username
11772
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11773
     *
11774
     * @return string User ID or username, depending on configuration setting
11775
     */
11776
    public static function getUserIdentifierForExternalServices()
11777
    {
11778
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11779
            return api_get_user_info(api_get_user_id())['username'];
11780
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11781
            $extraFieldValue = new ExtraFieldValue('user');
11782
            $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'));
11783
11784
            return $extrafield['value'];
11785
        } else {
11786
            return api_get_user_id();
11787
        }
11788
    }
11789
11790
    /**
11791
     * Save the new order for learning path items.
11792
     *
11793
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11794
     *
11795
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11796
     * @param int   $courseId
11797
     */
11798
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11799
    {
11800
        $courseId = $courseId ?: api_get_course_int_id();
11801
        $itemList = new LpItemOrderList();
11802
11803
        foreach ($orderList as $id => $parentId) {
11804
            $item = new LpOrderItem($id, $parentId);
11805
            $itemList->add($item);
11806
        }
11807
11808
        $parents = $itemList->getListOfParents();
11809
11810
        foreach ($parents as $parentId) {
11811
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11812
            $previous_item_id = 0;
11813
            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...
11814
                $item_id = $sameParentLpItemList->list[$i]->id;
11815
                // display_order
11816
                $display_order = $i + 1;
11817
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11818
                // previous_item_id
11819
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11820
                $previous_item_id = $item_id;
11821
                // next_item_id
11822
                $next_item_id = 0;
11823
                if ($i < count($sameParentLpItemList->list) - 1) {
11824
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11825
                }
11826
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11827
            }
11828
        }
11829
11830
        $table = Database::get_course_table(TABLE_LP_ITEM);
11831
11832
        foreach ($itemList->list as $item) {
11833
            $params = [];
11834
            $params['display_order'] = $item->display_order;
11835
            $params['previous_item_id'] = $item->previous_item_id;
11836
            $params['next_item_id'] = $item->next_item_id;
11837
            $params['parent_item_id'] = $item->parent_item_id;
11838
11839
            Database::update(
11840
                $table,
11841
                $params,
11842
                [
11843
                    'iid = ? AND c_id = ? ' => [
11844
                        (int) $item->id,
11845
                        (int) $courseId,
11846
                    ],
11847
                ]
11848
            );
11849
        }
11850
    }
11851
11852
    /**
11853
     * Get the depth level of LP item.
11854
     *
11855
     * @param array $items
11856
     * @param int   $currentItemId
11857
     *
11858
     * @return int
11859
     */
11860
    private static function get_level_for_item($items, $currentItemId)
11861
    {
11862
        $parentItemId = 0;
11863
        if (isset($items[$currentItemId])) {
11864
            $parentItemId = $items[$currentItemId]->parent;
11865
        }
11866
11867
        if (0 == $parentItemId) {
11868
            return 0;
11869
        } else {
11870
            return self::get_level_for_item($items, $parentItemId) + 1;
11871
        }
11872
    }
11873
11874
    /**
11875
     * Generate the link for a learnpath category as course tool.
11876
     *
11877
     * @param int $categoryId
11878
     *
11879
     * @return string
11880
     */
11881
    private static function getCategoryLinkForTool($categoryId)
11882
    {
11883
        $categoryId = (int) $categoryId;
11884
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11885
            .http_build_query(
11886
                [
11887
                    'action' => 'view_category',
11888
                    'id' => $categoryId,
11889
                ]
11890
            );
11891
11892
        return $link;
11893
    }
11894
11895
    /**
11896
     * Return the scorm item type object with spaces replaced with _
11897
     * The return result is use to build a css classname like scorm_type_$return.
11898
     *
11899
     * @param $in_type
11900
     *
11901
     * @return mixed
11902
     */
11903
    private static function format_scorm_type_item($in_type)
11904
    {
11905
        return str_replace(' ', '_', $in_type);
11906
    }
11907
11908
    /**
11909
     * Check and obtain the lp final item if exist.
11910
     *
11911
     * @return learnpathItem
11912
     */
11913
    private function getFinalItem()
11914
    {
11915
        if (empty($this->items)) {
11916
            return null;
11917
        }
11918
11919
        foreach ($this->items as $item) {
11920
            if ('final_item' !== $item->type) {
11921
                continue;
11922
            }
11923
11924
            return $item;
11925
        }
11926
    }
11927
11928
    /**
11929
     * Get the LP Final Item Template.
11930
     *
11931
     * @return string
11932
     */
11933
    private function getFinalItemTemplate()
11934
    {
11935
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11936
    }
11937
11938
    /**
11939
     * Get the LP Final Item Url.
11940
     *
11941
     * @return string
11942
     */
11943
    private function getSavedFinalItem()
11944
    {
11945
        $finalItem = $this->getFinalItem();
11946
11947
        $repo = Container::getDocumentRepository();
11948
        /** @var CDocument $document */
11949
        $document = $repo->find($finalItem->path);
11950
11951
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11952
            return  $repo->getResourceFileContent($document);
11953
        }
11954
11955
        return '';
11956
    }
11957
}
11958