Passed
Push — master ( 49d6fc...f737dc )
by Julito
08:44
created

learnpath::displayDocumentForm()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 44
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 26
nc 6
nop 2
dl 0
loc 44
rs 9.504
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\User;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
11
use Chamilo\CourseBundle\Entity\CDocument;
12
use Chamilo\CourseBundle\Entity\CForumCategory;
13
use Chamilo\CourseBundle\Entity\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, $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, $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, $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, $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($action, CLpItem $lpItem, $resource)
7050
    {
7051
        $form = new FormValidator('frm_student_publication', 'post', '#');
7052
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7053
7054
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7055
7056
        $return = '<div class="sectioncomment">';
7057
        $return .= $form->returnForm();
7058
        $return .= '</div>';
7059
7060
        return $return;
7061
    }
7062
7063
    public function displayNewSectionForm()
7064
    {
7065
        $action = 'add_item';
7066
        $item_type = 'dir';
7067
7068
        $lpItem = new CLpItem();
7069
        $lpItem->setTitle('dir');
7070
        $lpItem->setItemType('dir');
7071
7072
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7073
7074
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7075
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7076
7077
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7078
        $form->addElement('hidden', 'type', 'dir');
7079
7080
        return $form->returnForm();
7081
    }
7082
7083
    /**
7084
     * Returns the form to update or create a document.
7085
     *
7086
     * @param string  $action (add/edit)
7087
     * @param CLpItem $lpItem
7088
     *
7089
     * @throws HTML_QuickForm_Error
7090
     * @throws Exception
7091
     *
7092
     * @return string HTML form
7093
     */
7094
    public function displayDocumentForm($action = 'add', $lpItem = null)
7095
    {
7096
        $courseInfo = api_get_course_info();
7097
7098
        $form = new FormValidator(
7099
            'form',
7100
            'POST',
7101
            $this->getCurrentBuildingModeURL(),
7102
            '',
7103
            ['enctype' => 'multipart/form-data']
7104
        );
7105
7106
        $data = $this->generate_lp_folder($courseInfo);
7107
7108
        if (null !== $lpItem) {
7109
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7110
        }
7111
7112
        switch ($action) {
7113
            case 'add':
7114
                $folders = DocumentManager::get_all_document_folders(
7115
                    $courseInfo,
7116
                    0,
7117
                    true
7118
                );
7119
                DocumentManager::build_directory_selector(
7120
                    $folders,
7121
                    '',
7122
                    [],
7123
                    true,
7124
                    $form,
7125
                    'directory_parent_id'
7126
                );
7127
7128
                if ($data) {
7129
                    $defaults['directory_parent_id'] = $data->getIid();
7130
                }
7131
7132
                break;
7133
        }
7134
7135
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7136
7137
        return $form->returnForm();
7138
    }
7139
7140
    /**
7141
     * @param array  $courseInfo
7142
     * @param string $content
7143
     * @param string $title
7144
     * @param int    $parentId
7145
     *
7146
     * @throws \Doctrine\ORM\ORMException
7147
     * @throws \Doctrine\ORM\OptimisticLockException
7148
     * @throws \Doctrine\ORM\TransactionRequiredException
7149
     *
7150
     * @return int
7151
     */
7152
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7153
    {
7154
        $creatorId = api_get_user_id();
7155
        $sessionId = api_get_session_id();
7156
7157
        // Generates folder
7158
        $result = $this->generate_lp_folder($courseInfo);
7159
        $dir = $result['dir'];
7160
7161
        if (empty($parentId) || '/' == $parentId) {
7162
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7163
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7164
7165
            if ('/' === $parentId) {
7166
                $dir = '/';
7167
            }
7168
7169
            // Please, do not modify this dirname formatting.
7170
            if (strstr($dir, '..')) {
7171
                $dir = '/';
7172
            }
7173
7174
            if (!empty($dir[0]) && '.' == $dir[0]) {
7175
                $dir = substr($dir, 1);
7176
            }
7177
            if (!empty($dir[0]) && '/' != $dir[0]) {
7178
                $dir = '/'.$dir;
7179
            }
7180
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7181
                $dir .= '/';
7182
            }
7183
        } else {
7184
            $parentInfo = DocumentManager::get_document_data_by_id(
7185
                $parentId,
7186
                $courseInfo['code']
7187
            );
7188
            if (!empty($parentInfo)) {
7189
                $dir = $parentInfo['path'].'/';
7190
            }
7191
        }
7192
7193
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7194
7195
        if (!is_dir($filepath)) {
7196
            $dir = '/';
7197
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7198
        }
7199
7200
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7201
7202
        if (!empty($title)) {
7203
            $title = api_replace_dangerous_char(stripslashes($title));
7204
        } else {
7205
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7206
        }
7207
7208
        $title = disable_dangerous_file($title);
7209
        $filename = $title;
7210
        $content = !empty($content) ? $content : $_POST['content_lp'];
7211
        $tmpFileName = $filename;
7212
7213
        $i = 0;
7214
        while (file_exists($filepath.$tmpFileName.'.html')) {
7215
            $tmpFileName = $filename.'_'.++$i;
7216
        }
7217
7218
        $filename = $tmpFileName.'.html';
7219
        $content = stripslashes($content);
7220
7221
        if (file_exists($filepath.$filename)) {
7222
            return 0;
7223
        }
7224
7225
        $putContent = file_put_contents($filepath.$filename, $content);
7226
7227
        if (false === $putContent) {
7228
            return 0;
7229
        }
7230
7231
        $fileSize = filesize($filepath.$filename);
7232
        $saveFilePath = $dir.$filename;
7233
7234
        $document = DocumentManager::addDocument(
7235
            $courseInfo,
7236
            $saveFilePath,
7237
            'file',
7238
            $fileSize,
7239
            $tmpFileName,
7240
            '',
7241
            0, //readonly
7242
            true,
7243
            null,
7244
            $sessionId,
7245
            $creatorId
7246
        );
7247
7248
        $documentId = $document->getId();
7249
7250
        if (!$document) {
7251
            return 0;
7252
        }
7253
7254
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7255
        $newTitle = $originalTitle;
7256
7257
        if ($newComment || $newTitle) {
7258
            $em = Database::getManager();
7259
7260
            if ($newComment) {
7261
                $document->setComment($newComment);
7262
            }
7263
7264
            if ($newTitle) {
7265
                $document->setTitle($newTitle);
7266
            }
7267
7268
            $em->persist($document);
7269
            $em->flush();
7270
        }
7271
7272
        return $documentId;
7273
    }
7274
7275
    /**
7276
     * Displays the menu for manipulating a step.
7277
     *
7278
     * @return string
7279
     */
7280
    public function displayItemMenu(CLpItem $lpItem)
7281
    {
7282
        $item_id = $lpItem->getIid();
7283
        $audio = $lpItem->getAudio();
7284
        $itemType = $lpItem->getItemType();
7285
        $path = $lpItem->getPath();
7286
7287
        $return = '<div class="actions">';
7288
        $audio_player = null;
7289
        // We display an audio player if needed.
7290
        if (!empty($audio)) {
7291
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7292
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7293
                .'<audio src="'.$webAudioPath.'" controls>'
7294
                .'</div><br>';*/
7295
        }
7296
7297
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7298
7299
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7300
            $return .= Display::url(
7301
                Display::return_icon(
7302
                    'edit.png',
7303
                    get_lang('Edit'),
7304
                    [],
7305
                    ICON_SIZE_SMALL
7306
                ),
7307
                $url.'&action=edit_item&path_item='.$path
7308
            );
7309
7310
            /*$return .= Display::url(
7311
                Display::return_icon(
7312
                    'move.png',
7313
                    get_lang('Move'),
7314
                    [],
7315
                    ICON_SIZE_SMALL
7316
                ),
7317
                $url.'&action=move_item'
7318
            );*/
7319
        }
7320
7321
        // Commented for now as prerequisites cannot be added to chapters.
7322
        if ('dir' !== $itemType) {
7323
            $return .= Display::url(
7324
                Display::return_icon(
7325
                    'accept.png',
7326
                    get_lang('Prerequisites'),
7327
                    [],
7328
                    ICON_SIZE_SMALL
7329
                ),
7330
                $url.'&action=edit_item_prereq'
7331
            );
7332
        }
7333
        $return .= Display::url(
7334
            Display::return_icon(
7335
                'delete.png',
7336
                get_lang('Delete'),
7337
                [],
7338
                ICON_SIZE_SMALL
7339
            ),
7340
            $url.'&action=delete_item'
7341
        );
7342
7343
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7344
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7345
            if (empty($documentData)) {
7346
                // Try with iid
7347
                $table = Database::get_course_table(TABLE_DOCUMENT);
7348
                $sql = "SELECT path FROM $table
7349
                        WHERE
7350
                              c_id = ".api_get_course_int_id()." AND
7351
                              iid = ".$path." AND
7352
                              path NOT LIKE '%_DELETED_%'";
7353
                $result = Database::query($sql);
7354
                $documentData = Database::fetch_array($result);
7355
                if ($documentData) {
7356
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7357
                }
7358
            }
7359
            if (isset($documentData['absolute_path_from_document'])) {
7360
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7361
            }
7362
        }*/
7363
7364
        $return .= '</div>';
7365
7366
        if (!empty($audio_player)) {
7367
            $return .= $audio_player;
7368
        }
7369
7370
        return $return;
7371
    }
7372
7373
    /**
7374
     * Creates the javascript needed for filling up the checkboxes without page reload.
7375
     *
7376
     * @return string
7377
     */
7378
    public function get_js_dropdown_array()
7379
    {
7380
        $course_id = api_get_course_int_id();
7381
        $return = 'var child_name = new Array();'."\n";
7382
        $return .= 'var child_value = new Array();'."\n\n";
7383
        $return .= 'child_name[0] = new Array();'."\n";
7384
        $return .= 'child_value[0] = new Array();'."\n\n";
7385
7386
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7387
        $sql = "SELECT * FROM ".$tbl_lp_item."
7388
                WHERE
7389
                    c_id = $course_id AND
7390
                    lp_id = ".$this->lp_id." AND
7391
                    parent_item_id = 0
7392
                ORDER BY display_order ASC";
7393
        Database::query($sql);
7394
        $i = 0;
7395
7396
        $list = $this->getItemsForForm(true);
7397
7398
        foreach ($list as $row_zero) {
7399
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7400
                if (TOOL_QUIZ == $row_zero['item_type']) {
7401
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7402
                }
7403
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7404
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7405
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7406
            }
7407
        }
7408
7409
        $return .= "\n";
7410
        $sql = "SELECT * FROM $tbl_lp_item
7411
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7412
        $res = Database::query($sql);
7413
        while ($row = Database::fetch_array($res)) {
7414
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7415
                           WHERE
7416
                                c_id = ".$course_id." AND
7417
                                parent_item_id = ".$row['iid']."
7418
                           ORDER BY display_order ASC";
7419
            $res_parent = Database::query($sql_parent);
7420
            $i = 0;
7421
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7422
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7423
7424
            while ($row_parent = Database::fetch_array($res_parent)) {
7425
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7426
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7427
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7428
            }
7429
            $return .= "\n";
7430
        }
7431
7432
        $return .= "
7433
            function load_cbo(id) {
7434
                if (!id) {
7435
                    return false;
7436
                }
7437
7438
                var cbo = document.getElementById('previous');
7439
                for(var i = cbo.length - 1; i > 0; i--) {
7440
                    cbo.options[i] = null;
7441
                }
7442
7443
                var k=0;
7444
                for(var i = 1; i <= child_name[id].length; i++){
7445
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7446
                    option.style.paddingLeft = '40px';
7447
                    cbo.options[i] = option;
7448
                    k = i;
7449
                }
7450
7451
                cbo.options[k].selected = true;
7452
                //$('#previous').selectpicker('refresh');
7453
            }";
7454
7455
        return $return;
7456
    }
7457
7458
    /**
7459
     * Display the form to allow moving an item.
7460
     *
7461
     * @param CLpItem $lpItem
7462
     *
7463
     * @throws Exception
7464
     * @throws HTML_QuickForm_Error
7465
     *
7466
     * @return string HTML form
7467
     */
7468
    public function display_move_item($lpItem)
7469
    {
7470
        $return = '';
7471
        $path = $lpItem->getPath();
7472
7473
        if ($lpItem) {
7474
            $itemType = $lpItem->getItemType();
7475
            switch ($itemType) {
7476
                case 'dir':
7477
                case 'asset':
7478
                    $return .= $this->displayItemMenu($lpItem);
7479
                    $return .= $this->display_item_form(
7480
                        $lpItem,
7481
                        get_lang('Move the current section'),
7482
                        'move',
7483
                        $row
7484
                    );
7485
                    break;
7486
                case TOOL_DOCUMENT:
7487
                    $return .= $this->displayItemMenu($lpItem);
7488
                    $return .= $this->displayDocumentForm('move', $lpItem);
7489
                    break;
7490
                case TOOL_LINK:
7491
                    $link = null;
7492
                    if (!empty($path)) {
7493
                        $repo = Container::getLinkRepository();
7494
                        $link = $repo->find($path);
7495
                    }
7496
                    $return .= $this->displayItemMenu($lpItem);
7497
                    $return .= $this->display_link_form('move', $lpItem, $link);
7498
                    break;
7499
                case TOOL_HOTPOTATOES:
7500
                    $return .= $this->displayItemMenu($lpItem);
7501
                    $return .= $this->display_link_form('move', $lpItem, $row);
7502
                    break;
7503
                case TOOL_QUIZ:
7504
                    $return .= $this->displayItemMenu($lpItem);
7505
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7506
                    break;
7507
                case TOOL_STUDENTPUBLICATION:
7508
                    $return .= $this->displayItemMenu($lpItem);
7509
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7510
                    break;
7511
                case TOOL_FORUM:
7512
                    $return .= $this->displayItemMenu($lpItem);
7513
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7514
                    break;
7515
                case TOOL_THREAD:
7516
                    $return .= $this->displayItemMenu($lpItem);
7517
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7518
                    break;
7519
            }
7520
        }
7521
7522
        return $return;
7523
    }
7524
7525
    /**
7526
     * Return HTML form to allow prerequisites selection.
7527
     *
7528
     * @todo use FormValidator
7529
     *
7530
     * @return string HTML form
7531
     */
7532
    public function display_item_prerequisites_form(CLpItem $lpItem)
7533
    {
7534
        $course_id = api_get_course_int_id();
7535
        $prerequisiteId = $lpItem->getPrerequisite();
7536
        $itemId = $lpItem->getIid();
7537
7538
        $return = '<legend>';
7539
        $return .= get_lang('Add/edit prerequisites');
7540
        $return .= '</legend>';
7541
        $return .= '<form method="POST">';
7542
        $return .= '<div class="table-responsive">';
7543
        $return .= '<table class="table table-hover">';
7544
        $return .= '<thead>';
7545
        $return .= '<tr>';
7546
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7547
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7548
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7549
        $return .= '</tr>';
7550
        $return .= '</thead>';
7551
7552
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7553
        $return .= '<tbody>';
7554
        $return .= '<tr>';
7555
        $return .= '<td colspan="3">';
7556
        $return .= '<div class="radio learnpath"><label for="idnone">';
7557
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7558
        $return .= get_lang('none').'</label>';
7559
        $return .= '</div>';
7560
        $return .= '</tr>';
7561
7562
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7563
        $sql = "SELECT * FROM $tbl_lp_item
7564
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7565
        $result = Database::query($sql);
7566
7567
        $selectedMinScore = [];
7568
        $selectedMaxScore = [];
7569
        $masteryScore = [];
7570
        while ($row = Database::fetch_array($result)) {
7571
            if ($row['iid'] == $itemId) {
7572
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7573
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7574
            }
7575
            $masteryScore[$row['iid']] = $row['mastery_score'];
7576
        }
7577
7578
        $arrLP = $this->getItemsForForm();
7579
        $this->tree_array($arrLP);
7580
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7581
        unset($this->arrMenu);
7582
7583
        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...
7584
            $item = $arrLP[$i];
7585
7586
            if ($item['id'] == $itemId) {
7587
                break;
7588
            }
7589
7590
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7591
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7592
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7593
7594
            $return .= '<tr>';
7595
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7596
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7597
            $return .= '<label for="id'.$item['id'].'">';
7598
7599
            $checked = '';
7600
            if (null !== $prerequisiteId) {
7601
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7602
            }
7603
7604
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
7605
7606
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
7607
7608
            $icon_name = str_replace(' ', '', $item['item_type']);
7609
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7610
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7611
            } else {
7612
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7613
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7614
                } else {
7615
                    $return .= Display::return_icon('folder_document.png');
7616
                }
