Completed
Push — master ( c65fe1...e7429e )
by Julito
11:34
created

learnpath::get_navigation_bar()   C

Complexity

Conditions 10
Paths 160

Size

Total Lines 73
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 45
nc 160
nop 1
dl 0
loc 73
rs 6.9333
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\ResourceLink;
6
use Chamilo\CoreBundle\Entity\User;
7
use Chamilo\CoreBundle\Framework\Container;
8
use Chamilo\CoreBundle\Repository\CourseRepository;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
12
use Chamilo\CourseBundle\Entity\CDocument;
13
use Chamilo\CourseBundle\Entity\CLink;
14
use Chamilo\CourseBundle\Entity\CLp;
15
use Chamilo\CourseBundle\Entity\CLpCategory;
16
use Chamilo\CourseBundle\Entity\CLpItem;
17
use Chamilo\CourseBundle\Entity\CLpItemView;
18
use Chamilo\CourseBundle\Entity\CQuiz;
19
use Chamilo\CourseBundle\Entity\CShortcut;
20
use Chamilo\CourseBundle\Entity\CStudentPublication;
21
use Chamilo\CourseBundle\Entity\CTool;
22
use ChamiloSession as Session;
23
use Gedmo\Sortable\Entity\Repository\SortableRepository;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Symfony\Component\Finder\Finder;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
28
/**
29
 * Class learnpath
30
 * This class defines the parent attributes and methods for Chamilo learnpaths
31
 * and SCORM learnpaths. It is used by the scorm class.
32
 *
33
 * @todo decouple class
34
 *
35
 * @author  Yannick Warnier <[email protected]>
36
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
37
 */
38
class learnpath
39
{
40
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
41
    public const STATUS_CSS_CLASS_NAME = [
42
        'not attempted' => 'scorm_not_attempted',
43
        'incomplete' => 'scorm_not_attempted',
44
        'failed' => 'scorm_failed',
45
        'completed' => 'scorm_completed',
46
        'passed' => 'scorm_completed',
47
        'succeeded' => 'scorm_completed',
48
        'browsed' => 'scorm_completed',
49
    ];
50
51
    public $attempt = 0; // The number for the current ID view.
52
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
53
    public $current; // Id of the current item the user is viewing.
54
    public $current_score; // The score of the current item.
55
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
56
    public $current_time_stop; // The time the user closed this resource.
57
    public $default_status = 'not attempted';
58
    public $encoding = 'UTF-8';
59
    public $error = '';
60
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
61
    public $index; // The index of the active learnpath_item in $ordered_items array.
62
    /** @var learnpathItem[] */
63
    public $items = [];
64
    public $last; // item_id of last item viewed in the learning path.
65
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
66
    public $license; // Which license this course has been given - not used yet on 20060522.
67
    public $lp_id; // DB iid for this learnpath.
68
    public $lp_view_id; // DB ID for lp_view
69
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
70
    public $message = '';
71
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
72
    public $name; // Learnpath name (they generally have one).
73
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
74
    public $path = ''; // Path inside the scorm directory (if scorm).
75
    public $theme; // The current theme of the learning path.
76
    public $preview_image; // The current image of the learning path.
77
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
78
    public $accumulateWorkTime; // The min time of learnpath
79
80
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
81
    public $prevent_reinit = 1;
82
83
    // Describes the mode of progress bar display.
84
    public $seriousgame_mode = 0;
85
    public $progress_bar_mode = '%';
86
87
    // Percentage progress as saved in the db.
88
    public $progress_db = 0;
89
    public $proximity; // Wether the content is distant or local or unknown.
90
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
91
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
92
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
93
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
94
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
95
    public $user_id; //ID of the user that is viewing/using the course
96
    public $update_queue = [];
97
    public $scorm_debug = 0;
98
    public $arrMenu = []; // Array for the menu items.
99
    public $debug = 0; // Logging level.
100
    public $lp_session_id = 0;
101
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
102
    public $prerequisite = 0;
103
    public $use_max_score = 1; // 1 or 0
104
    public $subscribeUsers = 0; // Subscribe users or not
105
    public $created_on = '';
106
    public $modified_on = '';
107
    public $publicated_on = '';
108
    public $expired_on = '';
109
    public $ref = null;
110
    public $course_int_id;
111
    public $course_info = [];
112
    public $categoryId;
113
    public $entity;
114
115
    /**
116
     * Constructor.
117
     * Needs a database handler, a course code and a learnpath id from the database.
118
     * Also builds the list of items into $this->items.
119
     *
120
     * @param string $course  Course code
121
     * @param int    $lp_id   c_lp.iid
122
     * @param int    $user_id
123
     */
124
    public function __construct($course, $lp_id, $user_id)
125
    {
126
        $debug = $this->debug;
127
        $this->encoding = api_get_system_encoding();
128
        if (empty($course)) {
129
            $course = api_get_course_id();
130
        }
131
        $course_info = api_get_course_info($course);
132
        if (!empty($course_info)) {
133
            $this->cc = $course_info['code'];
134
            $this->course_info = $course_info;
135
            $course_id = $course_info['real_id'];
136
        } else {
137
            $this->error = 'Course code does not exist in database.';
138
        }
139
140
        $lp_id = (int) $lp_id;
141
        $course_id = (int) $course_id;
142
        $this->set_course_int_id($course_id);
143
        // Check learnpath ID.
144
        if (empty($lp_id) || empty($course_id)) {
145
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
146
        } else {
147
            $repo = Container::getLpRepository();
148
            /** @var CLp $entity */
149
            $entity = $repo->find($lp_id);
150
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
151
                $this->entity = $entity;
152
                $this->lp_id = $lp_id;
153
                $this->type = $entity->getLpType();
154
                $this->name = stripslashes($entity->getName());
155
                $this->proximity = $entity->getContentLocal();
156
                $this->theme = $entity->getTheme();
157
                $this->maker = $entity->getContentLocal();
158
                $this->prevent_reinit = $entity->getPreventReinit();
159
                $this->seriousgame_mode = $entity->getSeriousgameMode();
160
                $this->license = $entity->getContentLicense();
161
                $this->scorm_debug = $entity->getDebug();
162
                $this->js_lib = $entity->getJsLib();
163
                $this->path = $entity->getPath();
164
                $this->preview_image = $entity->getPreviewImage();
165
                $this->author = $entity->getAuthor();
166
                $this->hide_toc_frame = $entity->getHideTocFrame();
167
                $this->lp_session_id = $entity->getSessionId();
168
                $this->use_max_score = $entity->getUseMaxScore();
169
                $this->subscribeUsers = $entity->getSubscribeUsers();
170
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
171
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
172
                $this->ref = $entity->getRef();
173
                $this->categoryId = $entity->getCategoryId();
174
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
175
176
                if (!empty($entity->getPublicatedOn())) {
177
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
178
                }
179
180
                if (!empty($entity->getExpiredOn())) {
181
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
182
                }
183
                if (2 == $this->type) {
184
                    if (1 == $entity->getForceCommit()) {
185
                        $this->force_commit = true;
186
                    }
187
                }
188
                $this->mode = $entity->getDefaultViewMod();
189
190
                // Check user ID.
191
                if (empty($user_id)) {
192
                    $this->error = 'User ID is empty';
193
                } else {
194
                    $userInfo = api_get_user_info($user_id);
195
                    if (!empty($userInfo)) {
196
                        $this->user_id = $userInfo['user_id'];
197
                    } else {
198
                        $this->error = 'User ID does not exist in database #'.$user_id;
199
                    }
200
                }
201
202
                // End of variables checking.
203
                $session_id = api_get_session_id();
204
                //  Get the session condition for learning paths of the base + session.
205
                $session = api_get_session_condition($session_id);
206
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
207
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
208
209
                // Selecting by view_count descending allows to get the highest view_count first.
210
                $sql = "SELECT * FROM $lp_table
211
                        WHERE
212
                            c_id = $course_id AND
213
                            lp_id = $lp_id AND
214
                            user_id = $user_id
215
                            $session
216
                        ORDER BY view_count DESC";
217
                $res = Database::query($sql);
218
219
                if (Database::num_rows($res) > 0) {
220
                    $row = Database::fetch_array($res);
221
                    $this->attempt = $row['view_count'];
222
                    $this->lp_view_id = $row['id'];
223
                    $this->last_item_seen = $row['last_item'];
224
                    $this->progress_db = $row['progress'];
225
                    $this->lp_view_session_id = $row['session_id'];
226
                } elseif (!api_is_invitee()) {
227
                    $this->attempt = 1;
228
                    $params = [
229
                        'c_id' => $course_id,
230
                        'lp_id' => $lp_id,
231
                        'user_id' => $user_id,
232
                        'view_count' => 1,
233
                        'session_id' => $session_id,
234
                        'last_item' => 0,
235
                    ];
236
                    $this->last_item_seen = 0;
237
                    $this->lp_view_session_id = $session_id;
238
                    $this->lp_view_id = Database::insert($lp_table, $params);
239
                }
240
241
                // Initialise items.
242
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
243
                $sql = "SELECT * FROM $lp_item_table
244
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
245
                        ORDER BY parent_item_id, display_order";
246
                $res = Database::query($sql);
247
248
                $lp_item_id_list = [];
249
                while ($row = Database::fetch_array($res)) {
250
                    $lp_item_id_list[] = $row['iid'];
251
                    switch ($this->type) {
252
                        case 3: //aicc
253
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
254
                            if (is_object($oItem)) {
255
                                $my_item_id = $oItem->get_id();
256
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
257
                                $oItem->set_prevent_reinit($this->prevent_reinit);
258
                                // Don't use reference here as the next loop will make the pointed object change.
259
                                $this->items[$my_item_id] = $oItem;
260
                                $this->refs_list[$oItem->ref] = $my_item_id;
261
                            }
262
                            break;
263
                        case 2:
264
                            $oItem = new scormItem('db', $row['iid'], $course_id);
265
                            if (is_object($oItem)) {
266
                                $my_item_id = $oItem->get_id();
267
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
268
                                $oItem->set_prevent_reinit($this->prevent_reinit);
269
                                // Don't use reference here as the next loop will make the pointed object change.
270
                                $this->items[$my_item_id] = $oItem;
271
                                $this->refs_list[$oItem->ref] = $my_item_id;
272
                            }
273
                            break;
274
                        case 1:
275
                        default:
276
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
277
                            if (is_object($oItem)) {
278
                                $my_item_id = $oItem->get_id();
279
                                // Moved down to when we are sure the item_view exists.
280
                                //$oItem->set_lp_view($this->lp_view_id);
281
                                $oItem->set_prevent_reinit($this->prevent_reinit);
282
                                // Don't use reference here as the next loop will make the pointed object change.
283
                                $this->items[$my_item_id] = $oItem;
284
                                $this->refs_list[$my_item_id] = $my_item_id;
285
                            }
286
                            break;
287
                    }
288
289
                    // Setting the object level with variable $this->items[$i][parent]
290
                    foreach ($this->items as $itemLPObject) {
291
                        $level = self::get_level_for_item(
292
                            $this->items,
293
                            $itemLPObject->db_id
294
                        );
295
                        $itemLPObject->level = $level;
296
                    }
297
298
                    // Setting the view in the item object.
299
                    if (is_object($this->items[$row['iid']])) {
300
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
301
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
302
                            $this->items[$row['iid']]->current_start_time = 0;
303
                            $this->items[$row['iid']]->current_stop_time = 0;
304
                        }
305
                    }
306
                }
307
308
                if (!empty($lp_item_id_list)) {
309
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
310
                    if (!empty($lp_item_id_list_to_string)) {
311
                        // Get last viewing vars.
312
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
313
                        // This query should only return one or zero result.
314
                        $sql = "SELECT lp_item_id, status
315
                                FROM $itemViewTable
316
                                WHERE
317
                                    c_id = $course_id AND
318
                                    lp_view_id = ".$this->get_view_id()." AND
319
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
320
                                ORDER BY view_count DESC ";
321
                        $status_list = [];
322
                        $res = Database::query($sql);
323
                        while ($row = Database:: fetch_array($res)) {
324
                            $status_list[$row['lp_item_id']] = $row['status'];
325
                        }
326
327
                        foreach ($lp_item_id_list as $item_id) {
328
                            if (isset($status_list[$item_id])) {
329
                                $status = $status_list[$item_id];
330
                                if (is_object($this->items[$item_id])) {
331
                                    $this->items[$item_id]->set_status($status);
332
                                    if (empty($status)) {
333
                                        $this->items[$item_id]->set_status(
334
                                            $this->default_status
335
                                        );
336
                                    }
337
                                }
338
                            } else {
339
                                if (!api_is_invitee()) {
340
                                    if (is_object($this->items[$item_id])) {
341
                                        $this->items[$item_id]->set_status(
342
                                            $this->default_status
343
                                        );
344
                                    }
345
346
                                    if (!empty($this->lp_view_id)) {
347
                                        // Add that row to the lp_item_view table so that
348
                                        // we have something to show in the stats page.
349
                                        $params = [
350
                                            'c_id' => $course_id,
351
                                            'lp_item_id' => $item_id,
352
                                            'lp_view_id' => $this->lp_view_id,
353
                                            'view_count' => 1,
354
                                            'status' => 'not attempted',
355
                                            'start_time' => time(),
356
                                            'total_time' => 0,
357
                                            'score' => 0,
358
                                        ];
359
                                        $insertId = Database::insert($itemViewTable, $params);
360
361
                                        $this->items[$item_id]->set_lp_view(
362
                                            $this->lp_view_id,
363
                                            $course_id
364
                                        );
365
                                    }
366
                                }
367
                            }
368
                        }
369
                    }
370
                }
371
372
                $this->ordered_items = self::get_flat_ordered_items_list(
373
                    $this->get_id(),
374
                    0,
375
                    $course_id
376
                );
377
                $this->max_ordered_items = 0;
378
                foreach ($this->ordered_items as $index => $dummy) {
379
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
380
                        $this->max_ordered_items = $index;
381
                    }
382
                }
383
                // TODO: Define the current item better.
384
                $this->first();
385
                if ($debug) {
386
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
387
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
388
                }
389
            } else {
390
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
391
            }
392
        }
393
    }
394
395
    public function getEntity(): CLp
396
    {
397
        return $this->entity;
398
    }
399
400
    /**
401
     * @return string
402
     */
403
    public function getCourseCode()
404
    {
405
        return $this->cc;
406
    }
407
408
    /**
409
     * @return int
410
     */
411
    public function get_course_int_id()
412
    {
413
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
414
    }
415
416
    /**
417
     * @param $course_id
418
     *
419
     * @return int
420
     */
421
    public function set_course_int_id($course_id)
422
    {
423
        return $this->course_int_id = (int) $course_id;
424
    }
425
426
    /**
427
     * Function rewritten based on old_add_item() from Yannick Warnier.
428
     * Due the fact that users can decide where the item should come, I had to overlook this function and
429
     * I found it better to rewrite it. Old function is still available.
430
     * Added also the possibility to add a description.
431
     *
432
     * @param int    $parent
433
     * @param int    $previous
434
     * @param string $type
435
     * @param int    $id               resource ID (ref)
436
     * @param string $title
437
     * @param string $description
438
     * @param int    $prerequisites
439
     * @param int    $max_time_allowed
440
     * @param int    $userId
441
     *
442
     * @return int
443
     */
444
    public function add_item(
445
        $parent,
446
        $previous,
447
        $type = 'dir',
448
        $id,
449
        $title,
450
        $description,
451
        $prerequisites = 0,
452
        $max_time_allowed = 0,
453
        $userId = 0
454
    ) {
455
        $course_id = $this->course_info['real_id'];
456
        if (empty($course_id)) {
457
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
458
            $this->course_info = api_get_course_info($this->cc);
459
            $course_id = $this->course_info['real_id'];
460
        }
461
        $userId = empty($userId) ? api_get_user_id() : $userId;
462
        $sessionId = api_get_session_id();
463
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
464
        $_course = $this->course_info;
465
        $parent = (int) $parent;
466
        $previous = (int) $previous;
467
        $id = (int) $id;
468
        $max_time_allowed = htmlentities($max_time_allowed);
469
        if (empty($max_time_allowed)) {
470
            $max_time_allowed = 0;
471
        }
472
        $sql = "SELECT COUNT(iid) AS num
473
                FROM $tbl_lp_item
474
                WHERE
475
                    c_id = $course_id AND
476
                    lp_id = ".$this->get_id()." AND
477
                    parent_item_id = $parent ";
478
479
        $res_count = Database::query($sql);
480
        $row = Database::fetch_array($res_count);
481
        $num = $row['num'];
482
483
        $tmp_previous = 0;
484
        $display_order = 0;
485
        $next = 0;
486
        if ($num > 0) {
487
            if (empty($previous)) {
488
                $sql = "SELECT iid, next_item_id, display_order
489
                        FROM $tbl_lp_item
490
                        WHERE
491
                            c_id = $course_id AND
492
                            lp_id = ".$this->get_id()." AND
493
                            parent_item_id = $parent AND
494
                            previous_item_id = 0 OR
495
                            previous_item_id = $parent";
496
                $result = Database::query($sql);
497
                $row = Database::fetch_array($result);
498
                if ($row) {
499
                    $next = $row['iid'];
500
                }
501
            } else {
502
                $previous = (int) $previous;
503
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
504
						FROM $tbl_lp_item
505
                        WHERE
506
                            c_id = $course_id AND
507
                            lp_id = ".$this->get_id()." AND
508
                            id = $previous";
509
                $result = Database::query($sql);
510
                $row = Database::fetch_array($result);
511
                if ($row) {
512
                    $tmp_previous = $row['iid'];
513
                    $next = $row['next_item_id'];
514
                    $display_order = $row['display_order'];
515
                }
516
            }
517
        }
518
519
        $id = (int) $id;
520
        $typeCleaned = Database::escape_string($type);
521
        $max_score = 100;
522
        if ($type === 'quiz' && $id) {
523
            $sql = 'SELECT SUM(ponderation)
524
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
525
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
526
                    ON
527
                        quiz_question.id = quiz_rel_question.question_id AND
528
                        quiz_question.c_id = quiz_rel_question.c_id
529
                    WHERE
530
                        quiz_rel_question.exercice_id = '.$id." AND
531
                        quiz_question.c_id = $course_id AND
532
                        quiz_rel_question.c_id = $course_id ";
533
            $rsQuiz = Database::query($sql);
534
            $max_score = Database::result($rsQuiz, 0, 0);
535
536
            // Disabling the exercise if we add it inside a LP
537
            $exercise = new Exercise($course_id);
538
            $exercise->read($id);
539
            $exercise->disable();
540
            $exercise->save();
541
        }
542
543
        $params = [
544
            'c_id' => $course_id,
545
            'lp_id' => $this->get_id(),
546
            'item_type' => $typeCleaned,
547
            'ref' => '',
548
            'title' => $title,
549
            'description' => $description,
550
            'path' => $id,
551
            'max_score' => $max_score,
552
            'parent_item_id' => $parent,
553
            'previous_item_id' => $previous,
554
            'next_item_id' => (int) $next,
555
            'display_order' => $display_order + 1,
556
            'prerequisite' => $prerequisites,
557
            'max_time_allowed' => $max_time_allowed,
558
            'min_score' => 0,
559
            'launch_data' => '',
560
        ];
561
562
        if (0 != $prerequisites) {
563
            $params['prerequisite'] = $prerequisites;
564
        }
565
566
        $new_item_id = Database::insert($tbl_lp_item, $params);
567
        if ($new_item_id) {
568
            if (!empty($next)) {
569
                $sql = "UPDATE $tbl_lp_item
570
                        SET previous_item_id = $new_item_id
571
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
572
                Database::query($sql);
573
            }
574
575
            // Update the item that should be before the new item.
576
            if (!empty($tmp_previous)) {
577
                $sql = "UPDATE $tbl_lp_item
578
                        SET next_item_id = $new_item_id
579
                        WHERE c_id = $course_id AND id = $tmp_previous";
580
                Database::query($sql);
581
            }
582
583
            // Update all the items after the new item.
584
            $sql = "UPDATE $tbl_lp_item
585
                        SET display_order = display_order + 1
586
                    WHERE
587
                        c_id = $course_id AND
588
                        lp_id = ".$this->get_id()." AND
589
                        iid <> $new_item_id AND
590
                        parent_item_id = $parent AND
591
                        display_order > $display_order";
592
            Database::query($sql);
593
594
            // Update the item that should come after the new item.
595
            $sql = "UPDATE $tbl_lp_item
596
                    SET ref = $new_item_id
597
                    WHERE c_id = $course_id AND iid = $new_item_id";
598
            Database::query($sql);
599
600
            $sql = "UPDATE $tbl_lp_item
601
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
602
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
603
            Database::query($sql);
604
605
            // Upload audio.
606
            if (!empty($_FILES['mp3']['name'])) {
607
                // Create the audio folder if it does not exist yet.
608
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
609
                if (!is_dir($filepath.'audio')) {
610
                    mkdir(
611
                        $filepath.'audio',
612
                        api_get_permissions_for_new_directories()
613
                    );
614
                    $audio_id = DocumentManager::addDocument(
615
                        $_course,
616
                        '/audio',
617
                        'folder',
618
                        0,
619
                        'audio',
620
                        '',
621
                        0,
622
                        true,
623
                        null,
624
                        $sessionId,
625
                        $userId
626
                    );
627
                }
628
629
                $file_path = handle_uploaded_document(
630
                    $_course,
631
                    $_FILES['mp3'],
632
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
633
                    '/audio',
634
                    $userId,
635
                    '',
636
                    '',
637
                    '',
638
                    '',
639
                    false
640
                );
641
642
                // Getting the filename only.
643
                $file_components = explode('/', $file_path);
644
                $file = $file_components[count($file_components) - 1];
645
646
                // Store the mp3 file in the lp_item table.
647
                $sql = "UPDATE $tbl_lp_item SET
648
                          audio = '".Database::escape_string($file)."'
649
                        WHERE iid = '".intval($new_item_id)."'";
650
                Database::query($sql);
651
            }
652
        }
653
654
        return $new_item_id;
655
    }
656
657
    /**
658
     * Static admin function allowing addition of a learnpath to a course.
659
     *
660
     * @param string $courseCode
661
     * @param string $name
662
     * @param string $description
663
     * @param string $learnpath
664
     * @param string $origin
665
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
666
     * @param string $publicated_on
667
     * @param string $expired_on
668
     * @param int    $categoryId
669
     * @param int    $userId
670
     *
671
     * @return int The new learnpath ID on success, 0 on failure
672
     */
673
    public static function add_lp(
674
        $courseCode,
675
        $name,
676
        $description = '',
677
        $learnpath = 'guess',
678
        $origin = 'zip',
679
        $zipname = '',
680
        $publicated_on = '',
681
        $expired_on = '',
682
        $categoryId = 0,
683
        $userId = 0
684
    ) {
685
        global $charset;
686
687
        if (!empty($courseCode)) {
688
            $courseInfo = api_get_course_info($courseCode);
689
            $course_id = $courseInfo['real_id'];
690
        } else {
691
            $course_id = api_get_course_int_id();
692
            $courseInfo = api_get_course_info();
693
        }
694
695
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
696
        // Check course code exists.
697
        // Check lp_name doesn't exist, otherwise append something.
698
        $i = 0;
699
        $categoryId = (int) $categoryId;
700
        // Session id.
701
        $session_id = api_get_session_id();
702
        $userId = empty($userId) ? api_get_user_id() : $userId;
703
704
        if (empty($publicated_on)) {
705
            $publicated_on = null;
706
        } else {
707
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
708
        }
709
710
        if (empty($expired_on)) {
711
            $expired_on = null;
712
        } else {
713
            $expired_on = api_get_utc_datetime($expired_on, true, true);
714
        }
715
716
        $check_name = "SELECT * FROM $tbl_lp
717
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
718
        $res_name = Database::query($check_name);
719
720
        while (Database::num_rows($res_name)) {
721
            // There is already one such name, update the current one a bit.
722
            $i++;
723
            $name = $name.' - '.$i;
724
            $check_name = "SELECT * FROM $tbl_lp
725
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
726
            $res_name = Database::query($check_name);
727
        }
728
        // New name does not exist yet; keep it.
729
        // Escape description.
730
        // Kevin: added htmlentities().
731
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
732
        $type = 1;
733
        switch ($learnpath) {
734
            case 'guess':
735
            case 'aicc':
736
                break;
737
            case 'dokeos':
738
            case 'chamilo':
739
                $type = 1;
740
                break;
741
        }
742
743
        $id = null;
744
        $sessionEntity = api_get_session_entity();
745
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
746
747
        switch ($origin) {
748
            case 'zip':
749
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
750
                break;
751
            case 'manual':
752
            default:
753
                $get_max = "SELECT MAX(display_order)
754
                            FROM $tbl_lp WHERE c_id = $course_id";
755
                $res_max = Database::query($get_max);
756
                if (Database::num_rows($res_max) < 1) {
757
                    $dsp = 1;
758
                } else {
759
                    $row = Database::fetch_array($res_max);
760
                    $dsp = $row[0] + 1;
761
                }
762
763
                $lp = new CLp();
764
                $lp
765
                    ->setCId($course_id)
766
                    ->setLpType($type)
767
                    ->setName($name)
768
                    ->setDescription($description)
769
                    ->setDisplayOrder($dsp)
770
                    ->setSessionId($session_id)
771
                    ->setCategoryId($categoryId)
772
                    ->setPublicatedOn($publicated_on)
773
                    ->setExpiredOn($expired_on)
774
                    ->setParent($courseEntity)
775
                    ->addCourseLink($courseEntity, $sessionEntity)
776
                ;
777
778
                $repo = Container::getLpRepository();
779
                $em = $repo->getEntityManager();
780
                $em->persist($lp);
781
                $em->flush();
782
783
                if ($lp->getIid()) {
784
                    $id = $lp->getIid();
785
                }
786
787
                // Insert into item_property.
788
                /*api_item_property_update(
789
                    $courseInfo,
790
                    TOOL_LEARNPATH,
791
                    $id,
792
                    'LearnpathAdded',
793
                    $userId
794
                );
795
                api_set_default_visibility(
796
                    $id,
797
                    TOOL_LEARNPATH,
798
                    0,
799
                    $courseInfo,
800
                    $session_id,
801
                    $userId
802
                );*/
803
804
                break;
805
        }
806
807
        return $id;
808
    }
809
810
    /**
811
     * Auto completes the parents of an item in case it's been completed or passed.
812
     *
813
     * @param int $item Optional ID of the item from which to look for parents
814
     */
815
    public function autocomplete_parents($item)
816
    {
817
        $debug = $this->debug;
818
819
        if (empty($item)) {
820
            $item = $this->current;
821
        }
822
823
        $currentItem = $this->getItem($item);
824
        if ($currentItem) {
825
            $parent_id = $currentItem->get_parent();
826
            $parent = $this->getItem($parent_id);
827
            if ($parent) {
828
                // if $item points to an object and there is a parent.
829
                if ($debug) {
830
                    error_log(
831
                        'Autocompleting parent of item '.$item.' '.
832
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
833
                        0
834
                    );
835
                }
836
837
                // New experiment including failed and browsed in completed status.
838
                //$current_status = $currentItem->get_status();
839
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
840
                // Fixes chapter auto complete
841
                if (true) {
842
                    // If the current item is completed or passes or succeeded.
843
                    $updateParentStatus = true;
844
                    if ($debug) {
845
                        error_log('Status of current item is alright');
846
                    }
847
848
                    foreach ($parent->get_children() as $childItemId) {
849
                        $childItem = $this->getItem($childItemId);
850
851
                        // If children was not set try to get the info
852
                        if (empty($childItem->db_item_view_id)) {
853
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
854
                        }
855
856
                        // Check all his brothers (parent's children) for completion status.
857
                        if ($childItemId != $item) {
858
                            if ($debug) {
859
                                error_log(
860
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
861
                                    0
862
                                );
863
                            }
864
                            // Trying completing parents of failed and browsed items as well.
865
                            if ($childItem->status_is(
866
                                [
867
                                    'completed',
868
                                    'passed',
869
                                    'succeeded',
870
                                    'browsed',
871
                                    'failed',
872
                                ]
873
                            )
874
                            ) {
875
                                // Keep completion status to true.
876
                                continue;
877
                            } else {
878
                                if ($debug > 2) {
879
                                    error_log(
880
                                        '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,
881
                                        0
882
                                    );
883
                                }
884
                                $updateParentStatus = false;
885
                                break;
886
                            }
887
                        }
888
                    }
889
890
                    if ($updateParentStatus) {
891
                        // If all the children were completed:
892
                        $parent->set_status('completed');
893
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
894
                        // Force the status to "completed"
895
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
896
                        $this->update_queue[$parent->get_id()] = 'completed';
897
                        if ($debug) {
898
                            error_log(
899
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
900
                                print_r($this->update_queue, 1),
901
                                0
902
                            );
903
                        }
904
                        // Recursive call.
905
                        $this->autocomplete_parents($parent->get_id());
906
                    }
907
                }
908
            } else {
909
                if ($debug) {
910
                    error_log("Parent #$parent_id does not exists");
911
                }
912
            }
913
        } else {
914
            if ($debug) {
915
                error_log("#$item is an item that doesn't have parents");
916
            }
917
        }
918
    }
919
920
    /**
921
     * Closes the current resource.
922
     *
923
     * Stops the timer
924
     * Saves into the database if required
925
     * Clears the current resource data from this object
926
     *
927
     * @return bool True on success, false on failure
928
     */
929
    public function close()
930
    {
931
        if (empty($this->lp_id)) {
932
            $this->error = 'Trying to close this learnpath but no ID is set';
933
934
            return false;
935
        }
936
        $this->current_time_stop = time();
937
        $this->ordered_items = [];
938
        $this->index = 0;
939
        unset($this->lp_id);
940
        //unset other stuff
941
        return true;
942
    }
943
944
    /**
945
     * Static admin function allowing removal of a learnpath.
946
     *
947
     * @param array  $courseInfo
948
     * @param int    $id         Learnpath ID
949
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
950
     *
951
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
952
     */
953
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
954
    {
955
        $course_id = api_get_course_int_id();
956
        if (!empty($courseInfo)) {
957
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
958
        }
959
960
        // TODO: Implement a way of getting this to work when the current object is not set.
961
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
962
        // If an ID is specifically given and the current LP is not the same, prevent delete.
963
        if (!empty($id) && ($id != $this->lp_id)) {
964
            return false;
965
        }
966
967
        $lp = Database::get_course_table(TABLE_LP_MAIN);
968
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
969
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
970
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
971
972
        // Delete lp item id.
973
        foreach ($this->items as $lpItemId => $dummy) {
974
            $sql = "DELETE FROM $lp_item_view
975
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
976
            Database::query($sql);
977
        }
978
979
        // Proposed by Christophe (nickname: clefevre)
980
        $sql = "DELETE FROM $lp_item
981
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
982
        Database::query($sql);
983
984
        $sql = "DELETE FROM $lp_view
985
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
986
        Database::query($sql);
987
988
        self::toggle_publish($this->lp_id, 'i');
989
        //self::toggle_publish($this->lp_id, 'i');
990
991
        if (2 == $this->type || 3 == $this->type) {
992
            // This is a scorm learning path, delete the files as well.
993
            $sql = "SELECT path FROM $lp
994
                    WHERE iid = ".$this->lp_id;
995
            $res = Database::query($sql);
996
            if (Database::num_rows($res) > 0) {
997
                $row = Database::fetch_array($res);
998
                $path = $row['path'];
999
                $sql = "SELECT id FROM $lp
1000
                        WHERE
1001
                            c_id = $course_id AND
1002
                            path = '$path' AND
1003
                            iid != ".$this->lp_id;
1004
                $res = Database::query($sql);
1005
                if (Database::num_rows($res) > 0) {
1006
                    // Another learning path uses this directory, so don't delete it.
1007
                    if ($this->debug > 2) {
1008
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1009
                    }
1010
                } else {
1011
                    // No other LP uses that directory, delete it.
1012
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1013
                    // The absolute system path for this course.
1014
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1015
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1016
                        if ($this->debug > 2) {
1017
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1018
                        }
1019
                        // Proposed by Christophe (clefevre).
1020
                        if (0 == strcmp(substr($path, -2), "/.")) {
1021
                            $path = substr($path, 0, -1); // Remove "." at the end.
1022
                        }
1023
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1024
                        rmdirr($course_scorm_dir.$path);
1025
                    }
1026
                }
1027
            }
1028
        }
1029
1030
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1031
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1032
        // Delete tools
1033
        $sql = "DELETE FROM $tbl_tool
1034
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1035
        Database::query($sql);*/
1036
1037
        /*$sql = "DELETE FROM $lp
1038
                WHERE iid = ".$this->lp_id;
1039
        Database::query($sql);*/
1040
        $repo = Container::getLpRepository();
1041
        $lp = $repo->find($this->lp_id);
1042
        $repo->getEntityManager()->remove($lp);
1043
        $repo->getEntityManager()->flush();
1044
1045
        // Updates the display order of all lps.
1046
        $this->update_display_order();
1047
1048
        /*api_item_property_update(
1049
            api_get_course_info(),
1050
            TOOL_LEARNPATH,
1051
            $this->lp_id,
1052
            'delete',
1053
            api_get_user_id()
1054
        );*/
1055
1056
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1057
            api_get_course_id(),
1058
            4,
1059
            $id,
1060
            api_get_session_id()
1061
        );
1062
1063
        if (false !== $link_info) {
1064
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1065
        }
1066
1067
        if ('true' == api_get_setting('search_enabled')) {
1068
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1069
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1070
        }
1071
    }
1072
1073
    /**
1074
     * Removes all the children of one item - dangerous!
1075
     *
1076
     * @param int $id Element ID of which children have to be removed
1077
     *
1078
     * @return int Total number of children removed
1079
     */
1080
    public function delete_children_items($id)
1081
    {
1082
        $course_id = $this->course_info['real_id'];
1083
1084
        $num = 0;
1085
        $id = (int) $id;
1086
        if (empty($id) || empty($course_id)) {
1087
            return false;
1088
        }
1089
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1090
        $sql = "SELECT * FROM $lp_item
1091
                WHERE c_id = $course_id AND parent_item_id = $id";
1092
        $res = Database::query($sql);
1093
        while ($row = Database::fetch_array($res)) {
1094
            $num += $this->delete_children_items($row['iid']);
1095
            $sql = "DELETE FROM $lp_item
1096
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1097
            Database::query($sql);
1098
            $num++;
1099
        }
1100
1101
        return $num;
1102
    }
1103
1104
    /**
1105
     * Removes an item from the current learnpath.
1106
     *
1107
     * @param int $id Elem ID (0 if first)
1108
     *
1109
     * @return int Number of elements moved
1110
     *
1111
     * @todo implement resource removal
1112
     */
1113
    public function delete_item($id)
1114
    {
1115
        $course_id = api_get_course_int_id();
1116
        $id = (int) $id;
1117
        // TODO: Implement the resource removal.
1118
        if (empty($id) || empty($course_id)) {
1119
            return false;
1120
        }
1121
        // First select item to get previous, next, and display order.
1122
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1123
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1124
        $res_sel = Database::query($sql_sel);
1125
        if (Database::num_rows($res_sel) < 1) {
1126
            return false;
1127
        }
1128
        $row = Database::fetch_array($res_sel);
1129
        $previous = $row['previous_item_id'];
1130
        $next = $row['next_item_id'];
1131
        $display = $row['display_order'];
1132
        $parent = $row['parent_item_id'];
1133
        $lp = $row['lp_id'];
1134
        // Delete children items.
1135
        $this->delete_children_items($id);
1136
        // Now delete the item.
1137
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1138
        Database::query($sql_del);
1139
        // Now update surrounding items.
1140
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1141
                    WHERE iid = $previous";
1142
        Database::query($sql_upd);
1143
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1144
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1145
        Database::query($sql_upd);
1146
        // Now update all following items with new display order.
1147
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1148
                    WHERE
1149
                        c_id = $course_id AND
1150
                        lp_id = $lp AND
1151
                        parent_item_id = $parent AND
1152
                        display_order > $display";
1153
        Database::query($sql_all);
1154
1155
        //Removing prerequisites since the item will not longer exist
1156
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1157
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1158
        Database::query($sql_all);
1159
1160
        $sql = "UPDATE $lp_item
1161
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1162
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1163
        Database::query($sql);
1164
1165
        // Remove from search engine if enabled.
1166
        if ('true' === api_get_setting('search_enabled')) {
1167
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1168
            $sql = 'SELECT * FROM %s
1169
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1170
                    LIMIT 1';
1171
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1172
            $res = Database::query($sql);
1173
            if (Database::num_rows($res) > 0) {
1174
                $row2 = Database::fetch_array($res);
1175
                $di = new ChamiloIndexer();
1176
                $di->remove_document($row2['search_did']);
1177
            }
1178
            $sql = 'DELETE FROM %s
1179
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1180
                    LIMIT 1';
1181
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1182
            Database::query($sql);
1183
        }
1184
    }
1185
1186
    /**
1187
     * Updates an item's content in place.
1188
     *
1189
     * @param int    $id               Element ID
1190
     * @param int    $parent           Parent item ID
1191
     * @param int    $previous         Previous item ID
1192
     * @param string $title            Item title
1193
     * @param string $description      Item description
1194
     * @param string $prerequisites    Prerequisites (optional)
1195
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1196
     * @param int    $max_time_allowed
1197
     * @param string $url
1198
     *
1199
     * @return bool True on success, false on error
1200
     */
1201
    public function edit_item(
1202
        $id,
1203
        $parent,
1204
        $previous,
1205
        $title,
1206
        $description,
1207
        $prerequisites = '0',
1208
        $audio = [],
1209
        $max_time_allowed = 0,
1210
        $url = ''
1211
    ) {
1212
        $course_id = api_get_course_int_id();
1213
        $_course = api_get_course_info();
1214
        $id = (int) $id;
1215
1216
        if (empty($max_time_allowed)) {
1217
            $max_time_allowed = 0;
1218
        }
1219
1220
        if (empty($id) || empty($_course)) {
1221
            return false;
1222
        }
1223
1224
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1225
        $sql = "SELECT * FROM $tbl_lp_item
1226
                WHERE iid = $id";
1227
        $res_select = Database::query($sql);
1228
        $row_select = Database::fetch_array($res_select);
1229
        $audio_update_sql = '';
1230
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1231
            // Create the audio folder if it does not exist yet.
1232
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1233
            if (!is_dir($filepath.'audio')) {
1234
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1235
                $audio_id = DocumentManager::addDocument(
1236
                    $_course,
1237
                    '/audio',
1238
                    'folder',
1239
                    0,
1240
                    'audio'
1241
                );
1242
            }
1243
1244
            // Upload file in documents.
1245
            $pi = pathinfo($audio['name']);
1246
            if ('mp3' === $pi['extension']) {
1247
                $c_det = api_get_course_info($this->cc);
1248
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1249
                $path = handle_uploaded_document(
1250
                    $c_det,
1251
                    $audio,
1252
                    $bp,
1253
                    '/audio',
1254
                    api_get_user_id(),
1255
                    0,
1256
                    null,
1257
                    0,
1258
                    'rename',
1259
                    false,
1260
                    0
1261
                );
1262
                $path = substr($path, 7);
1263
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1264
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1265
            }
1266
        }
1267
1268
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1269
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1270
1271
        // TODO: htmlspecialchars to be checked for encoding related problems.
1272
        if ($same_parent && $same_previous) {
1273
            // Only update title and description.
1274
            $sql = "UPDATE $tbl_lp_item
1275
                    SET title = '".Database::escape_string($title)."',
1276
                        prerequisite = '".$prerequisites."',
1277
                        description = '".Database::escape_string($description)."'
1278
                        ".$audio_update_sql.",
1279
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1280
                    WHERE iid = $id";
1281
            Database::query($sql);
1282
        } else {
1283
            $old_parent = $row_select['parent_item_id'];
1284
            $old_previous = $row_select['previous_item_id'];
1285
            $old_next = $row_select['next_item_id'];
1286
            $old_order = $row_select['display_order'];
1287
            $old_prerequisite = $row_select['prerequisite'];
1288
            $old_max_time_allowed = $row_select['max_time_allowed'];
1289
1290
            /* BEGIN -- virtually remove the current item id */
1291
            /* for the next and previous item it is like the current item doesn't exist anymore */
1292
            if (0 != $old_previous) {
1293
                // Next
1294
                $sql = "UPDATE $tbl_lp_item
1295
                        SET next_item_id = $old_next
1296
                        WHERE iid = $old_previous";
1297
                Database::query($sql);
1298
            }
1299
1300
            if (!empty($old_next)) {
1301
                // Previous
1302
                $sql = "UPDATE $tbl_lp_item
1303
                        SET previous_item_id = $old_previous
1304
                        WHERE iid = $old_next";
1305
                Database::query($sql);
1306
            }
1307
1308
            // display_order - 1 for every item with a display_order
1309
            // bigger then the display_order of the current item.
1310
            $sql = "UPDATE $tbl_lp_item
1311
                    SET display_order = display_order - 1
1312
                    WHERE
1313
                        c_id = $course_id AND
1314
                        display_order > $old_order AND
1315
                        lp_id = ".$this->lp_id." AND
1316
                        parent_item_id = $old_parent";
1317
            Database::query($sql);
1318
            /* END -- virtually remove the current item id */
1319
1320
            /* BEGIN -- update the current item id to his new location */
1321
            if (0 == $previous) {
1322
                // Select the data of the item that should come after the current item.
1323
                $sql = "SELECT id, display_order
1324
                        FROM $tbl_lp_item
1325
                        WHERE
1326
                            c_id = $course_id AND
1327
                            lp_id = ".$this->lp_id." AND
1328
                            parent_item_id = $parent AND
1329
                            previous_item_id = $previous";
1330
                $res_select_old = Database::query($sql);
1331
                $row_select_old = Database::fetch_array($res_select_old);
1332
1333
                // If the new parent didn't have children before.
1334
                if (0 == Database::num_rows($res_select_old)) {
1335
                    $new_next = 0;
1336
                    $new_order = 1;
1337
                } else {
1338
                    $new_next = $row_select_old['id'];
1339
                    $new_order = $row_select_old['display_order'];
1340
                }
1341
            } else {
1342
                // Select the data of the item that should come before the current item.
1343
                $sql = "SELECT next_item_id, display_order
1344
                        FROM $tbl_lp_item
1345
                        WHERE iid = $previous";
1346
                $res_select_old = Database::query($sql);
1347
                $row_select_old = Database::fetch_array($res_select_old);
1348
                $new_next = $row_select_old['next_item_id'];
1349
                $new_order = $row_select_old['display_order'] + 1;
1350
            }
1351
1352
            // TODO: htmlspecialchars to be checked for encoding related problems.
1353
            // Update the current item with the new data.
1354
            $sql = "UPDATE $tbl_lp_item
1355
                    SET
1356
                        title = '".Database::escape_string($title)."',
1357
                        description = '".Database::escape_string($description)."',
1358
                        parent_item_id = $parent,
1359
                        previous_item_id = $previous,
1360
                        next_item_id = $new_next,
1361
                        display_order = $new_order
1362
                        $audio_update_sql
1363
                    WHERE iid = $id";
1364
            Database::query($sql);
1365
1366
            if (0 != $previous) {
1367
                // Update the previous item's next_item_id.
1368
                $sql = "UPDATE $tbl_lp_item
1369
                        SET next_item_id = $id
1370
                        WHERE iid = $previous";
1371
                Database::query($sql);
1372
            }
1373
1374
            if (!empty($new_next)) {
1375
                // Update the next item's previous_item_id.
1376
                $sql = "UPDATE $tbl_lp_item
1377
                        SET previous_item_id = $id
1378
                        WHERE iid = $new_next";
1379
                Database::query($sql);
1380
            }
1381
1382
            if ($old_prerequisite != $prerequisites) {
1383
                $sql = "UPDATE $tbl_lp_item
1384
                        SET prerequisite = '$prerequisites'
1385
                        WHERE iid = $id";
1386
                Database::query($sql);
1387
            }
1388
1389
            if ($old_max_time_allowed != $max_time_allowed) {
1390
                // update max time allowed
1391
                $sql = "UPDATE $tbl_lp_item
1392
                        SET max_time_allowed = $max_time_allowed
1393
                        WHERE iid = $id";
1394
                Database::query($sql);
1395
            }
1396
1397
            // Update all the items with the same or a bigger display_order than the current item.
1398
            $sql = "UPDATE $tbl_lp_item
1399
                    SET display_order = display_order + 1
1400
                    WHERE
1401
                       c_id = $course_id AND
1402
                       lp_id = ".$this->get_id()." AND
1403
                       iid <> $id AND
1404
                       parent_item_id = $parent AND
1405
                       display_order >= $new_order";
1406
            Database::query($sql);
1407
        }
1408
1409
        if ('link' == $row_select['item_type']) {
1410
            $link = new Link();
1411
            $linkId = $row_select['path'];
1412
            $link->updateLink($linkId, $url);
1413
        }
1414
    }
1415
1416
    /**
1417
     * Updates an item's prereq in place.
1418
     *
1419
     * @param int    $id              Element ID
1420
     * @param string $prerequisite_id Prerequisite Element ID
1421
     * @param int    $minScore        Prerequisite min score
1422
     * @param int    $maxScore        Prerequisite max score
1423
     *
1424
     * @return bool True on success, false on error
1425
     */
1426
    public function edit_item_prereq(
1427
        $id,
1428
        $prerequisite_id,
1429
        $minScore = 0,
1430
        $maxScore = 100
1431
    ) {
1432
        $id = (int) $id;
1433
        $prerequisite_id = (int) $prerequisite_id;
1434
1435
        if (empty($id)) {
1436
            return false;
1437
        }
1438
1439
        if (empty($minScore) || $minScore < 0) {
1440
            $minScore = 0;
1441
        }
1442
1443
        if (empty($maxScore) || $maxScore < 0) {
1444
            $maxScore = 100;
1445
        }
1446
1447
        $minScore = floatval($minScore);
1448
        $maxScore = floatval($maxScore);
1449
1450
        if (empty($prerequisite_id)) {
1451
            $prerequisite_id = 'NULL';
1452
            $minScore = 0;
1453
            $maxScore = 100;
1454
        }
1455
1456
        $table = Database::get_course_table(TABLE_LP_ITEM);
1457
        $sql = " UPDATE $table
1458
                 SET
1459
                    prerequisite = $prerequisite_id ,
1460
                    prerequisite_min_score = $minScore ,
1461
                    prerequisite_max_score = $maxScore
1462
                 WHERE iid = $id";
1463
        Database::query($sql);
1464
1465
        return true;
1466
    }
1467
1468
    /**
1469
     * Get the specific prefix index terms of this learning path.
1470
     *
1471
     * @param string $prefix
1472
     *
1473
     * @return array Array of terms
1474
     */
1475
    public function get_common_index_terms_by_prefix($prefix)
1476
    {
1477
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1478
        $terms = get_specific_field_values_list_by_prefix(
1479
            $prefix,
1480
            $this->cc,
1481
            TOOL_LEARNPATH,
1482
            $this->lp_id
1483
        );
1484
        $prefix_terms = [];
1485
        if (!empty($terms)) {
1486
            foreach ($terms as $term) {
1487
                $prefix_terms[] = $term['value'];
1488
            }
1489
        }
1490
1491
        return $prefix_terms;
1492
    }
1493
1494
    /**
1495
     * Gets the number of items currently completed.
1496
     *
1497
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1498
     *
1499
     * @return int The number of items currently completed
1500
     */
1501
    public function get_complete_items_count($failedStatusException = false)
1502
    {
1503
        $i = 0;
1504
        $completedStatusList = [
1505
            'completed',
1506
            'passed',
1507
            'succeeded',
1508
            'browsed',
1509
        ];
1510
1511
        if (!$failedStatusException) {
1512
            $completedStatusList[] = 'failed';
1513
        }
1514
1515
        foreach ($this->items as $id => $dummy) {
1516
            // Trying failed and browsed considered "progressed" as well.
1517
            if ($this->items[$id]->status_is($completedStatusList) &&
1518
                'dir' != $this->items[$id]->get_type()
1519
            ) {
1520
                $i++;
1521
            }
1522
        }
1523
1524
        return $i;
1525
    }
1526
1527
    /**
1528
     * Gets the current item ID.
1529
     *
1530
     * @return int The current learnpath item id
1531
     */
1532
    public function get_current_item_id()
1533
    {
1534
        $current = 0;
1535
        if (!empty($this->current)) {
1536
            $current = (int) $this->current;
1537
        }
1538
1539
        return $current;
1540
    }
1541
1542
    /**
1543
     * Force to get the first learnpath item id.
1544
     *
1545
     * @return int The current learnpath item id
1546
     */
1547
    public function get_first_item_id()
1548
    {
1549
        $current = 0;
1550
        if (is_array($this->ordered_items)) {
1551
            $current = $this->ordered_items[0];
1552
        }
1553
1554
        return $current;
1555
    }
1556
1557
    /**
1558
     * Gets the total number of items available for viewing in this SCORM.
1559
     *
1560
     * @return int The total number of items
1561
     */
1562
    public function get_total_items_count()
1563
    {
1564
        return count($this->items);
1565
    }
1566
1567
    /**
1568
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1569
     *
1570
     * @return int The total no-chapters number of items
1571
     */
1572
    public function getTotalItemsCountWithoutDirs()
1573
    {
1574
        $total = 0;
1575
        $typeListNotToCount = self::getChapterTypes();
1576
        foreach ($this->items as $temp2) {
1577
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1578
                $total++;
1579
            }
1580
        }
1581
1582
        return $total;
1583
    }
1584
1585
    /**
1586
     *  Sets the first element URL.
1587
     */
1588
    public function first()
1589
    {
1590
        if ($this->debug > 0) {
1591
            error_log('In learnpath::first()', 0);
1592
            error_log('$this->last_item_seen '.$this->last_item_seen);
1593
        }
1594
1595
        // Test if the last_item_seen exists and is not a dir.
1596
        if (0 == count($this->ordered_items)) {
1597
            $this->index = 0;
1598
        }
1599
1600
        if (!empty($this->last_item_seen) &&
1601
            !empty($this->items[$this->last_item_seen]) &&
1602
            'dir' != $this->items[$this->last_item_seen]->get_type()
1603
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1604
            //&& !$this->items[$this->last_item_seen]->is_done()
1605
        ) {
1606
            if ($this->debug > 2) {
1607
                error_log(
1608
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1609
                    $this->items[$this->last_item_seen]->get_type()
1610
                );
1611
            }
1612
            $index = -1;
1613
            foreach ($this->ordered_items as $myindex => $item_id) {
1614
                if ($item_id == $this->last_item_seen) {
1615
                    $index = $myindex;
1616
                    break;
1617
                }
1618
            }
1619
            if (-1 == $index) {
1620
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1621
                if ($this->debug > 2) {
1622
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1623
                }
1624
1625
                return false;
1626
            } else {
1627
                $this->last = $this->last_item_seen;
1628
                $this->current = $this->last_item_seen;
1629
                $this->index = $index;
1630
            }
1631
        } else {
1632
            if ($this->debug > 2) {
1633
                error_log('In learnpath::first() - No last item seen', 0);
1634
            }
1635
            $index = 0;
1636
            // Loop through all ordered items and stop at the first item that is
1637
            // not a directory *and* that has not been completed yet.
1638
            while (!empty($this->ordered_items[$index]) &&
1639
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1640
                (
1641
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1642
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1643
                ) && $index < $this->max_ordered_items) {
1644
                $index++;
1645
            }
1646
1647
            $this->last = $this->current;
1648
            // current is
1649
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1650
            $this->index = $index;
1651
            if ($this->debug > 2) {
1652
                error_log('$index '.$index);
1653
                error_log('In learnpath::first() - No last item seen');
1654
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1655
            }
1656
        }
1657
        if ($this->debug > 2) {
1658
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1659
        }
1660
    }
1661
1662
    /**
1663
     * Gets the js library from the database.
1664
     *
1665
     * @return string The name of the javascript library to be used
1666
     */
1667
    public function get_js_lib()
1668
    {
1669
        $lib = '';
1670
        if (!empty($this->js_lib)) {
1671
            $lib = $this->js_lib;
1672
        }
1673
1674
        return $lib;
1675
    }
1676
1677
    /**
1678
     * Gets the learnpath database ID.
1679
     *
1680
     * @return int Learnpath ID in the lp table
1681
     */
1682
    public function get_id()
1683
    {
1684
        if (!empty($this->lp_id)) {
1685
            return (int) $this->lp_id;
1686
        }
1687
1688
        return 0;
1689
    }
1690
1691
    /**
1692
     * Gets the last element URL.
1693
     *
1694
     * @return string URL to load into the viewer
1695
     */
1696
    public function get_last()
1697
    {
1698
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1699
        if (count($this->ordered_items) > 0) {
1700
            $this->index = count($this->ordered_items) - 1;
1701
1702
            return $this->ordered_items[$this->index];
1703
        }
1704
1705
        return false;
1706
    }
1707
1708
    /**
1709
     * Get the last element in the first level.
1710
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1711
     *
1712
     * @return mixed
1713
     */
1714
    public function getLastInFirstLevel()
1715
    {
1716
        try {
1717
            $lastId = Database::getManager()
1718
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1719
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1720
                ->setMaxResults(1)
1721
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1722
                ->getSingleScalarResult();
1723
1724
            return $lastId;
1725
        } catch (Exception $exception) {
1726
            return 0;
1727
        }
1728
    }
1729
1730
    /**
1731
     * Gets the navigation bar for the learnpath display screen.
1732
     *
1733
     * @param string $barId
1734
     *
1735
     * @return string The HTML string to use as a navigation bar
1736
     */
1737
    public function get_navigation_bar($barId = '')
1738
    {
1739
        if (empty($barId)) {
1740
            $barId = 'control-top';
1741
        }
1742
        $lpId = $this->lp_id;
1743
        $mycurrentitemid = $this->get_current_item_id();
1744
1745
        $reportingText = get_lang('Reporting');
1746
        $previousText = get_lang('Previous');
1747
        $nextText = get_lang('Next');
1748
        $fullScreenText = get_lang('Back to normal screen');
1749
1750
        $settings = api_get_configuration_value('lp_view_settings');
1751
        $display = isset($settings['display']) ? $settings['display'] : false;
1752
        $reportingIcon = '
1753
            <a class="icon-toolbar"
1754
                id="stats_link"
1755
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1756
                onclick="window.parent.API.save_asset(); return true;"
1757
                target="content_name" title="'.$reportingText.'">
1758
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1759
            </a>';
1760
1761
        if (!empty($display)) {
1762
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1763
            if (false === $showReporting) {
1764
                $reportingIcon = '';
1765
            }
1766
        }
1767
1768
        $hideArrows = false;
1769
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1770
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1771
        }
1772
1773
        $previousIcon = '';
1774
        $nextIcon = '';
1775
        if (false === $hideArrows) {
1776
            $previousIcon = '
1777
                <a class="icon-toolbar" id="scorm-previous" href="#"
1778
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1779
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1780
                </a>';
1781
1782
            $nextIcon = '
1783
                <a class="icon-toolbar" id="scorm-next" href="#"
1784
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1785
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1786
                </a>';
1787
        }
1788
1789
        if ('fullscreen' === $this->mode) {
1790
            $navbar = '
1791
                  <span id="'.$barId.'" class="buttons">
1792
                    '.$reportingIcon.'
1793
                    '.$previousIcon.'
1794
                    '.$nextIcon.'
1795
                    <a class="icon-toolbar" id="view-embedded"
1796
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1797
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1798
                    </a>
1799
                  </span>';
1800
        } else {
1801
            $navbar = '
1802
                 <span id="'.$barId.'" class="buttons text-right">
1803
                    '.$reportingIcon.'
1804
                    '.$previousIcon.'
1805
                    '.$nextIcon.'
1806
                </span>';
1807
        }
1808
1809
        return $navbar;
1810
    }
1811
1812
    /**
1813
     * Gets the next resource in queue (url).
1814
     *
1815
     * @return string URL to load into the viewer
1816
     */
1817
    public function get_next_index()
1818
    {
1819
        // TODO
1820
        $index = $this->index;
1821
        $index++;
1822
        while (
1823
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1824
            $index < $this->max_ordered_items
1825
        ) {
1826
            $index++;
1827
            if ($index == $this->max_ordered_items) {
1828
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1829
                    return $this->index;
1830
                }
1831
1832
                return $index;
1833
            }
1834
        }
1835
        if (empty($this->ordered_items[$index])) {
1836
            return $this->index;
1837
        }
1838
1839
        return $index;
1840
    }
1841
1842
    /**
1843
     * Gets item_id for the next element.
1844
     *
1845
     * @return int Next item (DB) ID
1846
     */
1847
    public function get_next_item_id()
1848
    {
1849
        $new_index = $this->get_next_index();
1850
        if (!empty($new_index)) {
1851
            if (isset($this->ordered_items[$new_index])) {
1852
                return $this->ordered_items[$new_index];
1853
            }
1854
        }
1855
1856
        return 0;
1857
    }
1858
1859
    /**
1860
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1861
     *
1862
     * Generally, the package provided is in the form of a zip file, so the function
1863
     * has been written to test a zip file. If not a zip, the function will return the
1864
     * default return value: ''
1865
     *
1866
     * @param string $file_path the path to the file
1867
     * @param string $file_name the original name of the file
1868
     *
1869
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1870
     */
1871
    public static function getPackageType($file_path, $file_name)
1872
    {
1873
        // Get name of the zip file without the extension.
1874
        $file_info = pathinfo($file_name);
1875
        $extension = $file_info['extension']; // Extension only.
1876
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1877
                'dll',
1878
                'exe',
1879
            ])) {
1880
            return 'oogie';
1881
        }
1882
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1883
                'dll',
1884
                'exe',
1885
            ])) {
1886
            return 'woogie';
1887
        }
1888
1889
        $zipFile = new PclZip($file_path);
1890
        // Check the zip content (real size and file extension).
1891
        $zipContentArray = $zipFile->listContent();
1892
        $package_type = '';
1893
        $manifest = '';
1894
        $aicc_match_crs = 0;
1895
        $aicc_match_au = 0;
1896
        $aicc_match_des = 0;
1897
        $aicc_match_cst = 0;
1898
        $countItems = 0;
1899
1900
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1901
        if (is_array($zipContentArray)) {
1902
            $countItems = count($zipContentArray);
1903
            if ($countItems > 0) {
1904
                foreach ($zipContentArray as $thisContent) {
1905
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1906
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1907
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1908
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1909
                        $package_type = 'scorm';
1910
                        break; // Exit the foreach loop.
1911
                    } elseif (
1912
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1913
                        in_array(
1914
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1915
                            ['crs', 'au', 'des', 'cst']
1916
                        )
1917
                    ) {
1918
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1919
                        switch ($ext) {
1920
                            case 'crs':
1921
                                $aicc_match_crs = 1;
1922
                                break;
1923
                            case 'au':
1924
                                $aicc_match_au = 1;
1925
                                break;
1926
                            case 'des':
1927
                                $aicc_match_des = 1;
1928
                                break;
1929
                            case 'cst':
1930
                                $aicc_match_cst = 1;
1931
                                break;
1932
                            default:
1933
                                break;
1934
                        }
1935
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1936
                    } else {
1937
                        $package_type = '';
1938
                    }
1939
                }
1940
            }
1941
        }
1942
1943
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1944
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1945
            $package_type = 'aicc';
1946
        }
1947
1948
        // Try with chamilo course builder
1949
        if (empty($package_type)) {
1950
            // Sometimes users will try to upload an empty zip, or a zip with
1951
            // only a folder. Catch that and make the calling function aware.
1952
            // If the single file was the imsmanifest.xml, then $package_type
1953
            // would be 'scorm' and we wouldn't be here.
1954
            if ($countItems < 2) {
1955
                return 'error-empty-package';
1956
            }
1957
            $package_type = 'chamilo';
1958
        }
1959
1960
        return $package_type;
1961
    }
1962
1963
    /**
1964
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1965
     *
1966
     * @return string URL to load into the viewer
1967
     */
1968
    public function get_previous_index()
1969
    {
1970
        $index = $this->index;
1971
        if (isset($this->ordered_items[$index - 1])) {
1972
            $index--;
1973
            while (isset($this->ordered_items[$index]) &&
1974
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
1975
            ) {
1976
                $index--;
1977
                if ($index < 0) {
1978
                    return $this->index;
1979
                }
1980
            }
1981
        }
1982
1983
        return $index;
1984
    }
1985
1986
    /**
1987
     * Gets item_id for the next element.
1988
     *
1989
     * @return int Previous item (DB) ID
1990
     */
1991
    public function get_previous_item_id()
1992
    {
1993
        $index = $this->get_previous_index();
1994
1995
        return $this->ordered_items[$index];
1996
    }
1997
1998
    /**
1999
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2000
     *
2001
     * @param int    $lpItemId
2002
     * @param string $autostart
2003
     *
2004
     * @return string The mediaplayer HTML
2005
     */
2006
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2007
    {
2008
        $course_id = api_get_course_int_id();
2009
        $courseInfo = api_get_course_info();
2010
        $lpItemId = (int) $lpItemId;
2011
2012
        if (empty($courseInfo) || empty($lpItemId)) {
2013
            return '';
2014
        }
2015
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2016
2017
        if (empty($item)) {
2018
            return '';
2019
        }
2020
2021
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2022
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2023
        $itemViewId = (int) $item->db_item_view_id;
2024
2025
        // Getting all the information about the item.
2026
        $sql = "SELECT lp_view.status
2027
                FROM $tbl_lp_item as lpi
2028
                INNER JOIN $tbl_lp_item_view as lp_view
2029
                ON (lpi.iid = lp_view.lp_item_id)
2030
                WHERE
2031
                    lp_view.iid = $itemViewId AND
2032
                    lpi.iid = $lpItemId AND
2033
                    lp_view.c_id = $course_id";
2034
        $result = Database::query($sql);
2035
        $row = Database::fetch_assoc($result);
2036
        $output = '';
2037
        $audio = $item->audio;
2038
2039
        if (!empty($audio)) {
2040
            $list = $_SESSION['oLP']->get_toc();
2041
2042
            switch ($item->get_type()) {
2043
                case 'quiz':
2044
                    $type_quiz = false;
2045
                    foreach ($list as $toc) {
2046
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2047
                            $type_quiz = true;
2048
                        }
2049
                    }
2050
2051
                    if ($type_quiz) {
2052
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2053
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2054
                        } else {
2055
                            $autostart_audio = $autostart;
2056
                        }
2057
                    }
2058
                    break;
2059
                case TOOL_READOUT_TEXT:
2060
                    $autostart_audio = 'false';
2061
                    break;
2062
                default:
2063
                    $autostart_audio = 'true';
2064
            }
2065
2066
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2067
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2068
2069
            $player = Display::getMediaPlayer(
2070
                $file,
2071
                [
2072
                    'id' => 'lp_audio_media_player',
2073
                    'url' => $url,
2074
                    'autoplay' => $autostart_audio,
2075
                    'width' => '100%',
2076
                ]
2077
            );
2078
2079
            // The mp3 player.
2080
            $output = '<div id="container">';
2081
            $output .= $player;
2082
            $output .= '</div>';
2083
        }
2084
2085
        return $output;
2086
    }
2087
2088
    /**
2089
     * @param int   $studentId
2090
     * @param int   $prerequisite
2091
     * @param array $courseInfo
2092
     * @param int   $sessionId
2093
     *
2094
     * @return bool
2095
     */
2096
    public static function isBlockedByPrerequisite(
2097
        $studentId,
2098
        $prerequisite,
2099
        $courseInfo,
2100
        $sessionId
2101
    ) {
2102
        if (empty($courseInfo)) {
2103
            return false;
2104
        }
2105
2106
        $courseId = $courseInfo['real_id'];
2107
2108
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2109
        if ($allow) {
2110
            if (api_is_allowed_to_edit() ||
2111
                api_is_platform_admin(true) ||
2112
                api_is_drh() ||
2113
                api_is_coach($sessionId, $courseId, false)
2114
            ) {
2115
                return false;
2116
            }
2117
        }
2118
2119
        $isBlocked = false;
2120
        if (!empty($prerequisite)) {
2121
            $progress = self::getProgress(
2122
                $prerequisite,
2123
                $studentId,
2124
                $courseId,
2125
                $sessionId
2126
            );
2127
            if ($progress < 100) {
2128
                $isBlocked = true;
2129
            }
2130
2131
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2132
                // Block if it does not exceed minimum time
2133
                // Minimum time (in minutes) to pass the learning path
2134
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2135
2136
                if ($accumulateWorkTime > 0) {
2137
                    // Total time in course (sum of times in learning paths from course)
2138
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2139
2140
                    // Connect with the plugin_licences_course_session table
2141
                    // which indicates what percentage of the time applies
2142
                    // Minimum connection percentage
2143
                    $perc = 100;
2144
                    // Time from the course
2145
                    $tc = $accumulateWorkTimeTotal;
2146
2147
                    // Percentage of the learning paths
2148
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2149
                    // Minimum time for each learning path
2150
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2151
2152
                    // Spent time (in seconds) so far in the learning path
2153
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2154
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2155
2156
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2157
                        $isBlocked = true;
2158
                    }
2159
                }
2160
            }
2161
        }
2162
2163
        return $isBlocked;
2164
    }
2165
2166
    /**
2167
     * Checks if the learning path is visible for student after the progress
2168
     * of its prerequisite is completed, considering the time availability and
2169
     * the LP visibility.
2170
     *
2171
     * @param int   $lp_id
2172
     * @param int   $student_id
2173
     * @param array $courseInfo
2174
     * @param int   $sessionId
2175
     *
2176
     * @return bool
2177
     */
2178
    public static function is_lp_visible_for_student(
2179
        CLp $lp,
2180
        $student_id,
2181
        $courseInfo = [],
2182
        $sessionId = 0
2183
    ) {
2184
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2185
        $sessionId = (int) $sessionId;
2186
2187
        if (empty($courseInfo)) {
2188
            return false;
2189
        }
2190
2191
        if (empty($sessionId)) {
2192
            $sessionId = api_get_session_id();
2193
        }
2194
2195
        $courseId = $courseInfo['real_id'];
2196
2197
        /*$itemInfo = api_get_item_property_info(
2198
            $courseId,
2199
            TOOL_LEARNPATH,
2200
            $lp_id,
2201
            $sessionId
2202
        );*/
2203
2204
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2205
        // If the item was deleted.
2206
        if (false === $visibility) {
2207
            return false;
2208
        }
2209
2210
        $lp_id = $lp->getIid();
2211
        // @todo remove this query and load the row info as a parameter
2212
        $table = Database::get_course_table(TABLE_LP_MAIN);
2213
        // Get current prerequisite
2214
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2215
                FROM $table
2216
                WHERE iid = $lp_id";
2217
        $rs = Database::query($sql);
2218
        $now = time();
2219
        if (Database::num_rows($rs) > 0) {
2220
            $row = Database::fetch_array($rs, 'ASSOC');
2221
2222
            if (!empty($row['category_id'])) {
2223
                $em = Database::getManager();
2224
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2225
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2226
                    return false;
2227
                }
2228
            }
2229
2230
            $prerequisite = $row['prerequisite'];
2231
            $is_visible = true;
2232
2233
            $isBlocked = self::isBlockedByPrerequisite(
2234
                $student_id,
2235
                $prerequisite,
2236
                $courseInfo,
2237
                $sessionId
2238
            );
2239
2240
            if ($isBlocked) {
2241
                $is_visible = false;
2242
            }
2243
2244
            // Also check the time availability of the LP
2245
            if ($is_visible) {
2246
                // Adding visibility restrictions
2247
                if (!empty($row['publicated_on'])) {
2248
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2249
                        $is_visible = false;
2250
                    }
2251
                }
2252
                // Blocking empty start times see BT#2800
2253
                global $_custom;
2254
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2255
                    $_custom['lps_hidden_when_no_start_date']
2256
                ) {
2257
                    if (empty($row['publicated_on'])) {
2258
                        $is_visible = false;
2259
                    }
2260
                }
2261
2262
                if (!empty($row['expired_on'])) {
2263
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2264
                        $is_visible = false;
2265
                    }
2266
                }
2267
            }
2268
2269
            if ($is_visible) {
2270
                $subscriptionSettings = self::getSubscriptionSettings();
2271
2272
                // Check if the subscription users/group to a LP is ON
2273
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2274
                    true === $subscriptionSettings['allow_add_users_to_lp']
2275
                ) {
2276
                    // Try group
2277
                    $is_visible = false;
2278
                    // Checking only the user visibility
2279
                    $userVisibility = api_get_item_visibility(
2280
                        $courseInfo,
2281
                        'learnpath',
2282
                        $row['id'],
2283
                        $sessionId,
2284
                        $student_id,
2285
                        'LearnpathSubscription'
2286
                    );
2287
2288
                    if (1 == $userVisibility) {
2289
                        $is_visible = true;
2290
                    } else {
2291
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2292
                        if (!empty($userGroups)) {
2293
                            foreach ($userGroups as $groupInfo) {
2294
                                $groupId = $groupInfo['iid'];
2295
                                $userVisibility = api_get_item_visibility(
2296
                                    $courseInfo,
2297
                                    'learnpath',
2298
                                    $row['id'],
2299
                                    $sessionId,
2300
                                    null,
2301
                                    'LearnpathSubscription',
2302
                                    $groupId
2303
                                );
2304
2305
                                if (1 == $userVisibility) {
2306
                                    $is_visible = true;
2307
                                    break;
2308
                                }
2309
                            }
2310
                        }
2311
                    }
2312
                }
2313
            }
2314
2315
            return $is_visible;
2316
        }
2317
2318
        return false;
2319
    }
2320
2321
    /**
2322
     * @param int $lpId
2323
     * @param int $userId
2324
     * @param int $courseId
2325
     * @param int $sessionId
2326
     *
2327
     * @return int
2328
     */
2329
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2330
    {
2331
        $lpId = (int) $lpId;
2332
        $userId = (int) $userId;
2333
        $courseId = (int) $courseId;
2334
        $sessionId = (int) $sessionId;
2335
2336
        $sessionCondition = api_get_session_condition($sessionId);
2337
        $table = Database::get_course_table(TABLE_LP_VIEW);
2338
        $sql = "SELECT progress FROM $table
2339
                WHERE
2340
                    c_id = $courseId AND
2341
                    lp_id = $lpId AND
2342
                    user_id = $userId $sessionCondition ";
2343
        $res = Database::query($sql);
2344
2345
        $progress = 0;
2346
        if (Database::num_rows($res) > 0) {
2347
            $row = Database::fetch_array($res);
2348
            $progress = (int) $row['progress'];
2349
        }
2350
2351
        return $progress;
2352
    }
2353
2354
    /**
2355
     * @param array $lpList
2356
     * @param int   $userId
2357
     * @param int   $courseId
2358
     * @param int   $sessionId
2359
     *
2360
     * @return array
2361
     */
2362
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2363
    {
2364
        $lpList = array_map('intval', $lpList);
2365
        if (empty($lpList)) {
2366
            return [];
2367
        }
2368
2369
        $lpList = implode("','", $lpList);
2370
2371
        $userId = (int) $userId;
2372
        $courseId = (int) $courseId;
2373
        $sessionId = (int) $sessionId;
2374
2375
        $sessionCondition = api_get_session_condition($sessionId);
2376
        $table = Database::get_course_table(TABLE_LP_VIEW);
2377
        $sql = "SELECT lp_id, progress FROM $table
2378
                WHERE
2379
                    c_id = $courseId AND
2380
                    lp_id IN ('".$lpList."') AND
2381
                    user_id = $userId $sessionCondition ";
2382
        $res = Database::query($sql);
2383
2384
        if (Database::num_rows($res) > 0) {
2385
            $list = [];
2386
            while ($row = Database::fetch_array($res)) {
2387
                $list[$row['lp_id']] = $row['progress'];
2388
            }
2389
2390
            return $list;
2391
        }
2392
2393
        return [];
2394
    }
2395
2396
    /**
2397
     * Displays a progress bar
2398
     * completed so far.
2399
     *
2400
     * @param int    $percentage Progress value to display
2401
     * @param string $text_add   Text to display near the progress value
2402
     *
2403
     * @return string HTML string containing the progress bar
2404
     */
2405
    public static function get_progress_bar($percentage = -1, $text_add = '')
2406
    {
2407
        $text = $percentage.$text_add;
2408
        $output = '<div class="progress">
2409
            <div id="progress_bar_value"
2410
                class="progress-bar progress-bar-success" role="progressbar"
2411
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2412
            '.$text.'
2413
            </div>
2414
        </div>';
2415
2416
        return $output;
2417
    }
2418
2419
    /**
2420
     * @param string $mode can be '%' or 'abs'
2421
     *                     otherwise this value will be used $this->progress_bar_mode
2422
     *
2423
     * @return string
2424
     */
2425
    public function getProgressBar($mode = null)
2426
    {
2427
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2428
2429
        return self::get_progress_bar($percentage, $text_add);
2430
    }
2431
2432
    /**
2433
     * Gets the progress bar info to display inside the progress bar.
2434
     * Also used by scorm_api.php.
2435
     *
2436
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2437
     *                     we display a number of completed elements per total elements
2438
     * @param int    $add  Additional steps to fake as completed
2439
     *
2440
     * @return array Percentage or number and symbol (% or /xx)
2441
     */
2442
    public function get_progress_bar_text($mode = '', $add = 0)
2443
    {
2444
        if (empty($mode)) {
2445
            $mode = $this->progress_bar_mode;
2446
        }
2447
        $text = '';
2448
        $percentage = 0;
2449
        // If the option to use the score as progress is set for this learning
2450
        // path, then the rules are completely different: we assume only one
2451
        // item exists and the progress of the LP depends on the score
2452
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2453
        if ($scoreAsProgressSetting === true) {
2454
            $scoreAsProgress = $this->getUseScoreAsProgress();
2455
            if ($scoreAsProgress) {
2456
                // Get single item's score
2457
                $itemId = $this->get_current_item_id();
2458
                $item = $this->getItem($itemId);
2459
                $score = $item->get_score();
2460
                $maxScore = $item->get_max();
2461
                if ($mode = '%') {
2462
                    if (!empty($maxScore)) {
2463
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2464
                    }
2465
                    $percentage = number_format($percentage, 0);
2466
                    $text = '%';
2467
                } else {
2468
                    $percentage = $score;
2469
                    $text = '/'.$maxScore;
2470
                }
2471
2472
                return [$percentage, $text];
2473
            }
2474
        }
2475
        // otherwise just continue the normal processing of progress
2476
        $total_items = $this->getTotalItemsCountWithoutDirs();
2477
        $completeItems = $this->get_complete_items_count();
2478
        if ($add != 0) {
2479
            $completeItems += $add;
2480
        }
2481
        if ($completeItems > $total_items) {
2482
            $completeItems = $total_items;
2483
        }
2484
        if ($mode == '%') {
2485
            if ($total_items > 0) {
2486
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2487
            }
2488
            $percentage = number_format($percentage, 0);
2489
            $text = '%';
2490
        } elseif ($mode === 'abs') {
2491
            $percentage = $completeItems;
2492
            $text = '/'.$total_items;
2493
        }
2494
2495
        return [
2496
            $percentage,
2497
            $text,
2498
        ];
2499
    }
2500
2501
    /**
2502
     * Gets the progress bar mode.
2503
     *
2504
     * @return string The progress bar mode attribute
2505
     */
2506
    public function get_progress_bar_mode()
2507
    {
2508
        if (!empty($this->progress_bar_mode)) {
2509
            return $this->progress_bar_mode;
2510
        }
2511
2512
        return '%';
2513
    }
2514
2515
    /**
2516
     * Gets the learnpath theme (remote or local).
2517
     *
2518
     * @return string Learnpath theme
2519
     */
2520
    public function get_theme()
2521
    {
2522
        if (!empty($this->theme)) {
2523
            return $this->theme;
2524
        }
2525
2526
        return '';
2527
    }
2528
2529
    /**
2530
     * Gets the learnpath session id.
2531
     *
2532
     * @return int
2533
     */
2534
    public function get_lp_session_id()
2535
    {
2536
        if (!empty($this->lp_session_id)) {
2537
            return (int) $this->lp_session_id;
2538
        }
2539
2540
        return 0;
2541
    }
2542
2543
    /**
2544
     * Gets the learnpath image.
2545
     *
2546
     * @return string Web URL of the LP image
2547
     */
2548
    public function get_preview_image()
2549
    {
2550
        if (!empty($this->preview_image)) {
2551
            return $this->preview_image;
2552
        }
2553
2554
        return '';
2555
    }
2556
2557
    /**
2558
     * @param string $size
2559
     * @param string $path_type
2560
     *
2561
     * @return bool|string
2562
     */
2563
    public function get_preview_image_path($size = null, $path_type = 'web')
2564
    {
2565
        $preview_image = $this->get_preview_image();
2566
        if (isset($preview_image) && !empty($preview_image)) {
2567
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2568
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2569
2570
            if (isset($size)) {
2571
                $info = pathinfo($preview_image);
2572
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2573
2574
                if (file_exists($image_sys_path.$image_custom_size)) {
2575
                    if ('web' == $path_type) {
2576
                        return $image_path.$image_custom_size;
2577
                    } else {
2578
                        return $image_sys_path.$image_custom_size;
2579
                    }
2580
                }
2581
            } else {
2582
                if ('web' == $path_type) {
2583
                    return $image_path.$preview_image;
2584
                } else {
2585
                    return $image_sys_path.$preview_image;
2586
                }
2587
            }
2588
        }
2589
2590
        return false;
2591
    }
2592
2593
    /**
2594
     * Gets the learnpath author.
2595
     *
2596
     * @return string LP's author
2597
     */
2598
    public function get_author()
2599
    {
2600
        if (!empty($this->author)) {
2601
            return $this->author;
2602
        }
2603
2604
        return '';
2605
    }
2606
2607
    /**
2608
     * Gets hide table of contents.
2609
     *
2610
     * @return int
2611
     */
2612
    public function getHideTableOfContents()
2613
    {
2614
        return (int) $this->hide_toc_frame;
2615
    }
2616
2617
    /**
2618
     * Generate a new prerequisites string for a given item. If this item was a sco and
2619
     * its prerequisites were strings (instead of IDs), then transform those strings into
2620
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2621
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2622
     * same rule as the scormExport() method.
2623
     *
2624
     * @param int $item_id Item ID
2625
     *
2626
     * @return string Prerequisites string ready for the export as SCORM
2627
     */
2628
    public function get_scorm_prereq_string($item_id)
2629
    {
2630
        if ($this->debug > 0) {
2631
            error_log('In learnpath::get_scorm_prereq_string()');
2632
        }
2633
        if (!is_object($this->items[$item_id])) {
2634
            return false;
2635
        }
2636
        /** @var learnpathItem $oItem */
2637
        $oItem = $this->items[$item_id];
2638
        $prereq = $oItem->get_prereq_string();
2639
2640
        if (empty($prereq)) {
2641
            return '';
2642
        }
2643
        if (preg_match('/^\d+$/', $prereq) &&
2644
            isset($this->items[$prereq]) &&
2645
            is_object($this->items[$prereq])
2646
        ) {
2647
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2648
            // then simply return it (with the ITEM_ prefix).
2649
            //return 'ITEM_' . $prereq;
2650
            return $this->items[$prereq]->ref;
2651
        } else {
2652
            if (isset($this->refs_list[$prereq])) {
2653
                // It's a simple string item from which the ID can be found in the refs list,
2654
                // so we can transform it directly to an ID for export.
2655
                return $this->items[$this->refs_list[$prereq]]->ref;
2656
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2657
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2658
            } else {
2659
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2660
                // and replace them, one by one, by the internal IDs (chamilo db)
2661
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2662
                // by a space as well.
2663
                $find = [
2664
                    '&',
2665
                    '|',
2666
                    '~',
2667
                    '=',
2668
                    '<>',
2669
                    '{',
2670
                    '}',
2671
                    '*',
2672
                    '(',
2673
                    ')',
2674
                ];
2675
                $replace = [
2676
                    ' ',
2677
                    ' ',
2678
                    ' ',
2679
                    ' ',
2680
                    ' ',
2681
                    ' ',
2682
                    ' ',
2683
                    ' ',
2684
                    ' ',
2685
                    ' ',
2686
                ];
2687
                $prereq_mod = str_replace($find, $replace, $prereq);
2688
                $ids = explode(' ', $prereq_mod);
2689
                foreach ($ids as $id) {
2690
                    $id = trim($id);
2691
                    if (isset($this->refs_list[$id])) {
2692
                        $prereq = preg_replace(
2693
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2694
                            'ITEM_'.$this->refs_list[$id],
2695
                            $prereq
2696
                        );
2697
                    }
2698
                }
2699
2700
                return $prereq;
2701
            }
2702
        }
2703
    }
2704
2705
    /**
2706
     * Returns the XML DOM document's node.
2707
     *
2708
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2709
     * @param string   $id       The identifier to look for
2710
     *
2711
     * @return mixed The reference to the element found with that identifier. False if not found
2712
     */
2713
    public function get_scorm_xml_node(&$children, $id)
2714
    {
2715
        for ($i = 0; $i < $children->length; $i++) {
2716
            $item_temp = $children->item($i);
2717
            if ('item' == $item_temp->nodeName) {
2718
                if ($item_temp->getAttribute('identifier') == $id) {
2719
                    return $item_temp;
2720
                }
2721
            }
2722
            $subchildren = $item_temp->childNodes;
2723
            if ($subchildren && $subchildren->length > 0) {
2724
                $val = $this->get_scorm_xml_node($subchildren, $id);
2725
                if (is_object($val)) {
2726
                    return $val;
2727
                }
2728
            }
2729
        }
2730
2731
        return false;
2732
    }
2733
2734
    /**
2735
     * Gets the status list for all LP's items.
2736
     *
2737
     * @return array Array of [index] => [item ID => current status]
2738
     */
2739
    public function get_items_status_list()
2740
    {
2741
        $list = [];
2742
        foreach ($this->ordered_items as $item_id) {
2743
            $list[] = [
2744
                $item_id => $this->items[$item_id]->get_status(),
2745
            ];
2746
        }
2747
2748
        return $list;
2749
    }
2750
2751
    /**
2752
     * Return the number of interactions for the given learnpath Item View ID.
2753
     * This method can be used as static.
2754
     *
2755
     * @param int $lp_iv_id  Item View ID
2756
     * @param int $course_id course id
2757
     *
2758
     * @return int
2759
     */
2760
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2761
    {
2762
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2763
        $lp_iv_id = (int) $lp_iv_id;
2764
        $course_id = (int) $course_id;
2765
2766
        $sql = "SELECT count(*) FROM $table
2767
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2768
        $res = Database::query($sql);
2769
        $num = 0;
2770
        if (Database::num_rows($res)) {
2771
            $row = Database::fetch_array($res);
2772
            $num = $row[0];
2773
        }
2774
2775
        return $num;
2776
    }
2777
2778
    /**
2779
     * Return the interactions as an array for the given lp_iv_id.
2780
     * This method can be used as static.
2781
     *
2782
     * @param int $lp_iv_id Learnpath Item View ID
2783
     *
2784
     * @return array
2785
     *
2786
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2787
     */
2788
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2789
    {
2790
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2791
        $list = [];
2792
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2793
        $lp_iv_id = (int) $lp_iv_id;
2794
2795
        if (empty($lp_iv_id) || empty($course_id)) {
2796
            return [];
2797
        }
2798
2799
        $sql = "SELECT * FROM $table
2800
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2801
                ORDER BY order_id ASC";
2802
        $res = Database::query($sql);
2803
        $num = Database::num_rows($res);
2804
        if ($num > 0) {
2805
            $list[] = [
2806
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2807
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2808
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2809
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2810
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2811
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2812
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2813
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2814
                'student_response_formatted' => '',
2815
            ];
2816
            while ($row = Database::fetch_array($res)) {
2817
                $studentResponseFormatted = urldecode($row['student_response']);
2818
                $content_student_response = explode('__|', $studentResponseFormatted);
2819
                if (count($content_student_response) > 0) {
2820
                    if (count($content_student_response) >= 3) {
2821
                        // Pop the element off the end of array.
2822
                        array_pop($content_student_response);
2823
                    }
2824
                    $studentResponseFormatted = implode(',', $content_student_response);
2825
                }
2826
2827
                $list[] = [
2828
                    'order_id' => $row['order_id'] + 1,
2829
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2830
                    'type' => $row['interaction_type'],
2831
                    'time' => $row['completion_time'],
2832
                    'correct_responses' => '', // Hide correct responses from students.
2833
                    'student_response' => $row['student_response'],
2834
                    'result' => $row['result'],
2835
                    'latency' => $row['latency'],
2836
                    'student_response_formatted' => $studentResponseFormatted,
2837
                ];
2838
            }
2839
        }
2840
2841
        return $list;
2842
    }
2843
2844
    /**
2845
     * Return the number of objectives for the given learnpath Item View ID.
2846
     * This method can be used as static.
2847
     *
2848
     * @param int $lp_iv_id  Item View ID
2849
     * @param int $course_id Course ID
2850
     *
2851
     * @return int Number of objectives
2852
     */
2853
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2854
    {
2855
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2856
        $course_id = (int) $course_id;
2857
        $lp_iv_id = (int) $lp_iv_id;
2858
        $sql = "SELECT count(*) FROM $table
2859
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2860
        //@todo seems that this always returns 0
2861
        $res = Database::query($sql);
2862
        $num = 0;
2863
        if (Database::num_rows($res)) {
2864
            $row = Database::fetch_array($res);
2865
            $num = $row[0];
2866
        }
2867
2868
        return $num;
2869
    }
2870
2871
    /**
2872
     * Return the objectives as an array for the given lp_iv_id.
2873
     * This method can be used as static.
2874
     *
2875
     * @param int $lpItemViewId Learnpath Item View ID
2876
     * @param int $course_id
2877
     *
2878
     * @return array
2879
     *
2880
     * @todo    Translate labels
2881
     */
2882
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2883
    {
2884
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2885
        $lpItemViewId = (int) $lpItemViewId;
2886
2887
        if (empty($course_id) || empty($lpItemViewId)) {
2888
            return [];
2889
        }
2890
2891
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2892
        $sql = "SELECT * FROM $table
2893
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2894
                ORDER BY order_id ASC";
2895
        $res = Database::query($sql);
2896
        $num = Database::num_rows($res);
2897
        $list = [];
2898
        if ($num > 0) {
2899
            $list[] = [
2900
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2901
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2902
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2903
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2904
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2905
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2906
            ];
2907
            while ($row = Database::fetch_array($res)) {
2908
                $list[] = [
2909
                    'order_id' => $row['order_id'] + 1,
2910
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2911
                    'score_raw' => $row['score_raw'],
2912
                    'score_max' => $row['score_max'],
2913
                    'score_min' => $row['score_min'],
2914
                    'status' => $row['status'],
2915
                ];
2916
            }
2917
        }
2918
2919
        return $list;
2920
    }
2921
2922
    /**
2923
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2924
     * used by get_html_toc() to be ready to display.
2925
     *
2926
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2927
     */
2928
    public function get_toc()
2929
    {
2930
        $toc = [];
2931
        foreach ($this->ordered_items as $item_id) {
2932
            // TODO: Change this link generation and use new function instead.
2933
            $toc[] = [
2934
                'id' => $item_id,
2935
                'title' => $this->items[$item_id]->get_title(),
2936
                'status' => $this->items[$item_id]->get_status(),
2937
                'level' => $this->items[$item_id]->get_level(),
2938
                'type' => $this->items[$item_id]->get_type(),
2939
                'description' => $this->items[$item_id]->get_description(),
2940
                'path' => $this->items[$item_id]->get_path(),
2941
                'parent' => $this->items[$item_id]->get_parent(),
2942
            ];
2943
        }
2944
2945
        return $toc;
2946
    }
2947
2948
    /**
2949
     * Returns the CSS class name associated with a given item status.
2950
     *
2951
     * @param $status string an item status
2952
     *
2953
     * @return string CSS class name
2954
     */
2955
    public static function getStatusCSSClassName($status)
2956
    {
2957
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2958
            return self::STATUS_CSS_CLASS_NAME[$status];
2959
        }
2960
2961
        return '';
2962
    }
2963
2964
    /**
2965
     * Generate the tree of contents for this learnpath as an associative array tree
2966
     * with keys id, title, status, type, description, path, parent_id, children
2967
     * (title and descriptions as secured)
2968
     * and clues for CSS class composition:
2969
     *  - booleans is_current, is_parent_of_current, is_chapter
2970
     *  - string status_css_class_name.
2971
     *
2972
     * @param $parentId int restrict returned list to children of this parent
2973
     *
2974
     * @return array TOC as a table
2975
     */
2976
    public function getTOCTree($parentId = 0)
2977
    {
2978
        $toc = [];
2979
        $currentItemId = $this->get_current_item_id();
2980
2981
        foreach ($this->ordered_items as $itemId) {
2982
            $item = $this->items[$itemId];
2983
            if ($item->get_parent() == $parentId) {
2984
                $title = $item->get_title();
2985
                if (empty($title)) {
2986
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
2987
                }
2988
2989
                $itemData = [
2990
                    'id' => $itemId,
2991
                    'title' => Security::remove_XSS($title),
2992
                    'status' => $item->get_status(),
2993
                    'level' => $item->get_level(), // FIXME should not be needed
2994
                    'type' => $item->get_type(),
2995
                    'description' => Security::remove_XSS($item->get_description()),
2996
                    'path' => $item->get_path(),
2997
                    'parent_id' => $item->get_parent(),
2998
                    'children' => $this->getTOCTree($itemId),
2999
                    'is_current' => ($itemId == $currentItemId),
3000
                    'is_parent_of_current' => false,
3001
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3002
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3003
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3004
                ];
3005
3006
                if (!empty($itemData['children'])) {
3007
                    foreach ($itemData['children'] as $child) {
3008
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3009
                            $itemData['is_parent_of_current'] = true;
3010
                            break;
3011
                        }
3012
                    }
3013
                }
3014
3015
                $toc[] = $itemData;
3016
            }
3017
        }
3018
3019
        return $toc;
3020
    }
3021
3022
    /**
3023
     * Generate and return the table of contents for this learnpath. The JS
3024
     * table returned is used inside of scorm_api.php.
3025
     *
3026
     * @param string $varname
3027
     *
3028
     * @return string A JS array variable construction
3029
     */
3030
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3031
    {
3032
        $toc = $varname.' = new Array();';
3033
        foreach ($this->ordered_items as $item_id) {
3034
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3035
        }
3036
3037
        return $toc;
3038
    }
3039
3040
    /**
3041
     * Gets the learning path type.
3042
     *
3043
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3044
     *
3045
     * @return mixed Type ID or name, depending on the parameter
3046
     */
3047
    public function get_type($get_name = false)
3048
    {
3049
        $res = false;
3050
        if (!empty($this->type) && (!$get_name)) {
3051
            $res = $this->type;
3052
        }
3053
3054
        return $res;
3055
    }
3056
3057
    /**
3058
     * Gets the learning path type as static method.
3059
     *
3060
     * @param int $lp_id
3061
     *
3062
     * @return mixed Type ID or name, depending on the parameter
3063
     */
3064
    public static function get_type_static($lp_id = 0)
3065
    {
3066
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3067
        $lp_id = (int) $lp_id;
3068
        $sql = "SELECT lp_type FROM $tbl_lp
3069
                WHERE iid = $lp_id";
3070
        $res = Database::query($sql);
3071
        if (false === $res) {
3072
            return null;
3073
        }
3074
        if (Database::num_rows($res) <= 0) {
3075
            return null;
3076
        }
3077
        $row = Database::fetch_array($res);
3078
3079
        return $row['lp_type'];
3080
    }
3081
3082
    /**
3083
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3084
     * This method can be used as abstract and is recursive.
3085
     *
3086
     * @param int $lp        Learnpath ID
3087
     * @param int $parent    Parent ID of the items to look for
3088
     * @param int $course_id
3089
     *
3090
     * @return array Ordered list of item IDs (empty array on error)
3091
     */
3092
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3093
    {
3094
        if (empty($course_id)) {
3095
            $course_id = api_get_course_int_id();
3096
        } else {
3097
            $course_id = (int) $course_id;
3098
        }
3099
        $list = [];
3100
3101
        if (empty($lp)) {
3102
            return $list;
3103
        }
3104
3105
        $lp = (int) $lp;
3106
        $parent = (int) $parent;
3107
3108
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3109
        $sql = "SELECT iid FROM $tbl_lp_item
3110
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3111
                ORDER BY display_order";
3112
3113
        $res = Database::query($sql);
3114
        while ($row = Database::fetch_array($res)) {
3115
            $sublist = self::get_flat_ordered_items_list(
3116
                $lp,
3117
                $row['iid'],
3118
                $course_id
3119
            );
3120
            $list[] = $row['iid'];
3121
            foreach ($sublist as $item) {
3122
                $list[] = $item;
3123
            }
3124
        }
3125
3126
        return $list;
3127
    }
3128
3129
    /**
3130
     * @return array
3131
     */
3132
    public static function getChapterTypes()
3133
    {
3134
        return [
3135
            'dir',
3136
        ];
3137
    }
3138
3139
    /**
3140
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3141
     *
3142
     * @param $tree
3143
     *
3144
     * @return array HTML TOC ready to display
3145
     */
3146
    public function getParentToc($tree)
3147
    {
3148
        if (empty($tree)) {
3149
            $tree = $this->get_toc();
3150
        }
3151
        $dirTypes = self::getChapterTypes();
3152
        $myCurrentId = $this->get_current_item_id();
3153
        $listParent = [];
3154
        $listChildren = [];
3155
        $listNotParent = [];
3156
        $list = [];
3157
        foreach ($tree as $subtree) {
3158
            if (in_array($subtree['type'], $dirTypes)) {
3159
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3160
                $subtree['children'] = $listChildren;
3161
                if (!empty($subtree['children'])) {
3162
                    foreach ($subtree['children'] as $subItem) {
3163
                        if ($subItem['id'] == $this->current) {
3164
                            $subtree['parent_current'] = 'in';
3165
                            $subtree['current'] = 'on';
3166
                        }
3167
                    }
3168
                }
3169
                $listParent[] = $subtree;
3170
            }
3171
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3172
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3173
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3174
                }
3175
3176
                $title = Security::remove_XSS($subtree['title']);
3177
                unset($subtree['title']);
3178
3179
                if (empty($title)) {
3180
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3181
                }
3182
                $classStyle = null;
3183
                if ($subtree['id'] == $this->current) {
3184
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3185
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3186
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3187
                }
3188
                $subtree['title'] = $title;
3189
                $subtree['class'] = $classStyle.' '.$cssStatus;
3190
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3191
                $subtree['current_id'] = $myCurrentId;
3192
                $listNotParent[] = $subtree;
3193
            }
3194
        }
3195
3196
        $list['are_parents'] = $listParent;
3197
        $list['not_parents'] = $listNotParent;
3198
3199
        return $list;
3200
    }
3201
3202
    /**
3203
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3204
     *
3205
     * @param array $tree
3206
     * @param int   $id
3207
     * @param bool  $parent
3208
     *
3209
     * @return array HTML TOC ready to display
3210
     */
3211
    public function getChildrenToc($tree, $id, $parent = true)
3212
    {
3213
        if (empty($tree)) {
3214
            $tree = $this->get_toc();
3215
        }
3216
3217
        $dirTypes = self::getChapterTypes();
3218
        $currentItemId = $this->get_current_item_id();
3219
        $list = [];
3220
3221
        foreach ($tree as $subtree) {
3222
            $subtree['tree'] = null;
3223
3224
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3225
                if ($subtree['id'] == $this->current) {
3226
                    $subtree['current'] = 'active';
3227
                } else {
3228
                    $subtree['current'] = null;
3229
                }
3230
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3231
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3232
                }
3233
3234
                $title = Security::remove_XSS($subtree['title']);
3235
                unset($subtree['title']);
3236
                if (empty($title)) {
3237
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3238
                }
3239
3240
                $classStyle = null;
3241
                if ($subtree['id'] == $this->current) {
3242
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3243
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3244
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3245
                }
3246
3247
                if (in_array($subtree['type'], $dirTypes)) {
3248
                    $subtree['title'] = stripslashes($title);
3249
                } else {
3250
                    $subtree['title'] = $title;
3251
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3252
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3253
                    $subtree['current_id'] = $currentItemId;
3254
                }
3255
                $list[] = $subtree;
3256
            }
3257
        }
3258
3259
        return $list;
3260
    }
3261
3262
    /**
3263
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3264
     *
3265
     * @param array $toc_list
3266
     *
3267
     * @return array HTML TOC ready to display
3268
     */
3269
    public function getListArrayToc($toc_list = [])
3270
    {
3271
        if (empty($toc_list)) {
3272
            $toc_list = $this->get_toc();
3273
        }
3274
        // Temporary variables.
3275
        $currentItemId = $this->get_current_item_id();
3276
        $list = [];
3277
        $arrayList = [];
3278
3279
        foreach ($toc_list as $item) {
3280
            $list['id'] = $item['id'];
3281
            $list['status'] = $item['status'];
3282
            $cssStatus = null;
3283
3284
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3285
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3286
            }
3287
3288
            $classStyle = ' ';
3289
            $dirTypes = self::getChapterTypes();
3290
3291
            if (in_array($item['type'], $dirTypes)) {
3292
                $classStyle = 'scorm_item_section ';
3293
            }
3294
            if ($item['id'] == $this->current) {
3295
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3296
            } elseif (!in_array($item['type'], $dirTypes)) {
3297
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3298
            }
3299
            $title = $item['title'];
3300
            if (empty($title)) {
3301
                $title = self::rl_get_resource_name(
3302
                    api_get_course_id(),
3303
                    $this->get_id(),
3304
                    $item['id']
3305
                );
3306
            }
3307
            $title = Security::remove_XSS($item['title']);
3308
3309
            if (empty($item['description'])) {
3310
                $list['description'] = $title;
3311
            } else {
3312
                $list['description'] = $item['description'];
3313
            }
3314
3315
            $list['class'] = $classStyle.' '.$cssStatus;
3316
            $list['level'] = $item['level'];
3317
            $list['type'] = $item['type'];
3318
3319
            if (in_array($item['type'], $dirTypes)) {
3320
                $list['css_level'] = 'level_'.$item['level'];
3321
            } else {
3322
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3323
            }
3324
3325
            if (in_array($item['type'], $dirTypes)) {
3326
                $list['title'] = stripslashes($title);
3327
            } else {
3328
                $list['title'] = stripslashes($title);
3329
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3330
                $list['current_id'] = $currentItemId;
3331
            }
3332
            $arrayList[] = $list;
3333
        }
3334
3335
        return $arrayList;
3336
    }
3337
3338
    /**
3339
     * Returns an HTML-formatted string ready to display with teacher buttons
3340
     * in LP view menu.
3341
     *
3342
     * @return string HTML TOC ready to display
3343
     */
3344
    public function get_teacher_toc_buttons()
3345
    {
3346
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3347
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3348
        $html = '';
3349
        if ($isAllow && false == $hideIcons) {
3350
            if ($this->get_lp_session_id() == api_get_session_id()) {
3351
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3352
                $html .= '<div class="btn-group">';
3353
                $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'>".
3354
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3355
                $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'>".
3356
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3357
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3358
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3359
                $html .= '</div>';
3360
                $html .= '</div>';
3361
            }
3362
        }
3363
3364
        return $html;
3365
    }
3366
3367
    /**
3368
     * Gets the learnpath maker name - generally the editor's name.
3369
     *
3370
     * @return string Learnpath maker name
3371
     */
3372
    public function get_maker()
3373
    {
3374
        if (!empty($this->maker)) {
3375
            return $this->maker;
3376
        }
3377
3378
        return '';
3379
    }
3380
3381
    /**
3382
     * Gets the learnpath name/title.
3383
     *
3384
     * @return string Learnpath name/title
3385
     */
3386
    public function get_name()
3387
    {
3388
        if (!empty($this->name)) {
3389
            return $this->name;
3390
        }
3391
3392
        return 'N/A';
3393
    }
3394
3395
    /**
3396
     * @return string
3397
     */
3398
    public function getNameNoTags()
3399
    {
3400
        return strip_tags($this->get_name());
3401
    }
3402
3403
    /**
3404
     * Gets a link to the resource from the present location, depending on item ID.
3405
     *
3406
     * @param string $type         Type of link expected
3407
     * @param int    $item_id      Learnpath item ID
3408
     * @param bool   $provided_toc
3409
     *
3410
     * @return string $provided_toc Link to the lp_item resource
3411
     */
3412
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3413
    {
3414
        $course_id = $this->get_course_int_id();
3415
        $item_id = (int) $item_id;
3416
3417
        if (empty($item_id)) {
3418
            $item_id = $this->get_current_item_id();
3419
3420
            if (empty($item_id)) {
3421
                //still empty, this means there was no item_id given and we are not in an object context or
3422
                //the object property is empty, return empty link
3423
                $this->first();
3424
3425
                return '';
3426
            }
3427
        }
3428
3429
        $file = '';
3430
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3431
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3432
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3433
3434
        $sql = "SELECT
3435
                    l.lp_type as ltype,
3436
                    l.path as lpath,
3437
                    li.item_type as litype,
3438
                    li.path as lipath,
3439
                    li.parameters as liparams
3440
        		FROM $lp_table l
3441
                INNER JOIN $lp_item_table li
3442
                ON (li.lp_id = l.iid)
3443
        		WHERE
3444
        		    li.iid = $item_id
3445
        		";
3446
        $res = Database::query($sql);
3447
        if (Database::num_rows($res) > 0) {
3448
            $row = Database::fetch_array($res);
3449
            $lp_type = $row['ltype'];
3450
            $lp_path = $row['lpath'];
3451
            $lp_item_type = $row['litype'];
3452
            $lp_item_path = $row['lipath'];
3453
            $lp_item_params = $row['liparams'];
3454
3455
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3456
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3457
            }
3458
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3459
            if ('http' === $type) {
3460
                //web path
3461
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3462
            } else {
3463
                //$course_path = $sys_course_path; //system path
3464
            }
3465
3466
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3467
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3468
            if (in_array(
3469
                $lp_item_type,
3470
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3471
            )
3472
            ) {
3473
                $lp_type = 1;
3474
            }
3475
3476
            // Now go through the specific cases to get the end of the path
3477
            // @todo Use constants instead of int values.
3478
            switch ($lp_type) {
3479
                case 1:
3480
                    $file = self::rl_get_resource_link_for_learnpath(
3481
                        $course_id,
3482
                        $this->get_id(),
3483
                        $item_id,
3484
                        $this->get_view_id()
3485
                    );
3486
                    switch ($lp_item_type) {
3487
                        case 'document':
3488
                            // Shows a button to download the file instead of just downloading the file directly.
3489
                            $documentPathInfo = pathinfo($file);
3490
                            if (isset($documentPathInfo['extension'])) {
3491
                                $parsed = parse_url($documentPathInfo['extension']);
3492
                                if (isset($parsed['path'])) {
3493
                                    $extension = $parsed['path'];
3494
                                    $extensionsToDownload = [
3495
                                        'zip',
3496
                                        'ppt',
3497
                                        'pptx',
3498
                                        'ods',
3499
                                        'xlsx',
3500
                                        'xls',
3501
                                        'csv',
3502
                                        'doc',
3503
                                        'docx',
3504
                                        'dot',
3505
                                    ];
3506
3507
                                    if (in_array($extension, $extensionsToDownload)) {
3508
                                        $file = api_get_path(WEB_CODE_PATH).
3509
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3510
                                    }
3511
                                }
3512
                            }
3513
                            break;
3514
                        case 'dir':
3515
                            $file = 'lp_content.php?type=dir';
3516
                            break;
3517
                        case 'link':
3518
                            if (Link::is_youtube_link($file)) {
3519
                                $src = Link::get_youtube_video_id($file);
3520
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3521
                            } elseif (Link::isVimeoLink($file)) {
3522
                                $src = Link::getVimeoLinkId($file);
3523
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3524
                            } else {
3525
                                // If the current site is HTTPS and the link is
3526
                                // HTTP, browsers will refuse opening the link
3527
                                $urlId = api_get_current_access_url_id();
3528
                                $url = api_get_access_url($urlId, false);
3529
                                $protocol = substr($url['url'], 0, 5);
3530
                                if ('https' === $protocol) {
3531
                                    $linkProtocol = substr($file, 0, 5);
3532
                                    if ('http:' === $linkProtocol) {
3533
                                        //this is the special intervention case
3534
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3535
                                    }
3536
                                }
3537
                            }
3538
                            break;
3539
                        case 'quiz':
3540
                            // Check how much attempts of a exercise exits in lp
3541
                            $lp_item_id = $this->get_current_item_id();
3542
                            $lp_view_id = $this->get_view_id();
3543
3544
                            $prevent_reinit = null;
3545
                            if (isset($this->items[$this->current])) {
3546
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3547
                            }
3548
3549
                            if (empty($provided_toc)) {
3550
                                $list = $this->get_toc();
3551
                            } else {
3552
                                $list = $provided_toc;
3553
                            }
3554
3555
                            $type_quiz = false;
3556
                            foreach ($list as $toc) {
3557
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3558
                                    $type_quiz = true;
3559
                                }
3560
                            }
3561
3562
                            if ($type_quiz) {
3563
                                $lp_item_id = (int) $lp_item_id;
3564
                                $lp_view_id = (int) $lp_view_id;
3565
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3566
                                        WHERE
3567
                                            c_id = $course_id AND
3568
                                            lp_item_id='".$lp_item_id."' AND
3569
                                            lp_view_id ='".$lp_view_id."' AND
3570
                                            status='completed'";
3571
                                $result = Database::query($sql);
3572
                                $row_count = Database:: fetch_row($result);
3573
                                $count_item_view = (int) $row_count[0];
3574
                                $not_multiple_attempt = 0;
3575
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3576
                                    $not_multiple_attempt = 1;
3577
                                }
3578
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3579
                            }
3580
                            break;
3581
                    }
3582
3583
                    $tmp_array = explode('/', $file);
3584
                    $document_name = $tmp_array[count($tmp_array) - 1];
3585
                    if (strpos($document_name, '_DELETED_')) {
3586
                        $file = 'blank.php?error=document_deleted';
3587
                    }
3588
                    break;
3589
                case 2:
3590
                    if ('dir' !== $lp_item_type) {
3591
                        // Quite complex here:
3592
                        // We want to make sure 'http://' (and similar) links can
3593
                        // be loaded as is (withouth the Chamilo path in front) but
3594
                        // some contents use this form: resource.htm?resource=http://blablabla
3595
                        // which means we have to find a protocol at the path's start, otherwise
3596
                        // it should not be considered as an external URL.
3597
                        // if ($this->prerequisites_match($item_id)) {
3598
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3599
                            if ($this->debug > 2) {
3600
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3601
                            }
3602
                            // Distant url, return as is.
3603
                            $file = $lp_item_path;
3604
                        } else {
3605
                            if ($this->debug > 2) {
3606
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3607
                            }
3608
                            // Prevent getting untranslatable urls.
3609
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3610
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3611
                            // Prepare the path.
3612
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3613
                            // TODO: Fix this for urls with protocol header.
3614
                            $file = str_replace('//', '/', $file);
3615
                            $file = str_replace(':/', '://', $file);
3616
                            if ('/' == substr($lp_path, -1)) {
3617
                                $lp_path = substr($lp_path, 0, -1);
3618
                            }
3619
3620
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3621
                                // if file not found.
3622
                                $decoded = html_entity_decode($lp_item_path);
3623
                                list($decoded) = explode('?', $decoded);
3624
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3625
                                    $file = self::rl_get_resource_link_for_learnpath(
3626
                                        $course_id,
3627
                                        $this->get_id(),
3628
                                        $item_id,
3629
                                        $this->get_view_id()
3630
                                    );
3631
                                    if (empty($file)) {
3632
                                        $file = 'blank.php?error=document_not_found';
3633
                                    } else {
3634
                                        $tmp_array = explode('/', $file);
3635
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3636
                                        if (strpos($document_name, '_DELETED_')) {
3637
                                            $file = 'blank.php?error=document_deleted';
3638
                                        } else {
3639
                                            $file = 'blank.php?error=document_not_found';
3640
                                        }
3641
                                    }
3642
                                } else {
3643
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3644
                                }
3645
                            }
3646
                        }
3647
3648
                        // We want to use parameters if they were defined in the imsmanifest
3649
                        if (false === strpos($file, 'blank.php')) {
3650
                            $lp_item_params = ltrim($lp_item_params, '?');
3651
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3652
                        }
3653
                    } else {
3654
                        $file = 'lp_content.php?type=dir';
3655
                    }
3656
                    break;
3657
                case 3:
3658
                    // Formatting AICC HACP append URL.
3659
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3660
                    if (!empty($lp_item_params)) {
3661
                        $aicc_append .= $lp_item_params.'&';
3662
                    }
3663
                    if ('dir' !== $lp_item_type) {
3664
                        // Quite complex here:
3665
                        // We want to make sure 'http://' (and similar) links can
3666
                        // be loaded as is (withouth the Chamilo path in front) but
3667
                        // some contents use this form: resource.htm?resource=http://blablabla
3668
                        // which means we have to find a protocol at the path's start, otherwise
3669
                        // it should not be considered as an external URL.
3670
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3671
                            if ($this->debug > 2) {
3672
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3673
                            }
3674
                            // Distant url, return as is.
3675
                            $file = $lp_item_path;
3676
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3677
                            /*
3678
                            if (stristr($file,'<servername>') !== false) {
3679
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3680
                            }
3681
                            */
3682
                            if (false !== stripos($file, '<servername>')) {
3683
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3684
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3685
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3686
                            }
3687
3688
                            $file .= $aicc_append;
3689
                        } else {
3690
                            if ($this->debug > 2) {
3691
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3692
                            }
3693
                            // Prevent getting untranslatable urls.
3694
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3695
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3696
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3697
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3698
                            // TODO: Fix this for urls with protocol header.
3699
                            $file = str_replace('//', '/', $file);
3700
                            $file = str_replace(':/', '://', $file);
3701
                            $file .= $aicc_append;
3702
                        }
3703
                    } else {
3704
                        $file = 'lp_content.php?type=dir';
3705
                    }
3706
                    break;
3707
                case 4:
3708
                    break;
3709
                default:
3710
                    break;
3711
            }
3712
            // Replace &amp; by & because &amp; will break URL with params
3713
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3714
        }
3715
        if ($this->debug > 2) {
3716
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3717
        }
3718
3719
        return $file;
3720
    }
3721
3722
    /**
3723
     * Gets the latest usable view or generate a new one.
3724
     *
3725
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3726
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3727
     *
3728
     * @return int DB lp_view id
3729
     */
3730
    public function get_view($attempt_num = 0, $userId = null)
3731
    {
3732
        $search = '';
3733
        // Use $attempt_num to enable multi-views management (disabled so far).
3734
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3735
            $search = 'AND view_count = '.$attempt_num;
3736
        }
3737
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3738
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3739
3740
        $course_id = api_get_course_int_id();
3741
        $sessionId = api_get_session_id();
3742
3743
        // Check user ID.
3744
        if (empty($userId)) {
3745
            if (empty($this->get_user_id())) {
3746
                $this->error = 'User ID is empty in learnpath::get_view()';
3747
3748
                return null;
3749
            } else {
3750
                $userId = $this->get_user_id();
3751
            }
3752
        }
3753
3754
        $sql = "SELECT iid, view_count FROM $lp_view_table
3755
        		WHERE
3756
        		    c_id = $course_id AND
3757
        		    lp_id = ".$this->get_id()." AND
3758
        		    user_id = ".$userId." AND
3759
        		    session_id = $sessionId
3760
        		    $search
3761
                ORDER BY view_count DESC";
3762
        $res = Database::query($sql);
3763
        if (Database::num_rows($res) > 0) {
3764
            $row = Database::fetch_array($res);
3765
            $this->lp_view_id = $row['iid'];
3766
        } elseif (!api_is_invitee()) {
3767
            // There is no database record, create one.
3768
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3769
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3770
            Database::query($sql);
3771
            $id = Database::insert_id();
3772
            $this->lp_view_id = $id;
3773
        }
3774
3775
        return $this->lp_view_id;
3776
    }
3777
3778
    /**
3779
     * Gets the current view id.
3780
     *
3781
     * @return int View ID (from lp_view)
3782
     */
3783
    public function get_view_id()
3784
    {
3785
        if (!empty($this->lp_view_id)) {
3786
            return (int) $this->lp_view_id;
3787
        }
3788
3789
        return 0;
3790
    }
3791
3792
    /**
3793
     * Gets the update queue.
3794
     *
3795
     * @return array Array containing IDs of items to be updated by JavaScript
3796
     */
3797
    public function get_update_queue()
3798
    {
3799
        return $this->update_queue;
3800
    }
3801
3802
    /**
3803
     * Gets the user ID.
3804
     *
3805
     * @return int User ID
3806
     */
3807
    public function get_user_id()
3808
    {
3809
        if (!empty($this->user_id)) {
3810
            return (int) $this->user_id;
3811
        }
3812
3813
        return false;
3814
    }
3815
3816
    /**
3817
     * Checks if any of the items has an audio element attached.
3818
     *
3819
     * @return bool True or false
3820
     */
3821
    public function has_audio()
3822
    {
3823
        $has = false;
3824
        foreach ($this->items as $i => $item) {
3825
            if (!empty($this->items[$i]->audio)) {
3826
                $has = true;
3827
                break;
3828
            }
3829
        }
3830
3831
        return $has;
3832
    }
3833
3834
    /**
3835
     * Moves an item up and down at its level.
3836
     *
3837
     * @param int    $id        Item to move up and down
3838
     * @param string $direction Direction 'up' or 'down'
3839
     *
3840
     * @return bool|int
3841
     */
3842
    public function move_item($id, $direction)
3843
    {
3844
        $course_id = api_get_course_int_id();
3845
        if (empty($id) || empty($direction)) {
3846
            return false;
3847
        }
3848
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3849
        $sql_sel = "SELECT *
3850
                    FROM $tbl_lp_item
3851
                    WHERE
3852
                        iid = $id
3853
                    ";
3854
        $res_sel = Database::query($sql_sel);
3855
        // Check if elem exists.
3856
        if (Database::num_rows($res_sel) < 1) {
3857
            return false;
3858
        }
3859
        // Gather data.
3860
        $row = Database::fetch_array($res_sel);
3861
        $previous = $row['previous_item_id'];
3862
        $next = $row['next_item_id'];
3863
        $display = $row['display_order'];
3864
        $parent = $row['parent_item_id'];
3865
        $lp = $row['lp_id'];
3866
        // Update the item (switch with previous/next one).
3867
        switch ($direction) {
3868
            case 'up':
3869
                if ($display > 1) {
3870
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3871
                                 WHERE iid = $previous";
3872
                    $res_sel2 = Database::query($sql_sel2);
3873
                    if (Database::num_rows($res_sel2) < 1) {
3874
                        $previous_previous = 0;
3875
                    }
3876
                    // Gather data.
3877
                    $row2 = Database::fetch_array($res_sel2);
3878
                    $previous_previous = $row2['previous_item_id'];
3879
                    // Update previous_previous item (switch "next" with current).
3880
                    if (0 != $previous_previous) {
3881
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3882
                                        next_item_id = $id
3883
                                    WHERE iid = $previous_previous";
3884
                        Database::query($sql_upd2);
3885
                    }
3886
                    // Update previous item (switch with current).
3887
                    if (0 != $previous) {
3888
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3889
                                    next_item_id = $next,
3890
                                    previous_item_id = $id,
3891
                                    display_order = display_order +1
3892
                                    WHERE iid = $previous";
3893
                        Database::query($sql_upd2);
3894
                    }
3895
3896
                    // Update current item (switch with previous).
3897
                    if (0 != $id) {
3898
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3899
                                        next_item_id = $previous,
3900
                                        previous_item_id = $previous_previous,
3901
                                        display_order = display_order-1
3902
                                    WHERE c_id = ".$course_id." AND id = $id";
3903
                        Database::query($sql_upd2);
3904
                    }
3905
                    // Update next item (new previous item).
3906
                    if (!empty($next)) {
3907
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3908
                                     WHERE iid = $next";
3909
                        Database::query($sql_upd2);
3910
                    }
3911
                    $display = $display - 1;
3912
                }
3913
                break;
3914
            case 'down':
3915
                if (0 != $next) {
3916
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3917
                                 WHERE iid = $next";
3918
                    $res_sel2 = Database::query($sql_sel2);
3919
                    if (Database::num_rows($res_sel2) < 1) {
3920
                        $next_next = 0;
3921
                    }
3922
                    // Gather data.
3923
                    $row2 = Database::fetch_array($res_sel2);
3924
                    $next_next = $row2['next_item_id'];
3925
                    // Update previous item (switch with current).
3926
                    if (0 != $previous) {
3927
                        $sql_upd2 = "UPDATE $tbl_lp_item
3928
                                     SET next_item_id = $next
3929
                                     WHERE iid = $previous";
3930
                        Database::query($sql_upd2);
3931
                    }
3932
                    // Update current item (switch with previous).
3933
                    if (0 != $id) {
3934
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3935
                                     previous_item_id = $next,
3936
                                     next_item_id = $next_next,
3937
                                     display_order = display_order + 1
3938
                                     WHERE iid = $id";
3939
                        Database::query($sql_upd2);
3940
                    }
3941
3942
                    // Update next item (new previous item).
3943
                    if (0 != $next) {
3944
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3945
                                     previous_item_id = $previous,
3946
                                     next_item_id = $id,
3947
                                     display_order = display_order-1
3948
                                     WHERE iid = $next";
3949
                        Database::query($sql_upd2);
3950
                    }
3951
3952
                    // Update next_next item (switch "previous" with current).
3953
                    if (0 != $next_next) {
3954
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3955
                                     previous_item_id = $id
3956
                                     WHERE iid = $next_next";
3957
                        Database::query($sql_upd2);
3958
                    }
3959
                    $display = $display + 1;
3960
                }
3961
                break;
3962
            default:
3963
                return false;
3964
        }
3965
3966
        return $display;
3967
    }
3968
3969
    /**
3970
     * Move a LP up (display_order).
3971
     *
3972
     * @param int $lp_id      Learnpath ID
3973
     * @param int $categoryId Category ID
3974
     *
3975
     * @return bool
3976
     */
3977
    public static function move_up($lp_id, $categoryId = 0)
3978
    {
3979
        $courseId = api_get_course_int_id();
3980
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3981
3982
        $categoryCondition = '';
3983
        if (!empty($categoryId)) {
3984
            $categoryId = (int) $categoryId;
3985
            $categoryCondition = " AND category_id = $categoryId";
3986
        }
3987
        $sql = "SELECT * FROM $lp_table
3988
                WHERE c_id = $courseId
3989
                $categoryCondition
3990
                ORDER BY display_order";
3991
        $res = Database::query($sql);
3992
        if (false === $res) {
3993
            return false;
3994
        }
3995
3996
        $lps = [];
3997
        $lp_order = [];
3998
        $num = Database::num_rows($res);
3999
        // First check the order is correct, globally (might be wrong because
4000
        // of versions < 1.8.4)
4001
        if ($num > 0) {
4002
            $i = 1;
4003
            while ($row = Database::fetch_array($res)) {
4004
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4005
                    $sql = "UPDATE $lp_table SET display_order = $i
4006
                            WHERE iid = ".$row['iid'];
4007
                    Database::query($sql);
4008
                }
4009
                $row['display_order'] = $i;
4010
                $lps[$row['iid']] = $row;
4011
                $lp_order[$i] = $row['iid'];
4012
                $i++;
4013
            }
4014
        }
4015
        if ($num > 1) { // If there's only one element, no need to sort.
4016
            $order = $lps[$lp_id]['display_order'];
4017
            if ($order > 1) { // If it's the first element, no need to move up.
4018
                $sql = "UPDATE $lp_table SET display_order = $order
4019
                        WHERE iid = ".$lp_order[$order - 1];
4020
                Database::query($sql);
4021
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4022
                        WHERE iid = $lp_id";
4023
                Database::query($sql);
4024
            }
4025
        }
4026
4027
        return true;
4028
    }
4029
4030
    /**
4031
     * Move a learnpath down (display_order).
4032
     *
4033
     * @param int $lp_id      Learnpath ID
4034
     * @param int $categoryId Category ID
4035
     *
4036
     * @return bool
4037
     */
4038
    public static function move_down($lp_id, $categoryId = 0)
4039
    {
4040
        $courseId = api_get_course_int_id();
4041
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4042
4043
        $categoryCondition = '';
4044
        if (!empty($categoryId)) {
4045
            $categoryId = (int) $categoryId;
4046
            $categoryCondition = " AND category_id = $categoryId";
4047
        }
4048
4049
        $sql = "SELECT * FROM $lp_table
4050
                WHERE c_id = $courseId
4051
                $categoryCondition
4052
                ORDER BY display_order";
4053
        $res = Database::query($sql);
4054
        if (false === $res) {
4055
            return false;
4056
        }
4057
        $lps = [];
4058
        $lp_order = [];
4059
        $num = Database::num_rows($res);
4060
        $max = 0;
4061
        // First check the order is correct, globally (might be wrong because
4062
        // of versions < 1.8.4).
4063
        if ($num > 0) {
4064
            $i = 1;
4065
            while ($row = Database::fetch_array($res)) {
4066
                $max = $i;
4067
                if ($row['display_order'] != $i) {
4068
                    // If we find a gap in the order, we need to fix it.
4069
                    $sql = "UPDATE $lp_table SET display_order = $i
4070
                              WHERE iid = ".$row['iid'];
4071
                    Database::query($sql);
4072
                }
4073
                $row['display_order'] = $i;
4074
                $lps[$row['iid']] = $row;
4075
                $lp_order[$i] = $row['iid'];
4076
                $i++;
4077
            }
4078
        }
4079
        if ($num > 1) { // If there's only one element, no need to sort.
4080
            $order = $lps[$lp_id]['display_order'];
4081
            if ($order < $max) { // If it's the first element, no need to move up.
4082
                $sql = "UPDATE $lp_table SET display_order = $order
4083
                        WHERE iid = ".$lp_order[$order + 1];
4084
                Database::query($sql);
4085
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4086
                        WHERE iid = $lp_id";
4087
                Database::query($sql);
4088
            }
4089
        }
4090
4091
        return true;
4092
    }
4093
4094
    /**
4095
     * Updates learnpath attributes to point to the next element
4096
     * The last part is similar to set_current_item but processing the other way around.
4097
     */
4098
    public function next()
4099
    {
4100
        if ($this->debug > 0) {
4101
            error_log('In learnpath::next()', 0);
4102
        }
4103
        $this->last = $this->get_current_item_id();
4104
        $this->items[$this->last]->save(
4105
            false,
4106
            $this->prerequisites_match($this->last)
4107
        );
4108
        $this->autocomplete_parents($this->last);
4109
        $new_index = $this->get_next_index();
4110
        if ($this->debug > 2) {
4111
            error_log('New index: '.$new_index, 0);
4112
        }
4113
        $this->index = $new_index;
4114
        if ($this->debug > 2) {
4115
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4116
        }
4117
        $this->current = $this->ordered_items[$new_index];
4118
        if ($this->debug > 2) {
4119
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4120
        }
4121
    }
4122
4123
    /**
4124
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4125
     * class, this might be redefined to allow several behaviours depending on the document type.
4126
     *
4127
     * @param int $id Resource ID
4128
     */
4129
    public function open($id)
4130
    {
4131
        // TODO:
4132
        // set the current resource attribute to this resource
4133
        // switch on element type (redefine in child class?)
4134
        // set status for this item to "opened"
4135
        // start timer
4136
        // initialise score
4137
        $this->index = 0; //or = the last item seen (see $this->last)
4138
    }
4139
4140
    /**
4141
     * Check that all prerequisites are fulfilled. Returns true and an
4142
     * empty string on success, returns false
4143
     * and the prerequisite string on error.
4144
     * This function is based on the rules for aicc_script language as
4145
     * described in the SCORM 1.2 CAM documentation page 108.
4146
     *
4147
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4148
     *
4149
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4150
     *              string otherwise
4151
     */
4152
    public function prerequisites_match($itemId = null)
4153
    {
4154
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4155
        if ($allow) {
4156
            if (api_is_allowed_to_edit() ||
4157
                api_is_platform_admin(true) ||
4158
                api_is_drh() ||
4159
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4160
            ) {
4161
                return true;
4162
            }
4163
        }
4164
4165
        $debug = $this->debug;
4166
        if ($debug > 0) {
4167
            error_log('In learnpath::prerequisites_match()');
4168
        }
4169
4170
        if (empty($itemId)) {
4171
            $itemId = $this->current;
4172
        }
4173
4174
        $currentItem = $this->getItem($itemId);
4175
4176
        if ($currentItem) {
4177
            if (2 == $this->type) {
4178
                // Getting prereq from scorm
4179
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4180
            } else {
4181
                $prereq_string = $currentItem->get_prereq_string();
4182
            }
4183
4184
            if (empty($prereq_string)) {
4185
                if ($debug > 0) {
4186
                    error_log('Found prereq_string is empty return true');
4187
                }
4188
4189
                return true;
4190
            }
4191
4192
            // Clean spaces.
4193
            $prereq_string = str_replace(' ', '', $prereq_string);
4194
            if ($debug > 0) {
4195
                error_log('Found prereq_string: '.$prereq_string, 0);
4196
            }
4197
4198
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4199
            $result = $currentItem->parse_prereq(
4200
                $prereq_string,
4201
                $this->items,
4202
                $this->refs_list,
4203
                $this->get_user_id()
4204
            );
4205
4206
            if (false === $result) {
4207
                $this->set_error_msg($currentItem->prereq_alert);
4208
            }
4209
        } else {
4210
            $result = true;
4211
            if ($debug > 1) {
4212
                error_log('$this->items['.$itemId.'] was not an object', 0);
4213
            }
4214
        }
4215
4216
        if ($debug > 1) {
4217
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4218
        }
4219
4220
        return $result;
4221
    }
4222
4223
    /**
4224
     * Updates learnpath attributes to point to the previous element
4225
     * The last part is similar to set_current_item but processing the other way around.
4226
     */
4227
    public function previous()
4228
    {
4229
        $this->last = $this->get_current_item_id();
4230
        $this->items[$this->last]->save(
4231
            false,
4232
            $this->prerequisites_match($this->last)
4233
        );
4234
        $this->autocomplete_parents($this->last);
4235
        $new_index = $this->get_previous_index();
4236
        $this->index = $new_index;
4237
        $this->current = $this->ordered_items[$new_index];
4238
    }
4239
4240
    /**
4241
     * Publishes a learnpath. This basically means show or hide the learnpath
4242
     * to normal users.
4243
     * Can be used as abstract.
4244
     *
4245
     * @param int $id         Learnpath ID
4246
     * @param int $visibility New visibility
4247
     *
4248
     * @return bool
4249
     */
4250
    public static function toggleVisibility($id, $visibility = 1)
4251
    {
4252
        $repo = Container::getLpRepository();
4253
        $lp = $repo->find($id);
4254
4255
        if (!$lp) {
4256
            return false;
4257
        }
4258
4259
        $visibility = (int) $visibility;
4260
4261
        if (1 === $visibility) {
4262
            $repo->setVisibilityPublished($lp);
4263
        } else {
4264
            $repo->setVisibilityDraft($lp);
4265
        }
4266
4267
        return true;
4268
4269
        /*$action = 'visible';
4270
        if (1 != $set_visibility) {
4271
            $action = 'invisible';
4272
            self::toggle_publish($lp_id, 'i');
4273
        }
4274
4275
        return api_item_property_update(
4276
            api_get_course_info(),
4277
            TOOL_LEARNPATH,
4278
            $lp_id,
4279
            $action,
4280
            api_get_user_id()
4281
        );*/
4282
    }
4283
4284
    /**
4285
     * Publishes a learnpath category.
4286
     * This basically means show or hide the learnpath category to normal users.
4287
     *
4288
     * @param int $id
4289
     * @param int $visibility
4290
     *
4291
     * @return bool
4292
     */
4293
    public static function toggleCategoryVisibility($id, $visibility = 1)
4294
    {
4295
        $repo = Container::getLpCategoryRepository();
4296
        $resource = $repo->find($id);
4297
4298
        if (!$resource) {
4299
            return false;
4300
        }
4301
4302
        $visibility = (int) $visibility;
4303
4304
        if (1 === $visibility) {
4305
            $repo->setVisibilityPublished($resource);
4306
        } else {
4307
            $repo->setVisibilityDraft($resource);
4308
            self::toggleCategoryPublish($id, 0);
4309
        }
4310
4311
        return false;
4312
        /*
4313
        $action = 'visible';
4314
        if (1 != $visibility) {
4315
            self::toggleCategoryPublish($id, 0);
4316
            $action = 'invisible';
4317
        }
4318
4319
        return api_item_property_update(
4320
            api_get_course_info(),
4321
            TOOL_LEARNPATH_CATEGORY,
4322
            $id,
4323
            $action,
4324
            api_get_user_id()
4325
        );*/
4326
    }
4327
4328
    /**
4329
     * Publishes a learnpath. This basically means show or hide the learnpath
4330
     * on the course homepage
4331
     *
4332
     * @param int    $id            Learnpath id
4333
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4334
     *
4335
     * @return bool
4336
     */
4337
    public static function togglePublish($id, $setVisibility = 'v')
4338
    {
4339
        $addShortcut = false;
4340
        if ('v' === $setVisibility) {
4341
            $addShortcut = true;
4342
        }
4343
        $repo = Container::getLpRepository();
4344
        /** @var CLp $lp */
4345
        $lp = $repo->find($id);
4346
        if (null === $lp) {
4347
            return false;
4348
        }
4349
        $repoShortcut = Container::getShortcutRepository();
4350
        $courseEntity = api_get_course_entity();
4351
4352
        if ($addShortcut) {
4353
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4354
        } else {
4355
            $repoShortcut->removeShortCut($lp);
4356
        }
4357
4358
        return true;
4359
4360
        /*
4361
        $course_id = api_get_course_int_id();
4362
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4363
        $lp_id = (int) $lp_id;
4364
        $sql = "SELECT * FROM $tbl_lp
4365
                WHERE iid = $lp_id";
4366
        $result = Database::query($sql);
4367
4368
        if (Database::num_rows($result)) {
4369
            $row = Database::fetch_array($result);
4370
            $name = Database::escape_string($row['name']);
4371
            if ($set_visibility == 'i') {
4372
                $v = 0;
4373
            }
4374
            if ($set_visibility == 'v') {
4375
                $v = 1;
4376
            }
4377
4378
            $session_id = api_get_session_id();
4379
            $session_condition = api_get_session_condition($session_id);
4380
4381
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4382
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4383
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4384
4385
            $sql = "SELECT * FROM $tbl_tool
4386
                    WHERE
4387
                        c_id = $course_id AND
4388
                        (link = '$link' OR link = '$oldLink') AND
4389
                        image = 'scormbuilder.gif' AND
4390
                        (
4391
                            link LIKE '$link%' OR
4392
                            link LIKE '$oldLink%'
4393
                        )
4394
                        $session_condition
4395
                    ";
4396
4397
            $result = Database::query($sql);
4398
            $num = Database::num_rows($result);
4399
            if ($set_visibility == 'i' && $num > 0) {
4400
                $sql = "DELETE FROM $tbl_tool
4401
                        WHERE
4402
                            c_id = $course_id AND
4403
                            (link = '$link' OR link = '$oldLink') AND
4404
                            image='scormbuilder.gif'
4405
                            $session_condition";
4406
                Database::query($sql);
4407
            } elseif ($set_visibility == 'v' && $num == 0) {
4408
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4409
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4410
                Database::query($sql);
4411
                $insertId = Database::insert_id();
4412
                if ($insertId) {
4413
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4414
                    Database::query($sql);
4415
                }
4416
            } elseif ($set_visibility == 'v' && $num > 0) {
4417
                $sql = "UPDATE $tbl_tool SET
4418
                            c_id = $course_id,
4419
                            name = '$name',
4420
                            link = '$link',
4421
                            image = 'scormbuilder.gif',
4422
                            visibility = '$v',
4423
                            admin = '0',
4424
                            address = 'pastillegris.gif',
4425
                            added_tool = 0,
4426
                            session_id = $session_id
4427
                        WHERE
4428
                            c_id = ".$course_id." AND
4429
                            (link = '$link' OR link = '$oldLink') AND
4430
                            image='scormbuilder.gif'
4431
                            $session_condition
4432
                        ";
4433
                Database::query($sql);
4434
            } else {
4435
                // Parameter and database incompatible, do nothing, exit.
4436
                return false;
4437
            }
4438
        } else {
4439
            return false;
4440
        }*/
4441
    }
4442
4443
    /**
4444
     * Show or hide the learnpath category on the course homepage.
4445
     *
4446
     * @param int $id
4447
     * @param int $setVisibility
4448
     *
4449
     * @return bool
4450
     */
4451
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4452
    {
4453
        $setVisibility = (int) $setVisibility;
4454
        $addShortcut = false;
4455
        if (1 === $setVisibility) {
4456
            $addShortcut = true;
4457
        }
4458
4459
        $repo = Container::getLpCategoryRepository();
4460
        /** @var CLpCategory $lp */
4461
        $category = $repo->find($id);
4462
4463
        if (null === $category) {
4464
            return false;
4465
        }
4466
4467
        $repoShortcut = Container::getShortcutRepository();
4468
        if ($addShortcut) {
4469
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4470
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4471
        } else {
4472
            $repoShortcut->removeShortCut($category);
4473
        }
4474
4475
        return true;
4476
4477
        $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...
4478
4479
        /** @var CLpCategory $category */
4480
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4481
4482
        if (!$category) {
4483
            return false;
4484
        }
4485
4486
        if (empty($courseId)) {
4487
            return false;
4488
        }
4489
4490
        $link = self::getCategoryLinkForTool($id);
4491
4492
        /** @var CTool $tool */
4493
        $tool = $em->createQuery("
4494
                SELECT t FROM ChamiloCourseBundle:CTool t
4495
                WHERE
4496
                    t.course = :course AND
4497
                    t.link = :link1 AND
4498
                    t.image LIKE 'lp_category.%' AND
4499
                    t.link LIKE :link2
4500
                    $sessionCondition
4501
            ")
4502
            ->setParameters([
4503
                'course' => $courseId,
4504
                'link1' => $link,
4505
                'link2' => "$link%",
4506
            ])
4507
            ->getOneOrNullResult();
4508
4509
        if (0 == $setVisibility && $tool) {
4510
            $em->remove($tool);
4511
            $em->flush();
4512
4513
            return true;
4514
        }
4515
4516
        if (1 == $setVisibility && !$tool) {
4517
            $tool = new CTool();
4518
            $tool
4519
                ->setCategory('authoring')
4520
                ->setCourse(api_get_course_entity($courseId))
4521
                ->setName(strip_tags($category->getName()))
4522
                ->setLink($link)
4523
                ->setImage('lp_category.png')
4524
                ->setVisibility(1)
4525
                ->setAdmin(0)
4526
                ->setAddress('pastillegris.gif')
4527
                ->setAddedTool(0)
4528
                ->setSessionId($sessionId)
4529
                ->setTarget('_self');
4530
4531
            $em->persist($tool);
4532
            $em->flush();
4533
4534
            $tool->setId($tool->getIid());
4535
4536
            $em->persist($tool);
4537
            $em->flush();
4538
4539
            return true;
4540
        }
4541
4542
        if (1 == $setVisibility && $tool) {
4543
            $tool
4544
                ->setName(strip_tags($category->getName()))
4545
                ->setVisibility(1);
4546
4547
            $em->persist($tool);
4548
            $em->flush();
4549
4550
            return true;
4551
        }
4552
4553
        return false;
4554
    }
4555
4556
    /**
4557
     * Check if the learnpath category is visible for a user.
4558
     *
4559
     * @param int
4560
     * @param int
4561
     *
4562
     * @return bool
4563
     */
4564
    public static function categoryIsVisibleForStudent(
4565
        CLpCategory $category,
4566
        User $user,
4567
        $courseId = 0,
4568
        $sessionId = 0
4569
    ) {
4570
        if (empty($category)) {
4571
            return false;
4572
        }
4573
4574
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4575
4576
        if ($isAllowedToEdit) {
4577
            return true;
4578
        }
4579
4580
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4581
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4582
4583
        $courseInfo = api_get_course_info_by_id($courseId);
4584
4585
        $categoryVisibility = api_get_item_visibility(
4586
            $courseInfo,
4587
            TOOL_LEARNPATH_CATEGORY,
4588
            $category->getId(),
4589
            $sessionId
4590
        );
4591
4592
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4593
            return false;
4594
        }
4595
4596
        $subscriptionSettings = self::getSubscriptionSettings();
4597
4598
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4599
            return true;
4600
        }
4601
4602
        $noUserSubscribed = false;
4603
        $noGroupSubscribed = true;
4604
        $users = $category->getUsers();
4605
        if (empty($users) || !$users->count()) {
4606
            $noUserSubscribed = true;
4607
        } elseif ($category->hasUserAdded($user)) {
4608
            return true;
4609
        }
4610
4611
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4612
            $em = Database::getManager();
4613
4614
            /** @var ItemPropertyRepository $itemRepo */
4615
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4616
4617
            /** @var CourseRepository $courseRepo */
4618
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4619
            $session = null;
4620
            if (!empty($sessionId)) {
4621
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4622
            }
4623
4624
                $course = $courseRepo->find($courseId);
4625
4626
        if ($courseId != 0) {
4627
                // Subscribed groups to a LP
4628
                $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4629
                    TOOL_LEARNPATH_CATEGORY,
4630
                    $category->getId(),
4631
                    $course,
4632
                    $session
4633
                );
4634
            }
4635
4636
            if (!empty($subscribedGroupsInLp)) {
4637
            $noGroupSubscribed = false;
4638
            if (!empty($groups)) {
4639
                $groups = array_column($groups, 'iid');
4640
                /** @var CItemProperty $item */
4641
                foreach ($subscribedGroupsInLp as $item) {
4642
                    if ($item->getGroup() &&
4643
                        in_array($item->getGroup()->getId(), $groups)
4644
                    ) {
4645
                        return true;
4646
                    }
4647
                }
4648
            }
4649
        }
4650
        $response = $noGroupSubscribed && $noUserSubscribed;
4651
4652
        return $response;
4653
    }
4654
4655
    /**
4656
     * Check if a learnpath category is published as course tool.
4657
     *
4658
     * @param int $courseId
4659
     *
4660
     * @return bool
4661
     */
4662
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4663
    {
4664
        return false;
4665
        $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...
4666
        $em = Database::getManager();
4667
4668
        $tools = $em
4669
            ->createQuery("
4670
                SELECT t FROM ChamiloCourseBundle:CTool t
4671
                WHERE t.course = :course AND
4672
                    t.name = :name AND
4673
                    t.image LIKE 'lp_category.%' AND
4674
                    t.link LIKE :link
4675
            ")
4676
            ->setParameters([
4677
                'course' => $courseId,
4678
                'name' => strip_tags($category->getName()),
4679
                'link' => "$link%",
4680
            ])
4681
            ->getResult();
4682
4683
        /** @var CTool $tool */
4684
        $tool = current($tools);
4685
4686
        return $tool ? $tool->getVisibility() : false;
4687
    }
4688
4689
    /**
4690
     * Restart the whole learnpath. Return the URL of the first element.
4691
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4692
     * To use a similar method  statically, use the create_new_attempt() method.
4693
     *
4694
     * @return bool
4695
     */
4696
    public function restart()
4697
    {
4698
        if ($this->debug > 0) {
4699
            error_log('In learnpath::restart()', 0);
4700
        }
4701
        // TODO
4702
        // Call autosave method to save the current progress.
4703
        //$this->index = 0;
4704
        if (api_is_invitee()) {
4705
            return false;
4706
        }
4707
        $session_id = api_get_session_id();
4708
        $course_id = api_get_course_int_id();
4709
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4710
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4711
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4712
        if ($this->debug > 2) {
4713
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4714
        }
4715
        Database::query($sql);
4716
        $view_id = Database::insert_id();
4717
4718
        if ($view_id) {
4719
            $this->lp_view_id = $view_id;
4720
            $this->attempt = $this->attempt + 1;
4721
        } else {
4722
            $this->error = 'Could not insert into item_view table...';
4723
4724
            return false;
4725
        }
4726
        $this->autocomplete_parents($this->current);
4727
        foreach ($this->items as $index => $dummy) {
4728
            $this->items[$index]->restart();
4729
            $this->items[$index]->set_lp_view($this->lp_view_id);
4730
        }
4731
        $this->first();
4732
4733
        return true;
4734
    }
4735
4736
    /**
4737
     * Saves the current item.
4738
     *
4739
     * @return bool
4740
     */
4741
    public function save_current()
4742
    {
4743
        $debug = $this->debug;
4744
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4745
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4746
        if ($debug) {
4747
            error_log('save_current() saving item '.$this->current, 0);
4748
            error_log(''.print_r($this->items, true), 0);
4749
        }
4750
        if (isset($this->items[$this->current]) &&
4751
            is_object($this->items[$this->current])
4752
        ) {
4753
            if ($debug) {
4754
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4755
            }
4756
4757
            $res = $this->items[$this->current]->save(
4758
                false,
4759
                $this->prerequisites_match($this->current)
4760
            );
4761
            $this->autocomplete_parents($this->current);
4762
            $status = $this->items[$this->current]->get_status();
4763
            $this->update_queue[$this->current] = $status;
4764
4765
            if ($debug) {
4766
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4767
            }
4768
4769
            return $res;
4770
        }
4771
4772
        return false;
4773
    }
4774
4775
    /**
4776
     * Saves the given item.
4777
     *
4778
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4779
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4780
     *
4781
     * @return bool
4782
     */
4783
    public function save_item($item_id = null, $from_outside = true)
4784
    {
4785
        $debug = $this->debug;
4786
        if ($debug) {
4787
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4788
        }
4789
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4790
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4791
        if (empty($item_id)) {
4792
            $item_id = (int) $_REQUEST['id'];
4793
        }
4794
4795
        if (empty($item_id)) {
4796
            $item_id = $this->get_current_item_id();
4797
        }
4798
        if (isset($this->items[$item_id]) &&
4799
            is_object($this->items[$item_id])
4800
        ) {
4801
            if ($debug) {
4802
                error_log('Object exists');
4803
            }
4804
4805
            // Saving the item.
4806
            $res = $this->items[$item_id]->save(
4807
                $from_outside,
4808
                $this->prerequisites_match($item_id)
4809
            );
4810
4811
            if ($debug) {
4812
                error_log('update_queue before:');
4813
                error_log(print_r($this->update_queue, 1));
4814
            }
4815
            $this->autocomplete_parents($item_id);
4816
4817
            $status = $this->items[$item_id]->get_status();
4818
            $this->update_queue[$item_id] = $status;
4819
4820
            if ($debug) {
4821
                error_log('get_status(): '.$status);
4822
                error_log('update_queue after:');
4823
                error_log(print_r($this->update_queue, 1));
4824
            }
4825
4826
            return $res;
4827
        }
4828
4829
        return false;
4830
    }
4831
4832
    /**
4833
     * Saves the last item seen's ID only in case.
4834
     */
4835
    public function save_last()
4836
    {
4837
        $course_id = api_get_course_int_id();
4838
        $debug = $this->debug;
4839
        if ($debug) {
4840
            error_log('In learnpath::save_last()', 0);
4841
        }
4842
        $session_condition = api_get_session_condition(
4843
            api_get_session_id(),
4844
            true,
4845
            false
4846
        );
4847
        $table = Database::get_course_table(TABLE_LP_VIEW);
4848
4849
        $userId = $this->get_user_id();
4850
        if (empty($userId)) {
4851
            $userId = api_get_user_id();
4852
            if ($debug) {
4853
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4854
            }
4855
        }
4856
        if (isset($this->current) && !api_is_invitee()) {
4857
            if ($debug) {
4858
                error_log('Saving current item ('.$this->current.') for later review', 0);
4859
            }
4860
            $sql = "UPDATE $table SET
4861
                        last_item = ".$this->get_current_item_id()."
4862
                    WHERE
4863
                        c_id = $course_id AND
4864
                        lp_id = ".$this->get_id()." AND
4865
                        user_id = ".$userId." ".$session_condition;
4866
4867
            if ($debug) {
4868
                error_log('Saving last item seen : '.$sql, 0);
4869
            }
4870
            Database::query($sql);
4871
        }
4872
4873
        if (!api_is_invitee()) {
4874
            // Save progress.
4875
            list($progress) = $this->get_progress_bar_text('%');
4876
            if ($progress >= 0 && $progress <= 100) {
4877
                $progress = (int) $progress;
4878
                $sql = "UPDATE $table SET
4879
                            progress = $progress
4880
                        WHERE
4881
                            c_id = $course_id AND
4882
                            lp_id = ".$this->get_id()." AND
4883
                            user_id = ".$userId." ".$session_condition;
4884
                // Ignore errors as some tables might not have the progress field just yet.
4885
                Database::query($sql);
4886
                $this->progress_db = $progress;
4887
            }
4888
        }
4889
    }
4890
4891
    /**
4892
     * Sets the current item ID (checks if valid and authorized first).
4893
     *
4894
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4895
     */
4896
    public function set_current_item($item_id = null)
4897
    {
4898
        $debug = $this->debug;
4899
        if ($debug) {
4900
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4901
        }
4902
        if (empty($item_id)) {
4903
            if ($debug) {
4904
                error_log('No new current item given, ignore...', 0);
4905
            }
4906
            // Do nothing.
4907
        } else {
4908
            if ($debug) {
4909
                error_log('New current item given is '.$item_id.'...', 0);
4910
            }
4911
            if (is_numeric($item_id)) {
4912
                $item_id = (int) $item_id;
4913
                // TODO: Check in database here.
4914
                $this->last = $this->current;
4915
                $this->current = $item_id;
4916
                // TODO: Update $this->index as well.
4917
                foreach ($this->ordered_items as $index => $item) {
4918
                    if ($item == $this->current) {
4919
                        $this->index = $index;
4920
                        break;
4921
                    }
4922
                }
4923
                if ($debug) {
4924
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4925
                }
4926
            } else {
4927
                if ($debug) {
4928
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4929
                }
4930
            }
4931
        }
4932
    }
4933
4934
    /**
4935
     * Sets the encoding.
4936
     *
4937
     * @param string $enc New encoding
4938
     *
4939
     * @return bool
4940
     *
4941
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4942
     */
4943
    public function set_encoding($enc = 'UTF-8')
4944
    {
4945
        $enc = api_refine_encoding_id($enc);
4946
        if (empty($enc)) {
4947
            $enc = api_get_system_encoding();
4948
        }
4949
        if (api_is_encoding_supported($enc)) {
4950
            $lp = $this->get_id();
4951
            if (0 != $lp) {
4952
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4953
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4954
                        WHERE iid = ".$lp;
4955
                $res = Database::query($sql);
4956
4957
                return $res;
4958
            }
4959
        }
4960
4961
        return false;
4962
    }
4963
4964
    /**
4965
     * Sets the JS lib setting in the database directly.
4966
     * This is the JavaScript library file this lp needs to load on startup.
4967
     *
4968
     * @param string $lib Proximity setting
4969
     *
4970
     * @return bool True on update success. False otherwise.
4971
     */
4972
    public function set_jslib($lib = '')
4973
    {
4974
        $lp = $this->get_id();
4975
4976
        if (0 != $lp) {
4977
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4978
            $lib = Database::escape_string($lib);
4979
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4980
                    WHERE iid = $lp";
4981
            $res = Database::query($sql);
4982
4983
            return $res;
4984
        }
4985
4986
        return false;
4987
    }
4988
4989
    /**
4990
     * Sets the name of the LP maker (publisher) (and save).
4991
     *
4992
     * @param string $name Optional string giving the new content_maker of this learnpath
4993
     *
4994
     * @return bool True
4995
     */
4996
    public function set_maker($name = '')
4997
    {
4998
        if (empty($name)) {
4999
            return false;
5000
        }
5001
        $this->maker = $name;
5002
        $table = Database::get_course_table(TABLE_LP_MAIN);
5003
        $lp_id = $this->get_id();
5004
        $sql = "UPDATE $table SET
5005
                content_maker = '".Database::escape_string($this->maker)."'
5006
                WHERE iid = $lp_id";
5007
        Database::query($sql);
5008
5009
        return true;
5010
    }
5011
5012
    /**
5013
     * Sets the name of the current learnpath (and save).
5014
     *
5015
     * @param string $name Optional string giving the new name of this learnpath
5016
     *
5017
     * @return bool True/False
5018
     */
5019
    public function set_name($name = null)
5020
    {
5021
        if (empty($name)) {
5022
            return false;
5023
        }
5024
        $this->name = $name;
5025
5026
        $lp_id = $this->get_id();
5027
5028
        $repo = Container::getLpRepository();
5029
        /** @var CLp $lp */
5030
        $lp = $repo->find($lp_id);
5031
        $lp->setName($name);
5032
        $repo->updateNodeForResource($lp);
5033
5034
        /*
5035
        $course_id = $this->course_info['real_id'];
5036
        $sql = "UPDATE $lp_table SET
5037
            name = '$name'
5038
            WHERE iid = $lp_id";
5039
        $result = Database::query($sql);
5040
        // If the lp is visible on the homepage, change his name there.
5041
        if (Database::affected_rows($result)) {
5042
        $session_id = api_get_session_id();
5043
        $session_condition = api_get_session_condition($session_id);
5044
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5045
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5046
        $sql = "UPDATE $tbl_tool SET name = '$name'
5047
        	    WHERE
5048
        	        c_id = $course_id AND
5049
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5050
        Database::query($sql);*/
5051
5052
        //return true;
5053
        //}
5054
5055
        return false;
5056
    }
5057
5058
    /**
5059
     * Set index specified prefix terms for all items in this path.
5060
     *
5061
     * @param string $terms_string Comma-separated list of terms
5062
     * @param string $prefix       Xapian term prefix
5063
     *
5064
     * @return bool False on error, true otherwise
5065
     */
5066
    public function set_terms_by_prefix($terms_string, $prefix)
5067
    {
5068
        $course_id = api_get_course_int_id();
5069
        if ('true' !== api_get_setting('search_enabled')) {
5070
            return false;
5071
        }
5072
5073
        if (!extension_loaded('xapian')) {
5074
            return false;
5075
        }
5076
5077
        $terms_string = trim($terms_string);
5078
        $terms = explode(',', $terms_string);
5079
        array_walk($terms, 'trim_value');
5080
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5081
5082
        // Don't do anything if no change, verify only at DB, not the search engine.
5083
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5084
            return false;
5085
        }
5086
5087
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5088
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5089
5090
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5091
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5092
        $lp_id = (int) $_POST['lp_id'];
5093
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5094
        $result = Database::query($sql);
5095
        $di = new ChamiloIndexer();
5096
5097
        while ($lp_item = Database::fetch_array($result)) {
5098
            // Get search_did.
5099
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5100
            $sql = 'SELECT * FROM %s
5101
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5102
                    LIMIT 1';
5103
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5104
5105
            //echo $sql; echo '<br>';
5106
            $res = Database::query($sql);
5107
            if (Database::num_rows($res) > 0) {
5108
                $se_ref = Database::fetch_array($res);
5109
                // Compare terms.
5110
                $doc = $di->get_document($se_ref['search_did']);
5111
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5112
                $xterms = [];
5113
                foreach ($xapian_terms as $xapian_term) {
5114
                    $xterms[] = substr($xapian_term['name'], 1);
5115
                }
5116
5117
                $dterms = $terms;
5118
                $missing_terms = array_diff($dterms, $xterms);
5119
                $deprecated_terms = array_diff($xterms, $dterms);
5120
5121
                // Save it to search engine.
5122
                foreach ($missing_terms as $term) {
5123
                    $doc->add_term($prefix.$term, 1);
5124
                }
5125
                foreach ($deprecated_terms as $term) {
5126
                    $doc->remove_term($prefix.$term);
5127
                }
5128
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5129
                $di->getDb()->flush();
5130
            }
5131
        }
5132
5133
        return true;
5134
    }
5135
5136
    /**
5137
     * Sets the theme of the LP (local/remote) (and save).
5138
     *
5139
     * @param string $name Optional string giving the new theme of this learnpath
5140
     *
5141
     * @return bool Returns true if theme name is not empty
5142
     */
5143
    public function set_theme($name = '')
5144
    {
5145
        $this->theme = $name;
5146
        $table = Database::get_course_table(TABLE_LP_MAIN);
5147
        $lp_id = $this->get_id();
5148
        $sql = "UPDATE $table
5149
                SET theme = '".Database::escape_string($this->theme)."'
5150
                WHERE iid = $lp_id";
5151
        Database::query($sql);
5152
5153
        return true;
5154
    }
5155
5156
    /**
5157
     * Sets the image of an LP (and save).
5158
     *
5159
     * @param string $name Optional string giving the new image of this learnpath
5160
     *
5161
     * @return bool Returns true if theme name is not empty
5162
     */
5163
    public function set_preview_image($name = '')
5164
    {
5165
        $this->preview_image = $name;
5166
        $table = Database::get_course_table(TABLE_LP_MAIN);
5167
        $lp_id = $this->get_id();
5168
        $sql = "UPDATE $table SET
5169
                preview_image = '".Database::escape_string($this->preview_image)."'
5170
                WHERE iid = $lp_id";
5171
        Database::query($sql);
5172
5173
        return true;
5174
    }
5175
5176
    /**
5177
     * Sets the author of a LP (and save).
5178
     *
5179
     * @param string $name Optional string giving the new author of this learnpath
5180
     *
5181
     * @return bool Returns true if author's name is not empty
5182
     */
5183
    public function set_author($name = '')
5184
    {
5185
        $this->author = $name;
5186
        $table = Database::get_course_table(TABLE_LP_MAIN);
5187
        $lp_id = $this->get_id();
5188
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5189
                WHERE iid = $lp_id";
5190
        Database::query($sql);
5191
5192
        return true;
5193
    }
5194
5195
    /**
5196
     * Sets the hide_toc_frame parameter of a LP (and save).
5197
     *
5198
     * @param int $hide 1 if frame is hidden 0 then else
5199
     *
5200
     * @return bool Returns true if author's name is not empty
5201
     */
5202
    public function set_hide_toc_frame($hide)
5203
    {
5204
        if (intval($hide) == $hide) {
5205
            $this->hide_toc_frame = $hide;
5206
            $table = Database::get_course_table(TABLE_LP_MAIN);
5207
            $lp_id = $this->get_id();
5208
            $sql = "UPDATE $table SET
5209
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5210
                    WHERE iid = $lp_id";
5211
            Database::query($sql);
5212
5213
            return true;
5214
        }
5215
5216
        return false;
5217
    }
5218
5219
    /**
5220
     * Sets the prerequisite of a LP (and save).
5221
     *
5222
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5223
     *
5224
     * @return bool returns true if prerequisite is not empty
5225
     */
5226
    public function set_prerequisite($prerequisite)
5227
    {
5228
        $this->prerequisite = (int) $prerequisite;
5229
        $table = Database::get_course_table(TABLE_LP_MAIN);
5230
        $lp_id = $this->get_id();
5231
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5232
                WHERE iid = $lp_id";
5233
        Database::query($sql);
5234
5235
        return true;
5236
    }
5237
5238
    /**
5239
     * Sets the location/proximity of the LP (local/remote) (and save).
5240
     *
5241
     * @param string $name Optional string giving the new location of this learnpath
5242
     *
5243
     * @return bool True on success / False on error
5244
     */
5245
    public function set_proximity($name = '')
5246
    {
5247
        if (empty($name)) {
5248
            return false;
5249
        }
5250
5251
        $this->proximity = $name;
5252
        $table = Database::get_course_table(TABLE_LP_MAIN);
5253
        $lp_id = $this->get_id();
5254
        $sql = "UPDATE $table SET
5255
                    content_local = '".Database::escape_string($name)."'
5256
                WHERE iid = $lp_id";
5257
        Database::query($sql);
5258
5259
        return true;
5260
    }
5261
5262
    /**
5263
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5264
     *
5265
     * @param int $id DB ID of the item
5266
     */
5267
    public function set_previous_item($id)
5268
    {
5269
        if ($this->debug > 0) {
5270
            error_log('In learnpath::set_previous_item()', 0);
5271
        }
5272
        $this->last = $id;
5273
    }
5274
5275
    /**
5276
     * Sets use_max_score.
5277
     *
5278
     * @param int $use_max_score Optional string giving the new location of this learnpath
5279
     *
5280
     * @return bool True on success / False on error
5281
     */
5282
    public function set_use_max_score($use_max_score = 1)
5283
    {
5284
        $use_max_score = (int) $use_max_score;
5285
        $this->use_max_score = $use_max_score;
5286
        $table = Database::get_course_table(TABLE_LP_MAIN);
5287
        $lp_id = $this->get_id();
5288
        $sql = "UPDATE $table SET
5289
                    use_max_score = '".$this->use_max_score."'
5290
                WHERE iid = $lp_id";
5291
        Database::query($sql);
5292
5293
        return true;
5294
    }
5295
5296
    /**
5297
     * Sets and saves the expired_on date.
5298
     *
5299
     * @param string $expired_on Optional string giving the new author of this learnpath
5300
     *
5301
     * @throws \Doctrine\ORM\OptimisticLockException
5302
     *
5303
     * @return bool Returns true if author's name is not empty
5304
     */
5305
    public function set_expired_on($expired_on)
5306
    {
5307
        $em = Database::getManager();
5308
        /** @var CLp $lp */
5309
        $lp = $em
5310
            ->getRepository('ChamiloCourseBundle:CLp')
5311
            ->findOneBy(
5312
                [
5313
                    'iid' => $this->get_id(),
5314
                ]
5315
            );
5316
5317
        if (!$lp) {
5318
            return false;
5319
        }
5320
5321
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5322
5323
        $lp->setExpiredOn($this->expired_on);
5324
        $em->persist($lp);
5325
        $em->flush();
5326
5327
        return true;
5328
    }
5329
5330
    /**
5331
     * Sets and saves the publicated_on date.
5332
     *
5333
     * @param string $publicated_on Optional string giving the new author of this learnpath
5334
     *
5335
     * @throws \Doctrine\ORM\OptimisticLockException
5336
     *
5337
     * @return bool Returns true if author's name is not empty
5338
     */
5339
    public function set_publicated_on($publicated_on)
5340
    {
5341
        $em = Database::getManager();
5342
        /** @var CLp $lp */
5343
        $lp = $em
5344
            ->getRepository('ChamiloCourseBundle:CLp')
5345
            ->findOneBy(
5346
                [
5347
                    'iid' => $this->get_id(),
5348
                ]
5349
            );
5350
5351
        if (!$lp) {
5352
            return false;
5353
        }
5354
5355
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5356
        $lp->setPublicatedOn($this->publicated_on);
5357
        $em->persist($lp);
5358
        $em->flush();
5359
5360
        return true;
5361
    }
5362
5363
    /**
5364
     * Sets and saves the expired_on date.
5365
     *
5366
     * @return bool Returns true if author's name is not empty
5367
     */
5368
    public function set_modified_on()
5369
    {
5370
        $this->modified_on = api_get_utc_datetime();
5371
        $table = Database::get_course_table(TABLE_LP_MAIN);
5372
        $lp_id = $this->get_id();
5373
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5374
                WHERE iid = $lp_id";
5375
        Database::query($sql);
5376
5377
        return true;
5378
    }
5379
5380
    /**
5381
     * Sets the object's error message.
5382
     *
5383
     * @param string $error Error message. If empty, reinits the error string
5384
     */
5385
    public function set_error_msg($error = '')
5386
    {
5387
        if ($this->debug > 0) {
5388
            error_log('In learnpath::set_error_msg()', 0);
5389
        }
5390
        if (empty($error)) {
5391
            $this->error = '';
5392
        } else {
5393
            $this->error .= $error;
5394
        }
5395
    }
5396
5397
    /**
5398
     * Launches the current item if not 'sco'
5399
     * (starts timer and make sure there is a record ready in the DB).
5400
     *
5401
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5402
     *
5403
     * @return bool
5404
     */
5405
    public function start_current_item($allow_new_attempt = false)
5406
    {
5407
        $debug = $this->debug;
5408
        if ($debug) {
5409
            error_log('In learnpath::start_current_item()');
5410
            error_log('current: '.$this->current);
5411
        }
5412
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5413
            $type = $this->get_type();
5414
            $item_type = $this->items[$this->current]->get_type();
5415
            if ((2 == $type && 'sco' != $item_type) ||
5416
                (3 == $type && 'au' != $item_type) ||
5417
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5418
            ) {
5419
                if ($debug) {
5420
                    error_log('item type: '.$item_type);
5421
                    error_log('lp type: '.$type);
5422
                }
5423
                $this->items[$this->current]->open($allow_new_attempt);
5424
                $this->autocomplete_parents($this->current);
5425
                $prereq_check = $this->prerequisites_match($this->current);
5426
                if ($debug) {
5427
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5428
                }
5429
                $this->items[$this->current]->save(false, $prereq_check);
5430
            }
5431
            // If sco, then it is supposed to have been updated by some other call.
5432
            if ('sco' == $item_type) {
5433
                $this->items[$this->current]->restart();
5434
            }
5435
        }
5436
        if ($debug) {
5437
            error_log('lp_view_session_id');
5438
            error_log($this->lp_view_session_id);
5439
            error_log('api session id');
5440
            error_log(api_get_session_id());
5441
            error_log('End of learnpath::start_current_item()');
5442
        }
5443
5444
        return true;
5445
    }
5446
5447
    /**
5448
     * Stops the processing and counters for the old item (as held in $this->last).
5449
     *
5450
     * @return bool True/False
5451
     */
5452
    public function stop_previous_item()
5453
    {
5454
        $debug = $this->debug;
5455
        if ($debug) {
5456
            error_log('In learnpath::stop_previous_item()', 0);
5457
        }
5458
5459
        if (0 != $this->last && $this->last != $this->current &&
5460
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5461
        ) {
5462
            if ($debug) {
5463
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5464
            }
5465
            switch ($this->get_type()) {
5466
                case '3':
5467
                    if ('au' != $this->items[$this->last]->get_type()) {
5468
                        if ($debug) {
5469
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5470
                        }
5471
                        $this->items[$this->last]->close();
5472
                    } else {
5473
                        if ($debug) {
5474
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5475
                        }
5476
                    }
5477
                    break;
5478
                case '2':
5479
                    if ('sco' != $this->items[$this->last]->get_type()) {
5480
                        if ($debug) {
5481
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5482
                        }
5483
                        $this->items[$this->last]->close();
5484
                    } else {
5485
                        if ($debug) {
5486
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5487
                        }
5488
                    }
5489
                    break;
5490
                case '1':
5491
                default:
5492
                    if ($debug) {
5493
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5494
                    }
5495
                    $this->items[$this->last]->close();
5496
                    break;
5497
            }
5498
        } else {
5499
            if ($debug) {
5500
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5501
            }
5502
5503
            return false;
5504
        }
5505
5506
        return true;
5507
    }
5508
5509
    /**
5510
     * Updates the default view mode from fullscreen to embedded and inversely.
5511
     *
5512
     * @return string The current default view mode ('fullscreen' or 'embedded')
5513
     */
5514
    public function update_default_view_mode()
5515
    {
5516
        $table = Database::get_course_table(TABLE_LP_MAIN);
5517
        $sql = "SELECT * FROM $table
5518
                WHERE iid = ".$this->get_id();
5519
        $res = Database::query($sql);
5520
        if (Database::num_rows($res) > 0) {
5521
            $row = Database::fetch_array($res);
5522
            $default_view_mode = $row['default_view_mod'];
5523
            $view_mode = $default_view_mode;
5524
            switch ($default_view_mode) {
5525
                case 'fullscreen': // default with popup
5526
                    $view_mode = 'embedded';
5527
                    break;
5528
                case 'embedded': // default view with left menu
5529
                    $view_mode = 'embedframe';
5530
                    break;
5531
                case 'embedframe': //folded menu
5532
                    $view_mode = 'impress';
5533
                    break;
5534
                case 'impress':
5535
                    $view_mode = 'fullscreen';
5536
                    break;
5537
            }
5538
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5539
                    WHERE iid = ".$this->get_id();
5540
            Database::query($sql);
5541
            $this->mode = $view_mode;
5542
5543
            return $view_mode;
5544
        }
5545
5546
        return -1;
5547
    }
5548
5549
    /**
5550
     * Updates the default behaviour about auto-commiting SCORM updates.
5551
     *
5552
     * @return bool True if auto-commit has been set to 'on', false otherwise
5553
     */
5554
    public function update_default_scorm_commit()
5555
    {
5556
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5557
        $sql = "SELECT * FROM $lp_table
5558
                WHERE iid = ".$this->get_id();
5559
        $res = Database::query($sql);
5560
        if (Database::num_rows($res) > 0) {
5561
            $row = Database::fetch_array($res);
5562
            $force = $row['force_commit'];
5563
            if (1 == $force) {
5564
                $force = 0;
5565
                $force_return = false;
5566
            } elseif (0 == $force) {
5567
                $force = 1;
5568
                $force_return = true;
5569
            }
5570
            $sql = "UPDATE $lp_table SET force_commit = $force
5571
                    WHERE iid = ".$this->get_id();
5572
            Database::query($sql);
5573
            $this->force_commit = $force_return;
5574
5575
            return $force_return;
5576
        }
5577
5578
        return -1;
5579
    }
5580
5581
    /**
5582
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5583
     *
5584
     * @return bool True on success, false on failure
5585
     */
5586
    public function update_display_order()
5587
    {
5588
        $course_id = api_get_course_int_id();
5589
        $table = Database::get_course_table(TABLE_LP_MAIN);
5590
        $sql = "SELECT * FROM $table
5591
                WHERE c_id = $course_id
5592
                ORDER BY display_order";
5593
        $res = Database::query($sql);
5594
        if (false === $res) {
5595
            return false;
5596
        }
5597
5598
        $num = Database::num_rows($res);
5599
        // First check the order is correct, globally (might be wrong because
5600
        // of versions < 1.8.4).
5601
        if ($num > 0) {
5602
            $i = 1;
5603
            while ($row = Database::fetch_array($res)) {
5604
                if ($row['display_order'] != $i) {
5605
                    // If we find a gap in the order, we need to fix it.
5606
                    $sql = "UPDATE $table SET display_order = $i
5607
                            WHERE iid = ".$row['iid'];
5608
                    Database::query($sql);
5609
                }
5610
                $i++;
5611
            }
5612
        }
5613
5614
        return true;
5615
    }
5616
5617
    /**
5618
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5619
     *
5620
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5621
     */
5622
    public function update_reinit()
5623
    {
5624
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5625
        $sql = "SELECT * FROM $lp_table
5626
                WHERE iid = ".$this->get_id();
5627
        $res = Database::query($sql);
5628
        if (Database::num_rows($res) > 0) {
5629
            $row = Database::fetch_array($res);
5630
            $force = $row['prevent_reinit'];
5631
            if (1 == $force) {
5632
                $force = 0;
5633
            } elseif (0 == $force) {
5634
                $force = 1;
5635
            }
5636
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5637
                    WHERE iid = ".$this->get_id();
5638
            Database::query($sql);
5639
            $this->prevent_reinit = $force;
5640
5641
            return $force;
5642
        }
5643
5644
        return -1;
5645
    }
5646
5647
    /**
5648
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5649
     *
5650
     * @return string 'single', 'multi' or 'seriousgame'
5651
     *
5652
     * @author ndiechburg <[email protected]>
5653
     */
5654
    public function get_attempt_mode()
5655
    {
5656
        //Set default value for seriousgame_mode
5657
        if (!isset($this->seriousgame_mode)) {
5658
            $this->seriousgame_mode = 0;
5659
        }
5660
        // Set default value for prevent_reinit
5661
        if (!isset($this->prevent_reinit)) {
5662
            $this->prevent_reinit = 1;
5663
        }
5664
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5665
            return 'seriousgame';
5666
        }
5667
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5668
            return 'single';
5669
        }
5670
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5671
            return 'multiple';
5672
        }
5673
5674
        return 'single';
5675
    }
5676
5677
    /**
5678
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5679
     *
5680
     * @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...
5681
     *
5682
     * @return bool
5683
     *
5684
     * @author ndiechburg <[email protected]>
5685
     */
5686
    public function set_attempt_mode($mode)
5687
    {
5688
        switch ($mode) {
5689
            case 'seriousgame':
5690
                $sg_mode = 1;
5691
                $prevent_reinit = 1;
5692
                break;
5693
            case 'single':
5694
                $sg_mode = 0;
5695
                $prevent_reinit = 1;
5696
                break;
5697
            case 'multiple':
5698
                $sg_mode = 0;
5699
                $prevent_reinit = 0;
5700
                break;
5701
            default:
5702
                $sg_mode = 0;
5703
                $prevent_reinit = 0;
5704
                break;
5705
        }
5706
        $this->prevent_reinit = $prevent_reinit;
5707
        $this->seriousgame_mode = $sg_mode;
5708
        $table = Database::get_course_table(TABLE_LP_MAIN);
5709
        $sql = "UPDATE $table SET
5710
                prevent_reinit = $prevent_reinit ,
5711
                seriousgame_mode = $sg_mode
5712
                WHERE iid = ".$this->get_id();
5713
        $res = Database::query($sql);
5714
        if ($res) {
5715
            return true;
5716
        } else {
5717
            return false;
5718
        }
5719
    }
5720
5721
    /**
5722
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5723
     *
5724
     * @author ndiechburg <[email protected]>
5725
     */
5726
    public function switch_attempt_mode()
5727
    {
5728
        $mode = $this->get_attempt_mode();
5729
        switch ($mode) {
5730
            case 'single':
5731
                $next_mode = 'multiple';
5732
                break;
5733
            case 'multiple':
5734
                $next_mode = 'seriousgame';
5735
                break;
5736
            case 'seriousgame':
5737
            default:
5738
                $next_mode = 'single';
5739
                break;
5740
        }
5741
        $this->set_attempt_mode($next_mode);
5742
    }
5743
5744
    /**
5745
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5746
     * but possibility to do again a completed item.
5747
     *
5748
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5749
     *
5750
     * @author ndiechburg <[email protected]>
5751
     */
5752
    public function set_seriousgame_mode()
5753
    {
5754
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5755
        $sql = "SELECT * FROM $lp_table
5756
                WHERE iid = ".$this->get_id();
5757
        $res = Database::query($sql);
5758
        if (Database::num_rows($res) > 0) {
5759
            $row = Database::fetch_array($res);
5760
            $force = $row['seriousgame_mode'];
5761
            if (1 == $force) {
5762
                $force = 0;
5763
            } elseif (0 == $force) {
5764
                $force = 1;
5765
            }
5766
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5767
			        WHERE iid = ".$this->get_id();
5768
            Database::query($sql);
5769
            $this->seriousgame_mode = $force;
5770
5771
            return $force;
5772
        }
5773
5774
        return -1;
5775
    }
5776
5777
    /**
5778
     * Updates the "scorm_debug" value that shows or hide the debug window.
5779
     *
5780
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5781
     */
5782
    public function update_scorm_debug()
5783
    {
5784
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5785
        $sql = "SELECT * FROM $lp_table
5786
                WHERE iid = ".$this->get_id();
5787
        $res = Database::query($sql);
5788
        if (Database::num_rows($res) > 0) {
5789
            $row = Database::fetch_array($res);
5790
            $force = $row['debug'];
5791
            if (1 == $force) {
5792
                $force = 0;
5793
            } elseif (0 == $force) {
5794
                $force = 1;
5795
            }
5796
            $sql = "UPDATE $lp_table SET debug = $force
5797
                    WHERE iid = ".$this->get_id();
5798
            Database::query($sql);
5799
            $this->scorm_debug = $force;
5800
5801
            return $force;
5802
        }
5803
5804
        return -1;
5805
    }
5806
5807
    /**
5808
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5809
     *
5810
     * @author Kevin Van Den Haute
5811
     *
5812
     * @param  array
5813
     */
5814
    public function tree_array($array)
5815
    {
5816
        $array = $this->sort_tree_array($array);
5817
        $this->create_tree_array($array);
5818
    }
5819
5820
    /**
5821
     * Creates an array with the elements of the learning path tree in it.
5822
     *
5823
     * @author Kevin Van Den Haute
5824
     *
5825
     * @param array $array
5826
     * @param int   $parent
5827
     * @param int   $depth
5828
     * @param array $tmp
5829
     */
5830
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5831
    {
5832
        if (is_array($array)) {
5833
            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...
5834
                if ($array[$i]['parent_item_id'] == $parent) {
5835
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5836
                        $tmp[] = $array[$i]['parent_item_id'];
5837
                        $depth++;
5838
                    }
5839
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5840
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5841
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5842
5843
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5844
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5845
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5846
                    $this->arrMenu[] = [
5847
                        'id' => $array[$i]['id'],
5848
                        'ref' => $ref,
5849
                        'item_type' => $array[$i]['item_type'],
5850
                        'title' => $array[$i]['title'],
5851
                        'title_raw' => $array[$i]['title_raw'],
5852
                        'path' => $path,
5853
                        'description' => $array[$i]['description'],
5854
                        'parent_item_id' => $array[$i]['parent_item_id'],
5855
                        'previous_item_id' => $array[$i]['previous_item_id'],
5856
                        'next_item_id' => $array[$i]['next_item_id'],
5857
                        'min_score' => $array[$i]['min_score'],
5858
                        'max_score' => $array[$i]['max_score'],
5859
                        'mastery_score' => $array[$i]['mastery_score'],
5860
                        'display_order' => $array[$i]['display_order'],
5861
                        'prerequisite' => $preq,
5862
                        'depth' => $depth,
5863
                        'audio' => $audio,
5864
                        'prerequisite_min_score' => $prerequisiteMinScore,
5865
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5866
                    ];
5867
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5868
                }
5869
            }
5870
        }
5871
    }
5872
5873
    /**
5874
     * Sorts a multi dimensional array by parent id and display order.
5875
     *
5876
     * @author Kevin Van Den Haute
5877
     *
5878
     * @param array $array (array with al the learning path items in it)
5879
     *
5880
     * @return array
5881
     */
5882
    public function sort_tree_array($array)
5883
    {
5884
        foreach ($array as $key => $row) {
5885
            $parent[$key] = $row['parent_item_id'];
5886
            $position[$key] = $row['display_order'];
5887
        }
5888
5889
        if (count($array) > 0) {
5890
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5891
        }
5892
5893
        return $array;
5894
    }
5895
5896
    /**
5897
     * Function that creates a html list of learning path items so that we can add audio files to them.
5898
     *
5899
     * @author Kevin Van Den Haute
5900
     *
5901
     * @return string
5902
     */
5903
    public function overview()
5904
    {
5905
        $return = '';
5906
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5907
5908
        // we need to start a form when we want to update all the mp3 files
5909
        if ('true' == $update_audio) {
5910
            $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">';
5911
        }
5912
        $return .= '<div id="message"></div>';
5913
        if (0 == count($this->items)) {
5914
            $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');
5915
        } else {
5916
            $return_audio = '<table class="data_table">';
5917
            $return_audio .= '<tr>';
5918
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5919
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5920
            $return_audio .= '</tr>';
5921
5922
            if ('true' != $update_audio) {
5923
                $return .= '<div class="col-md-12">';
5924
                $return .= self::return_new_tree($update_audio);
5925
                $return .= '</div>';
5926
                $return .= Display::div(
5927
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5928
                    ['style' => 'float:left; margin-top:15px;width:100%']
5929
                );
5930
            } else {
5931
                $return_audio .= self::return_new_tree($update_audio);
5932
                $return .= $return_audio.'</table>';
5933
            }
5934
5935
            // We need to close the form when we are updating the mp3 files.
5936
            if ('true' == $update_audio) {
5937
                $return .= '<div class="footer-audio">';
5938
                $return .= Display::button(
5939
                    'save_audio',
5940
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5941
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5942
                );
5943
                $return .= '</div>';
5944
            }
5945
        }
5946
5947
        // We need to close the form when we are updating the mp3 files.
5948
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5949
            $return .= '</form>';
5950
        }
5951
5952
        return $return;
5953
    }
5954
5955
    /**
5956
     * @param string $update_audio
5957
     *
5958
     * @return array
5959
     */
5960
    public function processBuildMenuElements($update_audio = 'false')
5961
    {
5962
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5963
        $arrLP = $this->getItemsForForm();
5964
5965
        $this->tree_array($arrLP);
5966
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5967
        unset($this->arrMenu);
5968
        $default_data = null;
5969
        $default_content = null;
5970
        $elements = [];
5971
        $return_audio = null;
5972
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5973
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5974
        $countItems = count($arrLP);
5975
5976
        $upIcon = Display::return_icon(
5977
            'up.png',
5978
            get_lang('Up'),
5979
            [],
5980
            ICON_SIZE_TINY
5981
        );
5982
5983
        $disableUpIcon = Display::return_icon(
5984
            'up_na.png',
5985
            get_lang('Up'),
5986
            [],
5987
            ICON_SIZE_TINY
5988
        );
5989
5990
        $downIcon = Display::return_icon(
5991
            'down.png',
5992
            get_lang('Down'),
5993
            [],
5994
            ICON_SIZE_TINY
5995
        );
5996
5997
        $disableDownIcon = Display::return_icon(
5998
            'down_na.png',
5999
            get_lang('Down'),
6000
            [],
6001
            ICON_SIZE_TINY
6002
        );
6003
6004
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6005
6006
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
6007
        $plugin = null;
6008
        if ($pluginCalendar) {
6009
            $plugin = LearningCalendarPlugin::create();
6010
        }
6011
6012
        for ($i = 0; $i < $countItems; $i++) {
6013
            $parent_id = $arrLP[$i]['parent_item_id'];
6014
            $title = $arrLP[$i]['title'];
6015
            $title_cut = $arrLP[$i]['title_raw'];
6016
            if (false === $show) {
6017
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6018
            }
6019
            // Link for the documents
6020
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
6021
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6022
                $title_cut = Display::url(
6023
                    $title_cut,
6024
                    $url,
6025
                    [
6026
                        'class' => 'ajax moved',
6027
                        'data-title' => $title,
6028
                        'title' => $title,
6029
                    ]
6030
                );
6031
            }
6032
6033
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6034
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6035
                Session::write('pathItem', $arrLP[$i]['path']);
6036
            }
6037
6038
            $oddClass = 'row_even';
6039
            if (0 == ($i % 2)) {
6040
                $oddClass = 'row_odd';
6041
            }
6042
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6043
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6044
6045
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6046
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6047
            } else {
6048
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6049
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6050
                } else {
6051
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6052
                        $icon = Display::return_icon('certificate.png');
6053
                    } else {
6054
                        $icon = Display::return_icon('folder_document.png');
6055
                    }
6056
                }
6057
            }
6058
6059
            // The audio column.
6060
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6061
            $audio = '';
6062
            if (!$update_audio || 'true' != $update_audio) {
6063
                if (empty($arrLP[$i]['audio'])) {
6064
                    $audio .= '';
6065
                }
6066
            } else {
6067
                $types = self::getChapterTypes();
6068
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6069
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6070
                    if (!empty($arrLP[$i]['audio'])) {
6071
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6072
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6073
                    }
6074
                }
6075
            }
6076
6077
            $return_audio .= Display::span($icon.' '.$title).
6078
                Display::tag(
6079
                    'td',
6080
                    $audio,
6081
                    ['style' => '']
6082
                );
6083
            $return_audio .= '</td>';
6084
            $move_icon = '';
6085
            $move_item_icon = '';
6086
            $edit_icon = '';
6087
            $delete_icon = '';
6088
            $audio_icon = '';
6089
            $prerequisities_icon = '';
6090
            $forumIcon = '';
6091
            $previewIcon = '';
6092
            $pluginCalendarIcon = '';
6093
            $orderIcons = '';
6094
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6095
6096
            if ($is_allowed_to_edit) {
6097
                if (!$update_audio || 'true' != $update_audio) {
6098
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
6099
                        $move_icon .= '<a class="moved" href="#">';
6100
                        $move_icon .= Display::return_icon(
6101
                            'move_everywhere.png',
6102
                            get_lang('Move'),
6103
                            [],
6104
                            ICON_SIZE_TINY
6105
                        );
6106
                        $move_icon .= '</a>';
6107
                    }
6108
                }
6109
6110
                // No edit for this item types
6111
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6112
                    if ('dir' != $arrLP[$i]['item_type']) {
6113
                        $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">';
6114
                        $edit_icon .= Display::return_icon(
6115
                            'edit.png',
6116
                            get_lang('Edit section description/name'),
6117
                            [],
6118
                            ICON_SIZE_TINY
6119
                        );
6120
                        $edit_icon .= '</a>';
6121
6122
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6123
                            $forumThread = null;
6124
                            if (isset($this->items[$arrLP[$i]['id']])) {
6125
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6126
                                    $this->course_int_id,
6127
                                    $this->lp_session_id
6128
                                );
6129
                            }
6130
                            if ($forumThread) {
6131
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6132
                                        'action' => 'dissociate_forum',
6133
                                        'id' => $arrLP[$i]['id'],
6134
                                        'lp_id' => $this->lp_id,
6135
                                    ]);
6136
                                $forumIcon = Display::url(
6137
                                    Display::return_icon(
6138
                                        'forum.png',
6139
                                        get_lang('Dissociate the forum of this learning path item'),
6140
                                        [],
6141
                                        ICON_SIZE_TINY
6142
                                    ),
6143
                                    $forumIconUrl,
6144
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6145
                                );
6146
                            } else {
6147
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6148
                                        'action' => 'create_forum',
6149
                                        'id' => $arrLP[$i]['id'],
6150
                                        'lp_id' => $this->lp_id,
6151
                                    ]);
6152
                                $forumIcon = Display::url(
6153
                                    Display::return_icon(
6154
                                        'forum.png',
6155
                                        get_lang('Associate a forum to this learning path item'),
6156
                                        [],
6157
                                        ICON_SIZE_TINY
6158
                                    ),
6159
                                    $forumIconUrl,
6160
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6161
                                );
6162
                            }
6163
                        }
6164
                    } else {
6165
                        $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">';
6166
                        $edit_icon .= Display::return_icon(
6167
                            'edit.png',
6168
                            get_lang('Edit section description/name'),
6169
                            [],
6170
                            ICON_SIZE_TINY
6171
                        );
6172
                        $edit_icon .= '</a>';
6173
                    }
6174
                } else {
6175
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6176
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6177
                        $edit_icon .= Display::return_icon(
6178
                            'edit.png',
6179
                            get_lang('Edit'),
6180
                            [],
6181
                            ICON_SIZE_TINY
6182
                        );
6183
                        $edit_icon .= '</a>';
6184
                    }
6185
                }
6186
6187
                if ($pluginCalendar) {
6188
                    $pluginLink = $pluginUrl.
6189
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6190
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6191
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6192
                    if ($itemInfo && 1 == $itemInfo['value']) {
6193
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6194
                    }
6195
                    $pluginCalendarIcon = Display::url(
6196
                        $iconCalendar,
6197
                        $pluginLink,
6198
                        ['class' => 'btn btn-default']
6199
                    );
6200
                }
6201
6202
                if ('final_item' != $arrLP[$i]['item_type']) {
6203
                    $orderIcons = Display::url(
6204
                        $upIcon,
6205
                        'javascript:void(0)',
6206
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6207
                    );
6208
                    $orderIcons .= Display::url(
6209
                        $downIcon,
6210
                        'javascript:void(0)',
6211
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6212
                    );
6213
                }
6214
6215
                $delete_icon .= ' <a
6216
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6217
                    onclick="return confirmation(\''.addslashes($title).'\');"
6218
                    class="btn btn-default">';
6219
                $delete_icon .= Display::return_icon(
6220
                    'delete.png',
6221
                    get_lang('Delete section'),
6222
                    [],
6223
                    ICON_SIZE_TINY
6224
                );
6225
                $delete_icon .= '</a>';
6226
6227
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6228
                $previewImage = Display::return_icon(
6229
                    'preview_view.png',
6230
                    get_lang('Preview'),
6231
                    [],
6232
                    ICON_SIZE_TINY
6233
                );
6234
6235
                switch ($arrLP[$i]['item_type']) {
6236
                    case TOOL_DOCUMENT:
6237
                    case TOOL_LP_FINAL_ITEM:
6238
                    case TOOL_READOUT_TEXT:
6239
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6240
                        $previewIcon = Display::url(
6241
                            $previewImage,
6242
                            $urlPreviewLink,
6243
                            [
6244
                                'target' => '_blank',
6245
                                'class' => 'btn btn-default',
6246
                                'data-title' => $arrLP[$i]['title'],
6247
                                'title' => $arrLP[$i]['title'],
6248
                            ]
6249
                        );
6250
                        break;
6251
                    case TOOL_THREAD:
6252
                    case TOOL_FORUM:
6253
                    case TOOL_QUIZ:
6254
                    case TOOL_STUDENTPUBLICATION:
6255
                    case TOOL_LP_FINAL_ITEM:
6256
                    case TOOL_LINK:
6257
                        $class = 'btn btn-default';
6258
                        $target = '_blank';
6259
                        $link = self::rl_get_resource_link_for_learnpath(
6260
                            $this->course_int_id,
6261
                            $this->lp_id,
6262
                            $arrLP[$i]['id'],
6263
                            0
6264
                        );
6265
                        $previewIcon = Display::url(
6266
                            $previewImage,
6267
                            $link,
6268
                            [
6269
                                'class' => $class,
6270
                                'data-title' => $arrLP[$i]['title'],
6271
                                'title' => $arrLP[$i]['title'],
6272
                                'target' => $target,
6273
                            ]
6274
                        );
6275
                        break;
6276
                    default:
6277
                        $previewIcon = Display::url(
6278
                            $previewImage,
6279
                            $url.'&action=view_item',
6280
                            ['class' => 'btn btn-default', 'target' => '_blank']
6281
                        );
6282
                        break;
6283
                }
6284
6285
                if ('dir' != $arrLP[$i]['item_type']) {
6286
                    $prerequisities_icon = Display::url(
6287
                        Display::return_icon(
6288
                            'accept.png',
6289
                            get_lang('Prerequisites'),
6290
                            [],
6291
                            ICON_SIZE_TINY
6292
                        ),
6293
                        $url.'&action=edit_item_prereq',
6294
                        ['class' => 'btn btn-default']
6295
                    );
6296
                    if ('final_item' != $arrLP[$i]['item_type']) {
6297
                        /*$move_item_icon = Display::url(
6298
                            Display::return_icon(
6299
                                'move.png',
6300
                                get_lang('Move'),
6301
                                [],
6302
                                ICON_SIZE_TINY
6303
                            ),
6304
                            $url.'&action=move_item',
6305
                            ['class' => 'btn btn-default']
6306
                        );*/
6307
                    }
6308
                    $audio_icon = Display::url(
6309
                        Display::return_icon(
6310
                            'audio.png',
6311
                            get_lang('Upload'),
6312
                            [],
6313
                            ICON_SIZE_TINY
6314
                        ),
6315
                        $url.'&action=add_audio',
6316
                        ['class' => 'btn btn-default']
6317
                    );
6318
                }
6319
            }
6320
            if ('true' != $update_audio) {
6321
                $row = $move_icon.' '.$icon.
6322
                    Display::span($title_cut).
6323
                    Display::tag(
6324
                        'div',
6325
                        "<div class=\"btn-group btn-group-xs\">
6326
                                    $previewIcon
6327
                                    $audio
6328
                                    $edit_icon
6329
                                    $pluginCalendarIcon
6330
                                    $forumIcon
6331
                                    $prerequisities_icon
6332
                                    $move_item_icon
6333
                                    $audio_icon
6334
                                    $orderIcons
6335
                                    $delete_icon
6336
                                </div>",
6337
                        ['class' => 'btn-toolbar button_actions']
6338
                    );
6339
            } else {
6340
                $row =
6341
                    Display::span($title.$icon).
6342
                    Display::span($audio, ['class' => 'button_actions']);
6343
            }
6344
6345
            $default_data[$arrLP[$i]['id']] = $row;
6346
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6347
6348
            if (empty($parent_id)) {
6349
                $elements[$arrLP[$i]['id']]['data'] = $row;
6350
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6351
            } else {
6352
                $parent_arrays = [];
6353
                if ($arrLP[$i]['depth'] > 1) {
6354
                    // Getting list of parents
6355
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6356
                        foreach ($arrLP as $item) {
6357
                            if ($item['id'] == $parent_id) {
6358
                                if (0 == $item['parent_item_id']) {
6359
                                    $parent_id = $item['id'];
6360
                                    break;
6361
                                } else {
6362
                                    $parent_id = $item['parent_item_id'];
6363
                                    if (empty($parent_arrays)) {
6364
                                        $parent_arrays[] = intval($item['id']);
6365
                                    }
6366
                                    $parent_arrays[] = $parent_id;
6367
                                    break;
6368
                                }
6369
                            }
6370
                        }
6371
                    }
6372
                }
6373
6374
                if (!empty($parent_arrays)) {
6375
                    $parent_arrays = array_reverse($parent_arrays);
6376
                    $val = '$elements';
6377
                    $x = 0;
6378
                    foreach ($parent_arrays as $item) {
6379
                        if ($x != count($parent_arrays) - 1) {
6380
                            $val .= '["'.$item.'"]["children"]';
6381
                        } else {
6382
                            $val .= '["'.$item.'"]["children"]';
6383
                        }
6384
                        $x++;
6385
                    }
6386
                    $val .= "";
6387
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6388
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6389
                } else {
6390
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6391
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6392
                }
6393
            }
6394
        }
6395
6396
        return [
6397
            'elements' => $elements,
6398
            'default_data' => $default_data,
6399
            'default_content' => $default_content,
6400
            'return_audio' => $return_audio,
6401
        ];
6402
    }
6403
6404
    /**
6405
     * @param string $updateAudio true/false strings
6406
     *
6407
     * @return string
6408
     */
6409
    public function returnLpItemList($updateAudio)
6410
    {
6411
        $result = $this->processBuildMenuElements($updateAudio);
6412
6413
        $html = self::print_recursive(
6414
            $result['elements'],
6415
            $result['default_data'],
6416
            $result['default_content']
6417
        );
6418
6419
        if (!empty($html)) {
6420
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6421
        }
6422
6423
        return $html;
6424
    }
6425
6426
    /**
6427
     * @param string $update_audio
6428
     * @param bool   $drop_element_here
6429
     *
6430
     * @return string
6431
     */
6432
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6433
    {
6434
        $result = $this->processBuildMenuElements($update_audio);
6435
6436
        $list = '<ul id="lp_item_list">';
6437
        $tree = $this->print_recursive(
6438
            $result['elements'],
6439
            $result['default_data'],
6440
            $result['default_content']
6441
        );
6442
6443
        if (!empty($tree)) {
6444
            $list .= $tree;
6445
        } else {
6446
            if ($drop_element_here) {
6447
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6448
            }
6449
        }
6450
        $list .= '</ul>';
6451
6452
        $return = Display::panelCollapse(
6453
            $this->name,
6454
            $list,
6455
            'scorm-list',
6456
            null,
6457
            'scorm-list-accordion',
6458
            'scorm-list-collapse'
6459
        );
6460
6461
        if ('true' === $update_audio) {
6462
            $return = $result['return_audio'];
6463
        }
6464
6465
        return $return;
6466
    }
6467
6468
    /**
6469
     * @param array $elements
6470
     * @param array $default_data
6471
     * @param array $default_content
6472
     *
6473
     * @return string
6474
     */
6475
    public function print_recursive($elements, $default_data, $default_content)
6476
    {
6477
        $return = '';
6478
        foreach ($elements as $key => $item) {
6479
            if (isset($item['load_data']) || empty($item['data'])) {
6480
                $item['data'] = $default_data[$item['load_data']];
6481
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6482
            }
6483
            $sub_list = '';
6484
            if (isset($item['type']) && 'dir' === $item['type']) {
6485
                // empty value
6486
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6487
            }
6488
            if (empty($item['children'])) {
6489
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6490
                $active = null;
6491
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6492
                    $active = 'active';
6493
                }
6494
                $return .= Display::tag(
6495
                    'li',
6496
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6497
                    ['id' => $key, 'class' => 'record li_container']
6498
                );
6499
            } else {
6500
                // Sections
6501
                $data = '';
6502
                if (isset($item['children'])) {
6503
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6504
                }
6505
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6506
                $return .= Display::tag(
6507
                    'li',
6508
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6509
                    ['id' => $key, 'class' => 'record li_container']
6510
                );
6511
            }
6512
        }
6513
6514
        return $return;
6515
    }
6516
6517
    /**
6518
     * This function builds the action menu.
6519
     *
6520
     * @param bool   $returnString           Optional
6521
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6522
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6523
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6524
     * @param string $action
6525
     *
6526
     * @return string
6527
     */
6528
    public function build_action_menu(
6529
        $returnString = false,
6530
        $showRequirementButtons = true,
6531
        $isConfigPage = false,
6532
        $allowExpand = true,
6533
        $action = ''
6534
    ) {
6535
        $actionsRight = '';
6536
        $lpId = $this->lp_id;
6537
        $back = Display::url(
6538
            Display::return_icon(
6539
                'back.png',
6540
                get_lang('Back to learning paths'),
6541
                '',
6542
                ICON_SIZE_MEDIUM
6543
            ),
6544
            'lp_controller.php?'.api_get_cidreq()
6545
        );
6546
6547
        /*if ($backToBuild) {
6548
            $back = Display::url(
6549
                Display::return_icon(
6550
                    'back.png',
6551
                    get_lang('GoBack'),
6552
                    '',
6553
                    ICON_SIZE_MEDIUM
6554
                ),
6555
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6556
            );
6557
        }*/
6558
6559
        $actionsLeft = $back;
6560
6561
        $actionsLeft .= Display::url(
6562
            Display::return_icon(
6563
                'preview_view.png',
6564
                get_lang('Preview'),
6565
                '',
6566
                ICON_SIZE_MEDIUM
6567
            ),
6568
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6569
                'action' => 'view',
6570
                'lp_id' => $lpId,
6571
                'isStudentView' => 'true',
6572
            ])
6573
        );
6574
6575
        $actionsLeft .= Display::url(
6576
            Display::return_icon(
6577
                'upload_audio.png',
6578
                get_lang('Add audio'),
6579
                '',
6580
                ICON_SIZE_MEDIUM
6581
            ),
6582
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6583
                'action' => 'admin_view',
6584
                'lp_id' => $lpId,
6585
                'updateaudio' => 'true',
6586
            ])
6587
        );
6588
6589
        $subscriptionSettings = self::getSubscriptionSettings();
6590
6591
        $request = api_request_uri();
6592
        if (false === strpos($request, 'edit')) {
6593
            $actionsLeft .= Display::url(
6594
                Display::return_icon(
6595
                    'settings.png',
6596
                    get_lang('Course settings'),
6597
                    '',
6598
                    ICON_SIZE_MEDIUM
6599
                ),
6600
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6601
                    'action' => 'edit',
6602
                    'lp_id' => $lpId,
6603
                ])
6604
            );
6605
        }
6606
6607
        if ((strpos($request, 'build') === false &&
6608
            strpos($request, 'add_item') === false) ||
6609
            in_array($action, ['add_audio'])
6610
        ) {
6611
            $actionsLeft .= Display::url(
6612
                Display::return_icon(
6613
                    'edit.png',
6614
                    get_lang('Edit'),
6615
                    '',
6616
                    ICON_SIZE_MEDIUM
6617
                ),
6618
                'lp_controller.php?'.http_build_query([
6619
                    'action' => 'build',
6620
                    'lp_id' => $lpId,
6621
                ]).'&'.api_get_cidreq()
6622
            );
6623
        }
6624
6625
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6626
            if (1 == $this->subscribeUsers &&
6627
                $subscriptionSettings['allow_add_users_to_lp']) {
6628
                $actionsLeft .= Display::url(
6629
                    Display::return_icon(
6630
                        'user.png',
6631
                        get_lang('Subscribe users to learning path'),
6632
                        '',
6633
                        ICON_SIZE_MEDIUM
6634
                    ),
6635
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6636
                );
6637
            }
6638
        }
6639
6640
        if ($allowExpand) {
6641
            $actionsLeft .= Display::url(
6642
                Display::return_icon(
6643
                    'expand.png',
6644
                    get_lang('Expand'),
6645
                    ['id' => 'expand'],
6646
                    ICON_SIZE_MEDIUM
6647
                ).
6648
                Display::return_icon(
6649
                    'contract.png',
6650
                    get_lang('Collapse'),
6651
                    ['id' => 'contract', 'class' => 'hide'],
6652
                    ICON_SIZE_MEDIUM
6653
                ),
6654
                '#',
6655
                ['role' => 'button', 'id' => 'hide_bar_template']
6656
            );
6657
        }
6658
6659
        if ($showRequirementButtons) {
6660
            $buttons = [
6661
                [
6662
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6663
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6664
                        'action' => 'set_previous_step_as_prerequisite',
6665
                        'lp_id' => $lpId,
6666
                    ]),
6667
                ],
6668
                [
6669
                    'title' => get_lang('Clear all prerequisites'),
6670
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6671
                        'action' => 'clear_prerequisites',
6672
                        'lp_id' => $lpId,
6673
                    ]),
6674
                ],
6675
            ];
6676
            $actionsRight = Display::groupButtonWithDropDown(
6677
                get_lang('Prerequisites options'),
6678
                $buttons,
6679
                true
6680
            );
6681
        }
6682
6683
        $toolbar = Display::toolbarAction(
6684
            'actions-lp-controller',
6685
            [$actionsLeft, $actionsRight]
6686
        );
6687
6688
        if ($returnString) {
6689
            return $toolbar;
6690
        }
6691
6692
        echo $toolbar;
6693
    }
6694
6695
    /**
6696
     * Creates the default learning path folder.
6697
     *
6698
     * @param array $course
6699
     * @param int   $creatorId
6700
     *
6701
     * @return bool
6702
     */
6703
    public static function generate_learning_path_folder($course, $creatorId = 0)
6704
    {
6705
        // Creating learning_path folder
6706
        $dir = 'learning_path';
6707
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6708
        $folder = false;
6709
        $folderData = create_unexisting_directory(
6710
            $course,
6711
            $creatorId,
6712
            0,
6713
            null,
6714
            0,
6715
            '',
6716
            $dir,
6717
            get_lang('Learning paths'),
6718
            0
6719
        );
6720
6721
        if (!empty($folderData)) {
6722
            $folder = true;
6723
        }
6724
6725
        return $folder;
6726
    }
6727
6728
    /**
6729
     * @param array  $course
6730
     * @param string $lp_name
6731
     * @param int    $creatorId
6732
     *
6733
     * @return array
6734
     */
6735
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6736
    {
6737
        $filepath = '';
6738
        $dir = '/learning_path/';
6739
6740
        if (empty($lp_name)) {
6741
            $lp_name = $this->name;
6742
        }
6743
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6744
        $folder = self::generate_learning_path_folder($course, $creatorId);
6745
6746
        // Limits title size
6747
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6748
        $dir = $dir.$title;
6749
6750
        // Creating LP folder
6751
        $documentId = null;
6752
        if ($folder) {
6753
            $folderData = create_unexisting_directory(
6754
                $course,
6755
                $creatorId,
6756
                0,
6757
                0,
6758
                0,
6759
                $filepath,
6760
                $dir,
6761
                $lp_name
6762
            );
6763
            if (!empty($folderData)) {
6764
                $folder = true;
6765
            }
6766
6767
            $documentId = $folderData->getIid();
6768
            $dir = $dir.'/';
6769
            if ($folder) {
6770
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6771
            }
6772
        }
6773
6774
        if (empty($documentId)) {
6775
            $dir = api_remove_trailing_slash($dir);
6776
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6777
        }
6778
6779
        $array = [
6780
            'dir' => $dir,
6781
            'filepath' => $filepath,
6782
            'folder' => $folder,
6783
            'id' => $documentId,
6784
        ];
6785
6786
        return $array;
6787
    }
6788
6789
    /**
6790
     * Create a new document //still needs some finetuning.
6791
     *
6792
     * @param array  $courseInfo
6793
     * @param string $content
6794
     * @param string $title
6795
     * @param string $extension
6796
     * @param int    $parentId
6797
     * @param int    $creatorId  creator id
6798
     *
6799
     * @return int
6800
     */
6801
    public function create_document(
6802
        $courseInfo,
6803
        $content = '',
6804
        $title = '',
6805
        $extension = 'html',
6806
        $parentId = 0,
6807
        $creatorId = 0
6808
    ) {
6809
        if (!empty($courseInfo)) {
6810
            $course_id = $courseInfo['real_id'];
6811
        } else {
6812
            $course_id = api_get_course_int_id();
6813
        }
6814
6815
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6816
        $sessionId = api_get_session_id();
6817
6818
        // Generates folder
6819
        $result = $this->generate_lp_folder($courseInfo);
6820
        $dir = $result['dir'];
6821
6822
        if (empty($parentId) || '/' == $parentId) {
6823
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6824
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6825
6826
            if ('/' === $parentId) {
6827
                $dir = '/';
6828
            }
6829
6830
            // Please, do not modify this dirname formatting.
6831
            if (strstr($dir, '..')) {
6832
                $dir = '/';
6833
            }
6834
6835
            if (!empty($dir[0]) && '.' == $dir[0]) {
6836
                $dir = substr($dir, 1);
6837
            }
6838
            if (!empty($dir[0]) && '/' != $dir[0]) {
6839
                $dir = '/'.$dir;
6840
            }
6841
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6842
                $dir .= '/';
6843
            }
6844
        } else {
6845
            $parentInfo = DocumentManager::get_document_data_by_id(
6846
                $parentId,
6847
                $courseInfo['code']
6848
            );
6849
            if (!empty($parentInfo)) {
6850
                $dir = $parentInfo['path'].'/';
6851
            }
6852
        }
6853
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6854
        // is already escaped twice when it gets here.
6855
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6856
        if (!empty($title)) {
6857
            $title = api_replace_dangerous_char(stripslashes($title));
6858
        } else {
6859
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6860
        }
6861
6862
        $title = disable_dangerous_file($title);
6863
        $filename = $title;
6864
        $content = !empty($content) ? $content : $_POST['content_lp'];
6865
        $tmp_filename = $filename;
6866
        $filename = $tmp_filename.'.'.$extension;
6867
6868
        if ('html' === $extension) {
6869
            $content = stripslashes($content);
6870
            $content = str_replace(
6871
                api_get_path(WEB_COURSE_PATH),
6872
                api_get_path(REL_PATH).'courses/',
6873
                $content
6874
            );
6875
6876
            // Change the path of mp3 to absolute.
6877
            // The first regexp deals with :// urls.
6878
            $content = preg_replace(
6879
                "|(flashvars=\"file=)([^:/]+)/|",
6880
                "$1".api_get_path(
6881
                    REL_COURSE_PATH
6882
                ).$courseInfo['path'].'/document/',
6883
                $content
6884
            );
6885
            // The second regexp deals with audio/ urls.
6886
            $content = preg_replace(
6887
                "|(flashvars=\"file=)([^/]+)/|",
6888
                "$1".api_get_path(
6889
                    REL_COURSE_PATH
6890
                ).$courseInfo['path'].'/document/$2/',
6891
                $content
6892
            );
6893
            // For flv player: To prevent edition problem with firefox,
6894
            // we have to use a strange tip (don't blame me please).
6895
            $content = str_replace(
6896
                '</body>',
6897
                '<style type="text/css">body{}</style></body>',
6898
                $content
6899
            );
6900
        }
6901
6902
        $save_file_path = $dir.$filename;
6903
6904
        $document = DocumentManager::addDocument(
6905
            $courseInfo,
6906
            $save_file_path,
6907
            'file',
6908
            '',
6909
            $tmp_filename,
6910
            '',
6911
            0, //readonly
6912
            true,
6913
            null,
6914
            $sessionId,
6915
            $creatorId,
6916
            false,
6917
            $content,
6918
            $parentId
6919
        );
6920
6921
        $document_id = $document->getIid();
6922
        if ($document_id) {
6923
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6924
            $new_title = $originalTitle;
6925
6926
            if ($new_comment || $new_title) {
6927
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6928
                $ct = '';
6929
                if ($new_comment) {
6930
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6931
                }
6932
                if ($new_title) {
6933
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6934
                }
6935
6936
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6937
                        WHERE c_id = $course_id AND id = $document_id ";
6938
                Database::query($sql);
6939
            }
6940
        }
6941
6942
        return $document_id;
6943
    }
6944
6945
    /**
6946
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6947
     */
6948
    public function edit_document()
6949
    {
6950
        $repo = Container::getDocumentRepository();
6951
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6952
            $id = (int) $_REQUEST['document_id'];
6953
            /** @var CDocument $document */
6954
            $document = $repo->find($id);
6955
6956
            if ($document->getResourceNode()->hasEditableContent()) {
6957
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6958
            }
6959
6960
            $document->setTitle($_REQUEST['title']);
6961
            $repo->getEntityManager()->persist($document);
6962
            $repo->getEntityManager()->flush();
6963
        }
6964
    }
6965
6966
    /**
6967
     * Displays the selected item, with a panel for manipulating the item.
6968
     *
6969
     * @param CLpItem $lpItem
6970
     * @param string  $msg
6971
     * @param bool    $show_actions
6972
     *
6973
     * @return string
6974
     */
6975
    public function display_item($lpItem, $msg = null, $show_actions = true)
6976
    {
6977
        $course_id = api_get_course_int_id();
6978
        $return = '';
6979
6980
        if (empty($lpItem)) {
6981
            return '';
6982
        }
6983
        $item_id = $lpItem->getIid();
6984
        $itemType = $lpItem->getItemType();
6985
        $lpId = $lpItem->getLpId();
6986
        $path = $lpItem->getPath();
6987
6988
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6989
6990
        // Prevents wrong parent selection for document, see Bug#1251.
6991
        if ('dir' !== $itemType) {
6992
            Session::write('parent_item_id', $lpItem->getParentItemId());
6993
        }
6994
6995
        if ($show_actions) {
6996
            $return .= $this->displayItemMenu($lpItem);
6997
        }
6998
        $return .= '<div style="padding:10px;">';
6999
7000
        if ('' != $msg) {
7001
            $return .= $msg;
7002
        }
7003
7004
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
7005
7006
        switch ($itemType) {
7007
            case TOOL_THREAD:
7008
                $link = $this->rl_get_resource_link_for_learnpath(
7009
                    $course_id,
7010
                    $lpId,
7011
                    $item_id,
7012
                    0
7013
                );
7014
                $return .= Display::url(
7015
                    get_lang('Go to thread'),
7016
                    $link,
7017
                    ['class' => 'btn btn-primary']
7018
                );
7019
                break;
7020
            case TOOL_FORUM:
7021
                $return .= Display::url(
7022
                    get_lang('Go to the forum'),
7023
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
7024
                    ['class' => 'btn btn-primary']
7025
                );
7026
                break;
7027
            case TOOL_QUIZ:
7028
                if (!empty($path)) {
7029
                    $exercise = new Exercise();
7030
                    $exercise->read($path);
7031
                    $return .= $exercise->description.'<br />';
7032
                    $return .= Display::url(
7033
                        get_lang('Go to exercise'),
7034
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7035
                        ['class' => 'btn btn-primary']
7036
                    );
7037
                }
7038
                break;
7039
            case TOOL_LP_FINAL_ITEM:
7040
                $return .= $this->getSavedFinalItem();
7041
                break;
7042
            case TOOL_DOCUMENT:
7043
            case TOOL_READOUT_TEXT:
7044
                $repo = Container::getDocumentRepository();
7045
                /** @var CDocument $document */
7046
                $document = $repo->find($lpItem->getPath());
7047
                $return .= $this->display_document($document, true, true);
7048
                break;
7049
            case TOOL_HOTPOTATOES:
7050
                $return .= $this->display_document($document, false, true);
7051
                break;
7052
        }
7053
        $return .= '</div>';
7054
7055
        return $return;
7056
    }
7057
7058
    /**
7059
     * Shows the needed forms for editing a specific item.
7060
     *
7061
     * @param CLpItem $lpItem
7062
     *
7063
     * @throws Exception
7064
     * @throws HTML_QuickForm_Error
7065
     *
7066
     * @return string
7067
     */
7068
    public function display_edit_item($lpItem)
7069
    {
7070
        $course_id = api_get_course_int_id();
7071
        $return = '';
7072
7073
        if (empty($lpItem)) {
7074
            return '';
7075
        }
7076
        $item_id = $lpItem->getIid();
7077
        $itemType = $lpItem->getItemType();
7078
        $path = $lpItem->getPath();
7079
7080
        switch ($itemType) {
7081
            case 'dir':
7082
            case 'asset':
7083
            case 'sco':
7084
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
7085
                    $return .= $this->displayItemMenu($lpItem);
7086
                    $return .= $this->display_item_form(
7087
                        $lpItem,
7088
                        'edit'
7089
                    );
7090
                } else {
7091
                    $return .= $this->display_item_form(
7092
                        $lpItem,
7093
                        'edit_item'
7094
                    );
7095
                }
7096
                break;
7097
            case TOOL_LP_FINAL_ITEM:
7098
            case TOOL_DOCUMENT:
7099
            case TOOL_READOUT_TEXT:
7100
                $return .= $this->displayItemMenu($lpItem);
7101
                $return .= $this->displayDocumentForm('edit', $lpItem);
7102
                break;
7103
            case TOOL_LINK:
7104
                $link = null;
7105
                if (!empty($path)) {
7106
                    $repo = Container::getLinkRepository();
7107
                    $link = $repo->find($path);
7108
                }
7109
                $return .= $this->displayItemMenu($lpItem);
7110
                $return .= $this->display_link_form('edit', $lpItem, $link);
7111
7112
                break;
7113
            case TOOL_QUIZ:
7114
                if (!empty($path)) {
7115
                    $repo = Container::getExerciseRepository();
7116
                    $resource = $repo->find($path);
7117
                }
7118
                $return .= $this->displayItemMenu($lpItem);
7119
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7120
                break;
7121
            /*case TOOL_HOTPOTATOES:
7122
                $return .= $this->displayItemMenu($lpItem);
7123
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7124
                break;*/
7125
            case TOOL_STUDENTPUBLICATION:
7126
                if (!empty($path)) {
7127
                    $repo = Container::getStudentPublicationRepository();
7128
                    $resource = $repo->find($path);
7129
                }
7130
                $return .= $this->displayItemMenu($lpItem);
7131
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7132
                break;
7133
            case TOOL_FORUM:
7134
                if (!empty($path)) {
7135
                    $repo = Container::getForumRepository();
7136
                    $resource = $repo->find($path);
7137
                }
7138
                $return .= $this->displayItemMenu($lpItem);
7139
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7140
                break;
7141
            case TOOL_THREAD:
7142
                if (!empty($path)) {
7143
                    $repo = Container::getForumPostRepository();
7144
                    $resource = $repo->find($path);
7145
                }
7146
                $return .= $this->displayItemMenu($lpItem);
7147
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7148
                break;
7149
        }
7150
7151
        return $return;
7152
    }
7153
7154
    /**
7155
     * Function that displays a list with al the resources that
7156
     * could be added to the learning path.
7157
     *
7158
     * @throws Exception
7159
     * @throws HTML_QuickForm_Error
7160
     *
7161
     * @return bool
7162
     */
7163
    public function displayResources()
7164
    {
7165
        // Get all the docs.
7166
        $documents = $this->get_documents(true);
7167
7168
        // Get all the exercises.
7169
        $exercises = $this->get_exercises();
7170
7171
        // Get all the links.
7172
        $links = $this->get_links();
7173
7174
        // Get all the student publications.
7175
        $works = $this->get_student_publications();
7176
7177
        // Get all the forums.
7178
        $forums = $this->get_forums();
7179
7180
        // Get the final item form (see BT#11048) .
7181
        $finish = $this->getFinalItemForm();
7182
7183
        $headers = [
7184
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7185
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7186
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7187
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7188
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7189
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7190
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7191
        ];
7192
7193
        echo Display::return_message(
7194
            get_lang('Click on the [Learner view] button to see your learning path'),
7195
            'normal'
7196
        );
7197
        $section = $this->displayNewSectionForm();
7198
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7199
7200
        echo Display::tabs(
7201
            $headers,
7202
            [
7203
                $documents,
7204
                $exercises,
7205
                $links,
7206
                $works,
7207
                $forums,
7208
                $section,
7209
                $finish,
7210
            ],
7211
            'resource_tab',
7212
            [],
7213
            [],
7214
            $selected
7215
        );
7216
7217
        return true;
7218
    }
7219
7220
    /**
7221
     * Returns the extension of a document.
7222
     *
7223
     * @param string $filename
7224
     *
7225
     * @return string Extension (part after the last dot)
7226
     */
7227
    public function get_extension($filename)
7228
    {
7229
        $explode = explode('.', $filename);
7230
7231
        return $explode[count($explode) - 1];
7232
    }
7233
7234
    /**
7235
     * @return string
7236
     */
7237
    public function getCurrentBuildingModeURL()
7238
    {
7239
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7240
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7241
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7242
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7243
7244
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7245
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7246
7247
        return $currentUrl;
7248
    }
7249
7250
    /**
7251
     * Displays a document by id.
7252
     *
7253
     * @param CDocument $document
7254
     * @param bool      $show_title
7255
     * @param bool      $iframe
7256
     * @param bool      $edit_link
7257
     *
7258
     * @return string
7259
     */
7260
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7261
    {
7262
        $return = '';
7263
        if (!$document) {
7264
            return '';
7265
        }
7266
7267
        $repo = Container::getDocumentRepository();
7268
7269
        // TODO: Add a path filter.
7270
        if ($iframe) {
7271
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7272
            $url = $repo->getResourceFileUrl($document);
7273
7274
            $return .= '<iframe
7275
                id="learnpath_preview_frame"
7276
                frameborder="0"
7277
                height="400"
7278
                width="100%"
7279
                scrolling="auto"
7280
                src="'.$url.'"></iframe>';
7281
        } else {
7282
            $return = $repo->getResourceFileContent($document);
7283
        }
7284
7285
        return $return;
7286
    }
7287
7288
    /**
7289
     * Return HTML form to add/edit a link item.
7290
     *
7291
     * @param string  $action (add/edit)
7292
     * @param CLpItem $lpItem
7293
     * @param CLink   $link
7294
     *
7295
     * @throws Exception
7296
     * @throws HTML_QuickForm_Error
7297
     *
7298
     * @return string HTML form
7299
     */
7300
    public function display_link_form($action = 'add', $lpItem, $link)
7301
    {
7302
        $item_url = '';
7303
        if ($link) {
7304
            $item_url = stripslashes($link->getUrl());
7305
        }
7306
        $form = new FormValidator(
7307
            'edit_link',
7308
            'POST',
7309
            $this->getCurrentBuildingModeURL()
7310
        );
7311
7312
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7313
7314
        $urlAttributes = ['class' => 'learnpath_item_form'];
7315
        $urlAttributes['disabled'] = 'disabled';
7316
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7317
        $form->setDefault('url', $item_url);
7318
7319
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7320
7321
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7322
    }
7323
7324
    /**
7325
     * Return HTML form to add/edit a quiz.
7326
     *
7327
     * @param string  $action   Action (add/edit)
7328
     * @param CLpItem $lpItem   Item ID if already exists
7329
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7330
     *
7331
     * @throws Exception
7332
     *
7333
     * @return string HTML form
7334
     */
7335
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7336
    {
7337
        $form = new FormValidator(
7338
            'quiz_form',
7339
            'POST',
7340
            $this->getCurrentBuildingModeURL()
7341
        );
7342
7343
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7344
7345
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7346
7347
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7348
    }
7349
7350
    /**
7351
     * Return the form to display the forum edit/add option.
7352
     *
7353
     * @param CLpItem $lpItem
7354
     *
7355
     * @throws Exception
7356
     *
7357
     * @return string HTML form
7358
     */
7359
    public function display_forum_form($action = 'add', $lpItem, $resource)
7360
    {
7361
        $form = new FormValidator(
7362
            'forum_form',
7363
            'POST',
7364
            $this->getCurrentBuildingModeURL()
7365
        );
7366
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7367
7368
        if ('add' === $action) {
7369
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7370
        } else {
7371
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7372
        }
7373
7374
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7375
    }
7376
7377
    /**
7378
     * Return HTML form to add/edit forum threads.
7379
     *
7380
     * @param string  $action
7381
     * @param CLpItem $lpItem
7382
     * @param string  $resource
7383
     *
7384
     * @throws Exception
7385
     *
7386
     * @return string HTML form
7387
     */
7388
    public function display_thread_form($action = 'add', $lpItem, $resource)
7389
    {
7390
        $form = new FormValidator(
7391
            'thread_form',
7392
            'POST',
7393
            $this->getCurrentBuildingModeURL()
7394
        );
7395
7396
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7397
7398
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7399
7400
        return $form->returnForm();
7401
    }
7402
7403
    /**
7404
     * Return the HTML form to display an item (generally a dir item).
7405
     *
7406
     * @param CLpItem $lpItem
7407
     * @param string  $title
7408
     * @param string  $action
7409
     * @param string  $extra_info
7410
     *
7411
     * @throws Exception
7412
     * @throws HTML_QuickForm_Error
7413
     *
7414
     * @return string HTML form
7415
     */
7416
    public function display_item_form(
7417
        $lpItem,
7418
        $action = 'add_item'
7419
    ) {
7420
        $item_type = $lpItem->getItemType();
7421
7422
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7423
7424
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7425
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7426
7427
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7428
7429
        return $form->returnForm();
7430
    }
7431
7432
    /**
7433
     * Return HTML form to add/edit a student publication (work).
7434
     *
7435
     * @param string              $action
7436
     * @param CStudentPublication $resource
7437
     *
7438
     * @throws Exception
7439
     *
7440
     * @return string HTML form
7441
     */
7442
    public function display_student_publication_form(
7443
        $action = 'add',
7444
        CLpItem $lpItem,
7445
        $resource
7446
    ) {
7447
        $form = new FormValidator('frm_student_publication', 'post', '#');
7448
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7449
7450
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7451
7452
        $return = '<div class="sectioncomment">';
7453
        $return .= $form->returnForm();
7454
        $return .= '</div>';
7455
7456
        return $return;
7457
    }
7458
7459
    public function displayNewSectionForm()
7460
    {
7461
        $action = 'add_item';
7462
        $item_type = 'dir';
7463
7464
        $lpItem = new CLpItem();
7465
        $lpItem->setItemType('dir');
7466
7467
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7468
7469
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7470
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7471
7472
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7473
        $form->addElement('hidden', 'type', 'dir');
7474
7475
        return $form->returnForm();
7476
    }
7477
7478
    /**
7479
     * Returns the form to update or create a document.
7480
     *
7481
     * @param string  $action (add/edit)
7482
     * @param CLpItem $lpItem
7483
     *
7484
     * @throws HTML_QuickForm_Error
7485
     * @throws Exception
7486
     *
7487
     * @return string HTML form
7488
     */
7489
    public function displayDocumentForm($action = 'add', $lpItem = null)
7490
    {
7491
        if (empty($lpItem)) {
7492
            return '';
7493
        }
7494
7495
        $courseInfo = api_get_course_info();
7496
7497
        $form = new FormValidator(
7498
            'form',
7499
            'POST',
7500
            $this->getCurrentBuildingModeURL(),
7501
            '',
7502
            ['enctype' => 'multipart/form-data']
7503
        );
7504
7505
        $data = $this->generate_lp_folder($courseInfo);
7506
7507
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7508
7509
        switch ($action) {
7510
            case 'add':
7511
                $folders = DocumentManager::get_all_document_folders(
7512
                    $courseInfo,
7513
                    0,
7514
                    true
7515
                );
7516
                DocumentManager::build_directory_selector(
7517
                    $folders,
7518
                    '',
7519
                    [],
7520
                    true,
7521
                    $form,
7522
                    'directory_parent_id'
7523
                );
7524
7525
                if (isset($data['id'])) {
7526
                    $defaults['directory_parent_id'] = $data['id'];
7527
                }
7528
7529
                break;
7530
        }
7531
7532
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7533
7534
        return $form->returnForm();
7535
    }
7536
7537
    /**
7538
     * @param array  $courseInfo
7539
     * @param string $content
7540
     * @param string $title
7541
     * @param int    $parentId
7542
     *
7543
     * @throws \Doctrine\ORM\ORMException
7544
     * @throws \Doctrine\ORM\OptimisticLockException
7545
     * @throws \Doctrine\ORM\TransactionRequiredException
7546
     *
7547
     * @return int
7548
     */
7549
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7550
    {
7551
        $creatorId = api_get_user_id();
7552
        $sessionId = api_get_session_id();
7553
7554
        // Generates folder
7555
        $result = $this->generate_lp_folder($courseInfo);
7556
        $dir = $result['dir'];
7557
7558
        if (empty($parentId) || '/' == $parentId) {
7559
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7560
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7561
7562
            if ('/' === $parentId) {
7563
                $dir = '/';
7564
            }
7565
7566
            // Please, do not modify this dirname formatting.
7567
            if (strstr($dir, '..')) {
7568
                $dir = '/';
7569
            }
7570
7571
            if (!empty($dir[0]) && '.' == $dir[0]) {
7572
                $dir = substr($dir, 1);
7573
            }
7574
            if (!empty($dir[0]) && '/' != $dir[0]) {
7575
                $dir = '/'.$dir;
7576
            }
7577
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7578
                $dir .= '/';
7579
            }
7580
        } else {
7581
            $parentInfo = DocumentManager::get_document_data_by_id(
7582
                $parentId,
7583
                $courseInfo['code']
7584
            );
7585
            if (!empty($parentInfo)) {
7586
                $dir = $parentInfo['path'].'/';
7587
            }
7588
        }
7589
7590
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7591
7592
        if (!is_dir($filepath)) {
7593
            $dir = '/';
7594
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7595
        }
7596
7597
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7598
7599
        if (!empty($title)) {
7600
            $title = api_replace_dangerous_char(stripslashes($title));
7601
        } else {
7602
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7603
        }
7604
7605
        $title = disable_dangerous_file($title);
7606
        $filename = $title;
7607
        $content = !empty($content) ? $content : $_POST['content_lp'];
7608
        $tmpFileName = $filename;
7609
7610
        $i = 0;
7611
        while (file_exists($filepath.$tmpFileName.'.html')) {
7612
            $tmpFileName = $filename.'_'.++$i;
7613
        }
7614
7615
        $filename = $tmpFileName.'.html';
7616
        $content = stripslashes($content);
7617
7618
        if (file_exists($filepath.$filename)) {
7619
            return 0;
7620
        }
7621
7622
        $putContent = file_put_contents($filepath.$filename, $content);
7623
7624
        if (false === $putContent) {
7625
            return 0;
7626
        }
7627
7628
        $fileSize = filesize($filepath.$filename);
7629
        $saveFilePath = $dir.$filename;
7630
7631
        $document = DocumentManager::addDocument(
7632
            $courseInfo,
7633
            $saveFilePath,
7634
            'file',
7635
            $fileSize,
7636
            $tmpFileName,
7637
            '',
7638
            0, //readonly
7639
            true,
7640
            null,
7641
            $sessionId,
7642
            $creatorId
7643
        );
7644
7645
        $documentId = $document->getId();
7646
7647
        if (!$document) {
7648
            return 0;
7649
        }
7650
7651
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7652
        $newTitle = $originalTitle;
7653
7654
        if ($newComment || $newTitle) {
7655
            $em = Database::getManager();
7656
7657
            if ($newComment) {
7658
                $document->setComment($newComment);
7659
            }
7660
7661
            if ($newTitle) {
7662
                $document->setTitle($newTitle);
7663
            }
7664
7665
            $em->persist($document);
7666
            $em->flush();
7667
        }
7668
7669
        return $documentId;
7670
    }
7671
7672
    /**
7673
     * Displays the menu for manipulating a step.
7674
     *
7675
     * @return string
7676
     */
7677
    public function displayItemMenu(CLpItem $lpItem)
7678
    {
7679
        $item_id = $lpItem->getIid();
7680
        $audio = $lpItem->getAudio();
7681
        $itemType = $lpItem->getItemType();
7682
        $path = $lpItem->getPath();
7683
7684
        $return = '<div class="actions">';
7685
        $audio_player = null;
7686
        // We display an audio player if needed.
7687
        if (!empty($audio)) {
7688
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7689
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7690
                .'<audio src="'.$webAudioPath.'" controls>'
7691
                .'</div><br>';*/
7692
        }
7693
7694
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7695
7696
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7697
            $return .= Display::url(
7698
                Display::return_icon(
7699
                    'edit.png',
7700
                    get_lang('Edit'),
7701
                    [],
7702
                    ICON_SIZE_SMALL
7703
                ),
7704
                $url.'&action=edit_item&path_item='.$path
7705
            );
7706
7707
            /*$return .= Display::url(
7708
                Display::return_icon(
7709
                    'move.png',
7710
                    get_lang('Move'),
7711
                    [],
7712
                    ICON_SIZE_SMALL
7713
                ),
7714
                $url.'&action=move_item'
7715
            );*/
7716
        }
7717
7718
        // Commented for now as prerequisites cannot be added to chapters.
7719
        if ('dir' !== $itemType) {
7720
            $return .= Display::url(
7721
                Display::return_icon(
7722
                    'accept.png',
7723
                    get_lang('Prerequisites'),
7724
                    [],
7725
                    ICON_SIZE_SMALL
7726
                ),
7727
                $url.'&action=edit_item_prereq'
7728
            );
7729
        }
7730
        $return .= Display::url(
7731
            Display::return_icon(
7732
                'delete.png',
7733
                get_lang('Delete'),
7734
                [],
7735
                ICON_SIZE_SMALL
7736
            ),
7737
            $url.'&action=delete_item'
7738
        );
7739
7740
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7741
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7742
            if (empty($documentData)) {
7743
                // Try with iid
7744
                $table = Database::get_course_table(TABLE_DOCUMENT);
7745
                $sql = "SELECT path FROM $table
7746
                        WHERE
7747
                              c_id = ".api_get_course_int_id()." AND
7748
                              iid = ".$path." AND
7749
                              path NOT LIKE '%_DELETED_%'";
7750
                $result = Database::query($sql);
7751
                $documentData = Database::fetch_array($result);
7752
                if ($documentData) {
7753
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7754
                }
7755
            }
7756
            if (isset($documentData['absolute_path_from_document'])) {
7757
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7758
            }
7759
        }*/
7760
7761
        $return .= '</div>';
7762
7763
        if (!empty($audio_player)) {
7764
            $return .= $audio_player;
7765
        }
7766
7767
        return $return;
7768
    }
7769
7770
    /**
7771
     * Creates the javascript needed for filling up the checkboxes without page reload.
7772
     *
7773
     * @return string
7774
     */
7775
    public function get_js_dropdown_array()
7776
    {
7777
        $course_id = api_get_course_int_id();
7778
        $return = 'var child_name = new Array();'."\n";
7779
        $return .= 'var child_value = new Array();'."\n\n";
7780
        $return .= 'child_name[0] = new Array();'."\n";
7781
        $return .= 'child_value[0] = new Array();'."\n\n";
7782
7783
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7784
        $sql = "SELECT * FROM ".$tbl_lp_item."
7785
                WHERE
7786
                    c_id = $course_id AND
7787
                    lp_id = ".$this->lp_id." AND
7788
                    parent_item_id = 0
7789
                ORDER BY display_order ASC";
7790
        Database::query($sql);
7791
        $i = 0;
7792
7793
        $list = $this->getItemsForForm(true);
7794
7795
        foreach ($list as $row_zero) {
7796
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7797
                if (TOOL_QUIZ == $row_zero['item_type']) {
7798
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7799
                }
7800
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7801
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7802
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7803
            }
7804
        }
7805
7806
        $return .= "\n";
7807
        $sql = "SELECT * FROM $tbl_lp_item
7808
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7809
        $res = Database::query($sql);
7810
        while ($row = Database::fetch_array($res)) {
7811
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7812
                           WHERE
7813
                                c_id = ".$course_id." AND
7814
                                parent_item_id = ".$row['iid']."
7815
                           ORDER BY display_order ASC";
7816
            $res_parent = Database::query($sql_parent);
7817
            $i = 0;
7818
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7819
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7820
7821
            while ($row_parent = Database::fetch_array($res_parent)) {
7822
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7823
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7824
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7825
            }
7826
            $return .= "\n";
7827
        }
7828
7829
        $return .= "
7830
            function load_cbo(id) {
7831
                if (!id) {
7832
                    return false;
7833
                }
7834
7835
                var cbo = document.getElementById('previous');
7836
                for(var i = cbo.length - 1; i > 0; i--) {
7837
                    cbo.options[i] = null;
7838
                }
7839
7840
                var k=0;
7841
                for(var i = 1; i <= child_name[id].length; i++){
7842
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7843
                    option.style.paddingLeft = '40px';
7844
                    cbo.options[i] = option;
7845
                    k = i;
7846
                }
7847
7848
                cbo.options[k].selected = true;
7849
                $('#previous').selectpicker('refresh');
7850
            }";
7851
7852
        return $return;
7853
    }
7854
7855
    /**
7856
     * Display the form to allow moving an item.
7857
     *
7858
     * @param CLpItem $lpItem
7859
     *
7860
     * @throws Exception
7861
     * @throws HTML_QuickForm_Error
7862
     *
7863
     * @return string HTML form
7864
     */
7865
    public function display_move_item($lpItem)
7866
    {
7867
        $return = '';
7868
        $path = $lpItem->getPath();
7869
7870
        if ($lpItem) {
7871
            $itemType = $lpItem->getItemType();
7872
            switch ($itemType) {
7873
                case 'dir':
7874
                case 'asset':
7875
                    $return .= $this->displayItemMenu($lpItem);
7876
                    $return .= $this->display_item_form(
7877
                        $lpItem,
7878
                        get_lang('Move the current section'),
7879
                        'move',
7880
                        $row
7881
                    );
7882
                    break;
7883
                case TOOL_DOCUMENT:
7884
                    $return .= $this->displayItemMenu($lpItem);
7885
                    $return .= $this->displayDocumentForm('move', $lpItem);
7886
                    break;
7887
                case TOOL_LINK:
7888
                    $link = null;
7889
                    if (!empty($path)) {
7890
                        $repo = Container::getLinkRepository();
7891
                        $link = $repo->find($path);
7892
                    }
7893
                    $return .= $this->displayItemMenu($lpItem);
7894
                    $return .= $this->display_link_form('move', $lpItem, $link);
7895
                    break;
7896
                case TOOL_HOTPOTATOES:
7897
                    $return .= $this->displayItemMenu($lpItem);
7898
                    $return .= $this->display_link_form('move', $lpItem, $row);
7899
                    break;
7900
                case TOOL_QUIZ:
7901
                    $return .= $this->displayItemMenu($lpItem);
7902
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7903
                    break;
7904
                case TOOL_STUDENTPUBLICATION:
7905
                    $return .= $this->displayItemMenu($lpItem);
7906
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7907
                    break;
7908
                case TOOL_FORUM:
7909
                    $return .= $this->displayItemMenu($lpItem);
7910
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7911
                    break;
7912
                case TOOL_THREAD:
7913
                    $return .= $this->displayItemMenu($lpItem);
7914
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7915
                    break;
7916
            }
7917
        }
7918
7919
        return $return;
7920
    }
7921
7922
    /**
7923
     * Return HTML form to allow prerequisites selection.
7924
     *
7925
     * @todo use FormValidator
7926
     *
7927
     * @return string HTML form
7928
     */
7929
    public function display_item_prerequisites_form(CLpItem $lpItem)
7930
    {
7931
        $course_id = api_get_course_int_id();
7932
        $prerequisiteId = $lpItem->getPrerequisite();
7933
        $itemId = $lpItem->getIid();
7934
7935
        $return = '<legend>';
7936
        $return .= get_lang('Add/edit prerequisites');
7937
        $return .= '</legend>';
7938
        $return .= '<form method="POST">';
7939
        $return .= '<div class="table-responsive">';
7940
        $return .= '<table class="table table-hover">';
7941
        $return .= '<thead>';
7942
        $return .= '<tr>';
7943
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7944
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7945
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7946
        $return .= '</tr>';
7947
        $return .= '</thead>';
7948
7949
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7950
        $return .= '<tbody>';
7951
        $return .= '<tr>';
7952
        $return .= '<td colspan="3">';
7953
        $return .= '<div class="radio learnpath"><label for="idnone">';
7954
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7955
        $return .= get_lang('none').'</label>';
7956
        $return .= '</div>';
7957
        $return .= '</tr>';
7958
7959
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7960
        $sql = "SELECT * FROM $tbl_lp_item
7961
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7962
        $result = Database::query($sql);
7963
7964
        $selectedMinScore = [];
7965
        $selectedMaxScore = [];
7966
        $masteryScore = [];
7967
        while ($row = Database::fetch_array($result)) {
7968
            if ($row['iid'] == $itemId) {
7969
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7970
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7971
            }
7972
            $masteryScore[$row['iid']] = $row['mastery_score'];
7973
        }
7974
7975
        $arrLP = $this->getItemsForForm();
7976
        $this->tree_array($arrLP);
7977
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7978
        unset($this->arrMenu);
7979
7980
        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...
7981
            $item = $arrLP[$i];
7982
7983
            if ($item['id'] == $itemId) {
7984
                break;
7985
            }
7986
7987
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7988
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7989
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7990
7991
            $return .= '<tr>';
7992
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7993
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7994
            $return .= '<label for="id'.$item['id'].'">';
7995
7996
            $checked = '';
7997
            if (null !== $prerequisiteId) {
7998
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7999
            }
8000
8001
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
8002
8003
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
8004
8005
            $icon_name = str_replace(' ', '', $item['item_type']);
8006
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
8007
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
8008
            } else {
8009
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
8010
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
8011
                } else {
8012
                    $return .= Display::return_icon('folder_document.png');
8013
                }
8014
            }
8015
8016
            $return .= $item['title'].'</label>';
8017
            $return .= '</div>';
8018
            $return .= '</td>';
8019
8020
            if (TOOL_QUIZ == $item['item_type']) {
8021
                // lets update max_score Tests information depending of the Tests Advanced properties
8022
                $lpItemObj = new LpItem($course_id, $item['id']);
8023
                $exercise = new Exercise($course_id);
8024
                $exercise->read($lpItemObj->path);
8025
                $lpItemObj->max_score = $exercise->get_max_score();
8026
                $lpItemObj->update();
8027
                $item['max_score'] = $lpItemObj->max_score;
8028
8029
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
8030
                    // Backwards compatibility with 1.9.x use mastery_score as min value
8031
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
8032
                }
8033
8034
                $return .= '<td>';
8035
                $return .= '<input
8036
                    class="form-control"
8037
                    size="4" maxlength="3"
8038
                    name="min_'.$item['id'].'"
8039
                    type="number"
8040
                    min="0"
8041
                    step="any"
8042
                    max="'.$item['max_score'].'"
8043
                    value="'.$selectedMinScoreValue.'"
8044
                />';
8045
                $return .= '</td>';
8046
                $return .= '<td>';
8047
                $return .= '<input
8048
                    class="form-control"
8049
                    size="4"
8050
                    maxlength="3"
8051
                    name="max_'.$item['id'].'"
8052
                    type="number"
8053
                    min="0"
8054
                    step="any"
8055
                    max="'.$item['max_score'].'"
8056
                    value="'.$selectedMaxScoreValue.'"
8057
                />';
8058
                $return .= '</td>';
8059
            }
8060
8061
            if (TOOL_HOTPOTATOES == $item['item_type']) {
8062
                $return .= '<td>';
8063
                $return .= '<input
8064
                    size="4"
8065
                    maxlength="3"
8066
                    name="min_'.$item['id'].'"
8067
                    type="number"
8068
                    min="0"
8069
                    step="any"
8070
                    max="'.$item['max_score'].'"
8071
                    value="'.$selectedMinScoreValue.'"
8072
                />';
8073
                $return .= '</td>';
8074
                $return .= '<td>';
8075
                $return .= '<input
8076
                    size="4"
8077
                    maxlength="3"
8078
                    name="max_'.$item['id'].'"
8079
                    type="number"
8080
                    min="0"
8081
                    step="any"
8082
                    max="'.$item['max_score'].'"
8083
                    value="'.$selectedMaxScoreValue.'"
8084
                />';
8085
                $return .= '</td>';
8086
            }
8087
            $return .= '</tr>';
8088
        }
8089
        $return .= '<tr>';
8090
        $return .= '</tr>';
8091
        $return .= '</tbody>';
8092
        $return .= '</table>';
8093
        $return .= '</div>';
8094
        $return .= '<div class="form-group">';
8095
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
8096
            get_lang('Save prerequisites settings').'</button>';
8097
        $return .= '</form>';
8098
8099
        return $return;
8100
    }
8101
8102
    /**
8103
     * Return HTML list to allow prerequisites selection for lp.
8104
     *
8105
     * @return string HTML form
8106
     */
8107
    public function display_lp_prerequisites_list()
8108
    {
8109
        $course_id = api_get_course_int_id();
8110
        $lp_id = $this->lp_id;
8111
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8112
8113
        // get current prerequisite
8114
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8115
        $result = Database::query($sql);
8116
        $row = Database::fetch_array($result);
8117
        $prerequisiteId = $row['prerequisite'];
8118
        $session_id = api_get_session_id();
8119
        $session_condition = api_get_session_condition($session_id, true, true);
8120
        $sql = "SELECT * FROM $tbl_lp
8121
                WHERE c_id = $course_id $session_condition
8122
                ORDER BY display_order ";
8123
        $rs = Database::query($sql);
8124
        $return = '';
8125
        $return .= '<select name="prerequisites" class="form-control">';
8126
        $return .= '<option value="0">'.get_lang('none').'</option>';
8127
        if (Database::num_rows($rs) > 0) {
8128
            while ($row = Database::fetch_array($rs)) {
8129
                if ($row['id'] == $lp_id) {
8130
                    continue;
8131
                }
8132
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
8133
            }
8134
        }
8135
        $return .= '</select>';
8136
8137
        return $return;
8138
    }
8139
8140
    /**
8141
     * Creates a list with all the documents in it.
8142
     *
8143
     * @param bool $showInvisibleFiles
8144
     *
8145
     * @throws Exception
8146
     * @throws HTML_QuickForm_Error
8147
     *
8148
     * @return string
8149
     */
8150
    public function get_documents($showInvisibleFiles = false)
8151
    {
8152
        $course_info = api_get_course_info();
8153
        $sessionId = api_get_session_id();
8154
        $documentTree = DocumentManager::get_document_preview(
8155
            $course_info,
8156
            $this->lp_id,
8157
            null,
8158
            $sessionId,
8159
            true,
8160
            null,
8161
            null,
8162
            $showInvisibleFiles,
8163
            true
8164
        );
8165
8166
        $headers = [
8167
            get_lang('Files'),
8168
            get_lang('CreateTheDocument'),
8169
            get_lang('CreateReadOutText'),
8170
            get_lang('Upload'),
8171
        ];
8172
8173
        $form = new FormValidator(
8174
            'form_upload',
8175
            'POST',
8176
            $this->getCurrentBuildingModeURL(),
8177
            '',
8178
            ['enctype' => 'multipart/form-data']
8179
        );
8180
8181
        $folders = DocumentManager::get_all_document_folders(
8182
            api_get_course_info(),
8183
            0,
8184
            true
8185
        );
8186
8187
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8188
8189
        DocumentManager::build_directory_selector(
8190
            $folders,
8191
            $lpPathInfo['id'],
8192
            [],
8193
            true,
8194
            $form,
8195
            'directory_parent_id'
8196
        );
8197
8198
        $group = [
8199
            $form->createElement(
8200
                'radio',
8201
                'if_exists',
8202
                get_lang('If file exists:'),
8203
                get_lang('Do nothing'),
8204
                'nothing'
8205
            ),
8206
            $form->createElement(
8207
                'radio',
8208
                'if_exists',
8209
                null,
8210
                get_lang('Overwrite the existing file'),
8211
                'overwrite'
8212
            ),
8213
            $form->createElement(
8214
                'radio',
8215
                'if_exists',
8216
                null,
8217
                get_lang('Rename the uploaded file if it exists'),
8218
                'rename'
8219
            ),
8220
        ];
8221
        $form->addGroup($group, null, get_lang('If file exists:'));
8222
8223
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8224
        $defaultFileExistsOption = 'rename';
8225
        if (!empty($fileExistsOption)) {
8226
            $defaultFileExistsOption = $fileExistsOption;
8227
        }
8228
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8229
8230
        // Check box options
8231
        $form->addElement(
8232
            'checkbox',
8233
            'unzip',
8234
            get_lang('Options'),
8235
            get_lang('Uncompress zip')
8236
        );
8237
8238
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8239
        $form->addMultipleUpload($url);
8240
8241
        $lpItem = new CLpItem();
8242
        $lpItem->setItemType(TOOL_DOCUMENT);
8243
        $new = $this->displayDocumentForm('add', $lpItem);
8244
8245
        /*$lpItem = new CLpItem();
8246
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8247
        $frmReadOutText = $this->displayDocumentForm('add');*/
8248
8249
        $headers = [
8250
            get_lang('Files'),
8251
            get_lang('Create a new document'),
8252
            get_lang('Create read-out text'),
8253
            get_lang('Upload'),
8254
        ];
8255
8256
        return Display::tabs(
8257
            $headers,
8258
            [$documentTree, $new, $form->returnForm()],
8259
            'subtab'
8260
        );
8261
    }
8262
8263
    /**
8264
     * Creates a list with all the exercises (quiz) in it.
8265
     *
8266
     * @return string
8267
     */
8268
    public function get_exercises()
8269
    {
8270
        $course_id = api_get_course_int_id();
8271
        $session_id = api_get_session_id();
8272
        $userInfo = api_get_user_info();
8273
8274
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8275
        $condition_session = api_get_session_condition($session_id, true, true);
8276
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8277
8278
        $activeCondition = ' active <> -1 ';
8279
        if ($setting) {
8280
            $activeCondition = ' active = 1 ';
8281
        }
8282
8283
        $categoryCondition = '';
8284
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8285
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8286
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8287
        }
8288
8289
        $keywordCondition = '';
8290
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8291
8292
        if (!empty($keyword)) {
8293
            $keyword = Database::escape_string($keyword);
8294
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8295
        }
8296
8297
        $sql_quiz = "SELECT * FROM $tbl_quiz
8298
                     WHERE
8299
                            c_id = $course_id AND
8300
                            $activeCondition
8301
                            $condition_session
8302
                            $categoryCondition
8303
                            $keywordCondition
8304
                     ORDER BY title ASC";
8305
        $res_quiz = Database::query($sql_quiz);
8306
8307
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8308
8309
        // Create a search-box
8310
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8311
        $form->addHidden('action', 'add_item');
8312
        $form->addHidden('type', 'step');
8313
        $form->addHidden('lp_id', $this->lp_id);
8314
        $form->addHidden('lp_build_selected', '2');
8315
8316
        $form->addCourseHiddenParams();
8317
        $form->addText(
8318
            'keyword',
8319
            get_lang('Search'),
8320
            false,
8321
            [
8322
                'aria-label' => get_lang('Search'),
8323
            ]
8324
        );
8325
8326
        if (api_get_configuration_value('allow_exercise_categories')) {
8327
            $manager = new ExerciseCategoryManager();
8328
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8329
            if (!empty($options)) {
8330
                $form->addSelect(
8331
                    'category_id',
8332
                    get_lang('Category'),
8333
                    $options,
8334
                    ['placeholder' => get_lang('Please select an option')]
8335
                );
8336
            }
8337
        }
8338
8339
        $form->addButtonSearch(get_lang('Search'));
8340
        $return = $form->returnForm();
8341
8342
        $return .= '<ul class="lp_resource">';
8343
        $return .= '<li class="lp_resource_element">';
8344
        $return .= Display::return_icon('new_exercice.png');
8345
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8346
            get_lang('New test').'</a>';
8347
        $return .= '</li>';
8348
8349
        $previewIcon = Display::return_icon(
8350
            'preview_view.png',
8351
            get_lang('Preview')
8352
        );
8353
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8354
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8355
8356
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8357
        $repo = Container::getExerciseRepository();
8358
        $courseEntity = api_get_course_entity();
8359
        $sessionEntity = api_get_session_entity();
8360
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8361
            /** @var CQuiz $exercise */
8362
            $exercise = $repo->find($row_quiz['id']);
8363
            $title = strip_tags(
8364
                api_html_entity_decode($row_quiz['title'])
8365
            );
8366
8367
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8368
            /*$visibility = api_get_item_visibility(
8369
                ['real_id' => $course_id],
8370
                TOOL_QUIZ,
8371
                $row_quiz['iid'],
8372
                $session_id
8373
            );*/
8374
8375
            $link = Display::url(
8376
                $previewIcon,
8377
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
8378
                ['target' => '_blank']
8379
            );
8380
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
8381
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8382
            $return .= $quizIcon;
8383
            $sessionStar = api_get_session_image(
8384
                $row_quiz['session_id'],
8385
                $userInfo['status']
8386
            );
8387
            $return .= Display::url(
8388
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8389
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
8390
                [
8391
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8392
                ]
8393
            );
8394
            $return .= '</li>';
8395
        }
8396
8397
        $return .= '</ul>';
8398
8399
        return $return;
8400
    }
8401
8402
    /**
8403
     * Creates a list with all the links in it.
8404
     *
8405
     * @return string
8406
     */
8407
    public function get_links()
8408
    {
8409
        $sessionId = api_get_session_id();
8410
        $repo = Container::getLinkRepository();
8411
8412
        $course = api_get_course_entity();
8413
        $session = api_get_session_entity($sessionId);
8414
        $qb = $repo->getResourcesByCourse($course, $session);
8415
        /** @var CLink[] $links */
8416
        $links = $qb->getQuery()->getResult();
8417
8418
        $selfUrl = api_get_self();
8419
        $courseIdReq = api_get_cidreq();
8420
        $userInfo = api_get_user_info();
8421
8422
        $moveEverywhereIcon = Display::return_icon(
8423
            'move_everywhere.png',
8424
            get_lang('Move'),
8425
            [],
8426
            ICON_SIZE_TINY
8427
        );
8428
8429
        /*$condition_session = api_get_session_condition(
8430
            $session_id,
8431
            true,
8432
            true,
8433
            'link.session_id'
8434
        );
8435
8436
        $sql = "SELECT
8437
                    link.id as link_id,
8438
                    link.title as link_title,
8439
                    link.session_id as link_session_id,
8440
                    link.category_id as category_id,
8441
                    link_category.category_title as category_title
8442
                FROM $tbl_link as link
8443
                LEFT JOIN $linkCategoryTable as link_category
8444
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8445
                WHERE link.c_id = $course_id $condition_session
8446
                ORDER BY link_category.category_title ASC, link.title ASC";
8447
        $result = Database::query($sql);*/
8448
        $categorizedLinks = [];
8449
        $categories = [];
8450
8451
        foreach ($links as $link) {
8452
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8453
8454
            if (empty($categoryId)) {
8455
                $categories[0] = get_lang('Uncategorized');
8456
            } else {
8457
                $category = $link->getCategory();
8458
                $categories[$categoryId] = $category->getCategoryTitle();
8459
            }
8460
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8461
        }
8462
8463
        $linksHtmlCode =
8464
            '<script>
8465
            function toggle_tool(tool, id) {
8466
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8467
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8468
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8469
                } else {
8470
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8471
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8472
                }
8473
            }
8474
        </script>
8475
8476
        <ul class="lp_resource">
8477
            <li class="lp_resource_element">
8478
                '.Display::return_icon('linksnew.gif').'
8479
                <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').'">'.
8480
                get_lang('Add a link').'
8481
                </a>
8482
            </li>';
8483
8484
        foreach ($categorizedLinks as $categoryId => $links) {
8485
            $linkNodes = null;
8486
            /** @var CLink $link */
8487
            foreach ($links as $key => $link) {
8488
                $title = $link->getTitle();
8489
                $linkSessionId = $link->getSessionId();
8490
8491
                $linkUrl = Display::url(
8492
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8493
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8494
                    ['target' => '_blank']
8495
                );
8496
8497
                if ($link->isVisible($course, $session)) {
8498
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8499
                    $linkNodes .=
8500
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8501
                        <a class="moved" href="#">'.
8502
                            $moveEverywhereIcon.
8503
                        '</a>
8504
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8505
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8506
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8507
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8508
                        '</a>
8509
                    </li>';
8510
                }
8511
            }
8512
            $linksHtmlCode .=
8513
                '<li>
8514
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8515
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8516
                    align="absbottom" />
8517
                </a>
8518
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8519
            </li>
8520
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8521
        }
8522
        $linksHtmlCode .= '</ul>';
8523
8524
        return $linksHtmlCode;
8525
    }
8526
8527
    /**
8528
     * Creates a list with all the student publications in it.
8529
     *
8530
     * @return string
8531
     */
8532
    public function get_student_publications()
8533
    {
8534
        $return = '<ul class="lp_resource">';
8535
        $return .= '<li class="lp_resource_element">';
8536
        /*
8537
        $return .= Display::return_icon('works_new.gif');
8538
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8539
            get_lang('Add the Assignments tool to the course').'</a>';
8540
        $return .= '</li>';*/
8541
8542
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8543
        $works = getWorkListTeacher(0, 100, null, null, null);
8544
        if (!empty($works)) {
8545
            foreach ($works as $work) {
8546
                $link = Display::url(
8547
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8548
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8549
                    ['target' => '_blank']
8550
                );
8551
8552
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8553
                $return .= '<a class="moved" href="#">';
8554
                $return .= Display::return_icon(
8555
                    'move_everywhere.png',
8556
                    get_lang('Move'),
8557
                    [],
8558
                    ICON_SIZE_TINY
8559
                );
8560
                $return .= '</a> ';
8561
8562
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8563
                $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.'">'.
8564
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8565
                </a>';
8566
8567
                $return .= '</li>';
8568
            }
8569
        }
8570
8571
        $return .= '</ul>';
8572
8573
        return $return;
8574
    }
8575
8576
    /**
8577
     * Creates a list with all the forums in it.
8578
     *
8579
     * @return string
8580
     */
8581
    public function get_forums()
8582
    {
8583
        require_once '../forum/forumfunction.inc.php';
8584
8585
        $forumCategories = get_forum_categories();
8586
        $forumsInNoCategory = get_forums_in_category(0);
8587
        if (!empty($forumsInNoCategory)) {
8588
            $forumCategories = array_merge(
8589
                $forumCategories,
8590
                [
8591
                    [
8592
                        'cat_id' => 0,
8593
                        'session_id' => 0,
8594
                        'visibility' => 1,
8595
                        'cat_comment' => null,
8596
                    ],
8597
                ]
8598
            );
8599
        }
8600
8601
        $a_forums = [];
8602
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8603
        $sessionEntity = api_get_session_entity(api_get_session_id());
8604
8605
        foreach ($forumCategories as $forumCategory) {
8606
            // The forums in this category.
8607
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8608
            if (!empty($forumsInCategory)) {
8609
                foreach ($forumsInCategory as $forum) {
8610
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8611
                        $a_forums[] = $forum;
8612
                    }
8613
                }
8614
            }
8615
        }
8616
8617
        $return = '<ul class="lp_resource">';
8618
8619
        // First add link
8620
        $return .= '<li class="lp_resource_element">';
8621
        $return .= Display::return_icon('new_forum.png');
8622
        $return .= Display::url(
8623
            get_lang('Create a new forum'),
8624
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8625
                'action' => 'add',
8626
                'content' => 'forum',
8627
                'lp_id' => $this->lp_id,
8628
            ]),
8629
            ['title' => get_lang('Create a new forum')]
8630
        );
8631
        $return .= '</li>';
8632
8633
        $return .= '<script>
8634
            function toggle_forum(forum_id) {
8635
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8636
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8637
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8638
                } else {
8639
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8640
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8641
                }
8642
            }
8643
        </script>';
8644
8645
        foreach ($a_forums as $forum) {
8646
            $forumId = $forum->getIid();
8647
            $title = Security::remove_XSS($forum->getForumTitle());
8648
            $link = Display::url(
8649
                Display::return_icon('preview_view.png', get_lang('Preview')),
8650
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8651
                ['target' => '_blank']
8652
            );
8653
8654
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8655
            $return .= '<a class="moved" href="#">';
8656
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8657
            $return .= ' </a>';
8658
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8659
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8660
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8661
                        </a>
8662
                        <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">'.
8663
                $title.' '.$link.'</a>';
8664
8665
            $return .= '</li>';
8666
8667
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8668
            $threads = get_threads($forumId);
8669
            if (is_array($threads)) {
8670
                foreach ($threads as $thread) {
8671
                    $threadId = $thread->getIid();
8672
                    $link = Display::url(
8673
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8674
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8675
                        ['target' => '_blank']
8676
                    );
8677
8678
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8679
                    $return .= '&nbsp;<a class="moved" href="#">';
8680
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8681
                    $return .= ' </a>';
8682
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8683
                    $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.'">'.
8684
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8685
                    $return .= '</li>';
8686
                }
8687
            }
8688
            $return .= '</div>';
8689
        }
8690
        $return .= '</ul>';
8691
8692
        return $return;
8693
    }
8694
8695
    /**
8696
     * // TODO: The output encoding should be equal to the system encoding.
8697
     *
8698
     * Exports the learning path as a SCORM package. This is the main function that
8699
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8700
     * whole thing and returns the zip.
8701
     *
8702
     * This method needs to be called in PHP5, as it will fail with non-adequate
8703
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8704
     * you need to call it on a learnpath object.
8705
     *
8706
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8707
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8708
     * path has been modified, it should use the generic method here below.
8709
     *
8710
     * @return string Returns the zip package string, or null if error
8711
     */
8712
    public function scormExport()
8713
    {
8714
        api_set_more_memory_and_time_limits();
8715
8716
        $_course = api_get_course_info();
8717
        $course_id = $_course['real_id'];
8718
        // Create the zip handler (this will remain available throughout the method).
8719
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8720
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8721
        $temp_dir_short = uniqid('scorm_export', true);
8722
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8723
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8724
        $zip_folder = new PclZip($temp_zip_file);
8725
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8726
        $root_path = $main_path = api_get_path(SYS_PATH);
8727
        $files_cleanup = [];
8728
8729
        // Place to temporarily stash the zip file.
8730
        // create the temp dir if it doesn't exist
8731
        // or do a cleanup before creating the zip file.
8732
        if (!is_dir($temp_zip_dir)) {
8733
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8734
        } else {
8735
            // Cleanup: Check the temp dir for old files and delete them.
8736
            $handle = opendir($temp_zip_dir);
8737
            while (false !== ($file = readdir($handle))) {
8738
                if ('.' != $file && '..' != $file) {
8739
                    unlink("$temp_zip_dir/$file");
8740
                }
8741
            }
8742
            closedir($handle);
8743
        }
8744
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8745
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8746
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8747
        ) {
8748
            // Remove the possible . at the end of the path.
8749
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8750
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8751
            mkdir(
8752
                $dest_path_to_scorm_folder,
8753
                api_get_permissions_for_new_directories(),
8754
                true
8755
            );
8756
            copyr(
8757
                $current_course_path.'/scorm/'.$this->path,
8758
                $dest_path_to_scorm_folder,
8759
                ['imsmanifest'],
8760
                $zip_files
8761
            );
8762
        }
8763
8764
        // Build a dummy imsmanifest structure.
8765
        // Do not add to the zip yet (we still need it).
8766
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8767
        // Aggregation Model official document, section "2.3 Content Packaging".
8768
        // We are going to build a UTF-8 encoded manifest.
8769
        // Later we will recode it to the desired (and supported) encoding.
8770
        $xmldoc = new DOMDocument('1.0');
8771
        $root = $xmldoc->createElement('manifest');
8772
        $root->setAttribute('identifier', 'SingleCourseManifest');
8773
        $root->setAttribute('version', '1.1');
8774
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8775
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8776
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8777
        $root->setAttribute(
8778
            'xsi:schemaLocation',
8779
            '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'
8780
        );
8781
        // Build mandatory sub-root container elements.
8782
        $metadata = $xmldoc->createElement('metadata');
8783
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8784
        $metadata->appendChild($md_schema);
8785
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8786
        $metadata->appendChild($md_schemaversion);
8787
        $root->appendChild($metadata);
8788
8789
        $organizations = $xmldoc->createElement('organizations');
8790
        $resources = $xmldoc->createElement('resources');
8791
8792
        // Build the only organization we will use in building our learnpaths.
8793
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8794
        $organization = $xmldoc->createElement('organization');
8795
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8796
        // To set the title of the SCORM entity (=organization), we take the name given
8797
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8798
        // learning path charset) as it is the encoding that defines how it is stored
8799
        // in the database. Then we convert it to HTML entities again as the "&" character
8800
        // alone is not authorized in XML (must be &amp;).
8801
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8802
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8803
        $organization->appendChild($org_title);
8804
        $folder_name = 'document';
8805
8806
        // Removes the learning_path/scorm_folder path when exporting see #4841
8807
        $path_to_remove = '';
8808
        $path_to_replace = '';
8809
        $result = $this->generate_lp_folder($_course);
8810
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8811
            $path_to_remove = 'document'.$result['dir'];
8812
            $path_to_replace = $folder_name.'/';
8813
        }
8814
8815
        // Fixes chamilo scorm exports
8816
        if ('chamilo_scorm_export' === $this->ref) {
8817
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8818
        }
8819
8820
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8821
        $link_updates = [];
8822
        $links_to_create = [];
8823
        foreach ($this->ordered_items as $index => $itemId) {
8824
            /** @var learnpathItem $item */
8825
            $item = $this->items[$itemId];
8826
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8827
                // Get included documents from this item.
8828
                if ('sco' === $item->type) {
8829
                    $inc_docs = $item->get_resources_from_source(
8830
                        null,
8831
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8832
                    );
8833
                } else {
8834
                    $inc_docs = $item->get_resources_from_source();
8835
                }
8836
8837
                // Give a child element <item> to the <organization> element.
8838
                $my_item_id = $item->get_id();
8839
                $my_item = $xmldoc->createElement('item');
8840
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8841
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8842
                $my_item->setAttribute('isvisible', 'true');
8843
                // Give a child element <title> to the <item> element.
8844
                $my_title = $xmldoc->createElement(
8845
                    'title',
8846
                    htmlspecialchars(
8847
                        api_utf8_encode($item->get_title()),
8848
                        ENT_QUOTES,
8849
                        'UTF-8'
8850
                    )
8851
                );
8852
                $my_item->appendChild($my_title);
8853
                // Give a child element <adlcp:prerequisites> to the <item> element.
8854
                $my_prereqs = $xmldoc->createElement(
8855
                    'adlcp:prerequisites',
8856
                    $this->get_scorm_prereq_string($my_item_id)
8857
                );
8858
                $my_prereqs->setAttribute('type', 'aicc_script');
8859
                $my_item->appendChild($my_prereqs);
8860
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8861
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8862
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8863
                //$xmldoc->createElement('adlcp:timelimitaction','');
8864
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8865
                //$xmldoc->createElement('adlcp:datafromlms','');
8866
                // Give a child element <adlcp:masteryscore> to the <item> element.
8867
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8868
                $my_item->appendChild($my_masteryscore);
8869
8870
                // Attach this item to the organization element or hits parent if there is one.
8871
                if (!empty($item->parent) && 0 != $item->parent) {
8872
                    $children = $organization->childNodes;
8873
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8874
                    if (is_object($possible_parent)) {
8875
                        $possible_parent->appendChild($my_item);
8876
                    } else {
8877
                        if ($this->debug > 0) {
8878
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8879
                        }
8880
                    }
8881
                } else {
8882
                    if ($this->debug > 0) {
8883
                        error_log('No parent');
8884
                    }
8885
                    $organization->appendChild($my_item);
8886
                }
8887
8888
                // Get the path of the file(s) from the course directory root.
8889
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8890
                $my_xml_file_path = $my_file_path;
8891
                if (!empty($path_to_remove)) {
8892
                    // From docs
8893
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8894
8895
                    // From quiz
8896
                    if ('chamilo_scorm_export' === $this->ref) {
8897
                        $path_to_remove = 'scorm/'.$this->path.'/';
8898
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8899
                    }
8900
                }
8901
8902
                $my_sub_dir = dirname($my_file_path);
8903
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8904
                $my_xml_sub_dir = $my_sub_dir;
8905
                // Give a <resource> child to the <resources> element
8906
                $my_resource = $xmldoc->createElement('resource');
8907
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8908
                $my_resource->setAttribute('type', 'webcontent');
8909
                $my_resource->setAttribute('href', $my_xml_file_path);
8910
                // adlcp:scormtype can be either 'sco' or 'asset'.
8911
                if ('sco' === $item->type) {
8912
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8913
                } else {
8914
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8915
                }
8916
                // xml:base is the base directory to find the files declared in this resource.
8917
                $my_resource->setAttribute('xml:base', '');
8918
                // Give a <file> child to the <resource> element.
8919
                $my_file = $xmldoc->createElement('file');
8920
                $my_file->setAttribute('href', $my_xml_file_path);
8921
                $my_resource->appendChild($my_file);
8922
8923
                // Dependency to other files - not yet supported.
8924
                $i = 1;
8925
                if ($inc_docs) {
8926
                    foreach ($inc_docs as $doc_info) {
8927
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8928
                            continue;
8929
                        }
8930
                        $my_dep = $xmldoc->createElement('resource');
8931
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8932
                        $my_dep->setAttribute('identifier', $res_id);
8933
                        $my_dep->setAttribute('type', 'webcontent');
8934
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8935
                        $my_dep_file = $xmldoc->createElement('file');
8936
                        // Check type of URL.
8937
                        if ('remote' == $doc_info[1]) {
8938
                            // Remote file. Save url as is.
8939
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8940
                            $my_dep->setAttribute('xml:base', '');
8941
                        } elseif ('local' === $doc_info[1]) {
8942
                            switch ($doc_info[2]) {
8943
                                case 'url':
8944
                                    // Local URL - save path as url for now, don't zip file.
8945
                                    $abs_path = api_get_path(SYS_PATH).
8946
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8947
                                    $current_dir = dirname($abs_path);
8948
                                    $current_dir = str_replace('\\', '/', $current_dir);
8949
                                    $file_path = realpath($abs_path);
8950
                                    $file_path = str_replace('\\', '/', $file_path);
8951
                                    $my_dep_file->setAttribute('href', $file_path);
8952
                                    $my_dep->setAttribute('xml:base', '');
8953
                                    if (false !== strstr($file_path, $main_path)) {
8954
                                        // The calculated real path is really inside Chamilo's root path.
8955
                                        // Reduce file path to what's under the DocumentRoot.
8956
                                        $replace = $file_path;
8957
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8958
                                        $destinationFile = $file_path;
8959
8960
                                        if (false !== strstr($file_path, 'upload/users')) {
8961
                                            $pos = strpos($file_path, 'my_files/');
8962
                                            if (false !== $pos) {
8963
                                                $onlyDirectory = str_replace(
8964
                                                    'upload/users/',
8965
                                                    '',
8966
                                                    substr($file_path, $pos, strlen($file_path))
8967
                                                );
8968
                                            }
8969
                                            $replace = $onlyDirectory;
8970
                                            $destinationFile = $replace;
8971
                                        }
8972
                                        $zip_files_abs[] = $file_path;
8973
                                        $link_updates[$my_file_path][] = [
8974
                                            'orig' => $doc_info[0],
8975
                                            'dest' => $destinationFile,
8976
                                            'replace' => $replace,
8977
                                        ];
8978
                                        $my_dep_file->setAttribute('href', $file_path);
8979
                                        $my_dep->setAttribute('xml:base', '');
8980
                                    } elseif (empty($file_path)) {
8981
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8982
                                        $file_path = str_replace('//', '/', $file_path);
8983
                                        if (file_exists($file_path)) {
8984
                                            // We get the relative path.
8985
                                            $file_path = substr($file_path, strlen($current_dir));
8986
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8987
                                            $link_updates[$my_file_path][] = [
8988
                                                'orig' => $doc_info[0],
8989
                                                'dest' => $file_path,
8990
                                            ];
8991
                                            $my_dep_file->setAttribute('href', $file_path);
8992
                                            $my_dep->setAttribute('xml:base', '');
8993
                                        }
8994
                                    }
8995
                                    break;
8996
                                case 'abs':
8997
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8998
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8999
                                    $my_dep->setAttribute('xml:base', '');
9000
9001
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
9002
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
9003
                                    $abs_img_path_without_subdir = $doc_info[0];
9004
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
9005
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
9006
                                    if (0 === $pos) {
9007
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
9008
                                    }
9009
9010
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
9011
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
9012
9013
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
9014
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
9015
                                    // Check if the current document is in that path.
9016
                                    if (false !== strstr($file_path, $cur_path)) {
9017
                                        $destinationFile = substr($file_path, strlen($cur_path));
9018
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
9019
9020
                                        $fileToTest = $cur_path.$my_file_path;
9021
                                        if (!empty($path_to_remove)) {
9022
                                            $fileToTest = str_replace(
9023
                                                $path_to_remove.'/',
9024
                                                $path_to_replace,
9025
                                                $cur_path.$my_file_path
9026
                                            );
9027
                                        }
9028
9029
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
9030
9031
                                        // Put the current document in the zip (this array is the array
9032
                                        // that will manage documents already in the course folder - relative).
9033
                                        $zip_files[] = $filePathNoCoursePart;
9034
                                        // Update the links to the current document in the
9035
                                        // containing document (make them relative).
9036
                                        $link_updates[$my_file_path][] = [
9037
                                            'orig' => $doc_info[0],
9038
                                            'dest' => $destinationFile,
9039
                                            'replace' => $relative_path,
9040
                                        ];
9041
9042
                                        $my_dep_file->setAttribute('href', $file_path);
9043
                                        $my_dep->setAttribute('xml:base', '');
9044
                                    } elseif (false !== strstr($file_path, $main_path)) {
9045
                                        // The calculated real path is really inside Chamilo's root path.
9046
                                        // Reduce file path to what's under the DocumentRoot.
9047
                                        $file_path = substr($file_path, strlen($root_path));
9048
                                        $zip_files_abs[] = $file_path;
9049
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9050
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9051
                                        $my_dep->setAttribute('xml:base', '');
9052
                                    } elseif (empty($file_path)) {
9053
                                        // Probably this is an image inside "/main" directory
9054
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
9055
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9056
9057
                                        if (file_exists($file_path)) {
9058
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
9059
                                                // We get the relative path.
9060
                                                $pos = strpos($file_path, 'main/default_course_document/');
9061
                                                if (false !== $pos) {
9062
                                                    $onlyDirectory = str_replace(
9063
                                                        'main/default_course_document/',
9064
                                                        '',
9065
                                                        substr($file_path, $pos, strlen($file_path))
9066
                                                    );
9067
                                                }
9068
9069
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
9070
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
9071
                                                $zip_files_abs[] = $fileAbs;
9072
                                                $link_updates[$my_file_path][] = [
9073
                                                    'orig' => $doc_info[0],
9074
                                                    'dest' => $destinationFile,
9075
                                                ];
9076
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9077
                                                $my_dep->setAttribute('xml:base', '');
9078
                                            }
9079
                                        }
9080
                                    }
9081
                                    break;
9082
                                case 'rel':
9083
                                    // Path relative to the current document.
9084
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
9085
                                    if ('..' === substr($doc_info[0], 0, 2)) {
9086
                                        // Relative path going up.
9087
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9088
                                        $current_dir = str_replace('\\', '/', $current_dir);
9089
                                        $file_path = realpath($current_dir.$doc_info[0]);
9090
                                        $file_path = str_replace('\\', '/', $file_path);
9091
                                        if (false !== strstr($file_path, $main_path)) {
9092
                                            // The calculated real path is really inside Chamilo's root path.
9093
                                            // Reduce file path to what's under the DocumentRoot.
9094
                                            $file_path = substr($file_path, strlen($root_path));
9095
                                            $zip_files_abs[] = $file_path;
9096
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9097
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9098
                                            $my_dep->setAttribute('xml:base', '');
9099
                                        }
9100
                                    } else {
9101
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9102
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
9103
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9104
                                    }
9105
                                    break;
9106
                                default:
9107
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9108
                                    $my_dep->setAttribute('xml:base', '');
9109
                                    break;
9110
                            }
9111
                        }
9112
                        $my_dep->appendChild($my_dep_file);
9113
                        $resources->appendChild($my_dep);
9114
                        $dependency = $xmldoc->createElement('dependency');
9115
                        $dependency->setAttribute('identifierref', $res_id);
9116
                        $my_resource->appendChild($dependency);
9117
                        $i++;
9118
                    }
9119
                }
9120
                $resources->appendChild($my_resource);
9121
                $zip_files[] = $my_file_path;
9122
            } else {
9123
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
9124
                switch ($item->type) {
9125
                    case TOOL_LINK:
9126
                        $my_item = $xmldoc->createElement('item');
9127
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9128
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9129
                        $my_item->setAttribute('isvisible', 'true');
9130
                        // Give a child element <title> to the <item> element.
9131
                        $my_title = $xmldoc->createElement(
9132
                            'title',
9133
                            htmlspecialchars(
9134
                                api_utf8_encode($item->get_title()),
9135
                                ENT_QUOTES,
9136
                                'UTF-8'
9137
                            )
9138
                        );
9139
                        $my_item->appendChild($my_title);
9140
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9141
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9142
                        $my_prereqs->setAttribute('type', 'aicc_script');
9143
                        $my_item->appendChild($my_prereqs);
9144
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9145
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9146
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9147
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9148
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9149
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9150
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9151
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9152
                        $my_item->appendChild($my_masteryscore);
9153
9154
                        // Attach this item to the organization element or its parent if there is one.
9155
                        if (!empty($item->parent) && 0 != $item->parent) {
9156
                            $children = $organization->childNodes;
9157
                            for ($i = 0; $i < $children->length; $i++) {
9158
                                $item_temp = $children->item($i);
9159
                                if ('item' == $item_temp->nodeName) {
9160
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9161
                                        $item_temp->appendChild($my_item);
9162
                                    }
9163
                                }
9164
                            }
9165
                        } else {
9166
                            $organization->appendChild($my_item);
9167
                        }
9168
9169
                        $my_file_path = 'link_'.$item->get_id().'.html';
9170
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9171
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9172
                        $rs = Database::query($sql);
9173
                        if ($link = Database::fetch_array($rs)) {
9174
                            $url = $link['url'];
9175
                            $title = stripslashes($link['title']);
9176
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9177
                            $my_xml_file_path = $my_file_path;
9178
                            $my_sub_dir = dirname($my_file_path);
9179
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9180
                            $my_xml_sub_dir = $my_sub_dir;
9181
                            // Give a <resource> child to the <resources> element.
9182
                            $my_resource = $xmldoc->createElement('resource');
9183
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9184
                            $my_resource->setAttribute('type', 'webcontent');
9185
                            $my_resource->setAttribute('href', $my_xml_file_path);
9186
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9187
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9188
                            // xml:base is the base directory to find the files declared in this resource.
9189
                            $my_resource->setAttribute('xml:base', '');
9190
                            // give a <file> child to the <resource> element.
9191
                            $my_file = $xmldoc->createElement('file');
9192
                            $my_file->setAttribute('href', $my_xml_file_path);
9193
                            $my_resource->appendChild($my_file);
9194
                            $resources->appendChild($my_resource);
9195
                        }
9196
                        break;
9197
                    case TOOL_QUIZ:
9198
                        $exe_id = $item->path;
9199
                        // Should be using ref when everything will be cleaned up in this regard.
9200
                        $exe = new Exercise();
9201
                        $exe->read($exe_id);
9202
                        $my_item = $xmldoc->createElement('item');
9203
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9204
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9205
                        $my_item->setAttribute('isvisible', 'true');
9206
                        // Give a child element <title> to the <item> element.
9207
                        $my_title = $xmldoc->createElement(
9208
                            'title',
9209
                            htmlspecialchars(
9210
                                api_utf8_encode($item->get_title()),
9211
                                ENT_QUOTES,
9212
                                'UTF-8'
9213
                            )
9214
                        );
9215
                        $my_item->appendChild($my_title);
9216
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9217
                        $my_item->appendChild($my_max_score);
9218
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9219
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9220
                        $my_prereqs->setAttribute('type', 'aicc_script');
9221
                        $my_item->appendChild($my_prereqs);
9222
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9223
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9224
                        $my_item->appendChild($my_masteryscore);
9225
9226
                        // Attach this item to the organization element or hits parent if there is one.
9227
                        if (!empty($item->parent) && 0 != $item->parent) {
9228
                            $children = $organization->childNodes;
9229
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9230
                            if ($possible_parent) {
9231
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9232
                                    $possible_parent->appendChild($my_item);
9233
                                }
9234
                            }
9235
                        } else {
9236
                            $organization->appendChild($my_item);
9237
                        }
9238
9239
                        // Get the path of the file(s) from the course directory root
9240
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9241
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9242
                        // Write the contents of the exported exercise into a (big) html file
9243
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9244
                        $scormExercise = new ScormExercise($exe, true);
9245
                        $contents = $scormExercise->export();
9246
9247
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9248
                        $res = file_put_contents($tmp_file_path, $contents);
9249
                        if (false === $res) {
9250
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9251
                        }
9252
                        $files_cleanup[] = $tmp_file_path;
9253
                        $my_xml_file_path = $my_file_path;
9254
                        $my_sub_dir = dirname($my_file_path);
9255
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9256
                        $my_xml_sub_dir = $my_sub_dir;
9257
                        // Give a <resource> child to the <resources> element.
9258
                        $my_resource = $xmldoc->createElement('resource');
9259
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9260
                        $my_resource->setAttribute('type', 'webcontent');
9261
                        $my_resource->setAttribute('href', $my_xml_file_path);
9262
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9263
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9264
                        // xml:base is the base directory to find the files declared in this resource.
9265
                        $my_resource->setAttribute('xml:base', '');
9266
                        // Give a <file> child to the <resource> element.
9267
                        $my_file = $xmldoc->createElement('file');
9268
                        $my_file->setAttribute('href', $my_xml_file_path);
9269
                        $my_resource->appendChild($my_file);
9270
9271
                        // Get included docs.
9272
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9273
9274
                        // Dependency to other files - not yet supported.
9275
                        $i = 1;
9276
                        foreach ($inc_docs as $doc_info) {
9277
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9278
                                continue;
9279
                            }
9280
                            $my_dep = $xmldoc->createElement('resource');
9281
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9282
                            $my_dep->setAttribute('identifier', $res_id);
9283
                            $my_dep->setAttribute('type', 'webcontent');
9284
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9285
                            $my_dep_file = $xmldoc->createElement('file');
9286
                            // Check type of URL.
9287
                            if ('remote' == $doc_info[1]) {
9288
                                // Remote file. Save url as is.
9289
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9290
                                $my_dep->setAttribute('xml:base', '');
9291
                            } elseif ('local' == $doc_info[1]) {
9292
                                switch ($doc_info[2]) {
9293
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9294
                                        // Save file but as local file (retrieve from URL).
9295
                                        $abs_path = api_get_path(SYS_PATH).
9296
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9297
                                        $current_dir = dirname($abs_path);
9298
                                        $current_dir = str_replace('\\', '/', $current_dir);
9299
                                        $file_path = realpath($abs_path);
9300
                                        $file_path = str_replace('\\', '/', $file_path);
9301
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9302
                                        $my_dep->setAttribute('xml:base', '');
9303
                                        if (false !== strstr($file_path, $main_path)) {
9304
                                            // The calculated real path is really inside the chamilo root path.
9305
                                            // Reduce file path to what's under the DocumentRoot.
9306
                                            $file_path = substr($file_path, strlen($root_path));
9307
                                            $zip_files_abs[] = $file_path;
9308
                                            $link_updates[$my_file_path][] = [
9309
                                                'orig' => $doc_info[0],
9310
                                                'dest' => 'document/'.$file_path,
9311
                                            ];
9312
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9313
                                            $my_dep->setAttribute('xml:base', '');
9314
                                        } elseif (empty($file_path)) {
9315
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9316
                                            $file_path = str_replace('//', '/', $file_path);
9317
                                            if (file_exists($file_path)) {
9318
                                                $file_path = substr($file_path, strlen($current_dir));
9319
                                                // We get the relative path.
9320
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9321
                                                $link_updates[$my_file_path][] = [
9322
                                                    'orig' => $doc_info[0],
9323
                                                    'dest' => 'document/'.$file_path,
9324
                                                ];
9325
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9326
                                                $my_dep->setAttribute('xml:base', '');
9327
                                            }
9328
                                        }
9329
                                        break;
9330
                                    case 'abs':
9331
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9332
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9333
                                        $current_dir = str_replace('\\', '/', $current_dir);
9334
                                        $file_path = realpath($doc_info[0]);
9335
                                        $file_path = str_replace('\\', '/', $file_path);
9336
                                        $my_dep_file->setAttribute('href', $file_path);
9337
                                        $my_dep->setAttribute('xml:base', '');
9338
9339
                                        if (false !== strstr($file_path, $main_path)) {
9340
                                            // The calculated real path is really inside the chamilo root path.
9341
                                            // Reduce file path to what's under the DocumentRoot.
9342
                                            $file_path = substr($file_path, strlen($root_path));
9343
                                            $zip_files_abs[] = $file_path;
9344
                                            $link_updates[$my_file_path][] = [
9345
                                                'orig' => $doc_info[0],
9346
                                                'dest' => $file_path,
9347
                                            ];
9348
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9349
                                            $my_dep->setAttribute('xml:base', '');
9350
                                        } elseif (empty($file_path)) {
9351
                                            $docSysPartPath = str_replace(
9352
                                                api_get_path(REL_COURSE_PATH),
9353
                                                '',
9354
                                                $doc_info[0]
9355
                                            );
9356
9357
                                            $docSysPartPathNoCourseCode = str_replace(
9358
                                                $_course['directory'].'/',
9359
                                                '',
9360
                                                $docSysPartPath
9361
                                            );
9362
9363
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9364
                                            if (file_exists($docSysPath)) {
9365
                                                $file_path = $docSysPartPathNoCourseCode;
9366
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9367
                                                $link_updates[$my_file_path][] = [
9368
                                                    'orig' => $doc_info[0],
9369
                                                    'dest' => $file_path,
9370
                                                ];
9371
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9372
                                                $my_dep->setAttribute('xml:base', '');
9373
                                            }
9374
                                        }
9375
                                        break;
9376
                                    case 'rel':
9377
                                        // Path relative to the current document. Save xml:base as current document's
9378
                                        // directory and save file in zip as subdir.file_path
9379
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9380
                                            // Relative path going up.
9381
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9382
                                            $current_dir = str_replace('\\', '/', $current_dir);
9383
                                            $file_path = realpath($current_dir.$doc_info[0]);
9384
                                            $file_path = str_replace('\\', '/', $file_path);
9385
                                            if (false !== strstr($file_path, $main_path)) {
9386
                                                // The calculated real path is really inside Chamilo's root path.
9387
                                                // Reduce file path to what's under the DocumentRoot.
9388
9389
                                                $file_path = substr($file_path, strlen($root_path));
9390
                                                $file_path_dest = $file_path;
9391
9392
                                                // File path is courses/CHAMILO/document/....
9393
                                                $info_file_path = explode('/', $file_path);
9394
                                                if ('courses' == $info_file_path[0]) {
9395
                                                    // Add character "/" in file path.
9396
                                                    $file_path_dest = 'document/'.$file_path;
9397
                                                }
9398
                                                $zip_files_abs[] = $file_path;
9399
9400
                                                $link_updates[$my_file_path][] = [
9401
                                                    'orig' => $doc_info[0],
9402
                                                    'dest' => $file_path_dest,
9403
                                                ];
9404
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9405
                                                $my_dep->setAttribute('xml:base', '');
9406
                                            }
9407
                                        } else {
9408
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9409
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9410
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9411
                                        }
9412
                                        break;
9413
                                    default:
9414
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9415
                                        $my_dep->setAttribute('xml:base', '');
9416
                                        break;
9417
                                }
9418
                            }
9419
                            $my_dep->appendChild($my_dep_file);
9420
                            $resources->appendChild($my_dep);
9421
                            $dependency = $xmldoc->createElement('dependency');
9422
                            $dependency->setAttribute('identifierref', $res_id);
9423
                            $my_resource->appendChild($dependency);
9424
                            $i++;
9425
                        }
9426
                        $resources->appendChild($my_resource);
9427
                        $zip_files[] = $my_file_path;
9428
                        break;
9429
                    default:
9430
                        // Get the path of the file(s) from the course directory root
9431
                        $my_file_path = 'non_exportable.html';
9432
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9433
                        $my_xml_file_path = $my_file_path;
9434
                        $my_sub_dir = dirname($my_file_path);
9435
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9436
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9437
                        $my_xml_sub_dir = $my_sub_dir;
9438
                        // Give a <resource> child to the <resources> element.
9439
                        $my_resource = $xmldoc->createElement('resource');
9440
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9441
                        $my_resource->setAttribute('type', 'webcontent');
9442
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9443
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9444
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9445
                        // xml:base is the base directory to find the files declared in this resource.
9446
                        $my_resource->setAttribute('xml:base', '');
9447
                        // Give a <file> child to the <resource> element.
9448
                        $my_file = $xmldoc->createElement('file');
9449
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9450
                        $my_resource->appendChild($my_file);
9451
                        $resources->appendChild($my_resource);
9452
                        break;
9453
                }
9454
            }
9455
        }
9456
        $organizations->appendChild($organization);
9457
        $root->appendChild($organizations);
9458
        $root->appendChild($resources);
9459
        $xmldoc->appendChild($root);
9460
9461
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9462
9463
        // then add the file to the zip, then destroy the file (this is done automatically).
9464
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9465
        foreach ($zip_files as $file_path) {
9466
            if (empty($file_path)) {
9467
                continue;
9468
            }
9469
9470
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9471
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9472
9473
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9474
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9475
            }
9476
9477
            $this->create_path($dest_file);
9478
            @copy($filePath, $dest_file);
9479
9480
            // Check if the file needs a link update.
9481
            if (in_array($file_path, array_keys($link_updates))) {
9482
                $string = file_get_contents($dest_file);
9483
                unlink($dest_file);
9484
                foreach ($link_updates[$file_path] as $old_new) {
9485
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9486
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9487
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9488
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9489
                    if ('flv' === substr($old_new['dest'], -3) &&
9490
                        'main/' === substr($old_new['dest'], 0, 5)
9491
                    ) {
9492
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9493
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9494
                        'video/' === substr($old_new['dest'], 0, 6)
9495
                    ) {
9496
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9497
                    }
9498
9499
                    // Fix to avoid problems with default_course_document
9500
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9501
                        $newDestination = $old_new['dest'];
9502
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9503
                            $newDestination = $old_new['replace'];
9504
                        }
9505
                    } else {
9506
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9507
                    }
9508
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9509
9510
                    // Add files inside the HTMLs
9511
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9512
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9513
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9514
                        copy(
9515
                            $sys_course_path.$new_path,
9516
                            $destinationFile
9517
                        );
9518
                    }
9519
                }
9520
                file_put_contents($dest_file, $string);
9521
            }
9522
9523
            if (file_exists($filePath) && $copyAll) {
9524
                $extension = $this->get_extension($filePath);
9525
                if (in_array($extension, ['html', 'html'])) {
9526
                    $containerOrigin = dirname($filePath);
9527
                    $containerDestination = dirname($dest_file);
9528
9529
                    $finder = new Finder();
9530
                    $finder->files()->in($containerOrigin)
9531
                        ->notName('*_DELETED_*')
9532
                        ->exclude('share_folder')
9533
                        ->exclude('chat_files')
9534
                        ->exclude('certificates')
9535
                    ;
9536
9537
                    if (is_dir($containerOrigin) &&
9538
                        is_dir($containerDestination)
9539
                    ) {
9540
                        $fs = new Filesystem();
9541
                        $fs->mirror(
9542
                            $containerOrigin,
9543
                            $containerDestination,
9544
                            $finder
9545
                        );
9546
                    }
9547
                }
9548
            }
9549
        }
9550
9551
        foreach ($zip_files_abs as $file_path) {
9552
            if (empty($file_path)) {
9553
                continue;
9554
            }
9555
9556
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9557
                continue;
9558
            }
9559
9560
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9561
            if (false !== strstr($file_path, 'upload/users')) {
9562
                $pos = strpos($file_path, 'my_files/');
9563
                if (false !== $pos) {
9564
                    $onlyDirectory = str_replace(
9565
                        'upload/users/',
9566
                        '',
9567
                        substr($file_path, $pos, strlen($file_path))
9568
                    );
9569
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9570
                }
9571
            }
9572
9573
            if (false !== strstr($file_path, 'default_course_document/')) {
9574
                $replace = str_replace('/main', '', $file_path);
9575
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9576
            }
9577
9578
            if (empty($dest_file)) {
9579
                continue;
9580
            }
9581
9582
            $this->create_path($dest_file);
9583
            copy($main_path.$file_path, $dest_file);
9584
            // Check if the file needs a link update.
9585
            if (in_array($file_path, array_keys($link_updates))) {
9586
                $string = file_get_contents($dest_file);
9587
                unlink($dest_file);
9588
                foreach ($link_updates[$file_path] as $old_new) {
9589
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9590
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9591
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9592
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9593
                    if ('flv' == substr($old_new['dest'], -3) &&
9594
                        'main/' == substr($old_new['dest'], 0, 5)
9595
                    ) {
9596
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9597
                    }
9598
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9599
                }
9600
                file_put_contents($dest_file, $string);
9601
            }
9602
        }
9603
9604
        if (is_array($links_to_create)) {
9605
            foreach ($links_to_create as $file => $link) {
9606
                $content = '<!DOCTYPE html><head>
9607
                            <meta charset="'.api_get_language_isocode().'" />
9608
                            <title>'.$link['title'].'</title>
9609
                            </head>
9610
                            <body dir="'.api_get_text_direction().'">
9611
                            <div style="text-align:center">
9612
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9613
                            </body>
9614
                            </html>';
9615
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9616
            }
9617
        }
9618
9619
        // Add non exportable message explanation.
9620
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9621
        $file_content = '<!DOCTYPE html><head>
9622
                        <meta charset="'.api_get_language_isocode().'" />
9623
                        <title>'.$lang_not_exportable.'</title>
9624
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9625
                        </head>
9626
                        <body dir="'.api_get_text_direction().'">';
9627
        $file_content .=
9628
            <<<EOD
9629
                    <style>
9630
            .error-message {
9631
                font-family: arial, verdana, helvetica, sans-serif;
9632
                border-width: 1px;
9633
                border-style: solid;
9634
                left: 50%;
9635
                margin: 10px auto;
9636
                min-height: 30px;
9637
                padding: 5px;
9638
                right: 50%;
9639
                width: 500px;
9640
                background-color: #FFD1D1;
9641
                border-color: #FF0000;
9642
                color: #000;
9643
            }
9644
        </style>
9645
    <body>
9646
        <div class="error-message">
9647
            $lang_not_exportable
9648
        </div>
9649
    </body>
9650
</html>
9651
EOD;
9652
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9653
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9654
        }
9655
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9656
9657
        // Add the extra files that go along with a SCORM package.
9658
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9659
9660
        $fs = new Filesystem();
9661
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9662
9663
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9664
        $manifest = @$xmldoc->saveXML();
9665
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9666
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9667
        $zip_folder->add(
9668
            $archivePath.'/'.$temp_dir_short,
9669
            PCLZIP_OPT_REMOVE_PATH,
9670
            $archivePath.'/'.$temp_dir_short.'/'
9671
        );
9672
9673
        // Clean possible temporary files.
9674
        foreach ($files_cleanup as $file) {
9675
            $res = unlink($file);
9676
            if (false === $res) {
9677
                error_log(
9678
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9679
                    0
9680
                );
9681
            }
9682
        }
9683
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9684
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9685
    }
9686
9687
    /**
9688
     * @param int $lp_id
9689
     *
9690
     * @return bool
9691
     */
9692
    public function scorm_export_to_pdf($lp_id)
9693
    {
9694
        $lp_id = (int) $lp_id;
9695
        $files_to_export = [];
9696
9697
        $sessionId = api_get_session_id();
9698
        $course_data = api_get_course_info($this->cc);
9699
9700
        if (!empty($course_data)) {
9701
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9702
            $list = self::get_flat_ordered_items_list($lp_id);
9703
            if (!empty($list)) {
9704
                foreach ($list as $item_id) {
9705
                    $item = $this->items[$item_id];
9706
                    switch ($item->type) {
9707
                        case 'document':
9708
                            // Getting documents from a LP with chamilo documents
9709
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9710
                            // Try loading document from the base course.
9711
                            if (empty($file_data) && !empty($sessionId)) {
9712
                                $file_data = DocumentManager::get_document_data_by_id(
9713
                                    $item->path,
9714
                                    $this->cc,
9715
                                    false,
9716
                                    0
9717
                                );
9718
                            }
9719
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9720
                            if (file_exists($file_path)) {
9721
                                $files_to_export[] = [
9722
                                    'title' => $item->get_title(),
9723
                                    'path' => $file_path,
9724
                                ];
9725
                            }
9726
                            break;
9727
                        case 'asset': //commes from a scorm package generated by chamilo
9728
                        case 'sco':
9729
                            $file_path = $scorm_path.'/'.$item->path;
9730
                            if (file_exists($file_path)) {
9731
                                $files_to_export[] = [
9732
                                    'title' => $item->get_title(),
9733
                                    'path' => $file_path,
9734
                                ];
9735
                            }
9736
                            break;
9737
                        case 'dir':
9738
                            $files_to_export[] = [
9739
                                'title' => $item->get_title(),
9740
                                'path' => null,
9741
                            ];
9742
                            break;
9743
                    }
9744
                }
9745
            }
9746
9747
            $pdf = new PDF();
9748
            $result = $pdf->html_to_pdf(
9749
                $files_to_export,
9750
                $this->name,
9751
                $this->cc,
9752
                true,
9753
                true,
9754
                true,
9755
                $this->get_name()
9756
            );
9757
9758
            return $result;
9759
        }
9760
9761
        return false;
9762
    }
9763
9764
    /**
9765
     * Temp function to be moved in main_api or the best place around for this.
9766
     * Creates a file path if it doesn't exist.
9767
     *
9768
     * @param string $path
9769
     */
9770
    public function create_path($path)
9771
    {
9772
        $path_bits = explode('/', dirname($path));
9773
9774
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9775
        $path_built = IS_WINDOWS_OS ? '' : '/';
9776
        foreach ($path_bits as $bit) {
9777
            if (!empty($bit)) {
9778
                $new_path = $path_built.$bit;
9779
                if (is_dir($new_path)) {
9780
                    $path_built = $new_path.'/';
9781
                } else {
9782
                    mkdir($new_path, api_get_permissions_for_new_directories());
9783
                    $path_built = $new_path.'/';
9784
                }
9785
            }
9786
        }
9787
    }
9788
9789
    /**
9790
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
9791
     *
9792
     * @return bool The results of the unlink function, or false if there was no image to start with
9793
     */
9794
    public function delete_lp_image()
9795
    {
9796
        $img = $this->get_preview_image();
9797
        if ('' != $img) {
9798
            $del_file = $this->get_preview_image_path(null, 'sys');
9799
            if (isset($del_file) && file_exists($del_file)) {
9800
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
9801
                if (file_exists($del_file_2)) {
9802
                    unlink($del_file_2);
9803
                }
9804
                $this->set_preview_image('');
9805
9806
                return @unlink($del_file);
9807
            }
9808
        }
9809
9810
        return false;
9811
    }
9812
9813
    /**
9814
     * Uploads an author image to the upload/learning_path/images directory.
9815
     *
9816
     * @param array    The image array, coming from the $_FILES superglobal
9817
     *
9818
     * @return bool True on success, false on error
9819
     */
9820
    public function upload_image($image_array)
9821
    {
9822
        if (!empty($image_array['name'])) {
9823
            $upload_ok = process_uploaded_file($image_array);
9824
            $has_attachment = true;
9825
        }
9826
9827
        if ($upload_ok && $has_attachment) {
9828
            $courseDir = api_get_course_path().'/upload/learning_path/images';
9829
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
9830
            $updir = $sys_course_path.$courseDir;
9831
            // Try to add an extension to the file if it hasn't one.
9832
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
9833
9834
            if (filter_extension($new_file_name)) {
9835
                $file_extension = explode('.', $image_array['name']);
9836
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
9837
                $filename = uniqid('');
9838
                $new_file_name = $filename.'.'.$file_extension;
9839
                $new_path = $updir.'/'.$new_file_name;
9840
9841
                // Resize the image.
9842
                $temp = new Image($image_array['tmp_name']);
9843
                $temp->resize(104);
9844
                $result = $temp->send_image($new_path);
9845
9846
                // Storing the image filename.
9847
                if ($result) {
9848
                    $this->set_preview_image($new_file_name);
9849
9850
                    //Resize to 64px to use on course homepage
9851
                    $temp->resize(64);
9852
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
9853
9854
                    return true;
9855
                }
9856
            }
9857
        }
9858
9859
        return false;
9860
    }
9861
9862
    /**
9863
     * @param int    $lp_id
9864
     * @param string $status
9865
     */
9866
    public function set_autolaunch($lp_id, $status)
9867
    {
9868
        $course_id = api_get_course_int_id();
9869
        $lp_id = (int) $lp_id;
9870
        $status = (int) $status;
9871
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9872
9873
        // Setting everything to autolaunch = 0
9874
        $attributes['autolaunch'] = 0;
9875
        $where = [
9876
            'session_id = ? AND c_id = ? ' => [
9877
                api_get_session_id(),
9878
                $course_id,
9879
            ],
9880
        ];
9881
        Database::update($lp_table, $attributes, $where);
9882
        if (1 == $status) {
9883
            //Setting my lp_id to autolaunch = 1
9884
            $attributes['autolaunch'] = 1;
9885
            $where = [
9886
                'iid = ? AND session_id = ? AND c_id = ?' => [
9887
                    $lp_id,
9888
                    api_get_session_id(),
9889
                    $course_id,
9890
                ],
9891
            ];
9892
            Database::update($lp_table, $attributes, $where);
9893
        }
9894
    }
9895
9896
    /**
9897
     * Gets previous_item_id for the next element of the lp_item table.
9898
     *
9899
     * @author Isaac flores paz
9900
     *
9901
     * @return int Previous item ID
9902
     */
9903
    public function select_previous_item_id()
9904
    {
9905
        $course_id = api_get_course_int_id();
9906
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9907
9908
        // Get the max order of the items
9909
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9910
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9911
        $rs_max_order = Database::query($sql);
9912
        $row_max_order = Database::fetch_object($rs_max_order);
9913
        $max_order = $row_max_order->display_order;
9914
        // Get the previous item ID
9915
        $sql = "SELECT iid as previous FROM $table_lp_item
9916
                WHERE
9917
                    c_id = $course_id AND
9918
                    lp_id = ".$this->lp_id." AND
9919
                    display_order = '$max_order' ";
9920
        $rs_max = Database::query($sql);
9921
        $row_max = Database::fetch_object($rs_max);
9922
9923
        // Return the previous item ID
9924
        return $row_max->previous;
9925
    }
9926
9927
    /**
9928
     * Copies an LP.
9929
     */
9930
    public function copy()
9931
    {
9932
        // Course builder
9933
        $cb = new CourseBuilder();
9934
9935
        //Setting tools that will be copied
9936
        $cb->set_tools_to_build(['learnpaths']);
9937
9938
        //Setting elements that will be copied
9939
        $cb->set_tools_specific_id_list(
9940
            ['learnpaths' => [$this->lp_id]]
9941
        );
9942
9943
        $course = $cb->build();
9944
9945
        //Course restorer
9946
        $course_restorer = new CourseRestorer($course);
9947
        $course_restorer->set_add_text_in_items(true);
9948
        $course_restorer->set_tool_copy_settings(
9949
            ['learnpaths' => ['reset_dates' => true]]
9950
        );
9951
        $course_restorer->restore(
9952
            api_get_course_id(),
9953
            api_get_session_id(),
9954
            false,
9955
            false
9956
        );
9957
    }
9958
9959
    /**
9960
     * Verify document size.
9961
     *
9962
     * @param string $s
9963
     *
9964
     * @return bool
9965
     */
9966
    public static function verify_document_size($s)
9967
    {
9968
        $post_max = ini_get('post_max_size');
9969
        if ('M' == substr($post_max, -1, 1)) {
9970
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9971
        } elseif ('G' == substr($post_max, -1, 1)) {
9972
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9973
        }
9974
        $upl_max = ini_get('upload_max_filesize');
9975
        if ('M' == substr($upl_max, -1, 1)) {
9976
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9977
        } elseif ('G' == substr($upl_max, -1, 1)) {
9978
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9979
        }
9980
9981
        $repo = Container::getDocumentRepository();
9982
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9983
9984
        $course_max_space = DocumentManager::get_course_quota();
9985
        $total_size = filesize($s) + $documents_total_space;
9986
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9987
            return true;
9988
        }
9989
9990
        return false;
9991
    }
9992
9993
    /**
9994
     * Clear LP prerequisites.
9995
     */
9996
    public function clear_prerequisites()
9997
    {
9998
        $course_id = $this->get_course_int_id();
9999
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10000
        $lp_id = $this->get_id();
10001
        // Cleaning prerequisites
10002
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
10003
                WHERE c_id = $course_id AND lp_id = $lp_id";
10004
        Database::query($sql);
10005
10006
        // Cleaning mastery score for exercises
10007
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
10008
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
10009
        Database::query($sql);
10010
    }
10011
10012
    public function set_previous_step_as_prerequisite_for_all_items()
10013
    {
10014
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10015
        $course_id = $this->get_course_int_id();
10016
        $lp_id = $this->get_id();
10017
10018
        if (!empty($this->items)) {
10019
            $previous_item_id = null;
10020
            $previous_item_max = 0;
10021
            $previous_item_type = null;
10022
            $last_item_not_dir = null;
10023
            $last_item_not_dir_type = null;
10024
            $last_item_not_dir_max = null;
10025
10026
            foreach ($this->ordered_items as $itemId) {
10027
                $item = $this->getItem($itemId);
10028
                // if there was a previous item... (otherwise jump to set it)
10029
                if (!empty($previous_item_id)) {
10030
                    $current_item_id = $item->get_id(); //save current id
10031
                    if ('dir' != $item->get_type()) {
10032
                        // Current item is not a folder, so it qualifies to get a prerequisites
10033
                        if ('quiz' == $last_item_not_dir_type) {
10034
                            // if previous is quiz, mark its max score as default score to be achieved
10035
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
10036
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
10037
                            Database::query($sql);
10038
                        }
10039
                        // now simply update the prerequisite to set it to the last non-chapter item
10040
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
10041
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
10042
                        Database::query($sql);
10043
                        // record item as 'non-chapter' reference
10044
                        $last_item_not_dir = $item->get_id();
10045
                        $last_item_not_dir_type = $item->get_type();
10046
                        $last_item_not_dir_max = $item->get_max();
10047
                    }
10048
                } else {
10049
                    if ('dir' != $item->get_type()) {
10050
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
10051
                        $last_item_not_dir = $item->get_id();
10052
                        $last_item_not_dir_type = $item->get_type();
10053
                        $last_item_not_dir_max = $item->get_max();
10054
                    }
10055
                }
10056
                // Saving the item as "previous item" for the next loop
10057
                $previous_item_id = $item->get_id();
10058
                $previous_item_max = $item->get_max();
10059
                $previous_item_type = $item->get_type();
10060
            }
10061
        }
10062
    }
10063
10064
    /**
10065
     * @param array $params
10066
     *
10067
     * @return int
10068
     */
10069
    public static function createCategory($params)
10070
    {
10071
        $courseEntity = api_get_course_entity(api_get_course_int_id());
10072
10073
        $item = new CLpCategory();
10074
        $item
10075
            ->setName($params['name'])
10076
            ->setCId($params['c_id'])
10077
            ->setParent($courseEntity)
10078
            ->addCourseLink($courseEntity, api_get_session_entity())
10079
        ;
10080
10081
        $repo = Container::getLpCategoryRepository();
10082
        $em = $repo->getEntityManager();
10083
        $em->persist($item);
10084
        $em->flush();
10085
10086
        /*api_item_property_update(
10087
            api_get_course_info(),
10088
            TOOL_LEARNPATH_CATEGORY,
10089
            $item->getId(),
10090
            'visible',
10091
            api_get_user_id()
10092
        );*/
10093
10094
        return $item->getId();
10095
    }
10096
10097
    /**
10098
     * @param array $params
10099
     */
10100
    public static function updateCategory($params)
10101
    {
10102
        $em = Database::getManager();
10103
        /** @var CLpCategory $item */
10104
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
10105
        if ($item) {
10106
            $item->setName($params['name']);
10107
            $em->persist($item);
10108
            $em->flush();
10109
        }
10110
    }
10111
10112
    /**
10113
     * @param int $id
10114
     */
10115
    public static function moveUpCategory($id)
10116
    {
10117
        $id = (int) $id;
10118
        $em = Database::getManager();
10119
        /** @var CLpCategory $item */
10120
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10121
        if ($item) {
10122
            $position = $item->getPosition() - 1;
10123
            $item->setPosition($position);
10124
            $em->persist($item);
10125
            $em->flush();
10126
        }
10127
    }
10128
10129
    /**
10130
     * @param int $id
10131
     */
10132
    public static function moveDownCategory($id)
10133
    {
10134
        $id = (int) $id;
10135
        $em = Database::getManager();
10136
        /** @var CLpCategory $item */
10137
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10138
        if ($item) {
10139
            $position = $item->getPosition() + 1;
10140
            $item->setPosition($position);
10141
            $em->persist($item);
10142
            $em->flush();
10143
        }
10144
    }
10145
10146
    public static function getLpList($courseId)
10147
    {
10148
        $table = Database::get_course_table(TABLE_LP_MAIN);
10149
        $courseId = (int) $courseId;
10150
10151
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
10152
        $result = Database::query($sql);
10153
10154
        return Database::store_result($result, 'ASSOC');
10155
    }
10156
10157
    /**
10158
     * @param int $courseId
10159
     *
10160
     * @throws \Doctrine\ORM\Query\QueryException
10161
     *
10162
     * @return int|mixed
10163
     */
10164
    public static function getCountCategories($courseId)
10165
    {
10166
        if (empty($courseId)) {
10167
            return 0;
10168
        }
10169
        $em = Database::getManager();
10170
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10171
        $query->setParameter('id', $courseId);
10172
10173
        return $query->getSingleScalarResult();
10174
    }
10175
10176
    /**
10177
     * @param int $courseId
10178
     *
10179
     * @return mixed
10180
     */
10181
    public static function getCategories($courseId)
10182
    {
10183
        $em = Database::getManager();
10184
10185
        // Using doctrine extensions
10186
        /** @var SortableRepository $repo */
10187
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10188
10189
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
10190
    }
10191
10192
    public static function getCategorySessionId($id)
10193
    {
10194
        if (false === api_get_configuration_value('allow_session_lp_category')) {
10195
            return 0;
10196
        }
10197
10198
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
10199
        $id = (int) $id;
10200
10201
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
10202
        $result = Database::query($sql);
10203
        $result = Database::fetch_array($result, 'ASSOC');
10204
10205
        if ($result) {
10206
            return (int) $result['session_id'];
10207
        }
10208
10209
        return 0;
10210
    }
10211
10212
    /**
10213
     * @param int $id
10214
     *
10215
     * @return CLpCategory
10216
     */
10217
    public static function getCategory($id)
10218
    {
10219
        $id = (int) $id;
10220
        $em = Database::getManager();
10221
10222
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
10223
    }
10224
10225
    /**
10226
     * @param int $courseId
10227
     *
10228
     * @return array
10229
     */
10230
    public static function getCategoryByCourse($courseId)
10231
    {
10232
        $em = Database::getManager();
10233
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10234
            ['cId' => $courseId]
10235
        );
10236
10237
        return $items;
10238
    }
10239
10240
    /**
10241
     * @param int $id
10242
     *
10243
     * @return mixed
10244
     */
10245
    public static function deleteCategory($id)
10246
    {
10247
        $em = Database::getManager();
10248
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10249
        if ($item) {
10250
            $courseId = $item->getCId();
10251
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
10252
            $query->setParameter('id', $courseId);
10253
            $query->setParameter('catId', $item->getId());
10254
            $lps = $query->getResult();
10255
10256
            // Setting category = 0.
10257
            if ($lps) {
10258
                foreach ($lps as $lpItem) {
10259
                    $lpItem->setCategoryId(0);
10260
                }
10261
            }
10262
10263
            // Removing category.
10264
            $em->remove($item);
10265
            $em->flush();
10266
10267
            $courseInfo = api_get_course_info_by_id($courseId);
10268
            $sessionId = api_get_session_id();
10269
10270
            // Delete link tool
10271
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
10272
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
10273
            // Delete tools
10274
            $sql = "DELETE FROM $tbl_tool
10275
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
10276
            Database::query($sql);*/
10277
10278
            return true;
10279
        }
10280
10281
        return false;
10282
    }
10283
10284
    /**
10285
     * @param int  $courseId
10286
     * @param bool $addSelectOption
10287
     *
10288
     * @return mixed
10289
     */
10290
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10291
    {
10292
        $items = self::getCategoryByCourse($courseId);
10293
        $cats = [];
10294
        if ($addSelectOption) {
10295
            $cats = [get_lang('Select a category')];
10296
        }
10297
10298
        if (!empty($items)) {
10299
            foreach ($items as $cat) {
10300
                $cats[$cat->getId()] = $cat->getName();
10301
            }
10302
        }
10303
10304
        return $cats;
10305
    }
10306
10307
    /**
10308
     * @param string $courseCode
10309
     * @param int    $lpId
10310
     * @param int    $user_id
10311
     *
10312
     * @return learnpath
10313
     */
10314
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10315
    {
10316
        $debug = 0;
10317
        $learnPath = null;
10318
        $lpObject = Session::read('lpobject');
10319
        if (null !== $lpObject) {
10320
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10321
            if ($debug) {
10322
                error_log('getLpFromSession: unserialize');
10323
                error_log('------getLpFromSession------');
10324
                error_log('------unserialize------');
10325
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10326
                error_log("api_get_sessionid: ".api_get_session_id());
10327
            }
10328
        }
10329
10330
        if (!is_object($learnPath)) {
10331
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10332
            if ($debug) {
10333
                error_log('------getLpFromSession------');
10334
                error_log('getLpFromSession: create new learnpath');
10335
                error_log("create new LP with $courseCode - $lpId - $user_id");
10336
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10337
                error_log("api_get_sessionid: ".api_get_session_id());
10338
            }
10339
        }
10340
10341
        return $learnPath;
10342
    }
10343
10344
    /**
10345
     * @param int $itemId
10346
     *
10347
     * @return learnpathItem|false
10348
     */
10349
    public function getItem($itemId)
10350
    {
10351
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10352
            return $this->items[$itemId];
10353
        }
10354
10355
        return false;
10356
    }
10357
10358
    /**
10359
     * @return int
10360
     */
10361
    public function getCurrentAttempt()
10362
    {
10363
        $attempt = $this->getItem($this->get_current_item_id());
10364
        if ($attempt) {
10365
            return $attempt->get_attempt_id();
10366
        }
10367
10368
        return 0;
10369
    }
10370
10371
    /**
10372
     * @return int
10373
     */
10374
    public function getCategoryId()
10375
    {
10376
        return (int) $this->categoryId;
10377
    }
10378
10379
    /**
10380
     * @param int $categoryId
10381
     *
10382
     * @return bool
10383
     */
10384
    public function setCategoryId($categoryId)
10385
    {
10386
        $this->categoryId = (int) $categoryId;
10387
        $table = Database::get_course_table(TABLE_LP_MAIN);
10388
        $lp_id = $this->get_id();
10389
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10390
                WHERE iid = $lp_id";
10391
        Database::query($sql);
10392
10393
        return true;
10394
    }
10395
10396
    /**
10397
     * Get whether this is a learning path with the possibility to subscribe
10398
     * users or not.
10399
     *
10400
     * @return int
10401
     */
10402
    public function getSubscribeUsers()
10403
    {
10404
        return $this->subscribeUsers;
10405
    }
10406
10407
    /**
10408
     * Set whether this is a learning path with the possibility to subscribe
10409
     * users or not.
10410
     *
10411
     * @param int $value (0 = false, 1 = true)
10412
     *
10413
     * @return bool
10414
     */
10415
    public function setSubscribeUsers($value)
10416
    {
10417
        $this->subscribeUsers = (int) $value;
10418
        $table = Database::get_course_table(TABLE_LP_MAIN);
10419
        $lp_id = $this->get_id();
10420
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10421
                WHERE iid = $lp_id";
10422
        Database::query($sql);
10423
10424
        return true;
10425
    }
10426
10427
    /**
10428
     * Calculate the count of stars for a user in this LP
10429
     * This calculation is based on the following rules:
10430
     * - the student gets one star when he gets to 50% of the learning path
10431
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10432
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10433
     * - the student gets the final star when the score for the *last* test is >= 80%.
10434
     *
10435
     * @param int $sessionId Optional. The session ID
10436
     *
10437
     * @return int The count of stars
10438
     */
10439
    public function getCalculateStars($sessionId = 0)
10440
    {
10441
        $stars = 0;
10442
        $progress = self::getProgress(
10443
            $this->lp_id,
10444
            $this->user_id,
10445
            $this->course_int_id,
10446
            $sessionId
10447
        );
10448
10449
        if ($progress >= 50) {
10450
            $stars++;
10451
        }
10452
10453
        // Calculate stars chapters evaluation
10454
        $exercisesItems = $this->getExercisesItems();
10455
10456
        if (!empty($exercisesItems)) {
10457
            $totalResult = 0;
10458
10459
            foreach ($exercisesItems as $exerciseItem) {
10460
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10461
                    $this->user_id,
10462
                    $exerciseItem->path,
10463
                    $this->course_int_id,
10464
                    $sessionId,
10465
                    $this->lp_id,
10466
                    $exerciseItem->db_id
10467
                );
10468
10469
                $exerciseResultInfo = end($exerciseResultInfo);
10470
10471
                if (!$exerciseResultInfo) {
10472
                    continue;
10473
                }
10474
10475
                if (!empty($exerciseResultInfo['max_score'])) {
10476
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10477
                } else {
10478
                    $exerciseResult = 0;
10479
                }
10480
                $totalResult += $exerciseResult;
10481
            }
10482
10483
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10484
10485
            if ($totalExerciseAverage >= 50) {
10486
                $stars++;
10487
            }
10488
10489
            if ($totalExerciseAverage >= 80) {
10490
                $stars++;
10491
            }
10492
        }
10493
10494
        // Calculate star for final evaluation
10495
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10496
10497
        if (!empty($finalEvaluationItem)) {
10498
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10499
                $this->user_id,
10500
                $finalEvaluationItem->path,
10501
                $this->course_int_id,
10502
                $sessionId,
10503
                $this->lp_id,
10504
                $finalEvaluationItem->db_id
10505
            );
10506
10507
            $evaluationResultInfo = end($evaluationResultInfo);
10508
10509
            if ($evaluationResultInfo) {
10510
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10511
10512
                if ($evaluationResult >= 80) {
10513
                    $stars++;
10514
                }
10515
            }
10516
        }
10517
10518
        return $stars;
10519
    }
10520
10521
    /**
10522
     * Get the items of exercise type.
10523
     *
10524
     * @return array The items. Otherwise return false
10525
     */
10526
    public function getExercisesItems()
10527
    {
10528
        $exercises = [];
10529
        foreach ($this->items as $item) {
10530
            if ('quiz' != $item->type) {
10531
                continue;
10532
            }
10533
            $exercises[] = $item;
10534
        }
10535
10536
        array_pop($exercises);
10537
10538
        return $exercises;
10539
    }
10540
10541
    /**
10542
     * Get the item of exercise type (evaluation type).
10543
     *
10544
     * @return array The final evaluation. Otherwise return false
10545
     */
10546
    public function getFinalEvaluationItem()
10547
    {
10548
        $exercises = [];
10549
        foreach ($this->items as $item) {
10550
            if (TOOL_QUIZ !== $item->type) {
10551
                continue;
10552
            }
10553
10554
            $exercises[] = $item;
10555
        }
10556
10557
        return array_pop($exercises);
10558
    }
10559
10560
    /**
10561
     * Calculate the total points achieved for the current user in this learning path.
10562
     *
10563
     * @param int $sessionId Optional. The session Id
10564
     *
10565
     * @return int
10566
     */
10567
    public function getCalculateScore($sessionId = 0)
10568
    {
10569
        // Calculate stars chapters evaluation
10570
        $exercisesItems = $this->getExercisesItems();
10571
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10572
        $totalExercisesResult = 0;
10573
        $totalEvaluationResult = 0;
10574
10575
        if (false !== $exercisesItems) {
10576
            foreach ($exercisesItems as $exerciseItem) {
10577
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10578
                    $this->user_id,
10579
                    $exerciseItem->path,
10580
                    $this->course_int_id,
10581
                    $sessionId,
10582
                    $this->lp_id,
10583
                    $exerciseItem->db_id
10584
                );
10585
10586
                $exerciseResultInfo = end($exerciseResultInfo);
10587
10588
                if (!$exerciseResultInfo) {
10589
                    continue;
10590
                }
10591
10592
                $totalExercisesResult += $exerciseResultInfo['score'];
10593
            }
10594
        }
10595
10596
        if (!empty($finalEvaluationItem)) {
10597
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10598
                $this->user_id,
10599
                $finalEvaluationItem->path,
10600
                $this->course_int_id,
10601
                $sessionId,
10602
                $this->lp_id,
10603
                $finalEvaluationItem->db_id
10604
            );
10605
10606
            $evaluationResultInfo = end($evaluationResultInfo);
10607
10608
            if ($evaluationResultInfo) {
10609
                $totalEvaluationResult += $evaluationResultInfo['score'];
10610
            }
10611
        }
10612
10613
        return $totalExercisesResult + $totalEvaluationResult;
10614
    }
10615
10616
    /**
10617
     * Check if URL is not allowed to be show in a iframe.
10618
     *
10619
     * @param string $src
10620
     *
10621
     * @return string
10622
     */
10623
    public function fixBlockedLinks($src)
10624
    {
10625
        $urlInfo = parse_url($src);
10626
10627
        $platformProtocol = 'https';
10628
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10629
            $platformProtocol = 'http';
10630
        }
10631
10632
        $protocolFixApplied = false;
10633
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10634
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10635
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10636
10637
        if ($platformProtocol != $scheme) {
10638
            Session::write('x_frame_source', $src);
10639
            $src = 'blank.php?error=x_frames_options';
10640
            $protocolFixApplied = true;
10641
        }
10642
10643
        if (false == $protocolFixApplied) {
10644
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10645
                // Check X-Frame-Options
10646
                $ch = curl_init();
10647
                $options = [
10648
                    CURLOPT_URL => $src,
10649
                    CURLOPT_RETURNTRANSFER => true,
10650
                    CURLOPT_HEADER => true,
10651
                    CURLOPT_FOLLOWLOCATION => true,
10652
                    CURLOPT_ENCODING => "",
10653
                    CURLOPT_AUTOREFERER => true,
10654
                    CURLOPT_CONNECTTIMEOUT => 120,
10655
                    CURLOPT_TIMEOUT => 120,
10656
                    CURLOPT_MAXREDIRS => 10,
10657
                ];
10658
10659
                $proxySettings = api_get_configuration_value('proxy_settings');
10660
                if (!empty($proxySettings) &&
10661
                    isset($proxySettings['curl_setopt_array'])
10662
                ) {
10663
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10664
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10665
                }
10666
10667
                curl_setopt_array($ch, $options);
10668
                $response = curl_exec($ch);
10669
                $httpCode = curl_getinfo($ch);
10670
                $headers = substr($response, 0, $httpCode['header_size']);
10671
10672
                $error = false;
10673
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10674
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10675
                ) {
10676
                    $error = true;
10677
                }
10678
10679
                if ($error) {
10680
                    Session::write('x_frame_source', $src);
10681
                    $src = 'blank.php?error=x_frames_options';
10682
                }
10683
            }
10684
        }
10685
10686
        return $src;
10687
    }
10688
10689
    /**
10690
     * Check if this LP has a created forum in the basis course.
10691
     *
10692
     * @return bool
10693
     */
10694
    public function lpHasForum()
10695
    {
10696
        $forumTable = Database::get_course_table(TABLE_FORUM);
10697
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10698
10699
        $fakeFrom = "
10700
            $forumTable f
10701
            INNER JOIN $itemProperty ip
10702
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10703
        ";
10704
10705
        $resultData = Database::select(
10706
            'COUNT(f.iid) AS qty',
10707
            $fakeFrom,
10708
            [
10709
                'where' => [
10710
                    'ip.visibility != ? AND ' => 2,
10711
                    'ip.tool = ? AND ' => TOOL_FORUM,
10712
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10713
                    'f.lp_id = ?' => intval($this->lp_id),
10714
                ],
10715
            ],
10716
            'first'
10717
        );
10718
10719
        return $resultData['qty'] > 0;
10720
    }
10721
10722
    /**
10723
     * Get the forum for this learning path.
10724
     *
10725
     * @param int $sessionId
10726
     *
10727
     * @return array
10728
     */
10729
    public function getForum($sessionId = 0)
10730
    {
10731
        $repo = Container::getForumRepository();
10732
10733
        $course = api_get_course_entity();
10734
        $session = api_get_session_entity($sessionId);
10735
        $qb = $repo->getResourcesByCourse($course, $session);
10736
10737
        return $qb->getQuery()->getResult();
10738
    }
10739
10740
    /**
10741
     * Create a forum for this learning path.
10742
     *
10743
     * @param int $forumCategoryId
10744
     *
10745
     * @return int The forum ID if was created. Otherwise return false
10746
     */
10747
    public function createForum($forumCategoryId)
10748
    {
10749
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10750
10751
        return store_forum(
10752
            [
10753
                'lp_id' => $this->lp_id,
10754
                'forum_title' => $this->name,
10755
                'forum_comment' => null,
10756
                'forum_category' => (int) $forumCategoryId,
10757
                'students_can_edit_group' => ['students_can_edit' => 0],
10758
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10759
                'default_view_type_group' => ['default_view_type' => 'flat'],
10760
                'group_forum' => 0,
10761
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10762
            ],
10763
            [],
10764
            true
10765
        );
10766
    }
10767
10768
    /**
10769
     * Get the LP Final Item form.
10770
     *
10771
     * @throws Exception
10772
     * @throws HTML_QuickForm_Error
10773
     *
10774
     * @return string
10775
     */
10776
    public function getFinalItemForm()
10777
    {
10778
        $finalItem = $this->getFinalItem();
10779
        $title = '';
10780
10781
        if ($finalItem) {
10782
            $title = $finalItem->get_title();
10783
            $buttonText = get_lang('Save');
10784
            $content = $this->getSavedFinalItem();
10785
        } else {
10786
            $buttonText = get_lang('Add this document to the course');
10787
            $content = $this->getFinalItemTemplate();
10788
        }
10789
10790
        $editorConfig = [
10791
            'ToolbarSet' => 'LearningPathDocuments',
10792
            'Width' => '100%',
10793
            'Height' => '500',
10794
            'FullPage' => true,
10795
//            'CreateDocumentDir' => $relative_prefix,
10796
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10797
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10798
        ];
10799
10800
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10801
            'type' => 'document',
10802
            'lp_id' => $this->lp_id,
10803
        ]);
10804
10805
        $form = new FormValidator('final_item', 'POST', $url);
10806
        $form->addText('title', get_lang('Title'));
10807
        $form->addButtonSave($buttonText);
10808
        $form->addHtml(
10809
            Display::return_message(
10810
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10811
                'normal',
10812
                false
10813
            )
10814
        );
10815
10816
        $renderer = $form->defaultRenderer();
10817
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10818
10819
        $form->addHtmlEditor(
10820
            'content_lp_certificate',
10821
            null,
10822
            true,
10823
            false,
10824
            $editorConfig,
10825
            true
10826
        );
10827
        $form->addHidden('action', 'add_final_item');
10828
        $form->addHidden('path', Session::read('pathItem'));
10829
        $form->addHidden('previous', $this->get_last());
10830
        $form->setDefaults(
10831
            ['title' => $title, 'content_lp_certificate' => $content]
10832
        );
10833
10834
        if ($form->validate()) {
10835
            $values = $form->exportValues();
10836
            $lastItemId = $this->getLastInFirstLevel();
10837
10838
            if (!$finalItem) {
10839
                $documentId = $this->create_document(
10840
                    $this->course_info,
10841
                    $values['content_lp_certificate'],
10842
                    $values['title']
10843
                );
10844
                $this->add_item(
10845
                    0,
10846
                    $lastItemId,
10847
                    'final_item',
10848
                    $documentId,
10849
                    $values['title'],
10850
                    ''
10851
                );
10852
10853
                Display::addFlash(
10854
                    Display::return_message(get_lang('Added'))
10855
                );
10856
            } else {
10857
                $this->edit_document($this->course_info);
10858
            }
10859
        }
10860
10861
        return $form->returnForm();
10862
    }
10863
10864
    /**
10865
     * Check if the current lp item is first, both, last or none from lp list.
10866
     *
10867
     * @param int $currentItemId
10868
     *
10869
     * @return string
10870
     */
10871
    public function isFirstOrLastItem($currentItemId)
10872
    {
10873
        $lpItemId = [];
10874
        $typeListNotToVerify = self::getChapterTypes();
10875
10876
        // Using get_toc() function instead $this->items because returns the correct order of the items
10877
        foreach ($this->get_toc() as $item) {
10878
            if (!in_array($item['type'], $typeListNotToVerify)) {
10879
                $lpItemId[] = $item['id'];
10880
            }
10881
        }
10882
10883
        $lastLpItemIndex = count($lpItemId) - 1;
10884
        $position = array_search($currentItemId, $lpItemId);
10885
10886
        switch ($position) {
10887
            case 0:
10888
                if (!$lastLpItemIndex) {
10889
                    $answer = 'both';
10890
                    break;
10891
                }
10892
10893
                $answer = 'first';
10894
                break;
10895
            case $lastLpItemIndex:
10896
                $answer = 'last';
10897
                break;
10898
            default:
10899
                $answer = 'none';
10900
        }
10901
10902
        return $answer;
10903
    }
10904
10905
    /**
10906
     * Get whether this is a learning path with the accumulated SCORM time or not.
10907
     *
10908
     * @return int
10909
     */
10910
    public function getAccumulateScormTime()
10911
    {
10912
        return $this->accumulateScormTime;
10913
    }
10914
10915
    /**
10916
     * Set whether this is a learning path with the accumulated SCORM time or not.
10917
     *
10918
     * @param int $value (0 = false, 1 = true)
10919
     *
10920
     * @return bool Always returns true
10921
     */
10922
    public function setAccumulateScormTime($value)
10923
    {
10924
        $this->accumulateScormTime = (int) $value;
10925
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
10926
        $lp_id = $this->get_id();
10927
        $sql = "UPDATE $lp_table
10928
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
10929
                WHERE iid = $lp_id";
10930
        Database::query($sql);
10931
10932
        return true;
10933
    }
10934
10935
    /**
10936
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10937
     * the new learning path tool.
10938
     *
10939
     * The function is a big switch on tool type.
10940
     * In each case, we query the corresponding table for information and build the link
10941
     * with that information.
10942
     *
10943
     * @author Yannick Warnier <[email protected]> - rebranding based on
10944
     * previous work (display_addedresource_link_in_learnpath())
10945
     *
10946
     * @param int $course_id      Course code
10947
     * @param int $learningPathId The learning path ID (in lp table)
10948
     * @param int $id_in_path     the unique index in the items table
10949
     * @param int $lpViewId
10950
     *
10951
     * @return string
10952
     */
10953
    public static function rl_get_resource_link_for_learnpath(
10954
        $course_id,
10955
        $learningPathId,
10956
        $id_in_path,
10957
        $lpViewId
10958
    ) {
10959
        $session_id = api_get_session_id();
10960
        $course_info = api_get_course_info_by_id($course_id);
10961
10962
        $learningPathId = (int) $learningPathId;
10963
        $id_in_path = (int) $id_in_path;
10964
        $lpViewId = (int) $lpViewId;
10965
10966
        $em = Database::getManager();
10967
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
10968
10969
        /** @var CLpItem $rowItem */
10970
        $rowItem = $lpItemRepo->findOneBy([
10971
            'cId' => $course_id,
10972
            'lpId' => $learningPathId,
10973
            'iid' => $id_in_path,
10974
        ]);
10975
10976
        if (!$rowItem) {
10977
            // Try one more time with "id"
10978
            /** @var CLpItem $rowItem */
10979
            $rowItem = $lpItemRepo->findOneBy([
10980
                'cId' => $course_id,
10981
                'lpId' => $learningPathId,
10982
                'id' => $id_in_path,
10983
            ]);
10984
10985
            if (!$rowItem) {
10986
                return -1;
10987
            }
10988
        }
10989
10990
        $type = $rowItem->getItemType();
10991
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10992
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10993
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
10994
        $link = '';
10995
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
10996
10997
        switch ($type) {
10998
            case 'dir':
10999
                return $main_dir_path.'lp/blank.php';
11000
            case TOOL_CALENDAR_EVENT:
11001
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
11002
            case TOOL_ANNOUNCEMENT:
11003
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
11004
            case TOOL_LINK:
11005
                $linkInfo = Link::getLinkInfo($id);
11006
                if (isset($linkInfo['url'])) {
11007
                    return $linkInfo['url'];
11008
                }
11009
11010
                return '';
11011
            case TOOL_QUIZ:
11012
                if (empty($id)) {
11013
                    return '';
11014
                }
11015
11016
                // Get the lp_item_view with the highest view_count.
11017
                $learnpathItemViewResult = $em
11018
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
11019
                    ->findBy(
11020
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
11021
                        ['viewCount' => 'DESC'],
11022
                        1
11023
                    );
11024
                /** @var CLpItemView $learnpathItemViewData */
11025
                $learnpathItemViewData = current($learnpathItemViewResult);
11026
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
11027
11028
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
11029
                    .http_build_query([
11030
                        'lp_init' => 1,
11031
                        'learnpath_item_view_id' => $learnpathItemViewId,
11032
                        'learnpath_id' => $learningPathId,
11033
                        'learnpath_item_id' => $id_in_path,
11034
                        'exerciseId' => $id,
11035
                    ]);
11036
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
11037
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
11038
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
11039
                $myrow = Database::fetch_array($result);
11040
                $path = $myrow['path'];
11041
11042
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
11043
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
11044
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
11045
            case TOOL_FORUM:
11046
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
11047
            case TOOL_THREAD:
11048
                // forum post
11049
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
11050
                if (empty($id)) {
11051
                    return '';
11052
                }
11053
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
11054
                $result = Database::query($sql);
11055
                $myrow = Database::fetch_array($result);
11056
11057
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
11058
                    .$extraParams;
11059
            case TOOL_POST:
11060
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11061
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
11062
                $myrow = Database::fetch_array($result);
11063
11064
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
11065
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
11066
            case TOOL_READOUT_TEXT:
11067
                return api_get_path(WEB_CODE_PATH).
11068
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
11069
            case TOOL_DOCUMENT:
11070
                $repo = Container::getDocumentRepository();
11071
                $document = $repo->find($rowItem->getPath());
11072
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
11073
11074
                return $file;
11075
11076
                $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...
11077
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
11078
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
11079
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
11080
11081
                $openmethod = 2;
11082
                $officedoc = false;
11083
                Session::write('openmethod', $openmethod);
11084
                Session::write('officedoc', $officedoc);
11085
11086
                if ($showDirectUrl) {
11087
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
11088
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
11089
                        if (Link::isPdfLink($file)) {
11090
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
11091
11092
                            return $pdfUrl;
11093
                        }
11094
                    }
11095
11096
                    return $file;
11097
                }
11098
11099
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
11100
            case TOOL_LP_FINAL_ITEM:
11101
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
11102
                    .$extraParams;
11103
            case 'assignments':
11104
                return $main_dir_path.'work/work.php?'.$extraParams;
11105
            case TOOL_DROPBOX:
11106
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
11107
            case 'introduction_text': //DEPRECATED
11108
                return '';
11109
            case TOOL_COURSE_DESCRIPTION:
11110
                return $main_dir_path.'course_description?'.$extraParams;
11111
            case TOOL_GROUP:
11112
                return $main_dir_path.'group/group.php?'.$extraParams;
11113
            case TOOL_USER:
11114
                return $main_dir_path.'user/user.php?'.$extraParams;
11115
            case TOOL_STUDENTPUBLICATION:
11116
                if (!empty($rowItem->getPath())) {
11117
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
11118
                }
11119
11120
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
11121
        }
11122
11123
        return $link;
11124
    }
11125
11126
    /**
11127
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11128
     *
11129
     * @author Yannick Warnier <[email protected]>
11130
     *
11131
     * @param string $course_code    Course code
11132
     * @param int    $learningPathId
11133
     * @param int    $id_in_path     The resource ID
11134
     *
11135
     * @return string
11136
     */
11137
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11138
    {
11139
        $_course = api_get_course_info($course_code);
11140
        if (empty($_course)) {
11141
            return '';
11142
        }
11143
        $course_id = $_course['real_id'];
11144
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11145
        $learningPathId = (int) $learningPathId;
11146
        $id_in_path = (int) $id_in_path;
11147
11148
        $sql = "SELECT item_type, title, ref
11149
                FROM $tbl_lp_item
11150
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11151
        $res_item = Database::query($sql);
11152
11153
        if (Database::num_rows($res_item) < 1) {
11154
            return '';
11155
        }
11156
        $row_item = Database::fetch_array($res_item);
11157
        $type = strtolower($row_item['item_type']);
11158
        $id = $row_item['ref'];
11159
        $output = '';
11160
11161
        switch ($type) {
11162
            case TOOL_CALENDAR_EVENT:
11163
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11164
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11165
                $myrow = Database::fetch_array($result);
11166
                $output = $myrow['title'];
11167
                break;
11168
            case TOOL_ANNOUNCEMENT:
11169
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11170
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11171
                $myrow = Database::fetch_array($result);
11172
                $output = $myrow['title'];
11173
                break;
11174
            case TOOL_LINK:
11175
                // Doesn't take $target into account.
11176
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11177
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11178
                $myrow = Database::fetch_array($result);
11179
                $output = $myrow['title'];
11180
                break;
11181
            case TOOL_QUIZ:
11182
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11183
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11184
                $myrow = Database::fetch_array($result);
11185
                $output = $myrow['title'];
11186
                break;
11187
            case TOOL_FORUM:
11188
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11189
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11190
                $myrow = Database::fetch_array($result);
11191
                $output = $myrow['forum_name'];
11192
                break;
11193
            case TOOL_THREAD:
11194
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11195
                // Grabbing the title of the post.
11196
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11197
                $result_title = Database::query($sql_title);
11198
                $myrow_title = Database::fetch_array($result_title);
11199
                $output = $myrow_title['post_title'];
11200
                break;
11201
            case TOOL_POST:
11202
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11203
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11204
                $result = Database::query($sql);
11205
                $post = Database::fetch_array($result);
11206
                $output = $post['post_title'];
11207
                break;
11208
            case 'dir':
11209
            case TOOL_DOCUMENT:
11210
                $title = $row_item['title'];
11211
                $output = '-';
11212
                if (!empty($title)) {
11213
                    $output = $title;
11214
                }
11215
                break;
11216
            case 'hotpotatoes':
11217
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11218
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11219
                $myrow = Database::fetch_array($result);
11220
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11221
                $last = count($pathname) - 1; // Making a correct name for the link.
11222
                $filename = $pathname[$last]; // Making a correct name for the link.
11223
                $myrow['path'] = rawurlencode($myrow['path']);
11224
                $output = $filename;
11225
                break;
11226
        }
11227
11228
        return stripslashes($output);
11229
    }
11230
11231
    /**
11232
     * Get the parent names for the current item.
11233
     *
11234
     * @param int $newItemId Optional. The item ID
11235
     *
11236
     * @return array
11237
     */
11238
    public function getCurrentItemParentNames($newItemId = 0)
11239
    {
11240
        $newItemId = $newItemId ?: $this->get_current_item_id();
11241
        $return = [];
11242
        $item = $this->getItem($newItemId);
11243
        $parent = $this->getItem($item->get_parent());
11244
11245
        while ($parent) {
11246
            $return[] = $parent->get_title();
11247
            $parent = $this->getItem($parent->get_parent());
11248
        }
11249
11250
        return array_reverse($return);
11251
    }
11252
11253
    /**
11254
     * Reads and process "lp_subscription_settings" setting.
11255
     *
11256
     * @return array
11257
     */
11258
    public static function getSubscriptionSettings()
11259
    {
11260
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11261
        if (empty($subscriptionSettings)) {
11262
            // By default allow both settings
11263
            $subscriptionSettings = [
11264
                'allow_add_users_to_lp' => true,
11265
                'allow_add_users_to_lp_category' => true,
11266
            ];
11267
        } else {
11268
            $subscriptionSettings = $subscriptionSettings['options'];
11269
        }
11270
11271
        return $subscriptionSettings;
11272
    }
11273
11274
    /**
11275
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11276
     */
11277
    public function exportToCourseBuildFormat()
11278
    {
11279
        if (!api_is_allowed_to_edit()) {
11280
            return false;
11281
        }
11282
11283
        $courseBuilder = new CourseBuilder();
11284
        $itemList = [];
11285
        /** @var learnpathItem $item */
11286
        foreach ($this->items as $item) {
11287
            $itemList[$item->get_type()][] = $item->get_path();
11288
        }
11289
11290
        if (empty($itemList)) {
11291
            return false;
11292
        }
11293
11294
        if (isset($itemList['document'])) {
11295
            // Get parents
11296
            foreach ($itemList['document'] as $documentId) {
11297
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11298
                if (!empty($documentInfo['parents'])) {
11299
                    foreach ($documentInfo['parents'] as $parentInfo) {
11300
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11301
                            continue;
11302
                        }
11303
                        $itemList['document'][] = $parentInfo['iid'];
11304
                    }
11305
                }
11306
            }
11307
11308
            $courseInfo = api_get_course_info();
11309
            foreach ($itemList['document'] as $documentId) {
11310
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11311
                $items = DocumentManager::get_resources_from_source_html(
11312
                    $documentInfo['absolute_path'],
11313
                    true,
11314
                    TOOL_DOCUMENT
11315
                );
11316
11317
                if (!empty($items)) {
11318
                    foreach ($items as $item) {
11319
                        // Get information about source url
11320
                        $url = $item[0]; // url
11321
                        $scope = $item[1]; // scope (local, remote)
11322
                        $type = $item[2]; // type (rel, abs, url)
11323
11324
                        $origParseUrl = parse_url($url);
11325
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11326
11327
                        if ('local' == $scope) {
11328
                            if ('abs' == $type || 'rel' == $type) {
11329
                                $documentFile = strstr($realOrigPath, 'document');
11330
                                if (false !== strpos($realOrigPath, $documentFile)) {
11331
                                    $documentFile = str_replace('document', '', $documentFile);
11332
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11333
                                    // Document found! Add it to the list
11334
                                    if ($itemDocumentId) {
11335
                                        $itemList['document'][] = $itemDocumentId;
11336
                                    }
11337
                                }
11338
                            }
11339
                        }
11340
                    }
11341
                }
11342
            }
11343
11344
            $courseBuilder->build_documents(
11345
                api_get_session_id(),
11346
                $this->get_course_int_id(),
11347
                true,
11348
                $itemList['document']
11349
            );
11350
        }
11351
11352
        if (isset($itemList['quiz'])) {
11353
            $courseBuilder->build_quizzes(
11354
                api_get_session_id(),
11355
                $this->get_course_int_id(),
11356
                true,
11357
                $itemList['quiz']
11358
            );
11359
        }
11360
11361
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11362
11363
        /*if (!empty($itemList['thread'])) {
11364
            $postList = [];
11365
            foreach ($itemList['thread'] as $postId) {
11366
                $post = get_post_information($postId);
11367
                if ($post) {
11368
                    if (!isset($itemList['forum'])) {
11369
                        $itemList['forum'] = [];
11370
                    }
11371
                    $itemList['forum'][] = $post['forum_id'];
11372
                    $postList[] = $postId;
11373
                }
11374
            }
11375
11376
            if (!empty($postList)) {
11377
                $courseBuilder->build_forum_posts(
11378
                    $this->get_course_int_id(),
11379
                    null,
11380
                    null,
11381
                    $postList
11382
                );
11383
            }
11384
        }*/
11385
11386
        if (!empty($itemList['thread'])) {
11387
            $threadList = [];
11388
            $em = Database::getManager();
11389
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11390
            foreach ($itemList['thread'] as $threadId) {
11391
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11392
                $thread = $repo->find($threadId);
11393
                if ($thread) {
11394
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11395
                    $threadList[] = $thread->getIid();
11396
                }
11397
            }
11398
11399
            if (!empty($threadList)) {
11400
                $courseBuilder->build_forum_topics(
11401
                    api_get_session_id(),
11402
                    $this->get_course_int_id(),
11403
                    null,
11404
                    $threadList
11405
                );
11406
            }
11407
        }
11408
11409
        $forumCategoryList = [];
11410
        if (isset($itemList['forum'])) {
11411
            foreach ($itemList['forum'] as $forumId) {
11412
                $forumInfo = get_forums($forumId);
11413
                $forumCategoryList[] = $forumInfo['forum_category'];
11414
            }
11415
        }
11416
11417
        if (!empty($forumCategoryList)) {
11418
            $courseBuilder->build_forum_category(
11419
                api_get_session_id(),
11420
                $this->get_course_int_id(),
11421
                true,
11422
                $forumCategoryList
11423
            );
11424
        }
11425
11426
        if (!empty($itemList['forum'])) {
11427
            $courseBuilder->build_forums(
11428
                api_get_session_id(),
11429
                $this->get_course_int_id(),
11430
                true,
11431
                $itemList['forum']
11432
            );
11433
        }
11434
11435
        if (isset($itemList['link'])) {
11436
            $courseBuilder->build_links(
11437
                api_get_session_id(),
11438
                $this->get_course_int_id(),
11439
                true,
11440
                $itemList['link']
11441
            );
11442
        }
11443
11444
        if (!empty($itemList['student_publication'])) {
11445
            $courseBuilder->build_works(
11446
                api_get_session_id(),
11447
                $this->get_course_int_id(),
11448
                true,
11449
                $itemList['student_publication']
11450
            );
11451
        }
11452
11453
        $courseBuilder->build_learnpaths(
11454
            api_get_session_id(),
11455
            $this->get_course_int_id(),
11456
            true,
11457
            [$this->get_id()],
11458
            false
11459
        );
11460
11461
        $courseBuilder->restoreDocumentsFromList();
11462
11463
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11464
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11465
        $result = DocumentManager::file_send_for_download(
11466
            $zipPath,
11467
            true,
11468
            $this->get_name().'.zip'
11469
        );
11470
11471
        if ($result) {
11472
            api_not_allowed();
11473
        }
11474
11475
        return true;
11476
    }
11477
11478
    /**
11479
     * Get whether this is a learning path with the accumulated work time or not.
11480
     *
11481
     * @return int
11482
     */
11483
    public function getAccumulateWorkTime()
11484
    {
11485
        return (int) $this->accumulateWorkTime;
11486
    }
11487
11488
    /**
11489
     * Get whether this is a learning path with the accumulated work time or not.
11490
     *
11491
     * @return int
11492
     */
11493
    public function getAccumulateWorkTimeTotalCourse()
11494
    {
11495
        $table = Database::get_course_table(TABLE_LP_MAIN);
11496
        $sql = "SELECT SUM(accumulate_work_time) AS total
11497
                FROM $table
11498
                WHERE c_id = ".$this->course_int_id;
11499
        $result = Database::query($sql);
11500
        $row = Database::fetch_array($result);
11501
11502
        return (int) $row['total'];
11503
    }
11504
11505
    /**
11506
     * Set whether this is a learning path with the accumulated work time or not.
11507
     *
11508
     * @param int $value (0 = false, 1 = true)
11509
     *
11510
     * @return bool
11511
     */
11512
    public function setAccumulateWorkTime($value)
11513
    {
11514
        if (!api_get_configuration_value('lp_minimum_time')) {
11515
            return false;
11516
        }
11517
11518
        $this->accumulateWorkTime = (int) $value;
11519
        $table = Database::get_course_table(TABLE_LP_MAIN);
11520
        $lp_id = $this->get_id();
11521
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
11522
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
11523
        Database::query($sql);
11524
11525
        return true;
11526
    }
11527
11528
    /**
11529
     * @param int $lpId
11530
     * @param int $courseId
11531
     *
11532
     * @return mixed
11533
     */
11534
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11535
    {
11536
        $lpId = (int) $lpId;
11537
        $courseId = (int) $courseId;
11538
11539
        $table = Database::get_course_table(TABLE_LP_MAIN);
11540
        $sql = "SELECT accumulate_work_time
11541
                FROM $table
11542
                WHERE c_id = $courseId AND id = $lpId";
11543
        $result = Database::query($sql);
11544
        $row = Database::fetch_array($result);
11545
11546
        return $row['accumulate_work_time'];
11547
    }
11548
11549
    /**
11550
     * @param int $courseId
11551
     *
11552
     * @return int
11553
     */
11554
    public static function getAccumulateWorkTimeTotal($courseId)
11555
    {
11556
        $table = Database::get_course_table(TABLE_LP_MAIN);
11557
        $courseId = (int) $courseId;
11558
        $sql = "SELECT SUM(accumulate_work_time) AS total
11559
                FROM $table
11560
                WHERE c_id = $courseId";
11561
        $result = Database::query($sql);
11562
        $row = Database::fetch_array($result);
11563
11564
        return (int) $row['total'];
11565
    }
11566
11567
    /**
11568
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11569
     * and put the images in.
11570
     *
11571
     * @return array
11572
     */
11573
    public static function getIconSelect()
11574
    {
11575
        $theme = api_get_visual_theme();
11576
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11577
        $icons = ['' => get_lang('Please select an option')];
11578
11579
        if (is_dir($path)) {
11580
            $finder = new Finder();
11581
            $finder->files()->in($path);
11582
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11583
            /** @var SplFileInfo $file */
11584
            foreach ($finder as $file) {
11585
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11586
                    $icons[$file->getFilename()] = $file->getFilename();
11587
                }
11588
            }
11589
        }
11590
11591
        return $icons;
11592
    }
11593
11594
    /**
11595
     * @param int $lpId
11596
     *
11597
     * @return string
11598
     */
11599
    public static function getSelectedIcon($lpId)
11600
    {
11601
        $extraFieldValue = new ExtraFieldValue('lp');
11602
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11603
        $icon = '';
11604
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11605
            $icon = $lpIcon['value'];
11606
        }
11607
11608
        return $icon;
11609
    }
11610
11611
    /**
11612
     * @param int $lpId
11613
     *
11614
     * @return string
11615
     */
11616
    public static function getSelectedIconHtml($lpId)
11617
    {
11618
        $icon = self::getSelectedIcon($lpId);
11619
11620
        if (empty($icon)) {
11621
            return '';
11622
        }
11623
11624
        $theme = api_get_visual_theme();
11625
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11626
11627
        return Display::img($path);
11628
    }
11629
11630
    /**
11631
     * @param string $value
11632
     *
11633
     * @return string
11634
     */
11635
    public function cleanItemTitle($value)
11636
    {
11637
        $value = Security::remove_XSS(strip_tags($value));
11638
11639
        return $value;
11640
    }
11641
11642
    public function setItemTitle(FormValidator $form)
11643
    {
11644
        if (api_get_configuration_value('save_titles_as_html')) {
11645
            $form->addHtmlEditor(
11646
                'title',
11647
                get_lang('Title'),
11648
                true,
11649
                false,
11650
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11651
            );
11652
        } else {
11653
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11654
            $form->applyFilter('title', 'trim');
11655
            $form->applyFilter('title', 'html_filter');
11656
        }
11657
    }
11658
11659
    /**
11660
     * @return array
11661
     */
11662
    public function getItemsForForm($addParentCondition = false)
11663
    {
11664
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11665
        $course_id = api_get_course_int_id();
11666
11667
        $sql = "SELECT * FROM $tbl_lp_item
11668
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11669
11670
        if ($addParentCondition) {
11671
            $sql .= ' AND parent_item_id = 0 ';
11672
        }
11673
        $sql .= ' ORDER BY display_order ASC';
11674
11675
        $result = Database::query($sql);
11676
        $arrLP = [];
11677
        while ($row = Database::fetch_array($result)) {
11678
            $arrLP[] = [
11679
                'iid' => $row['iid'],
11680
                'id' => $row['iid'],
11681
                'item_type' => $row['item_type'],
11682
                'title' => $this->cleanItemTitle($row['title']),
11683
                'title_raw' => $row['title'],
11684
                'path' => $row['path'],
11685
                'description' => Security::remove_XSS($row['description']),
11686
                'parent_item_id' => $row['parent_item_id'],
11687
                'previous_item_id' => $row['previous_item_id'],
11688
                'next_item_id' => $row['next_item_id'],
11689
                'display_order' => $row['display_order'],
11690
                'max_score' => $row['max_score'],
11691
                'min_score' => $row['min_score'],
11692
                'mastery_score' => $row['mastery_score'],
11693
                'prerequisite' => $row['prerequisite'],
11694
                'max_time_allowed' => $row['max_time_allowed'],
11695
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11696
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11697
            ];
11698
        }
11699
11700
        return $arrLP;
11701
    }
11702
11703
    /**
11704
     * Gets whether this SCORM learning path has been marked to use the score
11705
     * as progress. Takes into account whether the learnpath matches (SCORM
11706
     * content + less than 2 items).
11707
     *
11708
     * @return bool True if the score should be used as progress, false otherwise
11709
     */
11710
    public function getUseScoreAsProgress()
11711
    {
11712
        // If not a SCORM, we don't care about the setting
11713
        if ($this->get_type() != 2) {
11714
            return false;
11715
        }
11716
        // If more than one step in the SCORM, we don't care about the setting
11717
        if ($this->get_total_items_count() > 1) {
11718
            return false;
11719
        }
11720
        $extraFieldValue = new ExtraFieldValue('lp');
11721
        $doUseScore = false;
11722
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
11723
        if (!empty($useScore) && isset($useScore['value'])) {
11724
            $doUseScore = $useScore['value'];
11725
        }
11726
11727
        return $doUseScore;
11728
    }
11729
11730
    /**
11731
     * Get the user identifier (user_id or username
11732
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11733
     *
11734
     * @return string User ID or username, depending on configuration setting
11735
     */
11736
    public static function getUserIdentifierForExternalServices()
11737
    {
11738
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11739
            return api_get_user_info(api_get_user_id())['username'];
11740
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
11741
            $extraFieldValue = new ExtraFieldValue('user');
11742
            $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'));
11743
11744
            return $extrafield['value'];
11745
        } else {
11746
            return api_get_user_id();
11747
        }
11748
    }
11749
11750
    /**
11751
     * Save the new order for learning path items.
11752
     *
11753
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11754
     *
11755
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11756
     * @param int   $courseId
11757
     */
11758
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11759
    {
11760
        $courseId = $courseId ?: api_get_course_int_id();
11761
        $itemList = new LpItemOrderList();
11762
11763
        foreach ($orderList as $id => $parentId) {
11764
            $item = new LpOrderItem($id, $parentId);
11765
            $itemList->add($item);
11766
        }
11767
11768
        $parents = $itemList->getListOfParents();
11769
11770
        foreach ($parents as $parentId) {
11771
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11772
            $previous_item_id = 0;
11773
            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...
11774
                $item_id = $sameParentLpItemList->list[$i]->id;
11775
                // display_order
11776
                $display_order = $i + 1;
11777
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11778
                // previous_item_id
11779
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11780
                $previous_item_id = $item_id;
11781
                // next_item_id
11782
                $next_item_id = 0;
11783
                if ($i < count($sameParentLpItemList->list) - 1) {
11784
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11785
                }
11786
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11787
            }
11788
        }
11789
11790
        $table = Database::get_course_table(TABLE_LP_ITEM);
11791
11792
        foreach ($itemList->list as $item) {
11793
            $params = [];
11794
            $params['display_order'] = $item->display_order;
11795
            $params['previous_item_id'] = $item->previous_item_id;
11796
            $params['next_item_id'] = $item->next_item_id;
11797
            $params['parent_item_id'] = $item->parent_item_id;
11798
11799
            Database::update(
11800
                $table,
11801
                $params,
11802
                [
11803
                    'iid = ? AND c_id = ? ' => [
11804
                        (int) $item->id,
11805
                        (int) $courseId,
11806
                    ],
11807
                ]
11808
            );
11809
        }
11810
    }
11811
11812
    /**
11813
     * Get the depth level of LP item.
11814
     *
11815
     * @param array $items
11816
     * @param int   $currentItemId
11817
     *
11818
     * @return int
11819
     */
11820
    private static function get_level_for_item($items, $currentItemId)
11821
    {
11822
        $parentItemId = 0;
11823
        if (isset($items[$currentItemId])) {
11824
            $parentItemId = $items[$currentItemId]->parent;
11825
        }
11826
11827
        if (0 == $parentItemId) {
11828
            return 0;
11829
        } else {
11830
            return self::get_level_for_item($items, $parentItemId) + 1;
11831
        }
11832
    }
11833
11834
    /**
11835
     * Generate the link for a learnpath category as course tool.
11836
     *
11837
     * @param int $categoryId
11838
     *
11839
     * @return string
11840
     */
11841
    private static function getCategoryLinkForTool($categoryId)
11842
    {
11843
        $categoryId = (int) $categoryId;
11844
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11845
            .http_build_query(
11846
                [
11847
                    'action' => 'view_category',
11848
                    'id' => $categoryId,
11849
                ]
11850
            );
11851
11852
        return $link;
11853
    }
11854
11855
    /**
11856
     * Return the scorm item type object with spaces replaced with _
11857
     * The return result is use to build a css classname like scorm_type_$return.
11858
     *
11859
     * @param $in_type
11860
     *
11861
     * @return mixed
11862
     */
11863
    private static function format_scorm_type_item($in_type)
11864
    {
11865
        return str_replace(' ', '_', $in_type);
11866
    }
11867
11868
    /**
11869
     * Check and obtain the lp final item if exist.
11870
     *
11871
     * @return learnpathItem
11872
     */
11873
    private function getFinalItem()
11874
    {
11875
        if (empty($this->items)) {
11876
            return null;
11877
        }
11878
11879
        foreach ($this->items as $item) {
11880
            if ('final_item' !== $item->type) {
11881
                continue;
11882
            }
11883
11884
            return $item;
11885
        }
11886
    }
11887
11888
    /**
11889
     * Get the LP Final Item Template.
11890
     *
11891
     * @return string
11892
     */
11893
    private function getFinalItemTemplate()
11894
    {
11895
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11896
    }
11897
11898
    /**
11899
     * Get the LP Final Item Url.
11900
     *
11901
     * @return string
11902
     */
11903
    private function getSavedFinalItem()
11904
    {
11905
        $finalItem = $this->getFinalItem();
11906
11907
        $repo = Container::getDocumentRepository();
11908
        /** @var CDocument $document */
11909
        $document = $repo->find($finalItem->path);
11910
11911
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11912
            return  $repo->getResourceFileContent($document);
11913
        }
11914
11915
        return '';
11916
    }
11917
}
11918