Passed
Push — master ( 193096...d89cdc )
by Julito
07:30
created

learnpath::fixBlockedLinks()   C

Complexity

Conditions 11
Paths 160

Size

Total Lines 64
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

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