7617
            }
7618
7619
            $return .= $item['title'].'</label>';
7620
            $return .= '</div>';
7621
            $return .= '</td>';
7622
7623
            if (TOOL_QUIZ == $item['item_type']) {
7624
                // lets update max_score Tests information depending of the Tests Advanced properties
7625
                $lpItemObj = new LpItem($course_id, $item['id']);
7626
                $exercise = new Exercise($course_id);
7627
                $exercise->read($lpItemObj->path);
7628
                $lpItemObj->max_score = $exercise->get_max_score();
7629
                $lpItemObj->update();
7630
                $item['max_score'] = $lpItemObj->max_score;
7631
7632
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7633
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7634
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7635
                }
7636
7637
                $return .= '<td>';
7638
                $return .= '<input
7639
                    class="form-control"
7640
                    size="4" maxlength="3"
7641
                    name="min_'.$item['id'].'"
7642
                    type="number"
7643
                    min="0"
7644
                    step="any"
7645
                    max="'.$item['max_score'].'"
7646
                    value="'.$selectedMinScoreValue.'"
7647
                />';
7648
                $return .= '</td>';
7649
                $return .= '<td>';
7650
                $return .= '<input
7651
                    class="form-control"
7652
                    size="4"
7653
                    maxlength="3"
7654
                    name="max_'.$item['id'].'"
7655
                    type="number"
7656
                    min="0"
7657
                    step="any"
7658
                    max="'.$item['max_score'].'"
7659
                    value="'.$selectedMaxScoreValue.'"
7660
                />';
7661
                $return .= '</td>';
7662
            }
7663
7664
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7665
                $return .= '<td>';
7666
                $return .= '<input
7667
                    size="4"
7668
                    maxlength="3"
7669
                    name="min_'.$item['id'].'"
7670
                    type="number"
7671
                    min="0"
7672
                    step="any"
7673
                    max="'.$item['max_score'].'"
7674
                    value="'.$selectedMinScoreValue.'"
7675
                />';
7676
                $return .= '</td>';
7677
                $return .= '<td>';
7678
                $return .= '<input
7679
                    size="4"
7680
                    maxlength="3"
7681
                    name="max_'.$item['id'].'"
7682
                    type="number"
7683
                    min="0"
7684
                    step="any"
7685
                    max="'.$item['max_score'].'"
7686
                    value="'.$selectedMaxScoreValue.'"
7687
                />';
7688
                $return .= '</td>';
7689
            }
7690
            $return .= '</tr>';
7691
        }
7692
        $return .= '<tr>';
7693
        $return .= '</tr>';
7694
        $return .= '</tbody>';
7695
        $return .= '</table>';
7696
        $return .= '</div>';
7697
        $return .= '<div class="form-group">';
7698
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7699
            get_lang('Save prerequisites settings').'</button>';
7700
        $return .= '</form>';
7701
7702
        return $return;
7703
    }
7704
7705
    /**
7706
     * Return HTML list to allow prerequisites selection for lp.
7707
     *
7708
     * @return string HTML form
7709
     */
7710
    public function display_lp_prerequisites_list()
7711
    {
7712
        $course_id = api_get_course_int_id();
7713
        $lp_id = $this->lp_id;
7714
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
7715
7716
        // get current prerequisite
7717
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
7718
        $result = Database::query($sql);
7719
        $row = Database::fetch_array($result);
7720
        $prerequisiteId = $row['prerequisite'];
7721
        $session_id = api_get_session_id();
7722
        $session_condition = api_get_session_condition($session_id, true, true);
7723
        $sql = "SELECT * FROM $tbl_lp
7724
                WHERE c_id = $course_id $session_condition
7725
                ORDER BY display_order ";
7726
        $rs = Database::query($sql);
7727
        $return = '';
7728
        $return .= '<select name="prerequisites" class="form-control">';
7729
        $return .= '<option value="0">'.get_lang('none').'</option>';
7730
        if (Database::num_rows($rs) > 0) {
7731
            while ($row = Database::fetch_array($rs)) {
7732
                if ($row['iid'] == $lp_id) {
7733
                    continue;
7734
                }
7735
                $return .= '<option
7736
                    value="'.$row['iid'].'" '.(($row['iid'] == $prerequisiteId) ? ' selected ' : '').'>'.
7737
                    $row['name'].
7738
                    '</option>';
7739
            }
7740
        }
7741
        $return .= '</select>';
7742
7743
        return $return;
7744
    }
7745
7746
    /**
7747
     * Creates a list with all the documents in it.
7748
     *
7749
     * @param bool $showInvisibleFiles
7750
     *
7751
     * @throws Exception
7752
     * @throws HTML_QuickForm_Error
7753
     *
7754
     * @return string
7755
     */
7756
    public function get_documents($showInvisibleFiles = false)
7757
    {
7758
        $course_info = api_get_course_info();
7759
        $sessionId = api_get_session_id();
7760
        $documentTree = DocumentManager::get_document_preview(
7761
            $course_info,
7762
            $this->lp_id,
7763
            null,
7764
            $sessionId,
7765
            true,
7766
            null,
7767
            null,
7768
            $showInvisibleFiles,
7769
            true
7770
        );
7771
7772
        $form = new FormValidator(
7773
            'form_upload',
7774
            'POST',
7775
            $this->getCurrentBuildingModeURL(),
7776
            '',
7777
            ['enctype' => 'multipart/form-data']
7778
        );
7779
7780
        $folders = DocumentManager::get_all_document_folders(
7781
            api_get_course_info(),
7782
            0,
7783
            true
7784
        );
7785
7786
        $folder = $this->generate_lp_folder(api_get_course_info());
7787
7788
        DocumentManager::build_directory_selector(
7789
            $folders,
7790
            $folder->getIid(),
7791
            [],
7792
            true,
7793
            $form,
7794
            'directory_parent_id'
7795
        );
7796
7797
        $group = [
7798
            $form->createElement(
7799
                'radio',
7800
                'if_exists',
7801
                get_lang('If file exists:'),
7802
                get_lang('Do nothing'),
7803
                'nothing'
7804
            ),
7805
            $form->createElement(
7806
                'radio',
7807
                'if_exists',
7808
                null,
7809
                get_lang('Overwrite the existing file'),
7810
                'overwrite'
7811
            ),
7812
            $form->createElement(
7813
                'radio',
7814
                'if_exists',
7815
                null,
7816
                get_lang('Rename the uploaded file if it exists'),
7817
                'rename'
7818
            ),
7819
        ];
7820
        $form->addGroup($group, null, get_lang('If file exists:'));
7821
7822
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
7823
        $defaultFileExistsOption = 'rename';
7824
        if (!empty($fileExistsOption)) {
7825
            $defaultFileExistsOption = $fileExistsOption;
7826
        }
7827
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
7828
7829
        // Check box options
7830
        $form->addElement(
7831
            'checkbox',
7832
            'unzip',
7833
            get_lang('Options'),
7834
            get_lang('Uncompress zip')
7835
        );
7836
7837
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
7838
        $form->addMultipleUpload($url);
7839
7840
        $lpItem = new CLpItem();
7841
        $lpItem->setTitle('');
7842
        $lpItem->setItemType(TOOL_DOCUMENT);
7843
        $new = $this->displayDocumentForm('add', $lpItem);
7844
7845
        /*$lpItem = new CLpItem();
7846
        $lpItem->setItemType(TOOL_READOUT_TEXT);
7847
        $frmReadOutText = $this->displayDocumentForm('add');*/
7848
7849
        $headers = [
7850
            get_lang('Files'),
7851
            get_lang('Create a new document'),
7852
            //get_lang('Create read-out text'),
7853
            get_lang('Upload'),
7854
        ];
7855
7856
        return Display::tabs(
7857
            $headers,
7858
            [$documentTree, $new, $form->returnForm()],
7859
            'subtab'
7860
        );
7861
    }
7862
7863
    /**
7864
     * Creates a list with all the exercises (quiz) in it.
7865
     *
7866
     * @return string
7867
     */
7868
    public function get_exercises()
7869
    {
7870
        $course_id = api_get_course_int_id();
7871
        $session_id = api_get_session_id();
7872
        $userInfo = api_get_user_info();
7873
7874
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7875
        $condition_session = api_get_session_condition($session_id, true, true);
7876
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
7877
7878
        //$activeCondition = ' active <> -1 ';
7879
        $active = 2;
7880
        if ($setting) {
7881
            $active = 1;
7882
            //$activeCondition = ' active = 1 ';
7883
        }
7884
7885
        $categoryCondition = '';
7886
7887
        $keyword = $_REQUEST['keyword'] ?? null;
7888
        $categoryId = $_REQUEST['category_id'] ?? null;
7889
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
7890
            $categoryCondition = " AND exercise_category_id = $categoryId ";
7891
        }
7892
7893
        $keywordCondition = '';
7894
7895
        if (!empty($keyword)) {
7896
            $keyword = Database::escape_string($keyword);
7897
            $keywordCondition = " AND title LIKE '%$keyword%' ";
7898
        }
7899
        */
7900
        $course = api_get_course_entity($course_id);
7901
        $session = api_get_session_entity($session_id);
7902
7903
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
7904
        /** @var CQuiz[] $exercises */
7905
        $exercises = $qb->getQuery()->getResult();
7906
7907
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
7908
                     WHERE
7909
                            c_id = $course_id AND
7910
                            $activeCondition
7911
                            $condition_session
7912
                            $categoryCondition
7913
                            $keywordCondition
7914
                     ORDER BY title ASC";
7915
        $res_quiz = Database::query($sql_quiz);*/
7916
7917
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
7918
7919
        // Create a search-box
7920
        $form = new FormValidator('search_simple', 'get', $currentUrl);
7921
        $form->addHidden('action', 'add_item');
7922
        $form->addHidden('type', 'step');
7923
        $form->addHidden('lp_id', $this->lp_id);
7924
        $form->addHidden('lp_build_selected', '2');
7925
7926
        $form->addCourseHiddenParams();
7927
        $form->addText(
7928
            'keyword',
7929
            get_lang('Search'),
7930
            false,
7931
            [
7932
                'aria-label' => get_lang('Search'),
7933
            ]
7934
        );
7935
7936
        if (api_get_configuration_value('allow_exercise_categories')) {
7937
            $manager = new ExerciseCategoryManager();
7938
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
7939
            if (!empty($options)) {
7940
                $form->addSelect(
7941
                    'category_id',
7942
                    get_lang('Category'),
7943
                    $options,
7944
                    ['placeholder' => get_lang('Please select an option')]
7945
                );
7946
            }
7947
        }
7948
7949
        $form->addButtonSearch(get_lang('Search'));
7950
        $return = $form->returnForm();
7951
7952
        $return .= '<ul class="lp_resource">';
7953
        $return .= '<li class="lp_resource_element">';
7954
        $return .= Display::return_icon('new_exercice.png');
7955
        $return .= '<a
7956
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
7957
            get_lang('New test').'</a>';
7958
        $return .= '</li>';
7959
7960
        $previewIcon = Display::return_icon(
7961
            'preview_view.png',
7962
            get_lang('Preview')
7963
        );
7964
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
7965
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7966
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
7967
        foreach ($exercises as $exercise) {
7968
            $exerciseId = $exercise->getIid();
7969
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
7970
            $visibility = $exercise->isVisible($course, $session);
7971
7972
            $link = Display::url(
7973
                $previewIcon,
7974
                $exerciseUrl.'&exerciseId='.$exerciseId,
7975
                ['target' => '_blank']
7976
            );
7977
            $return .= '<li class="lp_resource_element" title="'.$title.'">';
7978
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
7979
            $return .= $quizIcon;
7980
            $sessionStar = '';
7981
            /*$sessionStar = api_get_session_image(
7982
                $row_quiz['session_id'],
7983
                $userInfo['status']
7984
            );*/
7985
            $return .= Display::url(
7986
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
7987
                api_get_self().'?'.
7988
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
7989
                [
7990
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
7991
                    'data_type' => 'quiz',
7992
                    'data_id' => $exerciseId,
7993
                ]
7994
            );
7995
            $return .= '</li>';
7996
        }
7997
7998
        $return .= '</ul>';
7999
8000
        return $return;
8001
    }
8002
8003
    /**
8004
     * Creates a list with all the links in it.
8005
     *
8006
     * @return string
8007
     */
8008
    public function get_links()
8009
    {
8010
        $sessionId = api_get_session_id();
8011
        $repo = Container::getLinkRepository();
8012
8013
        $course = api_get_course_entity();
8014
        $session = api_get_session_entity($sessionId);
8015
        $qb = $repo->getResourcesByCourse($course, $session);
8016
        /** @var CLink[] $links */
8017
        $links = $qb->getQuery()->getResult();
8018
8019
        $selfUrl = api_get_self();
8020
        $courseIdReq = api_get_cidreq();
8021
        $userInfo = api_get_user_info();
8022
8023
        $moveEverywhereIcon = Display::return_icon(
8024
            'move_everywhere.png',
8025
            get_lang('Move'),
8026
            [],
8027
            ICON_SIZE_TINY
8028
        );
8029
8030
        /*$condition_session = api_get_session_condition(
8031
            $session_id,
8032
            true,
8033
            true,
8034
            'link.session_id'
8035
        );
8036
8037
        $sql = "SELECT
8038
                    link.id as link_id,
8039
                    link.title as link_title,
8040
                    link.session_id as link_session_id,
8041
                    link.category_id as category_id,
8042
                    link_category.category_title as category_title
8043
                FROM $tbl_link as link
8044
                LEFT JOIN $linkCategoryTable as link_category
8045
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8046
                WHERE link.c_id = $course_id $condition_session
8047
                ORDER BY link_category.category_title ASC, link.title ASC";
8048
        $result = Database::query($sql);*/
8049
        $categorizedLinks = [];
8050
        $categories = [];
8051
8052
        foreach ($links as $link) {
8053
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8054
8055
            if (empty($categoryId)) {
8056
                $categories[0] = get_lang('Uncategorized');
8057
            } else {
8058
                $category = $link->getCategory();
8059
                $categories[$categoryId] = $category->getCategoryTitle();
8060
            }
8061
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8062
        }
8063
8064
        $linksHtmlCode =
8065
            '<script>
8066
            function toggle_tool(tool, id) {
8067
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8068
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8069
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8070
                } else {
8071
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8072
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8073
                }
8074
            }
8075
        </script>
8076
8077
        <ul class="lp_resource">
8078
            <li class="lp_resource_element">
8079
                '.Display::return_icon('linksnew.gif').'
8080
                <a
8081
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
8082
                title="'.get_lang('Add a link').'">'.
8083
                get_lang('Add a link').'
8084
                </a>
8085
            </li>';
8086
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
8087
        foreach ($categorizedLinks as $categoryId => $links) {
8088
            $linkNodes = null;
8089
            /** @var CLink $link */
8090
            foreach ($links as $key => $link) {
8091
                $title = $link->getTitle();
8092
8093
                $linkUrl = Display::url(
8094
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8095
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8096
                    ['target' => '_blank']
8097
                );
8098
8099
                if ($link->isVisible($course, $session)) {
8100
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8101
                    $sessionStar = '';
8102
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
8103
                    $link = Display::url(
8104
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
8105
                        $url,
8106
                        [
8107
                            'class' => 'moved link_with_id',
8108
                            'data_id' => $key,
8109
                            'data_type' => TOOL_LINK,
8110
                            'title' => $title,
8111
                        ]
8112
                    );
8113
                    $linkNodes .=
8114
                        '<li class="lp_resource_element">
8115
                         <a class="moved" href="#">'.
8116
                            $moveEverywhereIcon.
8117
                        '</a>
8118
                        '.$linkIcon.$link.'
8119
                        </li>';
8120
                }
8121
            }
8122
            $linksHtmlCode .=
8123
                '<li>
8124
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8125
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8126
                    align="absbottom" />
8127
                </a>
8128
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8129
            </li>
8130
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8131
        }
8132
        $linksHtmlCode .= '</ul>';
8133
8134
        return $linksHtmlCode;
8135
    }
8136
8137
    /**
8138
     * Creates a list with all the student publications in it.
8139
     *
8140
     * @return string
8141
     */
8142
    public function get_student_publications()
