Passed
Push — master ( b95980...a81919 )
by Julito
08:46
created

learnpath::move_up()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 51
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 36
nc 14
nop 2
dl 0
loc 51
rs 8.0995
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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