8143
    {
8144
        $return = '<ul class="lp_resource">';
8145
        $return .= '<li class="lp_resource_element">';
8146
        /*
8147
        $return .= Display::return_icon('works_new.gif');
8148
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8149
            get_lang('Add the Assignments tool to the course').'</a>';
8150
        $return .= '</li>';*/
8151
8152
        $works = getWorkListTeacher(0, 100, null, null, null);
8153
        if (!empty($works)) {
8154
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8155
            foreach ($works as $work) {
8156
                $link = Display::url(
8157
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8158
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8159
                    ['target' => '_blank']
8160
                );
8161
8162
                $return .= '<li class="lp_resource_element">';
8163
                $return .= '<a class="moved" href="#">';
8164
                $return .= Display::return_icon(
8165
                    'move_everywhere.png',
8166
                    get_lang('Move'),
8167
                    [],
8168
                    ICON_SIZE_TINY
8169
                );
8170
                $return .= '</a> ';
8171
8172
                $return .= $icon;
8173
                $return .= Display::url(
8174
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
8175
                    api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
8176
                    [
8177
                        'class' => 'moved link_with_id',
8178
                        'data_id' => $work['iid'],
8179
                        'data_type' => TOOL_STUDENTPUBLICATION,
8180
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
8181
                    ]
8182
                );
8183
8184
                $return .= '</li>';
8185
            }
8186
        }
8187
8188
        $return .= '</ul>';
8189
8190
        return $return;
8191
    }
8192
8193
    /**
8194
     * Creates a list with all the forums in it.
8195
     *
8196
     * @return string
8197
     */
8198
    public function get_forums()
8199
    {
8200
        $forumCategories = get_forum_categories();
8201
        $forumsInNoCategory = get_forums_in_category(0);
8202
        if (!empty($forumsInNoCategory)) {
8203
            $forumCategories = array_merge(
8204
                $forumCategories,
8205
                [
8206
                    [
8207
                        'cat_id' => 0,
8208
                        'session_id' => 0,
8209
                        'visibility' => 1,
8210
                        'cat_comment' => null,
8211
                    ],
8212
                ]
8213
            );
8214
        }
8215
8216
        $a_forums = [];
8217
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8218
        $sessionEntity = api_get_session_entity(api_get_session_id());
8219
8220
        foreach ($forumCategories as $forumCategory) {
8221
            // The forums in this category.
8222
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8223
            if (!empty($forumsInCategory)) {
8224
                foreach ($forumsInCategory as $forum) {
8225
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8226
                        $a_forums[] = $forum;
8227
                    }
8228
                }
8229
            }
8230
        }
8231
8232
        $return = '<ul class="lp_resource">';
8233
8234
        // First add link
8235
        $return .= '<li class="lp_resource_element">';
8236
        $return .= Display::return_icon('new_forum.png');
8237
        $return .= Display::url(
8238
            get_lang('Create a new forum'),
8239
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8240
                'action' => 'add',
8241
                'content' => 'forum',
8242
                'lp_id' => $this->lp_id,
8243
            ]),
8244
            ['title' => get_lang('Create a new forum')]
8245
        );
8246
        $return .= '</li>';
8247
8248
        $return .= '<script>
8249
            function toggle_forum(forum_id) {
8250
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8251
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8252
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8253
                } else {
8254
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8255
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8256
                }
8257
            }
8258
        </script>';
8259
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8260
        foreach ($a_forums as $forum) {
8261
            $forumId = $forum->getIid();
8262
            $title = Security::remove_XSS($forum->getForumTitle());
8263
            $link = Display::url(
8264
                Display::return_icon('preview_view.png', get_lang('Preview')),
8265
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8266
                ['target' => '_blank']
8267
            );
8268
8269
            $return .= '<li class="lp_resource_element">';
8270
            $return .= '<a class="moved" href="#">';
8271
            $return .= $moveIcon;
8272
            $return .= ' </a>';
8273
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8274
8275
            $moveLink = Display::url(
8276
                $title.' '.$link,
8277
                api_get_self().'?'.
8278
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
8279
                [
8280
                    'class' => 'moved link_with_id',
8281
                    'data_id' => $forumId,
8282
                    'data_type' => TOOL_FORUM,
8283
                    'title' => $title,
8284
                    'style' => 'vertical-align:middle',
8285
                ]
8286
            );
8287
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8288
                            <img
8289
                                src="'.Display::returnIconPath('add.png').'"
8290
                                id="forum_'.$forumId.'_opener" align="absbottom"
8291
                             />
8292
                        </a>
8293
                        '.$moveLink;
8294
            $return .= '</li>';
8295
8296
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8297
            $threads = get_threads($forumId);
8298
            if (is_array($threads)) {
8299
                foreach ($threads as $thread) {
8300
                    $threadId = $thread->getIid();
8301
                    $link = Display::url(
8302
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8303
                        api_get_path(WEB_CODE_PATH).
8304
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8305
                        ['target' => '_blank']
8306
                    );
8307
8308
                    $return .= '<li class="lp_resource_element">';
8309
                    $return .= '&nbsp;<a class="moved" href="#">';
8310
                    $return .= $moveIcon;
8311
                    $return .= ' </a>';
8312
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8313
                    $return .= '<a
8314
                        class="moved link_with_id"
8315
                        data_id="'.$thread->getIid().'"
8316
                        data_type="'.TOOL_THREAD.'"
8317
                        title="'.$thread->getThreadTitle().'"
8318
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
8319
                        >'.
8320
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8321
                    $return .= '</li>';
8322
                }
8323
            }
8324
            $return .= '</div>';
8325
        }
8326
        $return .= '</ul>';
8327
8328
        return $return;
8329
    }
8330
8331
    /**
8332
     * // TODO: The output encoding should be equal to the system encoding.
8333
     *
8334
     * Exports the learning path as a SCORM package. This is the main function that
8335
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8336
     * whole thing and returns the zip.
8337
     *
8338
     * This method needs to be called in PHP5, as it will fail with non-adequate
8339
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8340
     * you need to call it on a learnpath object.
8341
     *
8342
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8343
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8344
     * path has been modified, it should use the generic method here below.
8345
     *
8346
     * @return string Returns the zip package string, or null if error
8347
     */
8348
    public function scormExport()
8349
    {
8350
        api_set_more_memory_and_time_limits();
8351
8352
        $_course = api_get_course_info();
8353
        $course_id = $_course['real_id'];
8354
        // Create the zip handler (this will remain available throughout the method).
8355
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8356
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8357
        $temp_dir_short = uniqid('scorm_export', true);
8358
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8359
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8360
        $zip_folder = new PclZip($temp_zip_file);
8361
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8362
        $root_path = $main_path = api_get_path(SYS_PATH);
8363
        $files_cleanup = [];
8364
8365
        // Place to temporarily stash the zip file.
8366
        // create the temp dir if it doesn't exist
8367
        // or do a cleanup before creating the zip file.
8368
        if (!is_dir($temp_zip_dir)) {
8369
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8370
        } else {
8371
            // Cleanup: Check the temp dir for old files and delete them.
8372
            $handle = opendir($temp_zip_dir);
8373
            while (false !== ($file = readdir($handle))) {
8374
                if ('.' != $file && '..' != $file) {
8375
                    unlink("$temp_zip_dir/$file");
8376
                }
8377
            }
8378
            closedir($handle);
8379
        }
8380
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8381
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8382
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8383
        ) {
8384
            // Remove the possible . at the end of the path.
8385
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8386
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8387
            mkdir(
8388
                $dest_path_to_scorm_folder,
8389
                api_get_permissions_for_new_directories(),
8390
                true
8391
            );
8392
            copyr(
8393
                $current_course_path.'/scorm/'.$this->path,
8394
                $dest_path_to_scorm_folder,
8395
                ['imsmanifest'],
8396
                $zip_files
8397
            );
8398
        }
8399
8400
        // Build a dummy imsmanifest structure.
8401
        // Do not add to the zip yet (we still need it).
8402
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8403
        // Aggregation Model official document, section "2.3 Content Packaging".
8404
        // We are going to build a UTF-8 encoded manifest.
8405
        // Later we will recode it to the desired (and supported) encoding.
8406
        $xmldoc = new DOMDocument('1.0');
8407
        $root = $xmldoc->createElement('manifest');
8408
        $root->setAttribute('identifier', 'SingleCourseManifest');
8409
        $root->setAttribute('version', '1.1');
8410
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8411
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8412
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8413
        $root->setAttribute(
8414
            'xsi:schemaLocation',
8415
            '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'
8416
        );
8417
        // Build mandatory sub-root container elements.
8418
        $metadata = $xmldoc->createElement('metadata');
8419
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8420
        $metadata->appendChild($md_schema);
8421
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8422
        $metadata->appendChild($md_schemaversion);
8423
        $root->appendChild($metadata);
8424
8425
        $organizations = $xmldoc->createElement('organizations');
8426
        $resources = $xmldoc->createElement('resources');
8427
8428
        // Build the only organization we will use in building our learnpaths.
8429
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8430
        $organization = $xmldoc->createElement('organization');
8431
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8432
        // To set the title of the SCORM entity (=organization), we take the name given
8433
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8434
        // learning path charset) as it is the encoding that defines how it is stored
8435
        // in the database. Then we convert it to HTML entities again as the "&" character
8436
        // alone is not authorized in XML (must be &amp;).
8437
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8438
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8439
        $organization->appendChild($org_title);
8440
        $folder_name = 'document';
8441
8442
        // Removes the learning_path/scorm_folder path when exporting see #4841
8443
        $path_to_remove = '';
8444
        $path_to_replace = '';
8445
        $result = $this->generate_lp_folder($_course);
8446
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8447
            $path_to_remove = 'document'.$result['dir'];
8448
            $path_to_replace = $folder_name.'/';
8449
        }
8450
8451
        // Fixes chamilo scorm exports
8452
        if ('chamilo_scorm_export' === $this->ref) {
8453
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8454
        }
8455
8456
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8457
        $link_updates = [];
8458
        $links_to_create = [];
8459
        foreach ($this->ordered_items as $index => $itemId) {
8460
            /** @var learnpathItem $item */
8461
            $item = $this->items[$itemId];
8462
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8463
                // Get included documents from this item.
8464
                if ('sco' === $item->type) {
8465
                    $inc_docs = $item->get_resources_from_source(
8466
                        null,
8467
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8468
                    );
8469
                } else {
8470
                    $inc_docs = $item->get_resources_from_source();
8471
                }
8472
8473
                // Give a child element <item> to the <organization> element.
8474
                $my_item_id = $item->get_id();
8475
                $my_item = $xmldoc->createElement('item');
8476
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8477
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8478
                $my_item->setAttribute('isvisible', 'true');
8479
                // Give a child element <title> to the <item> element.
8480
                $my_title = $xmldoc->createElement(
8481
                    'title',
8482
                    htmlspecialchars(
8483
                        api_utf8_encode($item->get_title()),
8484
                        ENT_QUOTES,
8485
                        'UTF-8'
8486
                    )
8487
                );
8488
                $my_item->appendChild($my_title);
8489
                // Give a child element <adlcp:prerequisites> to the <item> element.
8490
                $my_prereqs = $xmldoc->createElement(
8491
                    'adlcp:prerequisites',
8492
                    $this->get_scorm_prereq_string($my_item_id)
8493
                );
8494
                $my_prereqs->setAttribute('type', 'aicc_script');
8495
                $my_item->appendChild($my_prereqs);
8496
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8497
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8498
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8499
                //$xmldoc->createElement('adlcp:timelimitaction','');
8500
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8501
                //$xmldoc->createElement('adlcp:datafromlms','');
8502
                // Give a child element <adlcp:masteryscore> to the <item> element.
8503
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8504
                $my_item->appendChild($my_masteryscore);
8505
8506
                // Attach this item to the organization element or hits parent if there is one.
8507
                if (!empty($item->parent) && 0 != $item->parent) {
8508
                    $children = $organization->childNodes;
8509
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8510
                    if (is_object($possible_parent)) {
8511
                        $possible_parent->appendChild($my_item);
8512
                    } else {
8513
                        if ($this->debug > 0) {
8514
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8515
                        }
8516
                    }
8517
                } else {
8518
                    if ($this->debug > 0) {
8519
                        error_log('No parent');
8520
                    }
8521
                    $organization->appendChild($my_item);
8522
                }
8523
8524
                // Get the path of the file(s) from the course directory root.
8525
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8526
                $my_xml_file_path = $my_file_path;
8527
                if (!empty($path_to_remove)) {
8528
                    // From docs
8529
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8530
8531
                    // From quiz
8532
                    if ('chamilo_scorm_export' === $this->ref) {
8533
                        $path_to_remove = 'scorm/'.$this->path.'/';
8534
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8535
                    }
8536
                }
8537
8538
                $my_sub_dir = dirname($my_file_path);
8539
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8540
                $my_xml_sub_dir = $my_sub_dir;
8541
                // Give a <resource> child to the <resources> element
8542
                $my_resource = $xmldoc->createElement('resource');
8543
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8544
                $my_resource->setAttribute('type', 'webcontent');
8545
                $my_resource->setAttribute('href', $my_xml_file_path);
8546
                // adlcp:scormtype can be either 'sco' or 'asset'.
8547
                if ('sco' === $item->type) {
8548
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8549
                } else {
8550
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8551
                }
8552
                // xml:base is the base directory to find the files declared in this resource.
8553
                $my_resource->setAttribute('xml:base', '');
8554
                // Give a <file> child to the <resource> element.
8555
                $my_file = $xmldoc->createElement('file');
8556
                $my_file->setAttribute('href', $my_xml_file_path);
8557
                $my_resource->appendChild($my_file);
8558
8559
                // Dependency to other files - not yet supported.
8560
                $i = 1;
8561
                if ($inc_docs) {
8562
                    foreach ($inc_docs as $doc_info) {
8563
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8564
                            continue;
8565
                        }
8566
                        $my_dep = $xmldoc->createElement('resource');
8567
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8568
                        $my_dep->setAttribute('identifier', $res_id);
8569
                        $my_dep->setAttribute('type', 'webcontent');
8570
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8571
                        $my_dep_file = $xmldoc->createElement('file');
8572
                        // Check type of URL.
8573
                        if ('remote' == $doc_info[1]) {
8574
                            // Remote file. Save url as is.
8575
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8576
                            $my_dep->setAttribute('xml:base', '');
8577
                        } elseif ('local' === $doc_info[1]) {
8578
                            switch ($doc_info[2]) {
8579
                                case 'url':
8580
                                    // Local URL - save path as url for now, don't zip file.
8581
                                    $abs_path = api_get_path(SYS_PATH).
8582
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8583
                                    $current_dir = dirname($abs_path);
8584
                                    $current_dir = str_replace('\\', '/', $current_dir);
8585
                                    $file_path = realpath($abs_path);
8586
                                    $file_path = str_replace('\\', '/', $file_path);
8587
                                    $my_dep_file->setAttribute('href', $file_path);
8588
                                    $my_dep->setAttribute('xml:base', '');
8589
                                    if (false !== strstr($file_path, $main_path)) {
8590
                                        // The calculated real path is really inside Chamilo's root path.
8591
                                        // Reduce file path to what's under the DocumentRoot.
8592
                                        $replace = $file_path;
8593
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8594
                                        $destinationFile = $file_path;
8595
8596
                                        if (false !== strstr($file_path, 'upload/users')) {
8597
                                            $pos = strpos($file_path, 'my_files/');
8598
                                            if (false !== $pos) {
8599
                                                $onlyDirectory = str_replace(
8600
                                                    'upload/users/',
8601
                                                    '',
8602
                                                    substr($file_path, $pos, strlen($file_path))
8603
                                                );
8604
                                            }
8605
                                            $replace = $onlyDirectory;
8606
                                            $destinationFile = $replace;
8607
                                        }
8608
                                        $zip_files_abs[] = $file_path;
8609
                                        $link_updates[$my_file_path][] = [
8610
                                            'orig' => $doc_info[0],
8611
                                            'dest' => $destinationFile,
8612
                                            'replace' => $replace,
8613
                                        ];
8614
                                        $my_dep_file->setAttribute('href', $file_path);
8615
                                        $my_dep->setAttribute('xml:base', '');
8616
                                    } elseif (empty($file_path)) {
8617
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8618
                                        $file_path = str_replace('//', '/', $file_path);
8619
                                        if (file_exists($file_path)) {
8620
                                            // We get the relative path.
8621
                                            $file_path = substr($file_path, strlen($current_dir));
8622
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8623
                                            $link_updates[$my_file_path][] = [
8624
                                                'orig' => $doc_info[0],
8625
                                                'dest' => $file_path,
8626
                                            ];
8627
                                            $my_dep_file->setAttribute('href', $file_path);
8628
                                            $my_dep->setAttribute('xml:base', '');
8629
                                        }
8630
                                    }
8631
                                    break;
8632
                                case 'abs':
8633
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8634
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8635
                                    $my_dep->setAttribute('xml:base', '');
8636
8637
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8638
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8639
                                    $abs_img_path_without_subdir = $doc_info[0];
8640
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8641
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8642
                                    if (0 === $pos) {
8643
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8644
                                    }
8645
8646
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8647
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8648
8649
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8650
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8651
                                    // Check if the current document is in that path.
8652
                                    if (false !== strstr($file_path, $cur_path)) {
8653
                                        $destinationFile = substr($file_path, strlen($cur_path));
8654
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8655
8656
                                        $fileToTest = $cur_path.$my_file_path;
8657
                                        if (!empty($path_to_remove)) {
8658
                                            $fileToTest = str_replace(
8659
                                                $path_to_remove.'/',
8660
                                                $path_to_replace,
8661
                                                $cur_path.$my_file_path
8662
                                            );
8663
                                        }
8664
8665
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8666
8667
                                        // Put the current document in the zip (this array is the array
8668
                                        // that will manage documents already in the course folder - relative).
8669
                                        $zip_files[] = $filePathNoCoursePart;
8670
                                        // Update the links to the current document in the
8671
                                        // containing document (make them relative).
8672
                                        $link_updates[$my_file_path][] = [
8673
                                            'orig' => $doc_info[0],
8674
                                            'dest' => $destinationFile,
8675
                                            'replace' => $relative_path,
8676
                                        ];
8677
8678
                                        $my_dep_file->setAttribute('href', $file_path);
8679
                                        $my_dep->setAttribute('xml:base', '');
8680
                                    } elseif (false !== strstr($file_path, $main_path)) {
8681
                                        // The calculated real path is really inside Chamilo's root path.
8682
                                        // Reduce file path to what's under the DocumentRoot.
8683
                                        $file_path = substr($file_path, strlen($root_path));
8684
                                        $zip_files_abs[] = $file_path;
8685
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8686
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8687
                                        $my_dep->setAttribute('xml:base', '');
8688
                                    } elseif (empty($file_path)) {
8689
                                        // Probably this is an image inside "/main" directory
8690
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8691
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8692
8693
                                        if (file_exists($file_path)) {
8694
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8695
                                                // We get the relative path.
8696
                                                $pos = strpos($file_path, 'main/default_course_document/');
8697
                                                if (false !== $pos) {
8698
                                                    $onlyDirectory = str_replace(
8699
                                                        'main/default_course_document/',
8700
                                                        '',
8701
                                                        substr($file_path, $pos, strlen($file_path))
8702
                                                    );
8703
                                                }
8704
8705
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8706
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8707
                                                $zip_files_abs[] = $fileAbs;
8708
                                                $link_updates[$my_file_path][] = [
8709
                                                    'orig' => $doc_info[0],
8710
                                                    'dest' => $destinationFile,
8711
                                                ];
8712
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8713
                                                $my_dep->setAttribute('xml:base', '');
8714
                                            }
8715
                                        }
8716
                                    }
8717
                                    break;
8718
                                case 'rel':
8719
                                    // Path relative to the current document.
8720
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8721
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8722
                                        // Relative path going up.
8723
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8724
                                        $current_dir = str_replace('\\', '/', $current_dir);
8725
                                        $file_path = realpath($current_dir.$doc_info[0]);
8726
                                        $file_path = str_replace('\\', '/', $file_path);
8727
                                        if (false !== strstr($file_path, $main_path)) {
8728
                                            // The calculated real path is really inside Chamilo's root path.
8729
                                            // Reduce file path to what's under the DocumentRoot.
8730
                                            $file_path = substr($file_path, strlen($root_path));
8731
                                            $zip_files_abs[] = $file_path;
8732
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8733
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8734
                                            $my_dep->setAttribute('xml:base', '');
8735
                                        }
8736
                                    } else {
8737
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8738
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8739
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8740
                                    }
8741
                                    break;
8742
                                default:
8743
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8744
                                    $my_dep->setAttribute('xml:base', '');
8745
                                    break;
8746
                            }
8747
                        }
8748
                        $my_dep->appendChild($my_dep_file);
8749
                        $resources->appendChild($my_dep);
8750
                        $dependency = $xmldoc->createElement('dependency');
8751
                        $dependency->setAttribute('identifierref', $res_id);
8752
                        $my_resource->appendChild($dependency);
8753
                        $i++;
8754
                    }
8755
                }
8756
                $resources->appendChild($my_resource);
8757
                $zip_files[] = $my_file_path;
8758
            } else {
8759
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8760
                switch ($item->type) {
8761
                    case TOOL_LINK:
8762
                        $my_item = $xmldoc->createElement('item');
8763
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8764
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8765
                        $my_item->setAttribute('isvisible', 'true');
8766
                        // Give a child element <title> to the <item> element.
8767
                        $my_title = $xmldoc->createElement(
8768
                            'title',
8769
                            htmlspecialchars(
8770
                                api_utf8_encode($item->get_title()),
8771
                                ENT_QUOTES,
8772
                                'UTF-8'
8773
                            )
8774
                        );
8775
                        $my_item->appendChild($my_title);
8776
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8777
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8778
                        $my_prereqs->setAttribute('type', 'aicc_script');
8779
                        $my_item->appendChild($my_prereqs);
8780
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8781
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8782
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8783
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8784
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8785
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8786
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8787
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8788
                        $my_item->appendChild($my_masteryscore);
8789
8790
                        // Attach this item to the organization element or its parent if there is one.
8791
                        if (!empty($item->parent) && 0 != $item->parent) {
8792
                            $children = $organization->childNodes;
8793
                            for ($i = 0; $i < $children->length; $i++) {
8794
                                $item_temp = $children->item($i);
8795
                                if ('item' == $item_temp->nodeName) {
8796
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8797
                                        $item_temp->appendChild($my_item);
8798
                                    }
8799
                                }
8800
                            }
8801
                        } else {
8802
                            $organization->appendChild($my_item);
8803
                        }
8804
8805
                        $my_file_path = 'link_'.$item->get_id().'.html';
8806
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8807
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8808
                        $rs = Database::query($sql);
8809
                        if ($link = Database::fetch_array($rs)) {
8810
                            $url = $link['url'];
8811
                            $title = stripslashes($link['title']);
8812
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8813
                            $my_xml_file_path = $my_file_path;
8814
                            $my_sub_dir = dirname($my_file_path);
8815
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8816
                            $my_xml_sub_dir = $my_sub_dir;
8817
                            // Give a <resource> child to the <resources> element.
8818
                            $my_resource = $xmldoc->createElement('resource');
8819
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8820
                            $my_resource->setAttribute('type', 'webcontent');
8821
                            $my_resource->setAttribute('href', $my_xml_file_path);
8822
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8823
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8824
                            // xml:base is the base directory to find the files declared in this resource.
8825
                            $my_resource->setAttribute('xml:base', '');
8826
                            // give a <file> child to the <resource> element.
8827
                            $my_file = $xmldoc->createElement('file');
8828
                            $my_file->setAttribute('href', $my_xml_file_path);
8829
                            $my_resource->appendChild($my_file);
8830
                            $resources->appendChild($my_resource);
8831
                        }
8832
                        break;
8833
                    case TOOL_QUIZ:
8834
                        $exe_id = $item->path;
8835
                        // Should be using ref when everything will be cleaned up in this regard.
8836
                        $exe = new Exercise();
8837
                        $exe->read($exe_id);
8838
                        $my_item = $xmldoc->createElement('item');
8839
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8840
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8841
                        $my_item->setAttribute('isvisible', 'true');
8842
                        // Give a child element <title> to the <item> element.
8843
                        $my_title = $xmldoc->createElement(
8844
                            'title',
8845
                            htmlspecialchars(
8846
                                api_utf8_encode($item->get_title()),
8847
                                ENT_QUOTES,
8848
                                'UTF-8'
8849
                            )
8850
                        );
8851
                        $my_item->appendChild($my_title);
8852
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
8853
                        $my_item->appendChild($my_max_score);
8854
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8855
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8856
                        $my_prereqs->setAttribute('type', 'aicc_script');
8857
                        $my_item->appendChild($my_prereqs);
8858
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8859
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8860
                        $my_item->appendChild($my_masteryscore);
8861
8862
                        // Attach this item to the organization element or hits parent if there is one.
8863
                        if (!empty($item->parent) && 0 != $item->parent) {
8864
                            $children = $organization->childNodes;
8865
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8866
                            if ($possible_parent) {
8867
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
8868
                                    $possible_parent->appendChild($my_item);
8869
                                }
8870
                            }
8871
                        } else {
8872
                            $organization->appendChild($my_item);
8873
                        }
8874
8875
                        // Get the path of the file(s) from the course directory root
8876
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8877
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
8878
                        // Write the contents of the exported exercise into a (big) html file
8879
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
8880
                        $scormExercise = new ScormExercise($exe, true);
8881
                        $contents = $scormExercise->export();
8882
8883
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
8884
                        $res = file_put_contents($tmp_file_path, $contents);
8885
                        if (false === $res) {
8886
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
8887
                        }
8888
                        $files_cleanup[] = $tmp_file_path;
8889
                        $my_xml_file_path = $my_file_path;
8890
                        $my_sub_dir = dirname($my_file_path);
8891
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8892
                        $my_xml_sub_dir = $my_sub_dir;
8893
                        // Give a <resource> child to the <resources> element.
8894
                        $my_resource = $xmldoc->createElement('resource');
8895
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8896
                        $my_resource->setAttribute('type', 'webcontent');
8897
                        $my_resource->setAttribute('href', $my_xml_file_path);
8898
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8899
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
8900
                        // xml:base is the base directory to find the files declared in this resource.
8901
                        $my_resource->setAttribute('xml:base', '');
8902
                        // Give a <file> child to the <resource> element.
8903
                        $my_file = $xmldoc->createElement('file');
8904
                        $my_file->setAttribute('href', $my_xml_file_path);
8905
                        $my_resource->appendChild($my_file);
8906
8907
                        // Get included docs.
8908
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
8909
8910
                        // Dependency to other files - not yet supported.
8911
                        $i = 1;
8912
                        foreach ($inc_docs as $doc_info) {
8913
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
8914
                                continue;
8915
                            }
8916
                            $my_dep = $xmldoc->createElement('resource');
8917
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8918
                            $my_dep->setAttribute('identifier', $res_id);
8919
                            $my_dep->setAttribute('type', 'webcontent');
8920
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
8921
                            $my_dep_file = $xmldoc->createElement('file');
8922
                            // Check type of URL.
8923
                            if ('remote' == $doc_info[1]) {
8924
                                // Remote file. Save url as is.
8925
                                $my_dep_file->setAttribute('href', $doc_info[0]);
8926
                                $my_dep->setAttribute('xml:base', '');
8927
                            } elseif ('local' == $doc_info[1]) {
8928
                                switch ($doc_info[2]) {
8929
                                    case 'url': // Local URL - save path as url for now, don't zip file.
8930
                                        // Save file but as local file (retrieve from URL).
8931
                                        $abs_path = api_get_path(SYS_PATH).
8932
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8933
                                        $current_dir = dirname($abs_path);
8934
                                        $current_dir = str_replace('\\', '/', $current_dir);
8935
                                        $file_path = realpath($abs_path);
8936
                                        $file_path = str_replace('\\', '/', $file_path);
8937
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8938
                                        $my_dep->setAttribute('xml:base', '');
8939
                                        if (false !== strstr($file_path, $main_path)) {
8940
                                            // The calculated real path is really inside the chamilo root path.
8941
                                            // Reduce file path to what's under the DocumentRoot.
8942
                                            $file_path = substr($file_path, strlen($root_path));
8943
                                            $zip_files_abs[] = $file_path;
8944
                                            $link_updates[$my_file_path][] = [
8945
                                                'orig' => $doc_info[0],
8946
                                                'dest' => 'document/'.$file_path,
8947
                                            ];
8948
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8949
                                            $my_dep->setAttribute('xml:base', '');
8950
                                        } elseif (empty($file_path)) {
8951
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8952
                                            $file_path = str_replace('//', '/', $file_path);
8953
                                            if (file_exists($file_path)) {
8954
                                                $file_path = substr($file_path, strlen($current_dir));
8955
                                                // We get the relative path.
8956
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8957
                                                $link_updates[$my_file_path][] = [
8958
                                                    'orig' => $doc_info[0],
8959
                                                    'dest' => 'document/'.$file_path,
8960
                                                ];
8961
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8962
                                                $my_dep->setAttribute('xml:base', '');
8963
                                            }
8964
                                        }
8965
                                        break;
8966
                                    case 'abs':
8967
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8968
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8969
                                        $current_dir = str_replace('\\', '/', $current_dir);
8970
                                        $file_path = realpath($doc_info[0]);
8971
                                        $file_path = str_replace('\\', '/', $file_path);
8972
                                        $my_dep_file->setAttribute('href', $file_path);
8973
                                        $my_dep->setAttribute('xml:base', '');
8974
8975
                                        if (false !== strstr($file_path, $main_path)) {
8976
                                            // The calculated real path is really inside the chamilo root path.
8977
                                            // Reduce file path to what's under the DocumentRoot.
8978
                                            $file_path = substr($file_path, strlen($root_path));
8979
                                            $zip_files_abs[] = $file_path;
8980
                                            $link_updates[$my_file_path][] = [
8981
                                                'orig' => $doc_info[0],
8982
                                                'dest' => $file_path,
8983
                                            ];
8984
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8985
                                            $my_dep->setAttribute('xml:base', '');
8986
                                        } elseif (empty($file_path)) {
8987
                                            $docSysPartPath = str_replace(
8988
                                                api_get_path(REL_COURSE_PATH),
8989
                                                '',
8990
                                                $doc_info[0]
8991
                                            );
8992
8993
                                            $docSysPartPathNoCourseCode = str_replace(
8994
                                                $_course['directory'].'/',
8995
                                                '',
8996
                                                $docSysPartPath
8997
                                            );
8998
8999
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9000
                                            if (file_exists($docSysPath)) {
9001
                                                $file_path = $docSysPartPathNoCourseCode;
9002
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9003
                                                $link_updates[$my_file_path][] = [
9004
                                                    'orig' => $doc_info[0],
9005
                                                    'dest' => $file_path,
9006
                                                ];
9007
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9008
                                                $my_dep->setAttribute('xml:base', '');
9009
                                            }
9010
                                        }
9011
                                        break;
9012
                                    case 'rel':
9013
                                        // Path relative to the current document. Save xml:base as current document's
9014
                                        // directory and save file in zip as subdir.file_path
9015
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9016
                                            // Relative path going up.
9017
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9018
                                            $current_dir = str_replace('\\', '/', $current_dir);
9019
                                            $file_path = realpath($current_dir.$doc_info[0]);
9020
                                            $file_path = str_replace('\\', '/', $file_path);
9021
                                            if (false !== strstr($file_path, $main_path)) {
9022
                                                // The calculated real path is really inside Chamilo's root path.
9023
                                                // Reduce file path to what's under the DocumentRoot.
9024
9025
                                                $file_path = substr($file_path, strlen($root_path));
9026
                                                $file_path_dest = $file_path;
9027
9028
                                                // File path is courses/CHAMILO/document/....
9029
                                                $info_file_path = explode('/', $file_path);
9030
                                                if ('courses' == $info_file_path[0]) {
9031
                                                    // Add character "/" in file path.
9032
                                                    $file_path_dest = 'document/'.$file_path;
9033
                                                }
9034
                                                $zip_files_abs[] = $file_path;
9035
9036
                                                $link_updates[$my_file_path][] = [
9037
                                                    'orig' => $doc_info[0],
9038
                                                    'dest' => $file_path_dest,
9039
                                                ];
9040
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9041
                                                $my_dep->setAttribute('xml:base', '');
9042
                                            }
9043
                                        } else {
9044
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9045
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9046
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9047
                                        }
9048
                                        break;
9049
                                    default:
9050
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9051
                                        $my_dep->setAttribute('xml:base', '');
9052
                                        break;
9053
                                }
9054
                            }
9055
                            $my_dep->appendChild($my_dep_file);
9056
                            $resources->appendChild($my_dep);
9057
                            $dependency = $xmldoc->createElement('dependency');
9058
                            $dependency->setAttribute('identifierref', $res_id);
9059
                            $my_resource->appendChild($dependency);
9060
                            $i++;
9061
                        }
9062
                        $resources->appendChild($my_resource);
9063
                        $zip_files[] = $my_file_path;
9064
                        break;
9065
                    default:
9066
                        // Get the path of the file(s) from the course directory root
9067
                        $my_file_path = 'non_exportable.html';
9068
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9069
                        $my_xml_file_path = $my_file_path;
9070
                        $my_sub_dir = dirname($my_file_path);
9071
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9072
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9073
                        $my_xml_sub_dir = $my_sub_dir;
9074
                        // Give a <resource> child to the <resources> element.
9075
                        $my_resource = $xmldoc->createElement('resource');
9076
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9077
                        $my_resource->setAttribute('type', 'webcontent');
9078
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9079
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9080
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9081
                        // xml:base is the base directory to find the files declared in this resource.
9082
                        $my_resource->setAttribute('xml:base', '');
9083
                        // Give a <file> child to the <resource> element.
9084
                        $my_file = $xmldoc->createElement('file');
9085
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9086
                        $my_resource->appendChild($my_file);
9087
                        $resources->appendChild($my_resource);
9088
                        break;
9089
                }
9090
            }
9091
        }
9092
        $organizations->appendChild($organization);
9093
        $root->appendChild($organizations);
9094
        $root->appendChild($resources);
9095
        $xmldoc->appendChild($root);
9096
9097
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9098
9099
        // then add the file to the zip, then destroy the file (this is done automatically).
9100
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9101
        foreach ($zip_files as $file_path) {
9102
            if (empty($file_path)) {
9103
                continue;
9104
            }
9105
9106
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9107
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9108
9109
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9110
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9111
            }
9112
9113
            $this->create_path($dest_file);
9114
            @copy($filePath, $dest_file);
9115
9116
            // Check if the file needs a link update.
9117
            if (in_array($file_path, array_keys($link_updates))) {
9118
                $string = file_get_contents($dest_file);
9119
                unlink($dest_file);
9120
                foreach ($link_updates[$file_path] as $old_new) {
9121
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9122
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9123
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9124
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9125
                    if ('flv' === substr($old_new['dest'], -3) &&
9126
                        'main/' === substr($old_new['dest'], 0, 5)
9127
                    ) {
9128
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9129
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9130
                        'video/' === substr($old_new['dest'], 0, 6)
9131
                    ) {
9132
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9133
                    }
9134
9135
                    // Fix to avoid problems with default_course_document
9136
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9137
                        $newDestination = $old_new['dest'];
9138
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9139
                            $newDestination = $old_new['replace'];
9140
                        }
9141
                    } else {
9142
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9143
                    }
9144
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9145
9146
                    // Add files inside the HTMLs
9147
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9148
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9149
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9150
                        copy(
9151
                            $sys_course_path.$new_path,
9152
                            $destinationFile
9153
                        );
9154
                    }
9155
                }
9156
                file_put_contents($dest_file, $string);
9157
            }
9158
9159
            if (file_exists($filePath) && $copyAll) {
9160
                $extension = $this->get_extension($filePath);
9161
                if (in_array($extension, ['html', 'html'])) {
9162
                    $containerOrigin = dirname($filePath);
9163
                    $containerDestination = dirname($dest_file);
9164
9165
                    $finder = new Finder();
9166
                    $finder->files()->in($containerOrigin)
9167
                        ->notName('*_DELETED_*')
9168
                        ->exclude('share_folder')
9169
                        ->exclude('chat_files')
9170
                        ->exclude('certificates')
9171
                    ;
9172
9173
                    if (is_dir($containerOrigin) &&
9174
                        is_dir($containerDestination)
9175
                    ) {
9176
                        $fs = new Filesystem();
9177
                        $fs->mirror(
9178
                            $containerOrigin,
9179
                            $containerDestination,
9180
                            $finder
9181
                        );
9182
                    }
9183
                }
9184
            }
9185
        }
9186
9187
        foreach ($zip_files_abs as $file_path) {
9188
            if (empty($file_path)) {
9189
                continue;
9190
            }
9191
9192
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9193
                continue;
9194
            }
9195
9196
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9197
            if (false !== strstr($file_path, 'upload/users')) {
9198
                $pos = strpos($file_path, 'my_files/');
9199
                if (false !== $pos) {
9200
                    $onlyDirectory = str_replace(
9201
                        'upload/users/',
9202
                        '',
9203
                        substr($file_path, $pos, strlen($file_path))
9204
                    );
9205
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9206
                }
9207
            }
9208
9209
            if (false !== strstr($file_path, 'default_course_document/')) {
9210
                $replace = str_replace('/main', '', $file_path);
9211
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9212
            }
9213
9214
            if (empty($dest_file)) {
9215
                continue;
9216
            }
9217
9218
            $this->create_path($dest_file);
9219
            copy($main_path.$file_path, $dest_file);
9220
            // Check if the file needs a link update.
9221
            if (in_array($file_path, array_keys($link_updates))) {
9222
                $string = file_get_contents($dest_file);
9223
                unlink($dest_file);
9224
                foreach ($link_updates[$file_path] as $old_new) {
9225
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9226
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9227
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9228
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9229
                    if ('flv' == substr($old_new['dest'], -3) &&
9230
                        'main/' == substr($old_new['dest'], 0, 5)
9231
                    ) {
9232
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9233
                    }
9234
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9235
                }
9236
                file_put_contents($dest_file, $string);
9237
            }
9238
        }
9239
9240
        if (is_array($links_to_create)) {
9241
            foreach ($links_to_create as $file => $link) {
9242
                $content = '<!DOCTYPE html><head>
9243
                            <meta charset="'.api_get_language_isocode().'" />
9244
                            <title>'.$link['title'].'</title>
9245
                            </head>
9246
                            <body dir="'.api_get_text_direction().'">
9247
                            <div style="text-align:center">
9248
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9249
                            </body>
9250
                            </html>';
9251
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9252
            }
9253
        }
9254
9255
        // Add non exportable message explanation.
9256
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9257
        $file_content = '<!DOCTYPE html><head>
9258
                        <meta charset="'.api_get_language_isocode().'" />
9259
                        <title>'.$lang_not_exportable.'</title>
9260
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9261
                        </head>
9262
                        <body dir="'.api_get_text_direction().'">';
9263
        $file_content .=
9264
            <<<EOD
9265
                    <style>
9266
            .error-message {
9267
                font-family: arial, verdana, helvetica, sans-serif;
9268
                border-width: 1px;
9269
                border-style: solid;
9270
                left: 50%;
9271
                margin: 10px auto;
9272
                min-height: 30px;
9273
                padding: 5px;
9274
                right: 50%;
9275
                width: 500px;
9276
                background-color: #FFD1D1;
9277
                border-color: #FF0000;
9278
                color: #000;
9279
            }
9280
        </style>
9281
    <body>
9282
        <div class="error-message">
9283
            $lang_not_exportable
9284
        </div>
9285
    </body>
9286
</html>
9287
EOD;
9288
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9289
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9290
        }
9291
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9292
9293
        // Add the extra files that go along with a SCORM package.
9294
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9295
9296
        $fs = new Filesystem();
9297
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9298
9299
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9300
        $manifest = @$xmldoc->saveXML();
9301
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9302
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9303
        $zip_folder->add(
9304
            $archivePath.'/'.$temp_dir_short,
9305
            PCLZIP_OPT_REMOVE_PATH,
9306
            $archivePath.'/'.$temp_dir_short.'/'
9307
        );
9308
9309
        // Clean possible temporary files.
9310
        foreach ($files_cleanup as $file) {
9311
            $res = unlink($file);
9312
            if (false === $res) {
9313
                error_log(
9314
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9315
                    0
9316
                );
9317
            }
9318
        }
9319
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9320
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9321
    }
9322
9323
    /**
9324
     * @param int $lp_id
9325
     *
9326
     * @return bool
9327
     */
9328
    public function scorm_export_to_pdf($lp_id)
9329
    {
9330
        $lp_id = (int) $lp_id;
9331
        $files_to_export = [];
9332
9333
        $sessionId = api_get_session_id();
9334
        $course_data = api_get_course_info($this->cc);
9335
9336
        if (!empty($course_data)) {
9337
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9338
            $list = self::get_flat_ordered_items_list($lp_id);
9339
            if (!empty($list)) {
9340
                foreach ($list as $item_id) {
9341
                    $item = $this->items[$item_id];
9342
                    switch ($item->type) {
9343
                        case 'document':
9344
                            // Getting documents from a LP with chamilo documents
9345
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9346
                            // Try loading document from the base course.
9347
                            if (empty($file_data) && !empty($sessionId)) {
9348
                                $file_data = DocumentManager::get_document_data_by_id(
9349
                                    $item->path,
9350
                                    $this->cc,
9351
                                    false,
9352
                                    0
9353
                                );
9354
                            }
9355
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9356
                            if (file_exists($file_path)) {
9357
                                $files_to_export[] = [
9358
                                    'title' => $item->get_title(),
9359
                                    'path' => $file_path,
9360
                                ];
9361
                            }
9362
                            break;
9363
                        case 'asset': //commes from a scorm package generated by chamilo
9364
                        case 'sco':
9365
                            $file_path = $scorm_path.'/'.$item->path;
9366
                            if (file_exists($file_path)) {
9367
                                $files_to_export[] = [
9368
                                    'title' => $item->get_title(),
9369
                                    'path' => $file_path,
9370
                                ];
9371
                            }
9372
                            break;
9373
                        case 'dir':
9374
                            $files_to_export[] = [
9375
                                'title' => $item->get_title(),
9376
                                'path' => null,
9377
                            ];
9378
                            break;
9379
                    }
9380
                }
9381
            }
9382
9383
            $pdf = new PDF();
9384
            $result = $pdf->html_to_pdf(
9385
                $files_to_export,
9386
                $this->name,
9387
                $this->cc,
9388
                true,
9389
                true,
9390
                true,
9391
                $this->get_name()
9392
            );
9393
9394
            return $result;
9395
        }
9396
9397
        return false;
9398
    }
9399
9400
    /**
9401
     * Temp function to be moved in main_api or the best place around for this.
9402
     * Creates a file path if it doesn't exist.
9403
     *
9404
     * @param string $path
9405
     */
9406
    public function create_path($path)
9407
    {
9408
        $path_bits = explode('/', dirname($path));
9409
9410
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9411
        $path_built = IS_WINDOWS_OS ? '' : '/';
9412
        foreach ($path_bits as $bit) {
9413
            if (!empty($bit)) {
9414
                $new_path = $path_built.$bit;
9415
                if (is_dir($new_path)) {
9416
                    $path_built = $new_path.'/';
9417
                } else {
9418
                    mkdir($new_path, api_get_permissions_for_new_directories());
9419
                    $path_built = $new_path.'/';
9420
                }
9421
            }
9422
        }
9423
    }
9424
9425
    /**
9426
     * @param int    $lp_id
9427
     * @param string $status
9428
     */
9429
    public function set_autolaunch($lp_id, $status)
9430
    {
9431
        $course_id = api_get_course_int_id();
9432
        $lp_id = (int) $lp_id;
9433
        $status = (int) $status;
9434
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9435
9436
        // Setting everything to autolaunch = 0
9437
        $attributes['autolaunch'] = 0;
9438
        $where = [
9439
            'session_id = ? AND c_id = ? ' => [
9440
                api_get_session_id(),
9441
                $course_id,
9442
            ],
9443
        ];
9444
        Database::update($lp_table, $attributes, $where);
9445
        if (1 == $status) {
9446
            //Setting my lp_id to autolaunch = 1
9447
            $attributes['autolaunch'] = 1;
9448
            $where = [
9449
                'iid = ? AND session_id = ? AND c_id = ?' => [
9450
                    $lp_id,
9451
                    api_get_session_id(),
9452
                    $course_id,
9453
                ],
9454
            ];
9455
            Database::update($lp_table, $attributes, $where);
9456
        }
9457
    }
9458
9459
    /**
9460
     * Gets previous_item_id for the next element of the lp_item table.
9461
     *
9462
     * @author Isaac flores paz
9463
     *
9464
     * @return int Previous item ID
9465
     */
9466
    public function select_previous_item_id()
9467
    {
9468
        $course_id = api_get_course_int_id();
9469
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9470
9471
        // Get the max order of the items
9472
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9473
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9474
        $rs_max_order = Database::query($sql);
9475
        $row_max_order = Database::fetch_object($rs_max_order);
9476
        $max_order = $row_max_order->display_order;
9477
        // Get the previous item ID
9478
        $sql = "SELECT iid as previous FROM $table_lp_item
9479
                WHERE
9480
                    c_id = $course_id AND
9481
                    lp_id = ".$this->lp_id." AND
9482
                    display_order = '$max_order' ";
9483
        $rs_max = Database::query($sql);
9484
        $row_max = Database::fetch_object($rs_max);
9485
9486
        // Return the previous item ID
9487
        return $row_max->previous;
9488
    }
9489
9490
    /**
9491
     * Copies an LP.
9492
     */
9493
    public function copy()
9494
    {
9495
        // Course builder
9496
        $cb = new CourseBuilder();
9497
9498
        //Setting tools that will be copied
9499
        $cb->set_tools_to_build(['learnpaths']);
9500
9501
        //Setting elements that will be copied
9502
        $cb->set_tools_specific_id_list(
9503
            ['learnpaths' => [$this->lp_id]]
9504
        );
9505
9506
        $course = $cb->build();
9507
9508
        //Course restorer
9509
        $course_restorer = new CourseRestorer($course);
9510
        $course_restorer->set_add_text_in_items(true);
9511
        $course_restorer->set_tool_copy_settings(
9512
            ['learnpaths' => ['reset_dates' => true]]
9513
        );
9514
        $course_restorer->restore(
9515
            api_get_course_id(),
9516
            api_get_session_id(),
9517
            false,
9518
            false
9519
        );
9520
    }
9521
9522
    /**
9523
     * Verify document size.
9524
     *
9525
     * @param string $s
9526
     *
9527
     * @return bool
9528
     */
9529
    public static function verify_document_size($s)
9530
    {
9531
        $post_max = ini_get('post_max_size');
9532
        if ('M' == substr($post_max, -1, 1)) {
9533
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9534
        } elseif ('G' == substr($post_max, -1, 1)) {
9535
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9536
        }
9537
        $upl_max = ini_get('upload_max_filesize');
9538
        if ('M' == substr($upl_max, -1, 1)) {
9539
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9540
        } elseif ('G' == substr($upl_max, -1, 1)) {
9541
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9542
        }
9543
9544
        $repo = Container::getDocumentRepository();
9545
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9546
9547
        $course_max_space = DocumentManager::get_course_quota();
9548
        $total_size = filesize($s) + $documents_total_space;
9549
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9550
            return true;
9551
        }
9552
9553
        return false;
9554
    }
9555
9556
    /**
9557
     * Clear LP prerequisites.
9558
     */
9559
    public function clear_prerequisites()
9560
    {
9561
        $course_id = $this->get_course_int_id();
9562
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9563
        $lp_id = $this->get_id();
9564
        // Cleaning prerequisites
9565
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9566
                WHERE c_id = $course_id AND lp_id = $lp_id";
9567
        Database::query($sql);
9568
9569
        // Cleaning mastery score for exercises
9570
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9571
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
9572
        Database::query($sql);
9573
    }
9574
9575
    public function set_previous_step_as_prerequisite_for_all_items()
9576
    {
9577
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9578
        $course_id = $this->get_course_int_id();
9579
        $lp_id = $this->get_id();
9580
9581
        if (!empty($this->items)) {
9582
            $previous_item_id = null;
9583
            $previous_item_max = 0;
9584
            $previous_item_type = null;
9585
            $last_item_not_dir = null;
9586
            $last_item_not_dir_type = null;
9587
            $last_item_not_dir_max = null;
9588
9589
            foreach ($this->ordered_items as $itemId) {
9590
                $item = $this->getItem($itemId);
9591
                // if there was a previous item... (otherwise jump to set it)
9592
                if (!empty($previous_item_id)) {
9593
                    $current_item_id = $item->get_id(); //save current id
9594
                    if ('dir' != $item->get_type()) {
9595
                        // Current item is not a folder, so it qualifies to get a prerequisites
9596
                        if ('quiz' == $last_item_not_dir_type) {
9597
                            // if previous is quiz, mark its max score as default score to be achieved
9598
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9599
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9600
                            Database::query($sql);
9601
                        }
9602
                        // now simply update the prerequisite to set it to the last non-chapter item
9603
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9604
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
9605
                        Database::query($sql);
9606
                        // record item as 'non-chapter' reference
9607
                        $last_item_not_dir = $item->get_id();
9608
                        $last_item_not_dir_type = $item->get_type();
9609
                        $last_item_not_dir_max = $item->get_max();
9610
                    }
9611
                } else {
9612
                    if ('dir' != $item->get_type()) {
9613
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9614
                        $last_item_not_dir = $item->get_id();
9615
                        $last_item_not_dir_type = $item->get_type();
9616
                        $last_item_not_dir_max = $item->get_max();
9617
                    }
9618
                }
9619
                // Saving the item as "previous item" for the next loop
9620
                $previous_item_id = $item->get_id();
9621
                $previous_item_max = $item->get_max();
9622
                $previous_item_type = $item->get_type();
9623
            }
9624
        }
9625
    }
9626
9627
    /**
9628
     * @param array $params
9629
     *
9630
     * @return int
9631
     */
9632
    public static function createCategory($params)
9633
    {
9634
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9635
9636
        $item = new CLpCategory();
9637
        $item
9638
            ->setName($params['name'])
9639
            ->setParent($courseEntity)
9640
            ->addCourseLink($courseEntity, api_get_session_entity())
9641
        ;
9642
9643
        $repo = Container::getLpCategoryRepository();
9644
        $repo->create($item);
9645
9646
        return $item->getIid();
9647
    }
9648
9649
    /**
9650
     * @param array $params
9651
     */
9652
    public static function updateCategory($params)
9653
    {
9654
        $em = Database::getManager();
9655
        /** @var CLpCategory $item */
9656
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
9657
        if ($item) {
9658
            $item->setName($params['name']);
9659
            $em->persist($item);
9660
            $em->flush();
9661
        }
9662
    }
9663
9664
    /**
9665
     * @param int $id
9666
     */
9667
    public static function moveUpCategory($id)
9668
    {
9669
        $id = (int) $id;
9670
        $em = Database::getManager();
9671
        /** @var CLpCategory $item */
9672
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
9673
        if ($item) {
9674
            $position = $item->getPosition() - 1;
9675
            $item->setPosition($position);
9676
            $em->persist($item);
9677
            $em->flush();
9678
        }
9679
    }
9680
9681
    /**
9682
     * @param int $id
9683
     */
9684
    public static function moveDownCategory($id)
9685
    {
9686
        $id = (int) $id;
9687
        $em = Database::getManager();
9688
        /** @var CLpCategory $item */
9689
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
9690
        if ($item) {
9691
            $position = $item->getPosition() + 1;
9692
            $item->setPosition($position);
9693
            $em->persist($item);
9694
            $em->flush();
9695
        }
9696
    }
9697
9698
    public static function getLpList($courseId)
9699
    {
9700
        $table = Database::get_course_table(TABLE_LP_MAIN);
9701
        $courseId = (int) $courseId;
9702
9703
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
9704
        $result = Database::query($sql);
9705
9706
        return Database::store_result($result, 'ASSOC');
9707
    }
9708
9709
    /**
9710
     * @param int $courseId
9711
     *
9712
     * @return int|mixed
9713
     */
9714
    public static function getCountCategories($courseId)
9715
    {
9716
        if (empty($courseId)) {
9717
            return 0;
9718
        }
9719
        $repo = Container::getLpCategoryRepository();
9720
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9721
        $qb->addSelect('count(resource)');
9722
9723
        return $qb->getSingleScalarResult();
9724
    }
9725
9726
    /**
9727
     * @param int $courseId
9728
     *
9729
     * @return CLpCategory[]
9730
     */
9731
    public static function getCategories($courseId)
9732
    {
9733
        $em = Database::getManager();
9734
9735
        // Using doctrine extensions
9736
        $repo = Container::getLpCategoryRepository();
9737
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9738
9739
        return $qb->getQuery()->getResult();
9740
9741
        //return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9742
    }
9743
9744
    public static function getCategorySessionId($id)
9745
    {
9746
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9747
            return 0;
9748
        }
9749
9750
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9751
        $id = (int) $id;
9752
9753
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9754
        $result = Database::query($sql);
9755
        $result = Database::fetch_array($result, 'ASSOC');
9756
9757
        if ($result) {
9758
            return (int) $result['session_id'];
9759
        }
9760
9761
        return 0;
9762
    }
9763
9764
    /**
9765
     * @param int $id
9766
     */
9767
    public static function deleteCategory($id): bool
9768
    {
9769
        $repo = Container::getLpCategoryRepository();
9770
        /** @var CLpCategory $category */
9771
        $category = $repo->find($id);
9772
        if ($category) {
9773
            $em = Database::getManager();
9774
            $lps = $category->getLps();
9775
9776
            foreach ($lps as $lp) {
9777
                $lp->setCategory(null);
9778
                $em->persist($lp);
9779
            }
9780
9781
            // Removing category.
9782
            $em->remove($category);
9783
            $em->flush();
9784
9785
            return true;
9786
        }
9787
9788
        return false;
9789
    }
9790
9791
    /**
9792
     * @param int  $courseId
9793
     * @param bool $addSelectOption
9794
     *
9795
     * @return mixed
9796
     */
9797
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9798
    {
9799
        $repo = Container::getLpCategoryRepository();
9800
        $items = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9801
        $cats = [];
9802
        if ($addSelectOption) {
9803
            $cats = [get_lang('Select a category')];
9804
        }
9805
9806
        if (!empty($items)) {
9807
            foreach ($items as $cat) {
9808
                $cats[$cat->getIid()] = $cat->getName();
9809
            }
9810
        }
9811
9812
        return $cats;
9813
    }
9814
9815
    /**
9816
     * @param string $courseCode
9817
     * @param int    $lpId
9818
     * @param int    $user_id
9819
     *
9820
     * @return learnpath
9821
     */
9822
    public static function getLpFromSession($courseCode, $lpId, $user_id)
9823
    {
9824
        $debug = 0;
9825
        $learnPath = null;
9826
        $lpObject = Session::read('lpobject');
9827
9828
        if (null !== $lpObject) {
9829
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
9830
            if ($debug) {
9831
                error_log('getLpFromSession: unserialize');
9832
                error_log('------getLpFromSession------');
9833
                error_log('------unserialize------');
9834
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9835
                error_log("api_get_sessionid: ".api_get_session_id());
9836
            }
9837
        }
9838
9839
        if (!is_object($learnPath)) {
9840
            $repo = Container::getLpRepository();
9841
            $lp = $repo->find($lpId);
9842
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
9843
            if ($debug) {
9844
                error_log('------getLpFromSession------');
9845
                error_log('getLpFromSession: create new learnpath');
9846
                error_log("create new LP with $courseCode - $lpId - $user_id");
9847
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9848
                error_log("api_get_sessionid: ".api_get_session_id());
9849
            }
9850
        }
9851
9852
        return $learnPath;
9853
    }
9854
9855
    /**
9856
     * @param int $itemId
9857
     *
9858
     * @return learnpathItem|false
9859
     */
9860
    public function getItem($itemId)
9861
    {
9862
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
9863
            return $this->items[$itemId];
9864
        }
9865
9866
        return false;
9867
    }
9868
9869
    /**
9870
     * @return int
9871
     */
9872
    public function getCurrentAttempt()
9873
    {
9874
        $attempt = $this->getItem($this->get_current_item_id());
9875
        if ($attempt) {
9876
            return $attempt->get_attempt_id();
9877
        }
9878
9879
        return 0;
9880
    }
9881
9882
    /**
9883
     * @return int
9884
     */
9885
    public function getCategoryId()
9886
    {
9887
        return (int) $this->categoryId;
9888
    }
9889
9890
    /**
9891
     * Get whether this is a learning path with the possibility to subscribe
9892
     * users or not.
9893
     *
9894
     * @return int
9895
     */
9896
    public function getSubscribeUsers()
9897
    {
9898
        return $this->subscribeUsers;
9899
    }
9900
9901
    /**
9902
     * Calculate the count of stars for a user in this LP
9903
     * This calculation is based on the following rules:
9904
     * - the student gets one star when he gets to 50% of the learning path
9905
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
9906
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
9907
     * - the student gets the final star when the score for the *last* test is >= 80%.
9908
     *
9909
     * @param int $sessionId Optional. The session ID
9910
     *
9911
     * @return int The count of stars
9912
     */
9913
    public function getCalculateStars($sessionId = 0)
9914
    {
9915
        $stars = 0;
9916
        $progress = self::getProgress(
9917
            $this->lp_id,
9918
            $this->user_id,
9919
            $this->course_int_id,
9920
            $sessionId
9921
        );
9922
9923
        if ($progress >= 50) {
9924
            $stars++;
9925
        }
9926
9927
        // Calculate stars chapters evaluation
9928
        $exercisesItems = $this->getExercisesItems();
9929
9930
        if (!empty($exercisesItems)) {
9931
            $totalResult = 0;
9932
9933
            foreach ($exercisesItems as $exerciseItem) {
9934
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9935
                    $this->user_id,
9936
                    $exerciseItem->path,
9937
                    $this->course_int_id,
9938
                    $sessionId,
9939
                    $this->lp_id,
9940
                    $exerciseItem->db_id
9941
                );
9942
9943
                $exerciseResultInfo = end($exerciseResultInfo);
9944
9945
                if (!$exerciseResultInfo) {
9946
                    continue;
9947
                }
9948
9949
                if (!empty($exerciseResultInfo['max_score'])) {
9950
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
9951
                } else {
9952
                    $exerciseResult = 0;
9953
                }
9954
                $totalResult += $exerciseResult;
9955
            }
9956
9957
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
9958
9959
            if ($totalExerciseAverage >= 50) {
9960
                $stars++;
9961
            }
9962
9963
            if ($totalExerciseAverage >= 80) {
9964
                $stars++;
9965
            }
9966
        }
9967
9968
        // Calculate star for final evaluation
9969
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9970
9971
        if (!empty($finalEvaluationItem)) {
9972
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9973
                $this->user_id,
9974
                $finalEvaluationItem->path,
9975
                $this->course_int_id,
9976
                $sessionId,
9977
                $this->lp_id,
9978
                $finalEvaluationItem->db_id
9979
            );
9980
9981
            $evaluationResultInfo = end($evaluationResultInfo);
9982
9983
            if ($evaluationResultInfo) {
9984
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
9985
9986
                if ($evaluationResult >= 80) {
9987
                    $stars++;
9988
                }
9989
            }
9990
        }
9991
9992
        return $stars;
9993
    }
9994
9995
    /**
9996
     * Get the items of exercise type.
9997
     *
9998
     * @return array The items. Otherwise return false
9999
     */
10000
    public function getExercisesItems()
10001
    {
10002
        $exercises = [];
10003
        foreach ($this->items as $item) {
10004
            if ('quiz' != $item->type) {
10005
                continue;
10006
            }
10007
            $exercises[] = $item;
10008
        }
10009
10010
        array_pop($exercises);
10011
10012
        return $exercises;
10013
    }
10014
10015
    /**
10016
     * Get the item of exercise type (evaluation type).
10017
     *
10018
     * @return array The final evaluation. Otherwise return false
10019
     */
10020
    public function getFinalEvaluationItem()
10021
    {
10022
        $exercises = [];
10023
        foreach ($this->items as $item) {
10024
            if (TOOL_QUIZ !== $item->type) {
10025
                continue;
10026
            }
10027
10028
            $exercises[] = $item;
10029
        }
10030
10031
        return array_pop($exercises);
10032
    }
10033
10034
    /**
10035
     * Calculate the total points achieved for the current user in this learning path.
10036
     *
10037
     * @param int $sessionId Optional. The session Id
10038
     *
10039
     * @return int
10040
     */
10041
    public function getCalculateScore($sessionId = 0)
10042
    {
10043
        // Calculate stars chapters evaluation
10044
        $exercisesItems = $this->getExercisesItems();
10045
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10046
        $totalExercisesResult = 0;
10047
        $totalEvaluationResult = 0;
10048
10049
        if (false !== $exercisesItems) {
10050
            foreach ($exercisesItems as $exerciseItem) {
10051
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10052
                    $this->user_id,
10053
                    $exerciseItem->path,
10054
                    $this->course_int_id,
10055
                    $sessionId,
10056
                    $this->lp_id,
10057
                    $exerciseItem->db_id
10058
                );
10059
10060
                $exerciseResultInfo = end($exerciseResultInfo);
10061
10062
                if (!$exerciseResultInfo) {
10063
                    continue;
10064
                }
10065
10066
                $totalExercisesResult += $exerciseResultInfo['score'];
10067
            }
10068
        }
10069
10070
        if (!empty($finalEvaluationItem)) {
10071
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10072
                $this->user_id,
10073
                $finalEvaluationItem->path,
10074
                $this->course_int_id,
10075
                $sessionId,
10076
                $this->lp_id,
10077
                $finalEvaluationItem->db_id
10078
            );
10079
10080
            $evaluationResultInfo = end($evaluationResultInfo);
10081
10082
            if ($evaluationResultInfo) {
10083
                $totalEvaluationResult += $evaluationResultInfo['score'];
10084
            }
10085
        }
10086
10087
        return $totalExercisesResult + $totalEvaluationResult;
10088
    }
10089
10090
    /**
10091
     * Check if URL is not allowed to be show in a iframe.
10092
     *
10093
     * @param string $src
10094
     *
10095
     * @return string
10096
     */
10097
    public function fixBlockedLinks($src)
10098
    {
10099
        $urlInfo = parse_url($src);
10100
10101
        $platformProtocol = 'https';
10102
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10103
            $platformProtocol = 'http';
10104
        }
10105
10106
        $protocolFixApplied = false;
10107
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10108
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10109
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10110
10111
        if ($platformProtocol != $scheme) {
10112
            Session::write('x_frame_source', $src);
10113
            $src = 'blank.php?error=x_frames_options';
10114
            $protocolFixApplied = true;
10115
        }
10116
10117
        if (false == $protocolFixApplied) {
10118
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10119
                // Check X-Frame-Options
10120
                $ch = curl_init();
10121
                $options = [
10122
                    CURLOPT_URL => $src,
10123
                    CURLOPT_RETURNTRANSFER => true,
10124
                    CURLOPT_HEADER => true,
10125
                    CURLOPT_FOLLOWLOCATION => true,
10126
                    CURLOPT_ENCODING => "",
10127
                    CURLOPT_AUTOREFERER => true,
10128
                    CURLOPT_CONNECTTIMEOUT => 120,
10129
                    CURLOPT_TIMEOUT => 120,
10130
                    CURLOPT_MAXREDIRS => 10,
10131
                ];
10132
10133
                $proxySettings = api_get_configuration_value('proxy_settings');
10134
                if (!empty($proxySettings) &&
10135
                    isset($proxySettings['curl_setopt_array'])
10136
                ) {
10137
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10138
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10139
                }
10140
10141
                curl_setopt_array($ch, $options);
10142
                $response = curl_exec($ch);
10143
                $httpCode = curl_getinfo($ch);
10144
                $headers = substr($response, 0, $httpCode['header_size']);
10145
10146
                $error = false;
10147
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10148
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10149
                ) {
10150
                    $error = true;
10151
                }
10152
10153
                if ($error) {
10154
                    Session::write('x_frame_source', $src);
10155
                    $src = 'blank.php?error=x_frames_options';
10156
                }
10157
            }
10158
        }
10159
10160
        return $src;
10161
    }
10162
10163
    /**
10164
     * Check if this LP has a created forum in the basis course.
10165
     *
10166
     * @deprecated
10167
     *
10168
     * @return bool
10169
     */
10170
    public function lpHasForum()
10171
    {
10172
        $forumTable = Database::get_course_table(TABLE_FORUM);
10173
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10174
10175
        $fakeFrom = "
10176
            $forumTable f
10177
            INNER JOIN $itemProperty ip
10178
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10179
        ";
10180
10181
        $resultData = Database::select(
10182
            'COUNT(f.iid) AS qty',
10183
            $fakeFrom,
10184
            [
10185
                'where' => [
10186
                    'ip.visibility != ? AND ' => 2,
10187
                    'ip.tool = ? AND ' => TOOL_FORUM,
10188
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10189
                    'f.lp_id = ?' => intval($this->lp_id),
10190
                ],
10191
            ],
10192
            'first'
10193
        );
10194
10195
        return $resultData['qty'] > 0;
10196
    }
10197
10198
    /**
10199
     * Get the forum for this learning path.
10200
     *
10201
     * @param int $sessionId
10202
     *
10203
     * @return array
10204
     */
10205
    public function getForum($sessionId = 0)
10206
    {
10207
        $repo = Container::getForumRepository();
10208
10209
        $course = api_get_course_entity();
10210
        $session = api_get_session_entity($sessionId);
10211
        $qb = $repo->getResourcesByCourse($course, $session);
10212
10213
        return $qb->getQuery()->getResult();
10214
    }
10215
10216
    /**
10217
     * Create a forum for this learning path.
10218
     *
10219
     * @return int The forum ID if was created. Otherwise return false
10220
     */
10221
    public function createForum(CForumCategory $forumCategory)
10222
    {
10223
        return store_forum(
10224
            [
10225
                'lp_id' => $this->lp_id,
10226
                'forum_title' => $this->name,
10227
                'forum_comment' => null,
10228
                'forum_category' => $forumCategory->getIid(),
10229
                'students_can_edit_group' => ['students_can_edit' => 0],
10230
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10231
                'default_view_type_group' => ['default_view_type' => 'flat'],
10232
                'group_forum' => 0,
10233
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10234
            ],
10235
            [],
10236
            true
10237
        );
10238
    }
10239
10240
    /**
10241
     * Get the LP Final Item form.
10242
     *
10243
     * @throws Exception
10244
     * @throws HTML_QuickForm_Error
10245
     *
10246
     * @return string
10247
     */
10248
    public function getFinalItemForm()
10249
    {
10250
        $finalItem = $this->getFinalItem();
10251
        $title = '';
10252
10253
        if ($finalItem) {
10254
            $title = $finalItem->get_title();
10255
            $buttonText = get_lang('Save');
10256
            $content = $this->getSavedFinalItem();
10257
        } else {
10258
            $buttonText = get_lang('Add this document to the course');
10259
            $content = $this->getFinalItemTemplate();
10260
        }
10261
10262
        $editorConfig = [
10263
            'ToolbarSet' => 'LearningPathDocuments',
10264
            'Width' => '100%',
10265
            'Height' => '500',
10266
            'FullPage' => true,
10267
//            'CreateDocumentDir' => $relative_prefix,
10268
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10269
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10270
        ];
10271
10272
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10273
            'type' => 'document',
10274
            'lp_id' => $this->lp_id,
10275
        ]);
10276
10277
        $form = new FormValidator('final_item', 'POST', $url);
10278
        $form->addText('title', get_lang('Title'));
10279
        $form->addButtonSave($buttonText);
10280
        $form->addHtml(
10281
            Display::return_message(
10282
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10283
                'normal',
10284
                false
10285
            )
10286
        );
10287
10288
        $renderer = $form->defaultRenderer();
10289
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10290
10291
        $form->addHtmlEditor(
10292
            'content_lp_certificate',
10293
            null,
10294
            true,
10295
            false,
10296
            $editorConfig,
10297
            true
10298
        );
10299
        $form->addHidden('action', 'add_final_item');
10300
        $form->addHidden('path', Session::read('pathItem'));
10301
        $form->addHidden('previous', $this->get_last());
10302
        $form->setDefaults(
10303
            ['title' => $title, 'content_lp_certificate' => $content]
10304
        );
10305
10306
        if ($form->validate()) {
10307
            $values = $form->exportValues();
10308
            $lastItemId = $this->getLastInFirstLevel();
10309
10310
            if (!$finalItem) {
10311
                $documentId = $this->create_document(
10312
                    $this->course_info,
10313
                    $values['content_lp_certificate'],
10314
                    $values['title']
10315
                );
10316
                $this->add_item(
10317
                    0,
10318
                    $lastItemId,
10319
                    'final_item',
10320
                    $documentId,
10321
                    $values['title'],
10322
                    ''
10323
                );
10324
10325
                Display::addFlash(
10326
                    Display::return_message(get_lang('Added'))
10327
                );
10328
            } else {
10329
                $this->edit_document($this->course_info);
10330
            }
10331
        }
10332
10333
        return $form->returnForm();
10334
    }
10335
10336
    /**
10337
     * Check if the current lp item is first, both, last or none from lp list.
10338
     *
10339
     * @param int $currentItemId
10340
     *
10341
     * @return string
10342
     */
10343
    public function isFirstOrLastItem($currentItemId)
10344
    {
10345
        $lpItemId = [];
10346
        $typeListNotToVerify = self::getChapterTypes();
10347
10348
        // Using get_toc() function instead $this->items because returns the correct order of the items
10349
        foreach ($this->get_toc() as $item) {
10350
            if (!in_array($item['type'], $typeListNotToVerify)) {
10351
                $lpItemId[] = $item['id'];
10352
            }
10353
        }
10354
10355
        $lastLpItemIndex = count($lpItemId) - 1;
10356
        $position = array_search($currentItemId, $lpItemId);
10357
10358
        switch ($position) {
10359
            case 0:
10360
                if (!$lastLpItemIndex) {
10361
                    $answer = 'both';
10362
                    break;
10363
                }
10364
10365
                $answer = 'first';
10366
                break;
10367
            case $lastLpItemIndex:
10368
                $answer = 'last';
10369
                break;
10370
            default:
10371
                $answer = 'none';
10372
        }
10373
10374
        return $answer;
10375
    }
10376
10377
    /**
10378
     * Get whether this is a learning path with the accumulated SCORM time or not.
10379
     *
10380
     * @return int
10381
     */
10382
    public function getAccumulateScormTime()
10383
    {
10384
        return $this->accumulateScormTime;
10385
    }
10386
10387
    /**
10388
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10389
     * the new learning path tool.
10390
     *
10391
     * The function is a big switch on tool type.
10392
     * In each case, we query the corresponding table for information and build the link
10393
     * with that information.
10394
     *
10395
     * @author Yannick Warnier <[email protected]> - rebranding based on
10396
     * previous work (display_addedresource_link_in_learnpath())
10397
     *
10398
     * @param int $course_id      Course code
10399
     * @param int $learningPathId The learning path ID (in lp table)
10400
     * @param int $id_in_path     the unique index in the items table
10401
     * @param int $lpViewId
10402
     *
10403
     * @return string
10404
     */
10405
    public static function rl_get_resource_link_for_learnpath(
10406
        $course_id,
10407
        $learningPathId,
10408
        $id_in_path,
10409
        $lpViewId
10410
    ) {
10411
        $session_id = api_get_session_id();
10412
10413
        $learningPathId = (int) $learningPathId;
10414
        $id_in_path = (int) $id_in_path;
10415
        $lpViewId = (int) $lpViewId;
10416
10417
        $em = Database::getManager();
10418
        $lpItemRepo = $em->getRepository(CLpItem::class);
10419
10420
        /** @var CLpItem $rowItem */
10421
        $rowItem = $lpItemRepo->findOneBy([
10422
            'cId' => $course_id,
10423
            'lp' => $learningPathId,
10424
            'iid' => $id_in_path,
10425
        ]);
10426
10427
        if (!$rowItem) {
10428
            // Try one more time with "id"
10429
            /** @var CLpItem $rowItem */
10430
            $rowItem = $lpItemRepo->findOneBy([
10431
                'cId' => $course_id,
10432
                'lp' => $learningPathId,
10433
                'id' => $id_in_path,
10434
            ]);
10435
10436
            if (!$rowItem) {
10437
                return -1;
10438
            }
10439
        }
10440
10441
        $type = $rowItem->getItemType();
10442
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10443
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10444
        $link = '';
10445
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
10446
10447
        switch ($type) {
10448
            case 'dir':
10449
                return $main_dir_path.'lp/blank.php';
10450
            case TOOL_CALENDAR_EVENT:
10451
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10452
            case TOOL_ANNOUNCEMENT:
10453
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10454
            case TOOL_LINK:
10455
                $linkInfo = Link::getLinkInfo($id);
10456
                if (isset($linkInfo['url'])) {
10457
                    return $linkInfo['url'];
10458
                }
10459
10460
                return '';
10461
            case TOOL_QUIZ:
10462
                if (empty($id)) {
10463
                    return '';
10464
                }
10465
10466
                // Get the lp_item_view with the highest view_count.
10467
                $learnpathItemViewResult = $em
10468
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10469
                    ->findBy(
10470
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getIid(), 'lpViewId' => $lpViewId],
10471
                        ['viewCount' => 'DESC'],
10472
                        1
10473
                    );
10474
                /** @var CLpItemView $learnpathItemViewData */
10475
                $learnpathItemViewData = current($learnpathItemViewResult);
10476
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
10477
10478
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10479
                    .http_build_query([
10480
                        'lp_init' => 1,
10481
                        'learnpath_item_view_id' => $learnpathItemViewId,
10482
                        'learnpath_id' => $learningPathId,
10483
                        'learnpath_item_id' => $id_in_path,
10484
                        'exerciseId' => $id,
10485
                    ]);
10486
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10487
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10488
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10489
                $myrow = Database::fetch_array($result);
10490
                $path = $myrow['path'];
10491
10492
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10493
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10494
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10495
            case TOOL_FORUM:
10496
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10497
            case TOOL_THREAD:
10498
                // forum post
10499
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10500
                if (empty($id)) {
10501
                    return '';
10502
                }
10503
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
10504
                $result = Database::query($sql);
10505
                $myrow = Database::fetch_array($result);
10506
10507
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10508
                    .$extraParams;
10509
            case TOOL_POST:
10510
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10511
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10512
                $myrow = Database::fetch_array($result);
10513
10514
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10515
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10516
            case TOOL_READOUT_TEXT:
10517
                return api_get_path(WEB_CODE_PATH).
10518
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10519
            case TOOL_DOCUMENT:
10520
                $repo = Container::getDocumentRepository();
10521
                $document = $repo->find($rowItem->getPath());
10522
                $params = [
10523
                    'cid' => $course_id,
10524
                    'sid' => $session_id,
10525
                ];
10526
                $file = $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
10527
10528
                return $file;
10529
10530
                $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...
10531
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10532
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10533
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10534
10535
                $openmethod = 2;
10536
                $officedoc = false;
10537
                Session::write('openmethod', $openmethod);
10538
                Session::write('officedoc', $officedoc);
10539
10540
                if ($showDirectUrl) {
10541
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10542
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10543
                        if (Link::isPdfLink($file)) {
10544
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10545
10546
                            return $pdfUrl;
10547
                        }
10548
                    }
10549
10550
                    return $file;
10551
                }
10552
10553
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10554
            case TOOL_LP_FINAL_ITEM:
10555
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10556
                    .$extraParams;
10557
            case 'assignments':
10558
                return $main_dir_path.'work/work.php?'.$extraParams;
10559
            case TOOL_DROPBOX:
10560
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10561
            case 'introduction_text': //DEPRECATED
10562
                return '';
10563
            case TOOL_COURSE_DESCRIPTION:
10564
                return $main_dir_path.'course_description?'.$extraParams;
10565
            case TOOL_GROUP:
10566
                return $main_dir_path.'group/group.php?'.$extraParams;
10567
            case TOOL_USER:
10568
                return $main_dir_path.'user/user.php?'.$extraParams;
10569
            case TOOL_STUDENTPUBLICATION:
10570
                if (!empty($rowItem->getPath())) {
10571
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10572
                }
10573
10574
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10575
        }
10576
10577
        return $link;
10578
    }
10579
10580
    /**
10581
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10582
     *
10583
     * @author Yannick Warnier <[email protected]>
10584
     *
10585
     * @param string $course_code    Course code
10586
     * @param int    $learningPathId
10587
     * @param int    $id_in_path     The resource ID
10588
     *
10589
     * @return string
10590
     */
10591
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10592
    {
10593
        $_course = api_get_course_info($course_code);
10594
        if (empty($_course)) {
10595
            return '';
10596
        }
10597
        $course_id = $_course['real_id'];
10598
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10599
        $learningPathId = (int) $learningPathId;
10600
        $id_in_path = (int) $id_in_path;
10601
10602
        $sql = "SELECT item_type, title, ref
10603
                FROM $tbl_lp_item
10604
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10605
        $res_item = Database::query($sql);
10606
10607
        if (Database::num_rows($res_item) < 1) {
10608
            return '';
10609
        }
10610
        $row_item = Database::fetch_array($res_item);
10611
        $type = strtolower($row_item['item_type']);
10612
        $id = $row_item['ref'];
10613
        $output = '';
10614
10615
        switch ($type) {
10616
            case TOOL_CALENDAR_EVENT:
10617
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10618
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10619
                $myrow = Database::fetch_array($result);
10620
                $output = $myrow['title'];
10621
                break;
10622
            case TOOL_ANNOUNCEMENT:
10623
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10624
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10625
                $myrow = Database::fetch_array($result);
10626
                $output = $myrow['title'];
10627
                break;
10628
            case TOOL_LINK:
10629
                // Doesn't take $target into account.
10630
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10631
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10632
                $myrow = Database::fetch_array($result);
10633
                $output = $myrow['title'];
10634
                break;
10635
            case TOOL_QUIZ:
10636
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10637
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10638
                $myrow = Database::fetch_array($result);
10639
                $output = $myrow['title'];
10640
                break;
10641
            case TOOL_FORUM:
10642
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10643
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10644
                $myrow = Database::fetch_array($result);
10645
                $output = $myrow['forum_name'];
10646
                break;
10647
            case TOOL_THREAD:
10648
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10649
                // Grabbing the title of the post.
10650
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10651
                $result_title = Database::query($sql_title);
10652
                $myrow_title = Database::fetch_array($result_title);
10653
                $output = $myrow_title['post_title'];
10654
                break;
10655
            case TOOL_POST:
10656
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10657
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10658
                $result = Database::query($sql);
10659
                $post = Database::fetch_array($result);
10660
                $output = $post['post_title'];
10661
                break;
10662
            case 'dir':
10663
            case TOOL_DOCUMENT:
10664
                $title = $row_item['title'];
10665
                $output = '-';
10666
                if (!empty($title)) {
10667
                    $output = $title;
10668
                }
10669
                break;
10670
            case 'hotpotatoes':
10671
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10672
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10673
                $myrow = Database::fetch_array($result);
10674
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10675
                $last = count($pathname) - 1; // Making a correct name for the link.
10676
                $filename = $pathname[$last]; // Making a correct name for the link.
10677
                $myrow['path'] = rawurlencode($myrow['path']);
10678
                $output = $filename;
10679
                break;
10680
        }
10681
10682
        return stripslashes($output);
10683
    }
10684
10685
    /**
10686
     * Get the parent names for the current item.
10687
     *
10688
     * @param int $newItemId Optional. The item ID
10689
     *
10690
     * @return array
10691
     */
10692
    public function getCurrentItemParentNames($newItemId = 0)
10693
    {
10694
        $newItemId = $newItemId ?: $this->get_current_item_id();
10695
        $return = [];
10696
        $item = $this->getItem($newItemId);
10697
        $parent = $this->getItem($item->get_parent());
10698
10699
        while ($parent) {
10700
            $return[] = $parent->get_title();
10701
            $parent = $this->getItem($parent->get_parent());
10702
        }
10703
10704
        return array_reverse($return);
10705
    }
10706
10707
    /**
10708
     * Reads and process "lp_subscription_settings" setting.
10709
     *
10710
     * @return array
10711
     */
10712
    public static function getSubscriptionSettings()
10713
    {
10714
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10715
        if (empty($subscriptionSettings)) {
10716
            // By default allow both settings
10717
            $subscriptionSettings = [
10718
                'allow_add_users_to_lp' => true,
10719
                'allow_add_users_to_lp_category' => true,
10720
            ];
10721
        } else {
10722
            $subscriptionSettings = $subscriptionSettings['options'];
10723
        }
10724
10725
        return $subscriptionSettings;
10726
    }
10727
10728
    /**
10729
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10730
     */
10731
    public function exportToCourseBuildFormat()
10732
    {
10733
        if (!api_is_allowed_to_edit()) {
10734
            return false;
10735
        }
10736
10737
        $courseBuilder = new CourseBuilder();
10738
        $itemList = [];
10739
        /** @var learnpathItem $item */
10740
        foreach ($this->items as $item) {
10741
            $itemList[$item->get_type()][] = $item->get_path();
10742
        }
10743
10744
        if (empty($itemList)) {
10745
            return false;
10746
        }
10747
10748
        if (isset($itemList['document'])) {
10749
            // Get parents
10750
            foreach ($itemList['document'] as $documentId) {
10751
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10752
                if (!empty($documentInfo['parents'])) {
10753
                    foreach ($documentInfo['parents'] as $parentInfo) {
10754
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10755
                            continue;
10756
                        }
10757
                        $itemList['document'][] = $parentInfo['iid'];
10758
                    }
10759
                }
10760
            }
10761
10762
            $courseInfo = api_get_course_info();
10763
            foreach ($itemList['document'] as $documentId) {
10764
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10765
                $items = DocumentManager::get_resources_from_source_html(
10766
                    $documentInfo['absolute_path'],
10767
                    true,
10768
                    TOOL_DOCUMENT
10769
                );
10770
10771
                if (!empty($items)) {
10772
                    foreach ($items as $item) {
10773
                        // Get information about source url
10774
                        $url = $item[0]; // url
10775
                        $scope = $item[1]; // scope (local, remote)
10776
                        $type = $item[2]; // type (rel, abs, url)
10777
10778
                        $origParseUrl = parse_url($url);
10779
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10780
10781
                        if ('local' === $scope) {
10782
                            if ('abs' === $type || 'rel' === $type) {
10783
                                $documentFile = strstr($realOrigPath, 'document');
10784
                                if (false !== strpos($realOrigPath, $documentFile)) {
10785
                                    $documentFile = str_replace('document', '', $documentFile);
10786
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10787
                                    // Document found! Add it to the list
10788
                                    if ($itemDocumentId) {
10789
                                        $itemList['document'][] = $itemDocumentId;
10790
                                    }
10791
                                }
10792
                            }
10793
                        }
10794
                    }
10795
                }
10796
            }
10797
10798
            $courseBuilder->build_documents(
10799
                api_get_session_id(),
10800
                $this->get_course_int_id(),
10801
                true,
10802
                $itemList['document']
10803
            );
10804
        }
10805
10806
        if (isset($itemList['quiz'])) {
10807
            $courseBuilder->build_quizzes(
10808
                api_get_session_id(),
10809
                $this->get_course_int_id(),
10810
                true,
10811
                $itemList['quiz']
10812
            );
10813
        }
10814
10815
        /*if (!empty($itemList['thread'])) {
10816
            $postList = [];
10817
            foreach ($itemList['thread'] as $postId) {
10818
                $post = get_post_information($postId);
10819
                if ($post) {
10820
                    if (!isset($itemList['forum'])) {
10821
                        $itemList['forum'] = [];
10822
                    }
10823
                    $itemList['forum'][] = $post['forum_id'];
10824
                    $postList[] = $postId;
10825
                }
10826
            }
10827
10828
            if (!empty($postList)) {
10829
                $courseBuilder->build_forum_posts(
10830
                    $this->get_course_int_id(),
10831
                    null,
10832
                    null,
10833
                    $postList
10834
                );
10835
            }
10836
        }*/
10837
10838
        if (!empty($itemList['thread'])) {
10839
            $threadList = [];
10840
            $em = Database::getManager();
10841
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
10842
            foreach ($itemList['thread'] as $threadId) {
10843
                /** @var CForumThread $thread */
10844
                $thread = $repo->find($threadId);
10845
                if ($thread) {
10846
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
10847
                    $threadList[] = $thread->getIid();
10848
                }
10849
            }
10850
10851
            if (!empty($threadList)) {
10852
                $courseBuilder->build_forum_topics(
10853
                    api_get_session_id(),
10854
                    $this->get_course_int_id(),
10855
                    null,
10856
                    $threadList
10857
                );
10858
            }
10859
        }
10860
10861
        $forumCategoryList = [];
10862
        if (isset($itemList['forum'])) {
10863
            foreach ($itemList['forum'] as $forumId) {
10864
                $forumInfo = get_forums($forumId);
10865
                $forumCategoryList[] = $forumInfo['forum_category'];
10866
            }
10867
        }
10868
10869
        if (!empty($forumCategoryList)) {
10870
            $courseBuilder->build_forum_category(
10871
                api_get_session_id(),
10872
                $this->get_course_int_id(),
10873
                true,
10874
                $forumCategoryList
10875
            );
10876
        }
10877
10878
        if (!empty($itemList['forum'])) {
10879
            $courseBuilder->build_forums(
10880
                api_get_session_id(),
10881
                $this->get_course_int_id(),
10882
                true,
10883
                $itemList['forum']
10884
            );
10885
        }
10886
10887
        if (isset($itemList['link'])) {
10888
            $courseBuilder->build_links(
10889
                api_get_session_id(),
10890
                $this->get_course_int_id(),
10891
                true,
10892
                $itemList['link']
10893
            );
10894
        }
10895
10896
        if (!empty($itemList['student_publication'])) {
10897
            $courseBuilder->build_works(
10898
                api_get_session_id(),
10899
                $this->get_course_int_id(),
10900
                true,
10901
                $itemList['student_publication']
10902
            );
10903
        }
10904
10905
        $courseBuilder->build_learnpaths(
10906
            api_get_session_id(),
10907
            $this->get_course_int_id(),
10908
            true,
10909
            [$this->get_id()],
10910
            false
10911
        );
10912
10913
        $courseBuilder->restoreDocumentsFromList();
10914
10915
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
10916
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
10917
        $result = DocumentManager::file_send_for_download(
10918
            $zipPath,
10919
            true,
10920
            $this->get_name().'.zip'
10921
        );
10922
10923
        if ($result) {
10924
            api_not_allowed();
10925
        }
10926
10927
        return true;
10928
    }
10929
10930
    /**
10931
     * Get whether this is a learning path with the accumulated work time or not.
10932
     *
10933
     * @return int
10934
     */
10935
    public function getAccumulateWorkTime()
10936
    {
10937
        return (int) $this->accumulateWorkTime;
10938
    }
10939
10940
    /**
10941
     * Get whether this is a learning path with the accumulated work time or not.
10942
     *
10943
     * @return int
10944
     */
10945
    public function getAccumulateWorkTimeTotalCourse()
10946
    {
10947
        $table = Database::get_course_table(TABLE_LP_MAIN);
10948
        $sql = "SELECT SUM(accumulate_work_time) AS total
10949
                FROM $table
10950
                WHERE c_id = ".$this->course_int_id;
10951
        $result = Database::query($sql);
10952
        $row = Database::fetch_array($result);
10953
10954
        return (int) $row['total'];
10955
    }
10956
10957
    /**
10958
     * @param int $lpId
10959
     * @param int $courseId
10960
     *
10961
     * @return mixed
10962
     */
10963
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
10964
    {
10965
        $lpId = (int) $lpId;
10966
        $courseId = (int) $courseId;
10967
10968
        $table = Database::get_course_table(TABLE_LP_MAIN);
10969
        $sql = "SELECT accumulate_work_time
10970
                FROM $table
10971
                WHERE c_id = $courseId AND id = $lpId";
10972
        $result = Database::query($sql);
10973
        $row = Database::fetch_array($result);
10974
10975
        return $row['accumulate_work_time'];
10976
    }
10977
10978
    /**
10979
     * @param int $courseId
10980
     *
10981
     * @return int
10982
     */
10983
    public static function getAccumulateWorkTimeTotal($courseId)
10984
    {
10985
        $table = Database::get_course_table(TABLE_LP_MAIN);
10986
        $courseId = (int) $courseId;
10987
        $sql = "SELECT SUM(accumulate_work_time) AS total
10988
                FROM $table
10989
                WHERE c_id = $courseId";
10990
        $result = Database::query($sql);
10991
        $row = Database::fetch_array($result);
10992
10993
        return (int) $row['total'];
10994
    }
10995
10996
    /**
10997
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
10998
     * and put the images in.
10999
     *
11000
     * @return array
11001
     */
11002
    public static function getIconSelect()
11003
    {
11004
        $theme = api_get_visual_theme();
11005
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11006
        $icons = ['' => get_lang('Please select an option')];
11007
11008
        if (is_dir($path)) {
11009
            $finder = new Finder();
11010
            $finder->files()->in($path);
11011
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11012
            /** @var SplFileInfo $file */
11013
            foreach ($finder as $file) {
11014
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11015
                    $icons[$file->getFilename()] = $file->getFilename();
11016
                }
11017
            }
11018
        }
11019
11020
        return $icons;
11021
    }
11022
11023
    /**
11024
     * @param int $lpId
11025
     *
11026
     * @return string
11027
     */
11028
    public static function getSelectedIcon($lpId)
11029
    {
11030
        $extraFieldValue = new ExtraFieldValue('lp');
11031
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11032
        $icon = '';
11033
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11034
            $icon = $lpIcon['value'];
11035
        }
11036
11037
        return $icon;
11038
    }
11039
11040
    /**
11041
     * @param int $lpId
11042
     *
11043
     * @return string
11044
     */
11045
    public static function getSelectedIconHtml($lpId)
11046
    {
11047
        $icon = self::getSelectedIcon($lpId);
11048
11049
        if (empty($icon)) {
11050
            return '';
11051
        }
11052
11053
        $theme = api_get_visual_theme();
11054
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11055
11056
        return Display::img($path);
11057
    }
11058
11059
    /**
11060
     * @param string $value
11061
     *
11062
     * @return string
11063
     */
11064
    public function cleanItemTitle($value)
11065
    {
11066
        $value = Security::remove_XSS(strip_tags($value));
11067
11068
        return $value;
11069
    }
11070
11071
    public function setItemTitle(FormValidator $form)
11072
    {
11073
        if (api_get_configuration_value('save_titles_as_html')) {
11074
            $form->addHtmlEditor(
11075
                'title',
11076
                get_lang('Title'),
11077
                true,
11078
                false,
11079
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11080
            );
11081
        } else {
11082
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11083
            $form->applyFilter('title', 'trim');
11084
            $form->applyFilter('title', 'html_filter');
11085
        }
11086
    }
11087
11088
    /**
11089
     * @return array
11090
     */
11091
    public function getItemsForForm($addParentCondition = false)
11092
    {
11093
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11094
        $course_id = api_get_course_int_id();
11095
11096
        $sql = "SELECT * FROM $tbl_lp_item
11097
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11098
11099
        if ($addParentCondition) {
11100
            $sql .= ' AND parent_item_id = 0 ';
11101
        }
11102
        $sql .= ' ORDER BY display_order ASC';
11103
11104
        $result = Database::query($sql);
11105
        $arrLP = [];
11106
        while ($row = Database::fetch_array($result)) {
11107
            $arrLP[] = [
11108
                'iid' => $row['iid'],
11109
                'id' => $row['iid'],
11110
                'item_type' => $row['item_type'],
11111
                'title' => $this->cleanItemTitle($row['title']),
11112
                'title_raw' => $row['title'],
11113
                'path' => $row['path'],
11114
                'description' => Security::remove_XSS($row['description']),
11115
                'parent_item_id' => $row['parent_item_id'],
11116
                'previous_item_id' => $row['previous_item_id'],
11117
                'next_item_id' => $row['next_item_id'],
11118
                'display_order' => $row['display_order'],
11119
                'max_score' => $row['max_score'],
11120
                'min_score' => $row['min_score'],
11121
                'mastery_score' => $row['mastery_score'],
11122
                'prerequisite' => $row['prerequisite'],
11123
                'max_time_allowed' => $row['max_time_allowed'],
11124
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11125
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11126
            ];
11127
        }
11128
11129
        return $arrLP;
11130
    }
11131
11132
    /**
11133
     * Gets whether this SCORM learning path has been marked to use the score
11134
     * as progress. Takes into account whether the learnpath matches (SCORM
11135
     * content + less than 2 items).
11136
     *
11137
     * @return bool True if the score should be used as progress, false otherwise
11138
     */
11139
    public function getUseScoreAsProgress()
11140
    {
11141
        // If not a SCORM, we don't care about the setting
11142
        if (2 != $this->get_type()) {
11143
            return false;
11144
        }
11145
        // If more than one step in the SCORM, we don't care about the setting
11146
        if ($this->get_total_items_count() > 1) {
11147
            return false;
11148
        }
11149
        $extraFieldValue = new ExtraFieldValue('lp');
11150
        $doUseScore = false;
11151
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
11152
            $this->get_id(),
11153
            'use_score_as_progress'
11154
        );
11155
        if (!empty($useScore) && isset($useScore['value'])) {
11156
            $doUseScore = $useScore['value'];
11157
        }
11158
11159
        return $doUseScore;
11160
    }
11161
11162
    /**
11163
     * Get the user identifier (user_id or username
11164
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11165
     *
11166
     * @return string User ID or username, depending on configuration setting
11167
     */
11168
    public static function getUserIdentifierForExternalServices()
11169
    {
11170
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11171
            return api_get_user_info(api_get_user_id())['username'];
11172
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11173
            $extraFieldValue = new ExtraFieldValue('user');
11174
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
11175
                api_get_user_id(),
11176
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
11177
            );
11178
11179
            return $extrafield['value'];
11180
        } else {
11181
            return api_get_user_id();
11182
        }
11183
    }
11184
11185
    /**
11186
     * Save the new order for learning path items.
11187
     *
11188
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11189
     *
11190
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11191
     * @param int   $courseId
11192
     */
11193
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11194
    {
11195
        $courseId = $courseId ?: api_get_course_int_id();
11196
        $itemList = new LpItemOrderList();
11197
11198
        foreach ($orderList as $id => $parentId) {
11199
            $item = new LpOrderItem($id, $parentId);
11200
            $itemList->add($item);
11201
        }
11202
11203
        $parents = $itemList->getListOfParents();
11204
11205
        foreach ($parents as $parentId) {
11206
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11207
            $previous_item_id = 0;
11208
            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...
11209
                $item_id = $sameParentLpItemList->list[$i]->id;
11210
                // display_order
11211
                $display_order = $i + 1;
11212
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11213
                // previous_item_id
11214
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11215
                $previous_item_id = $item_id;
11216
                // next_item_id
11217
                $next_item_id = 0;
11218
                if ($i < count($sameParentLpItemList->list) - 1) {
11219
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11220
                }
11221
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11222
            }
11223
        }
11224
11225
        $table = Database::get_course_table(TABLE_LP_ITEM);
11226
11227
        foreach ($itemList->list as $item) {
11228
            $params = [];
11229
            $params['display_order'] = $item->display_order;
11230
            $params['previous_item_id'] = $item->previous_item_id;
11231
            $params['next_item_id'] = $item->next_item_id;
11232
            $params['parent_item_id'] = $item->parent_item_id;
11233
11234
            Database::update(
11235
                $table,
11236
                $params,
11237
                [
11238
                    'iid = ? AND c_id = ? ' => [
11239
                        (int) $item->id,
11240
                        (int) $courseId,
11241
                    ],
11242
                ]
11243
            );
11244
        }
11245
    }
11246
11247
    /**
11248
     * Get the depth level of LP item.
11249
     *
11250
     * @param array $items
11251
     * @param int   $currentItemId
11252
     *
11253
     * @return int
11254
     */
11255
    private static function get_level_for_item($items, $currentItemId)
11256
    {
11257
        $parentItemId = 0;
11258
        if (isset($items[$currentItemId])) {
11259
            $parentItemId = $items[$currentItemId]->parent;
11260
        }
11261
11262
        if (0 == $parentItemId) {
11263
            return 0;
11264
        } else {
11265
            return self::get_level_for_item($items, $parentItemId) + 1;
11266
        }
11267
    }
11268
11269
    /**
11270
     * Generate the link for a learnpath category as course tool.
11271
     *
11272
     * @param int $categoryId
11273
     *
11274
     * @return string
11275
     */
11276
    private static function getCategoryLinkForTool($categoryId)
11277
    {
11278
        $categoryId = (int) $categoryId;
11279
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11280
            .http_build_query(
11281
                [
11282
                    'action' => 'view_category',
11283
                    'id' => $categoryId,
11284
                ]
11285
            );
11286
11287
        return $link;
11288
    }
11289
11290
    /**
11291
     * Return the scorm item type object with spaces replaced with _
11292
     * The return result is use to build a css classname like scorm_type_$return.
11293
     *
11294
     * @param $in_type
11295
     *
11296
     * @return mixed
11297
     */
11298
    private static function format_scorm_type_item($in_type)
11299
    {
11300
        return str_replace(' ', '_', $in_type);
11301
    }
11302
11303
    /**
11304
     * Check and obtain the lp final item if exist.
11305
     *
11306
     * @return learnpathItem
11307
     */
11308
    private function getFinalItem()
11309
    {
11310
        if (empty($this->items)) {
11311
            return null;
11312
        }
11313
11314
        foreach ($this->items as $item) {
11315
            if ('final_item' !== $item->type) {
11316
                continue;
11317
            }
11318
11319
            return $item;
11320
        }
11321
    }
11322
11323
    /**
11324
     * Get the LP Final Item Template.
11325
     *
11326
     * @return string
11327
     */
11328
    private function getFinalItemTemplate()
11329
    {
11330
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11331
    }
11332
11333
    /**
11334
     * Get the LP Final Item Url.
11335
     *
11336
     * @return string
11337
     */
11338
    private function getSavedFinalItem()
11339
    {
11340
        $finalItem = $this->getFinalItem();
11341
11342
        $repo = Container::getDocumentRepository();
11343
        /** @var CDocument $document */
11344
        $document = $repo->find($finalItem->path);
11345
11346
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11347
            return $repo->getResourceFileContent($document);
11348
        }
11349
11350
        return '';
11351
    }
11352
}
11353