Passed
Push — master ( 680ed0...8b01a0 )
by Julito
09:16
created

learnpath::switch_attempt_mode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 16
rs 9.8333
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\User;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Framework\Container;
9
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
12
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
13
use Chamilo\CourseBundle\Entity\CDocument;
14
use Chamilo\CourseBundle\Entity\CForumCategory;
15
use Chamilo\CourseBundle\Entity\CForumThread;
16
use Chamilo\CourseBundle\Entity\CLink;
17
use Chamilo\CourseBundle\Entity\CLp;
18
use Chamilo\CourseBundle\Entity\CLpCategory;
19
use Chamilo\CourseBundle\Entity\CLpItem;
20
use Chamilo\CourseBundle\Entity\CLpItemView;
21
use Chamilo\CourseBundle\Entity\CQuiz;
22
use Chamilo\CourseBundle\Entity\CStudentPublication;
23
use Chamilo\CourseBundle\Entity\CTool;
24
use ChamiloSession as Session;
25
use Doctrine\Common\Collections\Criteria;
26
use PhpZip\ZipFile;
27
use Symfony\Component\Finder\Finder;
28
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
29
30
/**
31
 * Class learnpath
32
 * This class defines the parent attributes and methods for Chamilo learnpaths
33
 * and SCORM learnpaths. It is used by the scorm class.
34
 *
35
 * @todo decouple class
36
 *
37
 * @author  Yannick Warnier <[email protected]>
38
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
39
 */
40
class learnpath
41
{
42
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
43
    public const STATUS_CSS_CLASS_NAME = [
44
        'not attempted' => 'scorm_not_attempted',
45
        'incomplete' => 'scorm_not_attempted',
46
        'failed' => 'scorm_failed',
47
        'completed' => 'scorm_completed',
48
        'passed' => 'scorm_completed',
49
        'succeeded' => 'scorm_completed',
50
        'browsed' => 'scorm_completed',
51
    ];
52
53
    public $attempt = 0; // The number for the current ID view.
54
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
55
    public $current; // Id of the current item the user is viewing.
56
    public $current_score; // The score of the current item.
57
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
58
    public $current_time_stop; // The time the user closed this resource.
59
    public $default_status = 'not attempted';
60
    public $encoding = 'UTF-8';
61
    public $error = '';
62
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
63
    public $index; // The index of the active learnpath_item in $ordered_items array.
64
    /** @var learnpathItem[] */
65
    public $items = [];
66
    public $last; // item_id of last item viewed in the learning path.
67
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
68
    public $license; // Which license this course has been given - not used yet on 20060522.
69
    public $lp_id; // DB iid for this learnpath.
70
    public $lp_view_id; // DB ID for lp_view
71
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
72
    public $message = '';
73
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
74
    public $name; // Learnpath name (they generally have one).
75
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
76
    public $path = ''; // Path inside the scorm directory (if scorm).
77
    public $theme; // The current theme of the learning path.
78
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
79
    public $accumulateWorkTime; // The min time of learnpath
80
81
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
82
    public $prevent_reinit = 1;
83
84
    // Describes the mode of progress bar display.
85
    public $seriousgame_mode = 0;
86
    public $progress_bar_mode = '%';
87
88
    // Percentage progress as saved in the db.
89
    public $progress_db = 0;
90
    public $proximity; // Wether the content is distant or local or unknown.
91
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
92
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
93
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
94
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
95
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
96
    public $user_id; //ID of the user that is viewing/using the course
97
    public $update_queue = [];
98
    public $scorm_debug = 0;
99
    public $arrMenu = []; // Array for the menu items.
100
    public $debug = 0; // Logging level.
101
    public $lp_session_id = 0;
102
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
103
    public $prerequisite = 0;
104
    public $use_max_score = 1; // 1 or 0
105
    public $subscribeUsers = 0; // Subscribe users or not
106
    public $created_on = '';
107
    public $modified_on = '';
108
    public $publicated_on = '';
109
    public $expired_on = '';
110
    public $ref;
111
    public $course_int_id;
112
    public $course_info;
113
    public $categoryId;
114
    public $scormUrl;
115
    public $entity;
116
117
    public function __construct(CLp $entity = null, $course_info, $user_id)
118
    {
119
        $debug = $this->debug;
120
        $user_id = (int) $user_id;
121
        $this->encoding = api_get_system_encoding();
122
        $lp_id = 0;
123
        if (null !== $entity) {
124
            $lp_id = $entity->getIid();
125
        }
126
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
127
        $course_id = (int) $course_info['real_id'];
128
        $this->course_info = $course_info;
129
        $this->set_course_int_id($course_id);
130
        if (empty($lp_id) || empty($course_id)) {
131
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
132
        } else {
133
            //$this->entity = $entity;
134
            $this->lp_id = $lp_id;
135
            $this->type = $entity->getLpType();
136
            $this->name = stripslashes($entity->getName());
137
            $this->proximity = $entity->getContentLocal();
138
            $this->theme = $entity->getTheme();
139
            $this->maker = $entity->getContentLocal();
140
            $this->prevent_reinit = $entity->getPreventReinit();
141
            $this->seriousgame_mode = $entity->getSeriousgameMode();
142
            $this->license = $entity->getContentLicense();
143
            $this->scorm_debug = $entity->getDebug();
144
            $this->js_lib = $entity->getJsLib();
145
            $this->path = $entity->getPath();
146
            $this->author = $entity->getAuthor();
147
            $this->hide_toc_frame = $entity->getHideTocFrame();
148
            //$this->lp_session_id = $entity->getSessionId();
149
            $this->use_max_score = $entity->getUseMaxScore();
150
            $this->subscribeUsers = $entity->getSubscribeUsers();
151
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
152
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
153
            $this->ref = $entity->getRef();
154
            $this->categoryId = 0;
155
            if ($entity->getCategory()) {
156
                $this->categoryId = $entity->getCategory()->getIid();
157
            }
158
159
            if ($entity->hasAsset()) {
160
                $asset = $entity->getAsset();
161
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/'.$entity->getPath().'/';
162
            }
163
164
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
165
166
            if (!empty($entity->getPublicatedOn())) {
167
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
168
            }
169
170
            if (!empty($entity->getExpiredOn())) {
171
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
172
            }
173
            if (2 == $this->type) {
174
                if (1 == $entity->getForceCommit()) {
175
                    $this->force_commit = true;
176
                }
177
            }
178
            $this->mode = $entity->getDefaultViewMod();
179
180
            // Check user ID.
181
            if (empty($user_id)) {
182
                $this->error = 'User ID is empty';
183
            } else {
184
                $this->user_id = $user_id;
185
            }
186
187
            // End of variables checking.
188
            $session_id = api_get_session_id();
189
            //  Get the session condition for learning paths of the base + session.
190
            $session = api_get_session_condition($session_id);
191
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
192
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
193
194
            // Selecting by view_count descending allows to get the highest view_count first.
195
            $sql = "SELECT * FROM $lp_table
196
                    WHERE
197
                        c_id = $course_id AND
198
                        lp_id = $lp_id AND
199
                        user_id = $user_id
200
                        $session
201
                    ORDER BY view_count DESC";
202
            $res = Database::query($sql);
203
204
            if (Database::num_rows($res) > 0) {
205
                $row = Database::fetch_array($res);
206
                $this->attempt = $row['view_count'];
207
                $this->lp_view_id = $row['iid'];
208
                $this->last_item_seen = $row['last_item'];
209
                $this->progress_db = $row['progress'];
210
                $this->lp_view_session_id = $row['session_id'];
211
            } elseif (!api_is_invitee()) {
212
                $this->attempt = 1;
213
                $params = [
214
                    'c_id' => $course_id,
215
                    'lp_id' => $lp_id,
216
                    'user_id' => $user_id,
217
                    'view_count' => 1,
218
                    //'session_id' => $session_id,
219
                    'last_item' => 0,
220
                ];
221
                if (!empty($session_id)) {
222
                    $params['session_id'] = $session_id;
223
                }
224
                $this->last_item_seen = 0;
225
                $this->lp_view_session_id = $session_id;
226
                $this->lp_view_id = Database::insert($lp_table, $params);
227
            }
228
229
            $criteria = new Criteria();
230
            $criteria
231
                ->where($criteria->expr()->neq('path', 'root'))
232
                ->orderBy(
233
                    [
234
                        'parent' => Criteria::ASC,
235
                        'displayOrder' => Criteria::ASC,
236
                    ]
237
                );
238
            $items = $entity->getItems()->matching($criteria);
239
            $lp_item_id_list = [];
240
            foreach ($items as $item) {
241
                $itemId = $item->getIid();
242
                $lp_item_id_list[] = $itemId;
243
244
                switch ($this->type) {
245
                    case CLp::AICC_TYPE:
246
                        $oItem = new aiccItem('db', $itemId, $course_id);
247
                        if (is_object($oItem)) {
248
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
249
                            $oItem->set_prevent_reinit($this->prevent_reinit);
250
                            // Don't use reference here as the next loop will make the pointed object change.
251
                            $this->items[$itemId] = $oItem;
252
                            $this->refs_list[$oItem->ref] = $itemId;
253
                        }
254
                        break;
255
                    case CLp::SCORM_TYPE:
256
                        $oItem = new scormItem('db', $itemId);
257
                        if (is_object($oItem)) {
258
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
259
                            $oItem->set_prevent_reinit($this->prevent_reinit);
260
                            // Don't use reference here as the next loop will make the pointed object change.
261
                            $this->items[$itemId] = $oItem;
262
                            $this->refs_list[$oItem->ref] = $itemId;
263
                        }
264
                        break;
265
                    case CLp::LP_TYPE:
266
                    default:
267
                        $oItem = new learnpathItem(null, $item);
268
                        if (is_object($oItem)) {
269
                            // Moved down to when we are sure the item_view exists.
270
                            //$oItem->set_lp_view($this->lp_view_id);
271
                            $oItem->set_prevent_reinit($this->prevent_reinit);
272
                            // Don't use reference here as the next loop will make the pointed object change.
273
                            $this->items[$itemId] = $oItem;
274
                            $this->refs_list[$itemId] = $itemId;
275
                        }
276
                        break;
277
                }
278
279
                // Setting the object level with variable $this->items[$i][parent]
280
                foreach ($this->items as $itemLPObject) {
281
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
282
                    $itemLPObject->level = $level;
283
                }
284
285
                // Setting the view in the item object.
286
                if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
287
                    $this->items[$itemId]->set_lp_view($this->lp_view_id, $course_id);
288
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
289
                        $this->items[$itemId]->current_start_time = 0;
290
                        $this->items[$itemId]->current_stop_time = 0;
291
                    }
292
                }
293
            }
294
295
            if (!empty($lp_item_id_list)) {
296
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
297
                if (!empty($lp_item_id_list_to_string)) {
298
                    // Get last viewing vars.
299
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
300
                    // This query should only return one or zero result.
301
                    $sql = "SELECT lp_item_id, status
302
                            FROM $itemViewTable
303
                            WHERE
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
317
                            if (is_object($this->items[$item_id])) {
318
                                $this->items[$item_id]->set_status($status);
319
                                if (empty($status)) {
320
                                    $this->items[$item_id]->set_status(
321
                                        $this->default_status
322
                                    );
323
                                }
324
                            }
325
                        } else {
326
                            if (!api_is_invitee()) {
327
                                if (isset($this->items[$item_id]) && is_object($this->items[$item_id])) {
328
                                    $this->items[$item_id]->set_status(
329
                                        $this->default_status
330
                                    );
331
                                }
332
333
                                if (!empty($this->lp_view_id)) {
334
                                    // Add that row to the lp_item_view table so that
335
                                    // we have something to show in the stats page.
336
                                    $params = [
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($entity, null);
359
            $this->max_ordered_items = 0;
360
            foreach ($this->ordered_items as $index => $dummy) {
361
                if ($index > $this->max_ordered_items && !empty($dummy)) {
362
                    $this->max_ordered_items = $index;
363
                }
364
            }
365
            // TODO: Define the current item better.
366
            $this->first();
367
            if ($debug) {
368
                error_log('lp_view_session_id '.$this->lp_view_session_id);
369
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
370
            }
371
        }
372
    }
373
374
    /**
375
     * @return int
376
     */
377
    public function get_course_int_id()
378
    {
379
        return $this->course_int_id ?? api_get_course_int_id();
380
    }
381
382
    /**
383
     * @param $course_id
384
     *
385
     * @return int
386
     */
387
    public function set_course_int_id($course_id)
388
    {
389
        return $this->course_int_id = (int) $course_id;
390
    }
391
392
    /**
393
     * Function rewritten based on old_add_item() from Yannick Warnier.
394
     * Due the fact that users can decide where the item should come, I had to overlook this function and
395
     * I found it better to rewrite it. Old function is still available.
396
     * Added also the possibility to add a description.
397
     *
398
     * @param CLpItem $parent
399
     * @param int     $previousId
400
     * @param string  $type
401
     * @param int     $id resource ID (ref)
402
     * @param string  $title
403
     * @param string  $description
404
     * @param int     $prerequisites
405
     * @param int     $max_time_allowed
406
     * @param int     $userId
407
     *
408
     * @return int
409
     */
410
    public function add_item(
411
        ?CLpItem $parent,
412
        $previousId,
413
        $type,
414
        $id,
415
        $title,
416
        $description = '',
417
        $prerequisites = 0,
418
        $max_time_allowed = 0
419
    ) {
420
        $type = empty($type) ? 'dir' : $type;
421
        $course_id = $this->course_info['real_id'];
422
        if (empty($course_id)) {
423
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
424
            $this->course_info = api_get_course_info($this->cc);
425
            $course_id = $this->course_info['real_id'];
426
        }
427
        $id = (int) $id;
428
        $max_time_allowed = (int) $max_time_allowed;
429
        if (empty($max_time_allowed)) {
430
            $max_time_allowed = 0;
431
        }
432
        $max_score = 100;
433
        if ('quiz' === $type && $id) {
434
            // Disabling the exercise if we add it inside a LP
435
            $exercise = new Exercise($course_id);
436
            $exercise->read($id);
437
            $max_score = $exercise->get_max_score();
438
439
            $exercise->disable();
440
            $exercise->save();
441
            $title = $exercise->get_formated_title();
442
        }
443
444
        $lpItem = (new CLpItem())
445
            ->setTitle($title)
446
            ->setDescription($description)
447
            ->setPath($id)
448
            ->setLp(api_get_lp_entity($this->get_id()))
449
            ->setItemType($type)
450
            ->setMaxScore($max_score)
451
            ->setMaxTimeAllowed($max_time_allowed)
452
            ->setPrerequisite($prerequisites)
453
            //->setDisplayOrder($display_order + 1)
454
            //->setNextItemId((int) $next)
455
            //->setPreviousItemId($previous)
456
        ;
457
458
        if (!empty($parent))  {
459
            $lpItem->setParent($parent);
460
        }
461
        $em = Database::getManager();
462
        $em->persist($lpItem);
463
        $em->flush();
464
465
        $new_item_id = $lpItem->getIid();
466
        if ($new_item_id) {
467
            // @todo fix upload audio.
468
            // Upload audio.
469
            /*if (!empty($_FILES['mp3']['name'])) {
470
                // Create the audio folder if it does not exist yet.
471
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
472
                if (!is_dir($filepath.'audio')) {
473
                    mkdir(
474
                        $filepath.'audio',
475
                        api_get_permissions_for_new_directories()
476
                    );
477
                    DocumentManager::addDocument(
478
                        $_course,
479
                        '/audio',
480
                        'folder',
481
                        0,
482
                        'audio',
483
                        '',
484
                        0,
485
                        true,
486
                        null,
487
                        $sessionId,
488
                        $userId
489
                    );
490
                }
491
492
                $file_path = handle_uploaded_document(
493
                    $_course,
494
                    $_FILES['mp3'],
495
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
496
                    '/audio',
497
                    $userId,
498
                    '',
499
                    '',
500
                    '',
501
                    '',
502
                    false
503
                );
504
505
                // Getting the filename only.
506
                $file_components = explode('/', $file_path);
507
                $file = $file_components[count($file_components) - 1];
508
509
                // Store the mp3 file in the lp_item table.
510
                $sql = "UPDATE $tbl_lp_item SET
511
                          audio = '".Database::escape_string($file)."'
512
                        WHERE iid = '".intval($new_item_id)."'";
513
                Database::query($sql);
514
            }*/
515
        }
516
517
        return $new_item_id;
518
    }
519
520
    /**
521
     * Static admin function allowing addition of a learnpath to a course.
522
     *
523
     * @param string $courseCode
524
     * @param string $name
525
     * @param string $description
526
     * @param string $learnpath
527
     * @param string $origin
528
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
529
     * @param string $publicated_on
530
     * @param string $expired_on
531
     * @param int    $categoryId
532
     * @param int    $userId
533
     *
534
     * @return CLp
535
     */
536
    public static function add_lp(
537
        $courseCode,
538
        $name,
539
        $description = '',
540
        $learnpath = 'guess',
541
        $origin = 'zip',
542
        $zipname = '',
543
        $publicated_on = '',
544
        $expired_on = '',
545
        $categoryId = 0,
546
        $userId = 0
547
    ) {
548
        global $charset;
549
550
        if (!empty($courseCode)) {
551
            $courseInfo = api_get_course_info($courseCode);
552
            $course_id = $courseInfo['real_id'];
553
        } else {
554
            $course_id = api_get_course_int_id();
555
            $courseInfo = api_get_course_info();
556
        }
557
558
        $categoryId = (int) $categoryId;
559
560
        if (empty($publicated_on)) {
561
            $publicated_on = null;
562
        } else {
563
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
564
        }
565
566
        if (empty($expired_on)) {
567
            $expired_on = null;
568
        } else {
569
            $expired_on = api_get_utc_datetime($expired_on, true, true);
570
        }
571
572
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES));
573
        $type = 1;
574
        switch ($learnpath) {
575
            case 'guess':
576
            case 'aicc':
577
                break;
578
            case 'dokeos':
579
            case 'chamilo':
580
                $type = 1;
581
                break;
582
        }
583
584
        $sessionEntity = api_get_session_entity();
585
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
586
        $lp = null;
587
        switch ($origin) {
588
            case 'zip':
589
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
590
                break;
591
            case 'manual':
592
            default:
593
                /*$get_max = "SELECT MAX(display_order)
594
                            FROM $tbl_lp WHERE c_id = $course_id";
595
                $res_max = Database::query($get_max);
596
                if (Database::num_rows($res_max) < 1) {
597
                    $dsp = 1;
598
                } else {
599
                    $row = Database::fetch_array($res_max);
600
                    $dsp = $row[0] + 1;
601
                }*/
602
603
                $dsp = 1;
604
                $category = null;
605
                if (!empty($categoryId)) {
606
                    $category = Container::getLpCategoryRepository()->find($categoryId);
607
                }
608
609
                $lpRepo = Container::getLpRepository();
610
611
                $lp = (new CLp())
612
                    ->setLpType($type)
613
                    ->setName($name)
614
                    ->setDescription($description)
615
                    ->setDisplayOrder($dsp)
616
                    ->setCategory($category)
617
                    ->setPublicatedOn($publicated_on)
618
                    ->setExpiredOn($expired_on)
619
                    ->setParent($courseEntity)
620
                    ->addCourseLink($courseEntity, $sessionEntity)
621
                ;
622
                $lpRepo->createLp($lp);
623
624
                break;
625
        }
626
627
        return $lp;
628
    }
629
630
    /**
631
     * Auto completes the parents of an item in case it's been completed or passed.
632
     *
633
     * @param int $item Optional ID of the item from which to look for parents
634
     */
635
    public function autocomplete_parents($item)
636
    {
637
        $debug = $this->debug;
638
639
        if (empty($item)) {
640
            $item = $this->current;
641
        }
642
643
        $currentItem = $this->getItem($item);
644
        if ($currentItem) {
645
            $parent_id = $currentItem->get_parent();
646
            $parent = $this->getItem($parent_id);
647
            if ($parent) {
648
                // if $item points to an object and there is a parent.
649
                if ($debug) {
650
                    error_log(
651
                        'Autocompleting parent of item '.$item.' '.
652
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
653
                        0
654
                    );
655
                }
656
657
                // New experiment including failed and browsed in completed status.
658
                //$current_status = $currentItem->get_status();
659
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
660
                // Fixes chapter auto complete
661
                if (true) {
662
                    // If the current item is completed or passes or succeeded.
663
                    $updateParentStatus = true;
664
                    if ($debug) {
665
                        error_log('Status of current item is alright');
666
                    }
667
668
                    foreach ($parent->get_children() as $childItemId) {
669
                        $childItem = $this->getItem($childItemId);
670
671
                        // If children was not set try to get the info
672
                        if (empty($childItem->db_item_view_id)) {
673
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
674
                        }
675
676
                        // Check all his brothers (parent's children) for completion status.
677
                        if ($childItemId != $item) {
678
                            if ($debug) {
679
                                error_log(
680
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
681
                                    0
682
                                );
683
                            }
684
                            // Trying completing parents of failed and browsed items as well.
685
                            if ($childItem->status_is(
686
                                [
687
                                    'completed',
688
                                    'passed',
689
                                    'succeeded',
690
                                    'browsed',
691
                                    'failed',
692
                                ]
693
                            )
694
                            ) {
695
                                // Keep completion status to true.
696
                                continue;
697
                            } else {
698
                                if ($debug > 2) {
699
                                    error_log(
700
                                        '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,
701
                                        0
702
                                    );
703
                                }
704
                                $updateParentStatus = false;
705
                                break;
706
                            }
707
                        }
708
                    }
709
710
                    if ($updateParentStatus) {
711
                        // If all the children were completed:
712
                        $parent->set_status('completed');
713
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
714
                        // Force the status to "completed"
715
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
716
                        $this->update_queue[$parent->get_id()] = 'completed';
717
                        if ($debug) {
718
                            error_log(
719
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
720
                                print_r($this->update_queue, 1),
721
                                0
722
                            );
723
                        }
724
                        // Recursive call.
725
                        $this->autocomplete_parents($parent->get_id());
726
                    }
727
                }
728
            } else {
729
                if ($debug) {
730
                    error_log("Parent #$parent_id does not exists");
731
                }
732
            }
733
        } else {
734
            if ($debug) {
735
                error_log("#$item is an item that doesn't have parents");
736
            }
737
        }
738
    }
739
740
    /**
741
     * Closes the current resource.
742
     *
743
     * Stops the timer
744
     * Saves into the database if required
745
     * Clears the current resource data from this object
746
     *
747
     * @return bool True on success, false on failure
748
     */
749
    public function close()
750
    {
751
        if (empty($this->lp_id)) {
752
            $this->error = 'Trying to close this learnpath but no ID is set';
753
754
            return false;
755
        }
756
        $this->current_time_stop = time();
757
        $this->ordered_items = [];
758
        $this->index = 0;
759
        unset($this->lp_id);
760
        //unset other stuff
761
        return true;
762
    }
763
764
    /**
765
     * Static admin function allowing removal of a learnpath.
766
     *
767
     * @param array  $courseInfo
768
     * @param int    $id         Learnpath ID
769
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
770
     *
771
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
772
     */
773
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
774
    {
775
        $course_id = api_get_course_int_id();
776
        if (!empty($courseInfo)) {
777
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
778
        }
779
780
        // TODO: Implement a way of getting this to work when the current object is not set.
781
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
782
        // If an ID is specifically given and the current LP is not the same, prevent delete.
783
        if (!empty($id) && ($id != $this->lp_id)) {
784
            return false;
785
        }
786
787
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
788
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
789
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
790
791
        // Delete lp item id.
792
        foreach ($this->items as $lpItemId => $dummy) {
793
            $sql = "DELETE FROM $lp_item_view
794
                    WHERE lp_item_id = '".$lpItemId."'";
795
            Database::query($sql);
796
        }
797
798
        // Proposed by Christophe (nickname: clefevre)
799
        $sql = "DELETE FROM $lp_item
800
                WHERE lp_id = ".$this->lp_id;
801
        Database::query($sql);
802
803
        $sql = "DELETE FROM $lp_view
804
                WHERE lp_id = ".$this->lp_id;
805
        Database::query($sql);
806
807
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
808
        $sql = "DELETE FROM $table
809
                WHERE
810
                    lp_id = {$this->lp_id}";
811
        Database::query($sql);
812
813
        $repo = Container::getLpRepository();
814
        $lp = $repo->find($this->lp_id);
815
        Database::getManager()->remove($lp);
816
        Database::getManager()->flush();
817
818
        // Updates the display order of all lps.
819
        $this->update_display_order();
820
821
        $link_info = GradebookUtils::isResourceInCourseGradebook(
822
            api_get_course_id(),
823
            4,
824
            $id,
825
            api_get_session_id()
826
        );
827
828
        if (false !== $link_info) {
829
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
830
        }
831
832
        if ('true' === api_get_setting('search_enabled')) {
833
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
834
        }
835
    }
836
837
    /**
838
     * Removes all the children of one item - dangerous!
839
     *
840
     * @param int $id Element ID of which children have to be removed
841
     *
842
     * @return int Total number of children removed
843
     */
844
    public function delete_children_items($id)
845
    {
846
        $course_id = $this->course_info['real_id'];
847
848
        $num = 0;
849
        $id = (int) $id;
850
        if (empty($id) || empty($course_id)) {
851
            return false;
852
        }
853
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
854
        $sql = "SELECT * FROM $lp_item
855
                WHERE parent_item_id = $id";
856
        $res = Database::query($sql);
857
        while ($row = Database::fetch_array($res)) {
858
            $num += $this->delete_children_items($row['iid']);
859
            $sql = "DELETE FROM $lp_item
860
                    WHERE iid = ".$row['iid'];
861
            Database::query($sql);
862
            $num++;
863
        }
864
865
        return $num;
866
    }
867
868
    /**
869
     * Removes an item from the current learnpath.
870
     *
871
     * @param int $id Elem ID (0 if first)
872
     *
873
     * @return int Number of elements moved
874
     *
875
     * @todo implement resource removal
876
     */
877
    public function delete_item($id)
878
    {
879
        $course_id = api_get_course_int_id();
880
        $id = (int) $id;
881
        // TODO: Implement the resource removal.
882
        if (empty($id) || empty($course_id)) {
883
            return false;
884
        }
885
886
        $repo = Container::getLpItemRepository();
887
        $item = $repo->find($id);
888
        if (null === $item) {
889
            return false;
890
        }
891
892
        $em = Database::getManager();
893
        $repo->removeFromTree($item);
894
        $em->flush();
895
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
896
897
        //Removing prerequisites since the item will not longer exist
898
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
899
                    WHERE prerequisite = '$id'";
900
        Database::query($sql_all);
901
902
        $sql = "UPDATE $lp_item
903
                SET previous_item_id = ".$this->getLastInFirstLevel()."
904
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
905
        Database::query($sql);
906
907
        // Remove from search engine if enabled.
908
        if ('true' === api_get_setting('search_enabled')) {
909
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
910
            $sql = 'SELECT * FROM %s
911
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
912
                    LIMIT 1';
913
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
914
            $res = Database::query($sql);
915
            if (Database::num_rows($res) > 0) {
916
                $row2 = Database::fetch_array($res);
917
                $di = new ChamiloIndexer();
918
                $di->remove_document($row2['search_did']);
919
            }
920
            $sql = 'DELETE FROM %s
921
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
922
                    LIMIT 1';
923
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
924
            Database::query($sql);
925
        }
926
    }
927
928
    /**
929
     * Updates an item's content in place.
930
     *
931
     * @param int    $id               Element ID
932
     * @param int    $parent           Parent item ID
933
     * @param int    $previous         Previous item ID
934
     * @param string $title            Item title
935
     * @param string $description      Item description
936
     * @param string $prerequisites    Prerequisites (optional)
937
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
938
     * @param int    $max_time_allowed
939
     * @param string $url
940
     *
941
     * @return bool True on success, false on error
942
     */
943
    public function edit_item(
944
        $id,
945
        $parent,
946
        $previous,
947
        $title,
948
        $description,
949
        $prerequisites = '0',
950
        $audio = [],
951
        $max_time_allowed = 0,
952
        $url = ''
953
    ) {
954
        $_course = api_get_course_info();
955
        $id = (int) $id;
956
957
        if (empty($id) || empty($_course)) {
958
            return false;
959
        }
960
        $repo = Container::getLpItemRepository();
961
        /** @var CLpItem $item */
962
        $item = $repo->find($id);
963
        if (null === $item) {
964
            return false;
965
        }
966
967
        $item
968
            ->setTitle($title)
969
            ->setDescription($description)
970
            ->setPrerequisite($prerequisites)
971
            ->setMaxTimeAllowed((int) $max_time_allowed)
972
        ;
973
974
        $em = Database::getManager();
975
        if (!empty($parent)) {
976
            $parent = $repo->find($parent);
977
            $item->setParent($parent);
978
        } else {
979
            $item->setParent(null);
980
        }
981
982
        if (!empty($previous)) {
983
            $previous = $repo->find($previous);
984
            $repo->persistAsNextSiblingOf( $item, $previous);
985
        } else {
986
            $em->persist($item);
987
        }
988
989
        $em->flush();
990
991
        if ('link' === $item->getItemType()) {
992
            $link = new Link();
993
            $linkId = $item->getPath();
994
            $link->updateLink($linkId, $url);
995
        }
996
    }
997
998
    /**
999
     * Updates an item's prereq in place.
1000
     *
1001
     * @param int    $id              Element ID
1002
     * @param string $prerequisite_id Prerequisite Element ID
1003
     * @param int    $minScore        Prerequisite min score
1004
     * @param int    $maxScore        Prerequisite max score
1005
     *
1006
     * @return bool True on success, false on error
1007
     */
1008
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1009
    {
1010
        $id = (int) $id;
1011
1012
        if (empty($id)) {
1013
            return false;
1014
        }
1015
        $prerequisite_id = (int) $prerequisite_id;
1016
1017
        if (empty($minScore) || $minScore < 0) {
1018
            $minScore = 0;
1019
        }
1020
1021
        if (empty($maxScore) || $maxScore < 0) {
1022
            $maxScore = 100;
1023
        }
1024
1025
        $minScore = (float) $minScore;
1026
        $maxScore = (float) $maxScore;
1027
1028
        if (empty($prerequisite_id)) {
1029
            $prerequisite_id = 'NULL';
1030
            $minScore = 0;
1031
            $maxScore = 100;
1032
        }
1033
1034
        $table = Database::get_course_table(TABLE_LP_ITEM);
1035
        $sql = " UPDATE $table
1036
                 SET
1037
                    prerequisite = $prerequisite_id ,
1038
                    prerequisite_min_score = $minScore ,
1039
                    prerequisite_max_score = $maxScore
1040
                 WHERE iid = $id";
1041
        Database::query($sql);
1042
1043
        return true;
1044
    }
1045
1046
    /**
1047
     * Get the specific prefix index terms of this learning path.
1048
     *
1049
     * @param string $prefix
1050
     *
1051
     * @return array Array of terms
1052
     */
1053
    public function get_common_index_terms_by_prefix($prefix)
1054
    {
1055
        $terms = get_specific_field_values_list_by_prefix(
1056
            $prefix,
1057
            $this->cc,
1058
            TOOL_LEARNPATH,
1059
            $this->lp_id
1060
        );
1061
        $prefix_terms = [];
1062
        if (!empty($terms)) {
1063
            foreach ($terms as $term) {
1064
                $prefix_terms[] = $term['value'];
1065
            }
1066
        }
1067
1068
        return $prefix_terms;
1069
    }
1070
1071
    /**
1072
     * Gets the number of items currently completed.
1073
     *
1074
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1075
     *
1076
     * @return int The number of items currently completed
1077
     */
1078
    public function get_complete_items_count($failedStatusException = false)
1079
    {
1080
        $i = 0;
1081
        $completedStatusList = [
1082
            'completed',
1083
            'passed',
1084
            'succeeded',
1085
            'browsed',
1086
        ];
1087
1088
        if (!$failedStatusException) {
1089
            $completedStatusList[] = 'failed';
1090
        }
1091
1092
        foreach ($this->items as $id => $dummy) {
1093
            // Trying failed and browsed considered "progressed" as well.
1094
            if ($this->items[$id]->status_is($completedStatusList) &&
1095
                'dir' !== $this->items[$id]->get_type()
1096
            ) {
1097
                $i++;
1098
            }
1099
        }
1100
1101
        return $i;
1102
    }
1103
1104
    /**
1105
     * Gets the current item ID.
1106
     *
1107
     * @return int The current learnpath item id
1108
     */
1109
    public function get_current_item_id()
1110
    {
1111
        $current = 0;
1112
        if (!empty($this->current)) {
1113
            $current = (int) $this->current;
1114
        }
1115
1116
        return $current;
1117
    }
1118
1119
    /**
1120
     * Force to get the first learnpath item id.
1121
     *
1122
     * @return int The current learnpath item id
1123
     */
1124
    public function get_first_item_id()
1125
    {
1126
        $current = 0;
1127
        if (is_array($this->ordered_items)) {
1128
            $current = $this->ordered_items[0];
1129
        }
1130
1131
        return $current;
1132
    }
1133
1134
    /**
1135
     * Gets the total number of items available for viewing in this SCORM.
1136
     *
1137
     * @return int The total number of items
1138
     */
1139
    public function get_total_items_count()
1140
    {
1141
        return count($this->items);
1142
    }
1143
1144
    /**
1145
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1146
     *
1147
     * @return int The total no-chapters number of items
1148
     */
1149
    public function getTotalItemsCountWithoutDirs()
1150
    {
1151
        $total = 0;
1152
        $typeListNotToCount = self::getChapterTypes();
1153
        foreach ($this->items as $temp2) {
1154
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1155
                $total++;
1156
            }
1157
        }
1158
1159
        return $total;
1160
    }
1161
1162
    /**
1163
     *  Sets the first element URL.
1164
     */
1165
    public function first()
1166
    {
1167
        if ($this->debug > 0) {
1168
            error_log('In learnpath::first()', 0);
1169
            error_log('$this->last_item_seen '.$this->last_item_seen);
1170
        }
1171
1172
        // Test if the last_item_seen exists and is not a dir.
1173
        if (0 == count($this->ordered_items)) {
1174
            $this->index = 0;
1175
        }
1176
1177
        if (!empty($this->last_item_seen) &&
1178
            !empty($this->items[$this->last_item_seen]) &&
1179
            'dir' !== $this->items[$this->last_item_seen]->get_type()
1180
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1181
            //&& !$this->items[$this->last_item_seen]->is_done()
1182
        ) {
1183
            if ($this->debug > 2) {
1184
                error_log(
1185
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1186
                    $this->items[$this->last_item_seen]->get_type()
1187
                );
1188
            }
1189
            $index = -1;
1190
            foreach ($this->ordered_items as $myindex => $item_id) {
1191
                if ($item_id == $this->last_item_seen) {
1192
                    $index = $myindex;
1193
                    break;
1194
                }
1195
            }
1196
            if (-1 == $index) {
1197
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1198
                if ($this->debug > 2) {
1199
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1200
                }
1201
1202
                return false;
1203
            } else {
1204
                $this->last = $this->last_item_seen;
1205
                $this->current = $this->last_item_seen;
1206
                $this->index = $index;
1207
            }
1208
        } else {
1209
            if ($this->debug > 2) {
1210
                error_log('In learnpath::first() - No last item seen', 0);
1211
            }
1212
            $index = 0;
1213
            // Loop through all ordered items and stop at the first item that is
1214
            // not a directory *and* that has not been completed yet.
1215
            while (!empty($this->ordered_items[$index]) &&
1216
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1217
                (
1218
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1219
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1220
                ) && $index < $this->max_ordered_items
1221
            ) {
1222
                $index++;
1223
            }
1224
1225
            $this->last = $this->current;
1226
            // current is
1227
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1228
            $this->index = $index;
1229
            if ($this->debug > 2) {
1230
                error_log('$index '.$index);
1231
                error_log('In learnpath::first() - No last item seen');
1232
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1233
            }
1234
        }
1235
        if ($this->debug > 2) {
1236
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1237
        }
1238
    }
1239
1240
    /**
1241
     * Gets the js library from the database.
1242
     *
1243
     * @return string The name of the javascript library to be used
1244
     */
1245
    public function get_js_lib()
1246
    {
1247
        $lib = '';
1248
        if (!empty($this->js_lib)) {
1249
            $lib = $this->js_lib;
1250
        }
1251
1252
        return $lib;
1253
    }
1254
1255
    /**
1256
     * Gets the learnpath database ID.
1257
     *
1258
     * @return int Learnpath ID in the lp table
1259
     */
1260
    public function get_id()
1261
    {
1262
        if (!empty($this->lp_id)) {
1263
            return (int) $this->lp_id;
1264
        }
1265
1266
        return 0;
1267
    }
1268
1269
    /**
1270
     * Gets the last element URL.
1271
     *
1272
     * @return string URL to load into the viewer
1273
     */
1274
    public function get_last()
1275
    {
1276
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1277
        if (count($this->ordered_items) > 0) {
1278
            $this->index = count($this->ordered_items) - 1;
1279
1280
            return $this->ordered_items[$this->index];
1281
        }
1282
1283
        return false;
1284
    }
1285
1286
    /**
1287
     * Get the last element in the first level.
1288
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1289
     *
1290
     * @return mixed
1291
     */
1292
    public function getLastInFirstLevel()
1293
    {
1294
        try {
1295
            $lastId = Database::getManager()
1296
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1297
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1298
                ->setMaxResults(1)
1299
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1300
                ->getSingleScalarResult();
1301
1302
            return $lastId;
1303
        } catch (Exception $exception) {
1304
            return 0;
1305
        }
1306
    }
1307
1308
    /**
1309
     * Gets the navigation bar for the learnpath display screen.
1310
     *
1311
     * @param string $barId
1312
     *
1313
     * @return string The HTML string to use as a navigation bar
1314
     */
1315
    public function get_navigation_bar($barId = '')
1316
    {
1317
        if (empty($barId)) {
1318
            $barId = 'control-top';
1319
        }
1320
        $lpId = $this->lp_id;
1321
        $mycurrentitemid = $this->get_current_item_id();
1322
        $reportingText = get_lang('Reporting');
1323
        $previousText = get_lang('Previous');
1324
        $nextText = get_lang('Next');
1325
        $fullScreenText = get_lang('Back to normal screen');
1326
1327
        $settings = api_get_configuration_value('lp_view_settings');
1328
        $display = $settings['display'] ?? false;
1329
        $reportingIcon = '
1330
            <a class="icon-toolbar"
1331
                id="stats_link"
1332
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1333
                onclick="window.parent.API.save_asset(); return true;"
1334
                target="content_name" title="'.$reportingText.'">
1335
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1336
            </a>';
1337
1338
        if (!empty($display)) {
1339
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1340
            if (false === $showReporting) {
1341
                $reportingIcon = '';
1342
            }
1343
        }
1344
1345
        $hideArrows = false;
1346
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1347
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1348
        }
1349
1350
        $previousIcon = '';
1351
        $nextIcon = '';
1352
        if (false === $hideArrows) {
1353
            $previousIcon = '
1354
                <a class="icon-toolbar" id="scorm-previous" href="#"
1355
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1356
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1357
                </a>';
1358
1359
            $nextIcon = '
1360
                <a class="icon-toolbar" id="scorm-next" href="#"
1361
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1362
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1363
                </a>';
1364
        }
1365
1366
        if ('fullscreen' === $this->mode) {
1367
            $navbar = '
1368
                  <span id="'.$barId.'" class="buttons">
1369
                    '.$reportingIcon.'
1370
                    '.$previousIcon.'
1371
                    '.$nextIcon.'
1372
                    <a class="icon-toolbar" id="view-embedded"
1373
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1374
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1375
                    </a>
1376
                  </span>';
1377
        } else {
1378
            $navbar = '
1379
                 <span id="'.$barId.'" class="buttons text-right">
1380
                    '.$reportingIcon.'
1381
                    '.$previousIcon.'
1382
                    '.$nextIcon.'
1383
                </span>';
1384
        }
1385
1386
        return $navbar;
1387
    }
1388
1389
    /**
1390
     * Gets the next resource in queue (url).
1391
     *
1392
     * @return string URL to load into the viewer
1393
     */
1394
    public function get_next_index()
1395
    {
1396
        // TODO
1397
        $index = $this->index;
1398
        $index++;
1399
        while (
1400
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1401
            $index < $this->max_ordered_items
1402
        ) {
1403
            $index++;
1404
            if ($index == $this->max_ordered_items) {
1405
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1406
                    return $this->index;
1407
                }
1408
1409
                return $index;
1410
            }
1411
        }
1412
        if (empty($this->ordered_items[$index])) {
1413
            return $this->index;
1414
        }
1415
1416
        return $index;
1417
    }
1418
1419
    /**
1420
     * Gets item_id for the next element.
1421
     *
1422
     * @return int Next item (DB) ID
1423
     */
1424
    public function get_next_item_id()
1425
    {
1426
        $new_index = $this->get_next_index();
1427
        if (!empty($new_index)) {
1428
            if (isset($this->ordered_items[$new_index])) {
1429
                return $this->ordered_items[$new_index];
1430
            }
1431
        }
1432
1433
        return 0;
1434
    }
1435
1436
    /**
1437
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1438
     *
1439
     * Generally, the package provided is in the form of a zip file, so the function
1440
     * has been written to test a zip file. If not a zip, the function will return the
1441
     * default return value: ''
1442
     *
1443
     * @param string $filePath the path to the file
1444
     * @param string $file_name the original name of the file
1445
     *
1446
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1447
     *                if the package is empty, or '' if the package cannot be recognized
1448
     */
1449
    public static function getPackageType($filePath, $file_name)
1450
    {
1451
        // Get name of the zip file without the extension.
1452
        $file_info = pathinfo($file_name);
1453
        $extension = $file_info['extension']; // Extension only.
1454
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1455
                'dll',
1456
                'exe',
1457
            ])) {
1458
            return 'oogie';
1459
        }
1460
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1461
                'dll',
1462
                'exe',
1463
            ])) {
1464
            return 'woogie';
1465
        }
1466
1467
        $zipFile = new ZipFile();
1468
        $zipFile->openFile($filePath);
1469
        $zipContentArray = $zipFile->getEntries();
1470
        $package_type = '';
1471
        $manifest = '';
1472
        $aicc_match_crs = 0;
1473
        $aicc_match_au = 0;
1474
        $aicc_match_des = 0;
1475
        $aicc_match_cst = 0;
1476
        $countItems = 0;
1477
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1478
        if ($zipContentArray) {
1479
            $countItems = count($zipContentArray);
1480
            if ($countItems > 0) {
1481
                foreach ($zipContentArray as $thisContent) {
1482
                    $fileName = basename($thisContent->getName());
1483
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1484
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1485
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1486
                        $manifest = $fileName; // Just the relative directory inside scorm/
1487
                        $package_type = 'scorm';
1488
                        break; // Exit the foreach loop.
1489
                    } elseif (
1490
                        preg_match('/aicc\//i', $fileName) ||
1491
                        in_array(
1492
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1493
                            ['crs', 'au', 'des', 'cst']
1494
                        )
1495
                    ) {
1496
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1497
                        switch ($ext) {
1498
                            case 'crs':
1499
                                $aicc_match_crs = 1;
1500
                                break;
1501
                            case 'au':
1502
                                $aicc_match_au = 1;
1503
                                break;
1504
                            case 'des':
1505
                                $aicc_match_des = 1;
1506
                                break;
1507
                            case 'cst':
1508
                                $aicc_match_cst = 1;
1509
                                break;
1510
                            default:
1511
                                break;
1512
                        }
1513
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1514
                    } else {
1515
                        $package_type = '';
1516
                    }
1517
                }
1518
            }
1519
        }
1520
1521
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1522
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1523
            $package_type = 'aicc';
1524
        }
1525
1526
        // Try with chamilo course builder
1527
        if (empty($package_type)) {
1528
            // Sometimes users will try to upload an empty zip, or a zip with
1529
            // only a folder. Catch that and make the calling function aware.
1530
            // If the single file was the imsmanifest.xml, then $package_type
1531
            // would be 'scorm' and we wouldn't be here.
1532
            if ($countItems < 2) {
1533
                return 'error-empty-package';
1534
            }
1535
            $package_type = 'chamilo';
1536
        }
1537
1538
        return $package_type;
1539
    }
1540
1541
    /**
1542
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1543
     *
1544
     * @return string URL to load into the viewer
1545
     */
1546
    public function get_previous_index()
1547
    {
1548
        $index = $this->index;
1549
        if (isset($this->ordered_items[$index - 1])) {
1550
            $index--;
1551
            while (isset($this->ordered_items[$index]) &&
1552
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1553
            ) {
1554
                $index--;
1555
                if ($index < 0) {
1556
                    return $this->index;
1557
                }
1558
            }
1559
        }
1560
1561
        return $index;
1562
    }
1563
1564
    /**
1565
     * Gets item_id for the next element.
1566
     *
1567
     * @return int Previous item (DB) ID
1568
     */
1569
    public function get_previous_item_id()
1570
    {
1571
        $index = $this->get_previous_index();
1572
1573
        return $this->ordered_items[$index];
1574
    }
1575
1576
    /**
1577
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1578
     *
1579
     * @param int    $lpItemId
1580
     * @param string $autostart
1581
     *
1582
     * @return string The mediaplayer HTML
1583
     */
1584
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1585
    {
1586
        $courseInfo = api_get_course_info();
1587
        $lpItemId = (int) $lpItemId;
1588
1589
        if (empty($courseInfo) || empty($lpItemId)) {
1590
            return '';
1591
        }
1592
        $item = $this->items[$lpItemId] ?? null;
1593
1594
        if (empty($item)) {
1595
            return '';
1596
        }
1597
1598
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1599
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1600
        $itemViewId = (int) $item->db_item_view_id;
1601
1602
        // Getting all the information about the item.
1603
        $sql = "SELECT lp_view.status
1604
                FROM $tbl_lp_item as lpi
1605
                INNER JOIN $tbl_lp_item_view as lp_view
1606
                ON (lpi.iid = lp_view.lp_item_id)
1607
                WHERE
1608
                    lp_view.iid = $itemViewId AND
1609
                    lpi.iid = $lpItemId
1610
                ";
1611
        $result = Database::query($sql);
1612
        $row = Database::fetch_assoc($result);
1613
        $output = '';
1614
        $audio = $item->audio;
1615
1616
        if (!empty($audio)) {
1617
            $list = $_SESSION['oLP']->get_toc();
1618
1619
            switch ($item->get_type()) {
1620
                case 'quiz':
1621
                    $type_quiz = false;
1622
                    foreach ($list as $toc) {
1623
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1624
                            $type_quiz = true;
1625
                        }
1626
                    }
1627
1628
                    if ($type_quiz) {
1629
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1630
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1631
                        } else {
1632
                            $autostart_audio = $autostart;
1633
                        }
1634
                    }
1635
                    break;
1636
                case TOOL_READOUT_TEXT:
1637
                    $autostart_audio = 'false';
1638
                    break;
1639
                default:
1640
                    $autostart_audio = 'true';
1641
            }
1642
1643
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1644
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1645
1646
            $player = Display::getMediaPlayer(
1647
                $file,
1648
                [
1649
                    'id' => 'lp_audio_media_player',
1650
                    'url' => $url,
1651
                    'autoplay' => $autostart_audio,
1652
                    'width' => '100%',
1653
                ]
1654
            );
1655
1656
            // The mp3 player.
1657
            $output = '<div id="container">';
1658
            $output .= $player;
1659
            $output .= '</div>';
1660
        }
1661
1662
        return $output;
1663
    }
1664
1665
    /**
1666
     * @param int    $studentId
1667
     * @param int    $prerequisite
1668
     * @param Course $course
1669
     * @param int    $sessionId
1670
     *
1671
     * @return bool
1672
     */
1673
    public static function isBlockedByPrerequisite(
1674
        $studentId,
1675
        $prerequisite,
1676
        Course $course,
1677
        $sessionId
1678
    ) {
1679
        $courseId = $course->getId();
1680
1681
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
1682
        if ($allow) {
1683
            if (api_is_allowed_to_edit() ||
1684
                api_is_platform_admin(true) ||
1685
                api_is_drh() ||
1686
                api_is_coach($sessionId, $courseId, false)
1687
            ) {
1688
                return false;
1689
            }
1690
        }
1691
1692
        $isBlocked = false;
1693
        if (!empty($prerequisite)) {
1694
            $progress = self::getProgress(
1695
                $prerequisite,
1696
                $studentId,
1697
                $courseId,
1698
                $sessionId
1699
            );
1700
            if ($progress < 100) {
1701
                $isBlocked = true;
1702
            }
1703
1704
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1705
                // Block if it does not exceed minimum time
1706
                // Minimum time (in minutes) to pass the learning path
1707
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1708
1709
                if ($accumulateWorkTime > 0) {
1710
                    // Total time in course (sum of times in learning paths from course)
1711
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1712
1713
                    // Connect with the plugin_licences_course_session table
1714
                    // which indicates what percentage of the time applies
1715
                    // Minimum connection percentage
1716
                    $perc = 100;
1717
                    // Time from the course
1718
                    $tc = $accumulateWorkTimeTotal;
1719
1720
                    // Percentage of the learning paths
1721
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1722
                    // Minimum time for each learning path
1723
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1724
1725
                    // Spent time (in seconds) so far in the learning path
1726
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1727
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1728
1729
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1730
                        $isBlocked = true;
1731
                    }
1732
                }
1733
            }
1734
        }
1735
1736
        return $isBlocked;
1737
    }
1738
1739
    /**
1740
     * Checks if the learning path is visible for student after the progress
1741
     * of its prerequisite is completed, considering the time availability and
1742
     * the LP visibility.
1743
     */
1744
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1745
    {
1746
        $sessionId = $session ? $session->getId() : 0;
1747
        $courseId = $course->getId();
1748
        $visibility = $lp->isVisible($course, $session);
1749
1750
        // If the item was deleted.
1751
        if (false === $visibility) {
1752
            return false;
1753
        }
1754
1755
        $now = time();
1756
        if ($lp->hasCategory()) {
1757
            $category = $lp->getCategory();
1758
1759
            if (false === self::categoryIsVisibleForStudent(
1760
                    $category,
1761
                    api_get_user_entity($student_id),
1762
                    $course,
1763
                    $session
1764
                )) {
1765
                return false;
1766
            }
1767
1768
            $prerequisite = $lp->getPrerequisite();
1769
            $is_visible = true;
1770
1771
            $isBlocked = self::isBlockedByPrerequisite(
1772
                $student_id,
1773
                $prerequisite,
1774
                $course,
1775
                $sessionId
1776
            );
1777
1778
            if ($isBlocked) {
1779
                $is_visible = false;
1780
            }
1781
1782
            // Also check the time availability of the LP
1783
            if ($is_visible) {
1784
                // Adding visibility restrictions
1785
                if (null !== $lp->getPublicatedOn()) {
1786
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
1787
                        $is_visible = false;
1788
                    }
1789
                }
1790
                // Blocking empty start times see BT#2800
1791
                global $_custom;
1792
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1793
                    $_custom['lps_hidden_when_no_start_date']
1794
                ) {
1795
                    if (null !== $lp->getPublicatedOn()) {
1796
                        $is_visible = false;
1797
                    }
1798
                }
1799
1800
                if (null !== $lp->getExpiredOn()) {
1801
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1802
                        $is_visible = false;
1803
                    }
1804
                }
1805
            }
1806
1807
            if ($is_visible) {
1808
                $subscriptionSettings = self::getSubscriptionSettings();
1809
1810
                // Check if the subscription users/group to a LP is ON
1811
                if (1 == $lp->getSubscribeUsers() &&
1812
                    true === $subscriptionSettings['allow_add_users_to_lp']
1813
                ) {
1814
                    // Try group
1815
                    $is_visible = false;
1816
                    // Checking only the user visibility
1817
                    // @todo fix visibility
1818
                    $userVisibility = 1;
1819
                    if (1 == $userVisibility) {
1820
                        $is_visible = true;
1821
                    } else {
1822
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
1823
                        if (!empty($userGroups)) {
1824
                            foreach ($userGroups as $groupInfo) {
1825
                                $groupId = $groupInfo['iid'];
1826
                                // @todo fix visibility.
1827
                                $userVisibility = 1;
1828
                                if (1 == $userVisibility) {
1829
                                    $is_visible = true;
1830
                                    break;
1831
                                }
1832
                            }
1833
                        }
1834
                    }
1835
                }
1836
            }
1837
1838
            return $is_visible;
1839
        }
1840
1841
        return true;
1842
    }
1843
1844
    /**
1845
     * @param int $lpId
1846
     * @param int $userId
1847
     * @param int $courseId
1848
     * @param int $sessionId
1849
     *
1850
     * @return int
1851
     */
1852
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1853
    {
1854
        $lpId = (int) $lpId;
1855
        $userId = (int) $userId;
1856
        $courseId = (int) $courseId;
1857
        $sessionId = (int) $sessionId;
1858
1859
        $sessionCondition = api_get_session_condition($sessionId);
1860
        $table = Database::get_course_table(TABLE_LP_VIEW);
1861
        $sql = "SELECT progress FROM $table
1862
                WHERE
1863
                    c_id = $courseId AND
1864
                    lp_id = $lpId AND
1865
                    user_id = $userId $sessionCondition ";
1866
        $res = Database::query($sql);
1867
1868
        $progress = 0;
1869
        if (Database::num_rows($res) > 0) {
1870
            $row = Database::fetch_array($res);
1871
            $progress = (int) $row['progress'];
1872
        }
1873
1874
        return $progress;
1875
    }
1876
1877
    /**
1878
     * @param array $lpList
1879
     * @param int   $userId
1880
     * @param int   $courseId
1881
     * @param int   $sessionId
1882
     *
1883
     * @return array
1884
     */
1885
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
1886
    {
1887
        $lpList = array_map('intval', $lpList);
1888
        if (empty($lpList)) {
1889
            return [];
1890
        }
1891
1892
        $lpList = implode("','", $lpList);
1893
1894
        $userId = (int) $userId;
1895
        $courseId = (int) $courseId;
1896
        $sessionId = (int) $sessionId;
1897
1898
        $sessionCondition = api_get_session_condition($sessionId);
1899
        $table = Database::get_course_table(TABLE_LP_VIEW);
1900
        $sql = "SELECT lp_id, progress FROM $table
1901
                WHERE
1902
                    c_id = $courseId AND
1903
                    lp_id IN ('".$lpList."') AND
1904
                    user_id = $userId $sessionCondition ";
1905
        $res = Database::query($sql);
1906
1907
        if (Database::num_rows($res) > 0) {
1908
            $list = [];
1909
            while ($row = Database::fetch_array($res)) {
1910
                $list[$row['lp_id']] = $row['progress'];
1911
            }
1912
1913
            return $list;
1914
        }
1915
1916
        return [];
1917
    }
1918
1919
    /**
1920
     * Displays a progress bar
1921
     * completed so far.
1922
     *
1923
     * @param int    $percentage Progress value to display
1924
     * @param string $text_add   Text to display near the progress value
1925
     *
1926
     * @return string HTML string containing the progress bar
1927
     */
1928
    public static function get_progress_bar($percentage = -1, $text_add = '')
1929
    {
1930
        $text = $percentage.$text_add;
1931
1932
        return '<div class="progress">
1933
            <div id="progress_bar_value"
1934
                class="progress-bar progress-bar-success" role="progressbar"
1935
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
1936
            '.$text.'
1937
            </div>
1938
        </div>';
1939
    }
1940
1941
    /**
1942
     * @param string $mode can be '%' or 'abs'
1943
     *                     otherwise this value will be used $this->progress_bar_mode
1944
     *
1945
     * @return string
1946
     */
1947
    public function getProgressBar($mode = null)
1948
    {
1949
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
1950
1951
        return self::get_progress_bar($percentage, $text_add);
1952
    }
1953
1954
    /**
1955
     * Gets the progress bar info to display inside the progress bar.
1956
     * Also used by scorm_api.php.
1957
     *
1958
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
1959
     *                     we display a number of completed elements per total elements
1960
     * @param int    $add  Additional steps to fake as completed
1961
     *
1962
     * @return array Percentage or number and symbol (% or /xx)
1963
     */
1964
    public function get_progress_bar_text($mode = '', $add = 0)
1965
    {
1966
        if (empty($mode)) {
1967
            $mode = $this->progress_bar_mode;
1968
        }
1969
        $text = '';
1970
        $percentage = 0;
1971
        // If the option to use the score as progress is set for this learning
1972
        // path, then the rules are completely different: we assume only one
1973
        // item exists and the progress of the LP depends on the score
1974
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
1975
        if (true === $scoreAsProgressSetting) {
1976
            $scoreAsProgress = $this->getUseScoreAsProgress();
1977
            if ($scoreAsProgress) {
1978
                // Get single item's score
1979
                $itemId = $this->get_current_item_id();
1980
                $item = $this->getItem($itemId);
1981
                $score = $item->get_score();
1982
                $maxScore = $item->get_max();
1983
                if ($mode = '%') {
1984
                    if (!empty($maxScore)) {
1985
                        $percentage = ((float) $score / (float) $maxScore) * 100;
1986
                    }
1987
                    $percentage = number_format($percentage, 0);
1988
                    $text = '%';
1989
                } else {
1990
                    $percentage = $score;
1991
                    $text = '/'.$maxScore;
1992
                }
1993
1994
                return [$percentage, $text];
1995
            }
1996
        }
1997
        // otherwise just continue the normal processing of progress
1998
        $total_items = $this->getTotalItemsCountWithoutDirs();
1999
        $completeItems = $this->get_complete_items_count();
2000
        if (0 != $add) {
2001
            $completeItems += $add;
2002
        }
2003
        if ($completeItems > $total_items) {
2004
            $completeItems = $total_items;
2005
        }
2006
        if ('%' === $mode) {
2007
            if ($total_items > 0) {
2008
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2009
            }
2010
            $percentage = number_format($percentage, 0);
2011
            $text = '%';
2012
        } elseif ('abs' === $mode) {
2013
            $percentage = $completeItems;
2014
            $text = '/'.$total_items;
2015
        }
2016
2017
        return [
2018
            $percentage,
2019
            $text,
2020
        ];
2021
    }
2022
2023
    /**
2024
     * Gets the progress bar mode.
2025
     *
2026
     * @return string The progress bar mode attribute
2027
     */
2028
    public function get_progress_bar_mode()
2029
    {
2030
        if (!empty($this->progress_bar_mode)) {
2031
            return $this->progress_bar_mode;
2032
        }
2033
2034
        return '%';
2035
    }
2036
2037
    /**
2038
     * Gets the learnpath theme (remote or local).
2039
     *
2040
     * @return string Learnpath theme
2041
     */
2042
    public function get_theme()
2043
    {
2044
        if (!empty($this->theme)) {
2045
            return $this->theme;
2046
        }
2047
2048
        return '';
2049
    }
2050
2051
    /**
2052
     * Gets the learnpath session id.
2053
     *
2054
     * @return int
2055
     */
2056
    public function get_lp_session_id()
2057
    {
2058
        if (!empty($this->lp_session_id)) {
2059
            return (int) $this->lp_session_id;
2060
        }
2061
2062
        return 0;
2063
    }
2064
2065
    /**
2066
     * Generate a new prerequisites string for a given item. If this item was a sco and
2067
     * its prerequisites were strings (instead of IDs), then transform those strings into
2068
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2069
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2070
     * same rule as the scormExport() method.
2071
     *
2072
     * @param int $item_id Item ID
2073
     *
2074
     * @return string Prerequisites string ready for the export as SCORM
2075
     */
2076
    public function get_scorm_prereq_string($item_id)
2077
    {
2078
        if ($this->debug > 0) {
2079
            error_log('In learnpath::get_scorm_prereq_string()');
2080
        }
2081
        if (!is_object($this->items[$item_id])) {
2082
            return false;
2083
        }
2084
        /** @var learnpathItem $oItem */
2085
        $oItem = $this->items[$item_id];
2086
        $prereq = $oItem->get_prereq_string();
2087
2088
        if (empty($prereq)) {
2089
            return '';
2090
        }
2091
        if (preg_match('/^\d+$/', $prereq) &&
2092
            isset($this->items[$prereq]) &&
2093
            is_object($this->items[$prereq])
2094
        ) {
2095
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2096
            // then simply return it (with the ITEM_ prefix).
2097
            //return 'ITEM_' . $prereq;
2098
            return $this->items[$prereq]->ref;
2099
        } else {
2100
            if (isset($this->refs_list[$prereq])) {
2101
                // It's a simple string item from which the ID can be found in the refs list,
2102
                // so we can transform it directly to an ID for export.
2103
                return $this->items[$this->refs_list[$prereq]]->ref;
2104
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2105
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2106
            } else {
2107
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2108
                // and replace them, one by one, by the internal IDs (chamilo db)
2109
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2110
                // by a space as well.
2111
                $find = [
2112
                    '&',
2113
                    '|',
2114
                    '~',
2115
                    '=',
2116
                    '<>',
2117
                    '{',
2118
                    '}',
2119
                    '*',
2120
                    '(',
2121
                    ')',
2122
                ];
2123
                $replace = [
2124
                    ' ',
2125
                    ' ',
2126
                    ' ',
2127
                    ' ',
2128
                    ' ',
2129
                    ' ',
2130
                    ' ',
2131
                    ' ',
2132
                    ' ',
2133
                    ' ',
2134
                ];
2135
                $prereq_mod = str_replace($find, $replace, $prereq);
2136
                $ids = explode(' ', $prereq_mod);
2137
                foreach ($ids as $id) {
2138
                    $id = trim($id);
2139
                    if (isset($this->refs_list[$id])) {
2140
                        $prereq = preg_replace(
2141
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2142
                            'ITEM_'.$this->refs_list[$id],
2143
                            $prereq
2144
                        );
2145
                    }
2146
                }
2147
2148
                return $prereq;
2149
            }
2150
        }
2151
    }
2152
2153
    /**
2154
     * Returns the XML DOM document's node.
2155
     *
2156
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2157
     * @param string   $id       The identifier to look for
2158
     *
2159
     * @return mixed The reference to the element found with that identifier. False if not found
2160
     */
2161
    public function get_scorm_xml_node(&$children, $id)
2162
    {
2163
        for ($i = 0; $i < $children->length; $i++) {
2164
            $item_temp = $children->item($i);
2165
            if ('item' === $item_temp->nodeName) {
2166
                if ($item_temp->getAttribute('identifier') == $id) {
2167
                    return $item_temp;
2168
                }
2169
            }
2170
            $subchildren = $item_temp->childNodes;
2171
            if ($subchildren && $subchildren->length > 0) {
2172
                $val = $this->get_scorm_xml_node($subchildren, $id);
2173
                if (is_object($val)) {
2174
                    return $val;
2175
                }
2176
            }
2177
        }
2178
2179
        return false;
2180
    }
2181
2182
    /**
2183
     * Gets the status list for all LP's items.
2184
     *
2185
     * @return array Array of [index] => [item ID => current status]
2186
     */
2187
    public function get_items_status_list()
2188
    {
2189
        $list = [];
2190
        foreach ($this->ordered_items as $item_id) {
2191
            $list[] = [
2192
                $item_id => $this->items[$item_id]->get_status(),
2193
            ];
2194
        }
2195
2196
        return $list;
2197
    }
2198
2199
    /**
2200
     * Return the number of interactions for the given learnpath Item View ID.
2201
     * This method can be used as static.
2202
     *
2203
     * @param int $lp_iv_id  Item View ID
2204
     * @param int $course_id course id
2205
     *
2206
     * @return int
2207
     */
2208
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2209
    {
2210
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2211
        $lp_iv_id = (int) $lp_iv_id;
2212
        $course_id = (int) $course_id;
2213
2214
        $sql = "SELECT count(*) FROM $table
2215
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2216
        $res = Database::query($sql);
2217
        $num = 0;
2218
        if (Database::num_rows($res)) {
2219
            $row = Database::fetch_array($res);
2220
            $num = $row[0];
2221
        }
2222
2223
        return $num;
2224
    }
2225
2226
    /**
2227
     * Return the interactions as an array for the given lp_iv_id.
2228
     * This method can be used as static.
2229
     *
2230
     * @param int $lp_iv_id Learnpath Item View ID
2231
     *
2232
     * @return array
2233
     *
2234
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2235
     */
2236
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2237
    {
2238
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2239
        $list = [];
2240
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2241
        $lp_iv_id = (int) $lp_iv_id;
2242
2243
        if (empty($lp_iv_id) || empty($course_id)) {
2244
            return [];
2245
        }
2246
2247
        $sql = "SELECT * FROM $table
2248
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2249
                ORDER BY order_id ASC";
2250
        $res = Database::query($sql);
2251
        $num = Database::num_rows($res);
2252
        if ($num > 0) {
2253
            $list[] = [
2254
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2255
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2256
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2257
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2258
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2259
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2260
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2261
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2262
                'student_response_formatted' => '',
2263
            ];
2264
            while ($row = Database::fetch_array($res)) {
2265
                $studentResponseFormatted = urldecode($row['student_response']);
2266
                $content_student_response = explode('__|', $studentResponseFormatted);
2267
                if (count($content_student_response) > 0) {
2268
                    if (count($content_student_response) >= 3) {
2269
                        // Pop the element off the end of array.
2270
                        array_pop($content_student_response);
2271
                    }
2272
                    $studentResponseFormatted = implode(',', $content_student_response);
2273
                }
2274
2275
                $list[] = [
2276
                    'order_id' => $row['order_id'] + 1,
2277
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2278
                    'type' => $row['interaction_type'],
2279
                    'time' => $row['completion_time'],
2280
                    'correct_responses' => '', // Hide correct responses from students.
2281
                    'student_response' => $row['student_response'],
2282
                    'result' => $row['result'],
2283
                    'latency' => $row['latency'],
2284
                    'student_response_formatted' => $studentResponseFormatted,
2285
                ];
2286
            }
2287
        }
2288
2289
        return $list;
2290
    }
2291
2292
    /**
2293
     * Return the number of objectives for the given learnpath Item View ID.
2294
     * This method can be used as static.
2295
     *
2296
     * @param int $lp_iv_id  Item View ID
2297
     * @param int $course_id Course ID
2298
     *
2299
     * @return int Number of objectives
2300
     */
2301
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2302
    {
2303
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2304
        $course_id = (int) $course_id;
2305
        $lp_iv_id = (int) $lp_iv_id;
2306
        $sql = "SELECT count(*) FROM $table
2307
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2308
        //@todo seems that this always returns 0
2309
        $res = Database::query($sql);
2310
        $num = 0;
2311
        if (Database::num_rows($res)) {
2312
            $row = Database::fetch_array($res);
2313
            $num = $row[0];
2314
        }
2315
2316
        return $num;
2317
    }
2318
2319
    /**
2320
     * Return the objectives as an array for the given lp_iv_id.
2321
     * This method can be used as static.
2322
     *
2323
     * @param int $lpItemViewId Learnpath Item View ID
2324
     * @param int $course_id
2325
     *
2326
     * @return array
2327
     *
2328
     * @todo    Translate labels
2329
     */
2330
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2331
    {
2332
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2333
        $lpItemViewId = (int) $lpItemViewId;
2334
2335
        if (empty($course_id) || empty($lpItemViewId)) {
2336
            return [];
2337
        }
2338
2339
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2340
        $sql = "SELECT * FROM $table
2341
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2342
                ORDER BY order_id ASC";
2343
        $res = Database::query($sql);
2344
        $num = Database::num_rows($res);
2345
        $list = [];
2346
        if ($num > 0) {
2347
            $list[] = [
2348
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2349
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2350
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2351
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2352
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2353
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2354
            ];
2355
            while ($row = Database::fetch_array($res)) {
2356
                $list[] = [
2357
                    'order_id' => $row['order_id'] + 1,
2358
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2359
                    'score_raw' => $row['score_raw'],
2360
                    'score_max' => $row['score_max'],
2361
                    'score_min' => $row['score_min'],
2362
                    'status' => $row['status'],
2363
                ];
2364
            }
2365
        }
2366
2367
        return $list;
2368
    }
2369
2370
    /**
2371
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2372
     * used by get_html_toc() to be ready to display.
2373
     */
2374
    public function get_toc(): array
2375
    {
2376
        $toc = [];
2377
        foreach ($this->ordered_items as $item_id) {
2378
            // TODO: Change this link generation and use new function instead.
2379
            $toc[] = [
2380
                'id' => $item_id,
2381
                'title' => $this->items[$item_id]->get_title(),
2382
                'status' => $this->items[$item_id]->get_status(false),
2383
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status(false)),
2384
                'level' => $this->items[$item_id]->get_level(),
2385
                'type' => $this->items[$item_id]->get_type(),
2386
                'description' => $this->items[$item_id]->get_description(),
2387
                'path' => $this->items[$item_id]->get_path(),
2388
                'parent' => $this->items[$item_id]->get_parent(),
2389
            ];
2390
        }
2391
2392
        return $toc;
2393
    }
2394
2395
    /**
2396
     * Returns the CSS class name associated with a given item status.
2397
     *
2398
     * @param $status string an item status
2399
     *
2400
     * @return string CSS class name
2401
     */
2402
    public static function getStatusCSSClassName($status)
2403
    {
2404
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2405
            return self::STATUS_CSS_CLASS_NAME[$status];
2406
        }
2407
2408
        return '';
2409
    }
2410
2411
    /**
2412
     * Generate and return the table of contents for this learnpath. The JS
2413
     * table returned is used inside of scorm_api.php.
2414
     *
2415
     * @param string $varname
2416
     *
2417
     * @return string A JS array variable construction
2418
     */
2419
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2420
    {
2421
        $toc = $varname.' = new Array();';
2422
        foreach ($this->ordered_items as $item_id) {
2423
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2424
        }
2425
2426
        return $toc;
2427
    }
2428
2429
    /**
2430
     * Gets the learning path type.
2431
     *
2432
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2433
     *
2434
     * @return mixed Type ID or name, depending on the parameter
2435
     */
2436
    public function get_type($get_name = false)
2437
    {
2438
        $res = false;
2439
        if (!empty($this->type) && (!$get_name)) {
2440
            $res = $this->type;
2441
        }
2442
2443
        return $res;
2444
    }
2445
2446
    /**
2447
     * Gets the learning path type as static method.
2448
     *
2449
     * @param int $lp_id
2450
     *
2451
     * @return mixed Type ID or name, depending on the parameter
2452
     */
2453
    public static function get_type_static($lp_id = 0)
2454
    {
2455
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2456
        $lp_id = (int) $lp_id;
2457
        $sql = "SELECT lp_type FROM $tbl_lp
2458
                WHERE iid = $lp_id";
2459
        $res = Database::query($sql);
2460
        if (false === $res) {
2461
            return null;
2462
        }
2463
        if (Database::num_rows($res) <= 0) {
2464
            return null;
2465
        }
2466
        $row = Database::fetch_array($res);
2467
2468
        return $row['lp_type'];
2469
    }
2470
2471
    /**
2472
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2473
     * This method can be used as abstract and is recursive.
2474
     *
2475
     * @param CLp $lp
2476
     * @param int $parent    Parent ID of the items to look for
2477
     *
2478
     * @return array Ordered list of item IDs (empty array on error)
2479
     */
2480
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2481
    {
2482
        $parent = (int) $parent;
2483
        $lpItemRepo = Container::getLpItemRepository();
2484
        if (empty($parent)) {
2485
            $rootItem = $lpItemRepo->getRootItem($lp->getIid());
2486
            if (null !== $rootItem) {
2487
                $parent = $rootItem->getIid();
2488
            }
2489
        }
2490
2491
        if (empty($parent)) {
2492
            return [];
2493
        }
2494
2495
        $criteria = new Criteria();
2496
        $criteria
2497
            ->where($criteria->expr()->neq('path', 'root'))
2498
            ->orderBy(
2499
                [
2500
                    'displayOrder' => Criteria::ASC,
2501
                ]
2502
            );
2503
        $items = $lp->getItems()->matching($criteria);
2504
        $items = $items->filter(
2505
            function (CLpItem $element) use ($parent) {
2506
                if ('root' === $element->getPath()) {
2507
                    return false;
2508
                }
2509
2510
                if (null !== $element->getParent()) {
2511
                    return $element->getParent()->getIid() === $parent;
2512
                }
2513
                return false;
2514
2515
            }
2516
        );
2517
        $list = [];
2518
        foreach ($items as $item) {
2519
            $itemId = $item->getIid();
2520
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2521
            $list[] = $itemId;
2522
            foreach ($sublist as $subItem) {
2523
                $list[] = $subItem;
2524
            }
2525
        }
2526
2527
        return $list;
2528
    }
2529
2530
    public static function getChapterTypes(): array
2531
    {
2532
        return [
2533
            'dir',
2534
        ];
2535
    }
2536
2537
    /**
2538
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2539
     *
2540
     * @return array HTML TOC ready to display
2541
     */
2542
    public function getListArrayToc()
2543
    {
2544
        $lpItemRepo = Container::getLpItemRepository();
2545
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
2546
        $options = [
2547
            'decorate' => false,
2548
        ];
2549
2550
        return $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2551
    }
2552
2553
    /**
2554
     * Returns an HTML-formatted string ready to display with teacher buttons
2555
     * in LP view menu.
2556
     *
2557
     * @return string HTML TOC ready to display
2558
     */
2559
    public function get_teacher_toc_buttons()
2560
    {
2561
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2562
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2563
        $html = '';
2564
        if ($isAllow && false == $hideIcons) {
2565
            if ($this->get_lp_session_id() == api_get_session_id()) {
2566
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2567
                $html .= '<div class="flex flex-row justify-center">';
2568
                $html .= "<a
2569
                    class='btn btn-sm btn-default'
2570
                    href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false'
2571
                    target='_parent'>".
2572
                    Display::getMdiIcon('google-street-view').get_lang('Overview')."</a>";
2573
                $html .= "<a
2574
                    class='btn btn-sm btn-default'
2575
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2576
                    target='_parent'>".
2577
                    Display::getMdiIcon('pencil').get_lang('Edit')."</a>";
2578
                $html .= '<a
2579
                    class="btn btn-sm btn-default"
2580
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2581
                    Display::getMdiIcon('cog').get_lang('Settings').'</a>';
2582
                $html .= '</div>';
2583
                $html .= '</div>';
2584
            }
2585
        }
2586
2587
        return $html;
2588
    }
2589
2590
    /**
2591
     * Gets the learnpath name/title.
2592
     *
2593
     * @return string Learnpath name/title
2594
     */
2595
    public function get_name()
2596
    {
2597
        if (!empty($this->name)) {
2598
            return $this->name;
2599
        }
2600
2601
        return 'N/A';
2602
    }
2603
2604
    /**
2605
     * @return string
2606
     */
2607
    public function getNameNoTags()
2608
    {
2609
        return strip_tags($this->get_name());
2610
    }
2611
2612
    /**
2613
     * Gets a link to the resource from the present location, depending on item ID.
2614
     *
2615
     * @param string $type         Type of link expected
2616
     * @param int    $item_id      Learnpath item ID
2617
     * @param bool   $provided_toc
2618
     *
2619
     * @return string $provided_toc Link to the lp_item resource
2620
     */
2621
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2622
    {
2623
        $course_id = $this->get_course_int_id();
2624
        $item_id = (int) $item_id;
2625
2626
        if (empty($item_id)) {
2627
            $item_id = $this->get_current_item_id();
2628
2629
            if (empty($item_id)) {
2630
                //still empty, this means there was no item_id given and we are not in an object context or
2631
                //the object property is empty, return empty link
2632
                $this->first();
2633
2634
                return '';
2635
            }
2636
        }
2637
2638
        $file = '';
2639
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2640
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2641
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2642
2643
        $sql = "SELECT
2644
                    l.lp_type as ltype,
2645
                    l.path as lpath,
2646
                    li.item_type as litype,
2647
                    li.path as lipath,
2648
                    li.parameters as liparams
2649
        		FROM $lp_table l
2650
                INNER JOIN $lp_item_table li
2651
                ON (li.lp_id = l.iid)
2652
        		WHERE
2653
        		    li.iid = $item_id
2654
        		";
2655
        $res = Database::query($sql);
2656
        if (Database::num_rows($res) > 0) {
2657
            $row = Database::fetch_array($res);
2658
            $lp_type = $row['ltype'];
2659
            $lp_path = $row['lpath'];
2660
            $lp_item_type = $row['litype'];
2661
            $lp_item_path = $row['lipath'];
2662
            $lp_item_params = $row['liparams'];
2663
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2664
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2665
            }
2666
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2667
            if ('http' === $type) {
2668
                //web path
2669
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2670
            } else {
2671
                //$course_path = $sys_course_path; //system path
2672
            }
2673
2674
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2675
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2676
            if (in_array(
2677
                $lp_item_type,
2678
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2679
            )
2680
            ) {
2681
                $lp_type = CLp::LP_TYPE;
2682
            }
2683
2684
            // Now go through the specific cases to get the end of the path
2685
            // @todo Use constants instead of int values.
2686
            switch ($lp_type) {
2687
                case CLp::LP_TYPE:
2688
                    $file = self::rl_get_resource_link_for_learnpath(
2689
                        $course_id,
2690
                        $this->get_id(),
2691
                        $item_id,
2692
                        $this->get_view_id()
2693
                    );
2694
                    switch ($lp_item_type) {
2695
                        case 'document':
2696
                            // Shows a button to download the file instead of just downloading the file directly.
2697
                            $documentPathInfo = pathinfo($file);
2698
                            if (isset($documentPathInfo['extension'])) {
2699
                                $parsed = parse_url($documentPathInfo['extension']);
2700
                                if (isset($parsed['path'])) {
2701
                                    $extension = $parsed['path'];
2702
                                    $extensionsToDownload = [
2703
                                        'zip',
2704
                                        'ppt',
2705
                                        'pptx',
2706
                                        'ods',
2707
                                        'xlsx',
2708
                                        'xls',
2709
                                        'csv',
2710
                                        'doc',
2711
                                        'docx',
2712
                                        'dot',
2713
                                    ];
2714
2715
                                    if (in_array($extension, $extensionsToDownload)) {
2716
                                        $file = api_get_path(WEB_CODE_PATH).
2717
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2718
                                    }
2719
                                }
2720
                            }
2721
                            break;
2722
                        case 'dir':
2723
                            $file = 'lp_content.php?type=dir';
2724
                            break;
2725
                        case 'link':
2726
                            if (Link::is_youtube_link($file)) {
2727
                                $src = Link::get_youtube_video_id($file);
2728
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2729
                            } elseif (Link::isVimeoLink($file)) {
2730
                                $src = Link::getVimeoLinkId($file);
2731
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2732
                            } else {
2733
                                // If the current site is HTTPS and the link is
2734
                                // HTTP, browsers will refuse opening the link
2735
                                $urlId = api_get_current_access_url_id();
2736
                                $url = api_get_access_url($urlId, false);
2737
                                $protocol = substr($url['url'], 0, 5);
2738
                                if ('https' === $protocol) {
2739
                                    $linkProtocol = substr($file, 0, 5);
2740
                                    if ('http:' === $linkProtocol) {
2741
                                        //this is the special intervention case
2742
                                        $file = api_get_path(WEB_CODE_PATH).
2743
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2744
                                    }
2745
                                }
2746
                            }
2747
                            break;
2748
                        case 'quiz':
2749
                            // Check how much attempts of a exercise exits in lp
2750
                            $lp_item_id = $this->get_current_item_id();
2751
                            $lp_view_id = $this->get_view_id();
2752
2753
                            $prevent_reinit = null;
2754
                            if (isset($this->items[$this->current])) {
2755
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2756
                            }
2757
2758
                            if (empty($provided_toc)) {
2759
                                $list = $this->get_toc();
2760
                            } else {
2761
                                $list = $provided_toc;
2762
                            }
2763
2764
                            $type_quiz = false;
2765
                            foreach ($list as $toc) {
2766
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2767
                                    $type_quiz = true;
2768
                                }
2769
                            }
2770
2771
                            if ($type_quiz) {
2772
                                $lp_item_id = (int) $lp_item_id;
2773
                                $lp_view_id = (int) $lp_view_id;
2774
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2775
                                        WHERE
2776
                                            lp_item_id='".$lp_item_id."' AND
2777
                                            lp_view_id ='".$lp_view_id."' AND
2778
                                            status='completed'";
2779
                                $result = Database::query($sql);
2780
                                $row_count = Database:: fetch_row($result);
2781
                                $count_item_view = (int) $row_count[0];
2782
                                $not_multiple_attempt = 0;
2783
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2784
                                    $not_multiple_attempt = 1;
2785
                                }
2786
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2787
                            }
2788
                            break;
2789
                    }
2790
2791
                    $tmp_array = explode('/', $file);
2792
                    $document_name = $tmp_array[count($tmp_array) - 1];
2793
                    if (strpos($document_name, '_DELETED_')) {
2794
                        $file = 'blank.php?error=document_deleted';
2795
                    }
2796
                    break;
2797
                case CLp::SCORM_TYPE:
2798
                    if ('dir' !== $lp_item_type) {
2799
                        // Quite complex here:
2800
                        // We want to make sure 'http://' (and similar) links can
2801
                        // be loaded as is (withouth the Chamilo path in front) but
2802
                        // some contents use this form: resource.htm?resource=http://blablabla
2803
                        // which means we have to find a protocol at the path's start, otherwise
2804
                        // it should not be considered as an external URL.
2805
                        // if ($this->prerequisites_match($item_id)) {
2806
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2807
                            if ($this->debug > 2) {
2808
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2809
                            }
2810
                            // Distant url, return as is.
2811
                            $file = $lp_item_path;
2812
                        } else {
2813
                            if ($this->debug > 2) {
2814
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
2815
                            }
2816
                            // Prevent getting untranslatable urls.
2817
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2818
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2819
2820
                            /*$asset = $this->getEntity()->getAsset();
2821
                            $folder = Container::getAssetRepository()->getFolder($asset);
2822
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
2823
                            $file = null;
2824
                            if ($hasFile) {
2825
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
2826
                            }*/
2827
                            $file = $this->scormUrl.$lp_item_path;
2828
2829
                            // Prepare the path.
2830
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2831
                            // TODO: Fix this for urls with protocol header.
2832
                            $file = str_replace('//', '/', $file);
2833
                            $file = str_replace(':/', '://', $file);
2834
                            if ('/' === substr($lp_path, -1)) {
2835
                                $lp_path = substr($lp_path, 0, -1);
2836
                            }*/
2837
                            /*if (!$hasFile) {
2838
                                // if file not found.
2839
                                $decoded = html_entity_decode($lp_item_path);
2840
                                [$decoded] = explode('?', $decoded);
2841
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
2842
                                    $file = self::rl_get_resource_link_for_learnpath(
2843
                                        $course_id,
2844
                                        $this->get_id(),
2845
                                        $item_id,
2846
                                        $this->get_view_id()
2847
                                    );
2848
                                    if (empty($file)) {
2849
                                        $file = 'blank.php?error=document_not_found';
2850
                                    } else {
2851
                                        $tmp_array = explode('/', $file);
2852
                                        $document_name = $tmp_array[count($tmp_array) - 1];
2853
                                        if (strpos($document_name, '_DELETED_')) {
2854
                                            $file = 'blank.php?error=document_deleted';
2855
                                        } else {
2856
                                            $file = 'blank.php?error=document_not_found';
2857
                                        }
2858
                                    }
2859
                                } else {
2860
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
2861
                                }
2862
                            }*/
2863
                        }
2864
2865
                        // We want to use parameters if they were defined in the imsmanifest
2866
                        if (false === strpos($file, 'blank.php')) {
2867
                            $lp_item_params = ltrim($lp_item_params, '?');
2868
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
2869
                        }
2870
                    } else {
2871
                        $file = 'lp_content.php?type=dir';
2872
                    }
2873
                    break;
2874
                case CLp::AICC_TYPE:
2875
                    // Formatting AICC HACP append URL.
2876
                    $aicc_append = '?aicc_sid='.
2877
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
2878
                    if (!empty($lp_item_params)) {
2879
                        $aicc_append .= $lp_item_params.'&';
2880
                    }
2881
                    if ('dir' !== $lp_item_type) {
2882
                        // Quite complex here:
2883
                        // We want to make sure 'http://' (and similar) links can
2884
                        // be loaded as is (withouth the Chamilo path in front) but
2885
                        // some contents use this form: resource.htm?resource=http://blablabla
2886
                        // which means we have to find a protocol at the path's start, otherwise
2887
                        // it should not be considered as an external URL.
2888
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2889
                            if ($this->debug > 2) {
2890
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2891
                            }
2892
                            // Distant url, return as is.
2893
                            $file = $lp_item_path;
2894
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
2895
                            /*
2896
                            if (stristr($file,'<servername>') !== false) {
2897
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
2898
                            }
2899
                            */
2900
                            if (false !== stripos($file, '<servername>')) {
2901
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
2902
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
2903
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
2904
                            }
2905
2906
                            $file .= $aicc_append;
2907
                        } else {
2908
                            if ($this->debug > 2) {
2909
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
2910
                            }
2911
                            // Prevent getting untranslatable urls.
2912
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2913
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2914
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
2915
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2916
                            // TODO: Fix this for urls with protocol header.
2917
                            $file = str_replace('//', '/', $file);
2918
                            $file = str_replace(':/', '://', $file);
2919
                            $file .= $aicc_append;
2920
                        }
2921
                    } else {
2922
                        $file = 'lp_content.php?type=dir';
2923
                    }
2924
                    break;
2925
                case 4:
2926
                default:
2927
                    break;
2928
            }
2929
            // Replace &amp; by & because &amp; will break URL with params
2930
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
2931
        }
2932
        if ($this->debug > 2) {
2933
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
2934
        }
2935
2936
        return $file;
2937
    }
2938
2939
    /**
2940
     * Gets the latest usable view or generate a new one.
2941
     *
2942
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
2943
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
2944
     *
2945
     * @return int DB lp_view id
2946
     */
2947
    public function get_view($attempt_num = 0, $userId = null)
2948
    {
2949
        $search = '';
2950
        $attempt_num = (int) $attempt_num;
2951
        // Use $attempt_num to enable multi-views management (disabled so far).
2952
        if (!empty($attempt_num)) {
2953
            $search = 'AND view_count = '.$attempt_num;
2954
        }
2955
2956
        $course_id = api_get_course_int_id();
2957
        $sessionId = api_get_session_id();
2958
2959
        // Check user ID.
2960
        if (empty($userId)) {
2961
            if (empty($this->get_user_id())) {
2962
                $this->error = 'User ID is empty in learnpath::get_view()';
2963
2964
                return null;
2965
            } else {
2966
                $userId = $this->get_user_id();
2967
            }
2968
        }
2969
        $sessionCondition = api_get_session_condition($sessionId);
2970
2971
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
2972
        $table = Database::get_course_table(TABLE_LP_VIEW);
2973
        $sql = "SELECT iid FROM $table
2974
        		WHERE
2975
        		    c_id = $course_id AND
2976
        		    lp_id = ".$this->get_id()." AND
2977
        		    user_id = ".$userId."
2978
        		    $sessionCondition
2979
        		    $search
2980
                ORDER BY view_count DESC";
2981
        $res = Database::query($sql);
2982
        if (Database::num_rows($res) > 0) {
2983
            $row = Database::fetch_array($res);
2984
            $this->lp_view_id = $row['iid'];
2985
        } elseif (!api_is_invitee()) {
2986
            $params = [
2987
                'c_id' => $course_id,
2988
                'lp_id' => $this->get_id(),
2989
                'user_id' => $this->get_user_id(),
2990
                'view_count' => 1,
2991
                'last_item' => 0,
2992
            ];
2993
            if (!empty($sessionId)) {
2994
                $params['session_id']  = $sessionId;
2995
            }
2996
            $this->lp_view_id = Database::insert($table, $params);
2997
        }
2998
2999
        return $this->lp_view_id;
3000
    }
3001
3002
    /**
3003
     * Gets the current view id.
3004
     *
3005
     * @return int View ID (from lp_view)
3006
     */
3007
    public function get_view_id()
3008
    {
3009
        if (!empty($this->lp_view_id)) {
3010
            return (int) $this->lp_view_id;
3011
        }
3012
3013
        return 0;
3014
    }
3015
3016
    /**
3017
     * Gets the update queue.
3018
     *
3019
     * @return array Array containing IDs of items to be updated by JavaScript
3020
     */
3021
    public function get_update_queue()
3022
    {
3023
        return $this->update_queue;
3024
    }
3025
3026
    /**
3027
     * Gets the user ID.
3028
     *
3029
     * @return int User ID
3030
     */
3031
    public function get_user_id()
3032
    {
3033
        if (!empty($this->user_id)) {
3034
            return (int) $this->user_id;
3035
        }
3036
3037
        return false;
3038
    }
3039
3040
    /**
3041
     * Checks if any of the items has an audio element attached.
3042
     *
3043
     * @return bool True or false
3044
     */
3045
    public function has_audio()
3046
    {
3047
        $has = false;
3048
        foreach ($this->items as $i => $item) {
3049
            if (!empty($this->items[$i]->audio)) {
3050
                $has = true;
3051
                break;
3052
            }
3053
        }
3054
3055
        return $has;
3056
    }
3057
3058
    /**
3059
     * Updates learnpath attributes to point to the next element
3060
     * The last part is similar to set_current_item but processing the other way around.
3061
     */
3062
    public function next()
3063
    {
3064
        if ($this->debug > 0) {
3065
            error_log('In learnpath::next()', 0);
3066
        }
3067
        $this->last = $this->get_current_item_id();
3068
        $this->items[$this->last]->save(
3069
            false,
3070
            $this->prerequisites_match($this->last)
3071
        );
3072
        $this->autocomplete_parents($this->last);
3073
        $new_index = $this->get_next_index();
3074
        if ($this->debug > 2) {
3075
            error_log('New index: '.$new_index, 0);
3076
        }
3077
        $this->index = $new_index;
3078
        if ($this->debug > 2) {
3079
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3080
        }
3081
        $this->current = $this->ordered_items[$new_index];
3082
        if ($this->debug > 2) {
3083
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3084
        }
3085
    }
3086
3087
    /**
3088
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3089
     * class, this might be redefined to allow several behaviours depending on the document type.
3090
     *
3091
     * @param int $id Resource ID
3092
     */
3093
    public function open($id)
3094
    {
3095
        // TODO:
3096
        // set the current resource attribute to this resource
3097
        // switch on element type (redefine in child class?)
3098
        // set status for this item to "opened"
3099
        // start timer
3100
        // initialise score
3101
        $this->index = 0; //or = the last item seen (see $this->last)
3102
    }
3103
3104
    /**
3105
     * Check that all prerequisites are fulfilled. Returns true and an
3106
     * empty string on success, returns false
3107
     * and the prerequisite string on error.
3108
     * This function is based on the rules for aicc_script language as
3109
     * described in the SCORM 1.2 CAM documentation page 108.
3110
     *
3111
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3112
     *
3113
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3114
     *              string otherwise
3115
     */
3116
    public function prerequisites_match($itemId = null)
3117
    {
3118
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
3119
        if ($allow) {
3120
            if (api_is_allowed_to_edit() ||
3121
                api_is_platform_admin(true) ||
3122
                api_is_drh() ||
3123
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3124
            ) {
3125
                return true;
3126
            }
3127
        }
3128
3129
        $debug = $this->debug;
3130
        if ($debug > 0) {
3131
            error_log('In learnpath::prerequisites_match()');
3132
        }
3133
3134
        if (empty($itemId)) {
3135
            $itemId = $this->current;
3136
        }
3137
3138
        $currentItem = $this->getItem($itemId);
3139
3140
        if ($currentItem) {
3141
            if (2 == $this->type) {
3142
                // Getting prereq from scorm
3143
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3144
            } else {
3145
                $prereq_string = $currentItem->get_prereq_string();
3146
            }
3147
3148
            if (empty($prereq_string)) {
3149
                if ($debug > 0) {
3150
                    error_log('Found prereq_string is empty return true');
3151
                }
3152
3153
                return true;
3154
            }
3155
3156
            // Clean spaces.
3157
            $prereq_string = str_replace(' ', '', $prereq_string);
3158
            if ($debug > 0) {
3159
                error_log('Found prereq_string: '.$prereq_string, 0);
3160
            }
3161
3162
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3163
            $result = $currentItem->parse_prereq(
3164
                $prereq_string,
3165
                $this->items,
3166
                $this->refs_list,
3167
                $this->get_user_id()
3168
            );
3169
3170
            if (false === $result) {
3171
                $this->set_error_msg($currentItem->prereq_alert);
3172
            }
3173
        } else {
3174
            $result = true;
3175
            if ($debug > 1) {
3176
                error_log('$this->items['.$itemId.'] was not an object', 0);
3177
            }
3178
        }
3179
3180
        if ($debug > 1) {
3181
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3182
        }
3183
3184
        return $result;
3185
    }
3186
3187
    /**
3188
     * Updates learnpath attributes to point to the previous element
3189
     * The last part is similar to set_current_item but processing the other way around.
3190
     */
3191
    public function previous()
3192
    {
3193
        $this->last = $this->get_current_item_id();
3194
        $this->items[$this->last]->save(
3195
            false,
3196
            $this->prerequisites_match($this->last)
3197
        );
3198
        $this->autocomplete_parents($this->last);
3199
        $new_index = $this->get_previous_index();
3200
        $this->index = $new_index;
3201
        $this->current = $this->ordered_items[$new_index];
3202
    }
3203
3204
    /**
3205
     * Publishes a learnpath. This basically means show or hide the learnpath
3206
     * to normal users.
3207
     * Can be used as abstract.
3208
     *
3209
     * @param int $id         Learnpath ID
3210
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3211
     *
3212
     * @return bool
3213
     */
3214
    public static function toggleVisibility($id, $visibility = 1)
3215
    {
3216
        $repo = Container::getLpRepository();
3217
        $lp = $repo->find($id);
3218
3219
        if (!$lp) {
3220
            return false;
3221
        }
3222
3223
        $visibility = (int) $visibility;
3224
3225
        if (1 === $visibility) {
3226
            $repo->setVisibilityPublished($lp);
3227
        } else {
3228
            $repo->setVisibilityDraft($lp);
3229
        }
3230
3231
        return true;
3232
    }
3233
3234
    /**
3235
     * Publishes a learnpath category.
3236
     * This basically means show or hide the learnpath category to normal users.
3237
     *
3238
     * @param int $id
3239
     * @param int $visibility
3240
     *
3241
     * @return bool
3242
     */
3243
    public static function toggleCategoryVisibility($id, $visibility = 1)
3244
    {
3245
        $repo = Container::getLpCategoryRepository();
3246
        $resource = $repo->find($id);
3247
3248
        if (!$resource) {
3249
            return false;
3250
        }
3251
3252
        $visibility = (int) $visibility;
3253
3254
        if (1 === $visibility) {
3255
            $repo->setVisibilityPublished($resource);
3256
        } else {
3257
            $repo->setVisibilityDraft($resource);
3258
            self::toggleCategoryPublish($id, 0);
3259
        }
3260
3261
        return false;
3262
    }
3263
3264
    /**
3265
     * Publishes a learnpath. This basically means show or hide the learnpath
3266
     * on the course homepage.
3267
     *
3268
     * @param int    $id            Learnpath id
3269
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3270
     *
3271
     * @return bool
3272
     */
3273
    public static function togglePublish($id, $setVisibility = 'v')
3274
    {
3275
        $addShortcut = false;
3276
        if ('v' === $setVisibility) {
3277
            $addShortcut = true;
3278
        }
3279
        $repo = Container::getLpRepository();
3280
        /** @var CLp|null $lp */
3281
        $lp = $repo->find($id);
3282
        if (null === $lp) {
3283
            return false;
3284
        }
3285
        $repoShortcut = Container::getShortcutRepository();
3286
        $courseEntity = api_get_course_entity();
3287
3288
        if ($addShortcut) {
3289
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
3290
        } else {
3291
            $repoShortcut->removeShortCut($lp);
3292
        }
3293
3294
        return true;
3295
    }
3296
3297
    /**
3298
     * Show or hide the learnpath category on the course homepage.
3299
     *
3300
     * @param int $id
3301
     * @param int $setVisibility
3302
     *
3303
     * @return bool
3304
     */
3305
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3306
    {
3307
        $setVisibility = (int) $setVisibility;
3308
        $addShortcut = false;
3309
        if (1 === $setVisibility) {
3310
            $addShortcut = true;
3311
        }
3312
3313
        $repo = Container::getLpCategoryRepository();
3314
        /** @var CLpCategory|null $lp */
3315
        $category = $repo->find($id);
3316
3317
        if (null === $category) {
3318
            return false;
3319
        }
3320
3321
        $repoShortcut = Container::getShortcutRepository();
3322
        if ($addShortcut) {
3323
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3324
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
3325
        } else {
3326
            $repoShortcut->removeShortCut($category);
3327
        }
3328
3329
        return true;
3330
    }
3331
3332
    /**
3333
     * Check if the learnpath category is visible for a user.
3334
     *
3335
     * @return bool
3336
     */
3337
    public static function categoryIsVisibleForStudent(
3338
        CLpCategory $category,
3339
        User $user,
3340
        Course $course,
3341
        SessionEntity $session = null
3342
    ) {
3343
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3344
3345
        if ($isAllowedToEdit) {
3346
            return true;
3347
        }
3348
3349
        $categoryVisibility = $category->isVisible($course, $session);
3350
3351
        if (!$categoryVisibility) {
3352
            return false;
3353
        }
3354
3355
        $subscriptionSettings = self::getSubscriptionSettings();
3356
3357
        if (false === $subscriptionSettings['allow_add_users_to_lp_category']) {
3358
            return true;
3359
        }
3360
3361
        $noUserSubscribed = false;
3362
        $noGroupSubscribed = true;
3363
        $users = $category->getUsers();
3364
        if (empty($users) || !$users->count()) {
3365
            $noUserSubscribed = true;
3366
        } elseif ($category->hasUserAdded($user)) {
3367
            return true;
3368
        }
3369
3370
        //$groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3371
3372
        return $noGroupSubscribed && $noUserSubscribed;
3373
    }
3374
3375
    /**
3376
     * Check if a learnpath category is published as course tool.
3377
     *
3378
     * @param int $courseId
3379
     *
3380
     * @return bool
3381
     */
3382
    public static function categoryIsPublished(CLpCategory $category, $courseId)
3383
    {
3384
        return false;
3385
        $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...
3386
        $em = Database::getManager();
3387
3388
        $tools = $em
3389
            ->createQuery("
3390
                SELECT t FROM ChamiloCourseBundle:CTool t
3391
                WHERE t.course = :course AND
3392
                    t.name = :name AND
3393
                    t.image LIKE 'lp_category.%' AND
3394
                    t.link LIKE :link
3395
            ")
3396
            ->setParameters([
3397
                'course' => $courseId,
3398
                'name' => strip_tags($category->getName()),
3399
                'link' => "$link%",
3400
            ])
3401
            ->getResult();
3402
3403
        /** @var CTool $tool */
3404
        $tool = current($tools);
3405
3406
        return $tool ? $tool->getVisibility() : false;
3407
    }
3408
3409
    /**
3410
     * Restart the whole learnpath. Return the URL of the first element.
3411
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
3412
     * To use a similar method  statically, use the create_new_attempt() method.
3413
     *
3414
     * @return bool
3415
     */
3416
    public function restart()
3417
    {
3418
        if ($this->debug > 0) {
3419
            error_log('In learnpath::restart()', 0);
3420
        }
3421
        // TODO
3422
        // Call autosave method to save the current progress.
3423
        //$this->index = 0;
3424
        if (api_is_invitee()) {
3425
            return false;
3426
        }
3427
        $session_id = api_get_session_id();
3428
        $course_id = api_get_course_int_id();
3429
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3430
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
3431
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
3432
        if ($this->debug > 2) {
3433
            error_log('Inserting new lp_view for restart: '.$sql, 0);
3434
        }
3435
        Database::query($sql);
3436
        $view_id = Database::insert_id();
3437
3438
        if ($view_id) {
3439
            $this->lp_view_id = $view_id;
3440
            $this->attempt = $this->attempt + 1;
3441
        } else {
3442
            $this->error = 'Could not insert into item_view table...';
3443
3444
            return false;
3445
        }
3446
        $this->autocomplete_parents($this->current);
3447
        foreach ($this->items as $index => $dummy) {
3448
            $this->items[$index]->restart();
3449
            $this->items[$index]->set_lp_view($this->lp_view_id);
3450
        }
3451
        $this->first();
3452
3453
        return true;
3454
    }
3455
3456
    /**
3457
     * Saves the current item.
3458
     *
3459
     * @return bool
3460
     */
3461
    public function save_current()
3462
    {
3463
        $debug = $this->debug;
3464
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3465
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3466
        if ($debug) {
3467
            error_log('save_current() saving item '.$this->current, 0);
3468
            error_log(''.print_r($this->items, true), 0);
3469
        }
3470
        if (isset($this->items[$this->current]) &&
3471
            is_object($this->items[$this->current])
3472
        ) {
3473
            if ($debug) {
3474
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3475
            }
3476
3477
            $res = $this->items[$this->current]->save(
3478
                false,
3479
                $this->prerequisites_match($this->current)
3480
            );
3481
            $this->autocomplete_parents($this->current);
3482
            $status = $this->items[$this->current]->get_status();
3483
            $this->update_queue[$this->current] = $status;
3484
3485
            if ($debug) {
3486
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3487
            }
3488
3489
            return $res;
3490
        }
3491
3492
        return false;
3493
    }
3494
3495
    /**
3496
     * Saves the given item.
3497
     *
3498
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
3499
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
3500
     *
3501
     * @return bool
3502
     */
3503
    public function save_item($item_id = null, $from_outside = true)
3504
    {
3505
        $debug = $this->debug;
3506
        if ($debug) {
3507
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
3508
        }
3509
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3510
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3511
        if (empty($item_id)) {
3512
            $item_id = (int) $_REQUEST['id'];
3513
        }
3514
3515
        if (empty($item_id)) {
3516
            $item_id = $this->get_current_item_id();
3517
        }
3518
        if (isset($this->items[$item_id]) &&
3519
            is_object($this->items[$item_id])
3520
        ) {
3521
            if ($debug) {
3522
                error_log('Object exists');
3523
            }
3524
3525
            // Saving the item.
3526
            $res = $this->items[$item_id]->save(
3527
                $from_outside,
3528
                $this->prerequisites_match($item_id)
3529
            );
3530
3531
            if ($debug) {
3532
                error_log('update_queue before:');
3533
                error_log(print_r($this->update_queue, 1));
3534
            }
3535
            $this->autocomplete_parents($item_id);
3536
3537
            $status = $this->items[$item_id]->get_status();
3538
            $this->update_queue[$item_id] = $status;
3539
3540
            if ($debug) {
3541
                error_log('get_status(): '.$status);
3542
                error_log('update_queue after:');
3543
                error_log(print_r($this->update_queue, 1));
3544
            }
3545
3546
            return $res;
3547
        }
3548
3549
        return false;
3550
    }
3551
3552
    /**
3553
     * Saves the last item seen's ID only in case.
3554
     */
3555
    public function save_last()
3556
    {
3557
        $course_id = api_get_course_int_id();
3558
        $debug = $this->debug;
3559
        if ($debug) {
3560
            error_log('In learnpath::save_last()', 0);
3561
        }
3562
        $session_condition = api_get_session_condition(
3563
            api_get_session_id(),
3564
            true,
3565
            false
3566
        );
3567
        $table = Database::get_course_table(TABLE_LP_VIEW);
3568
3569
        $userId = $this->get_user_id();
3570
        if (empty($userId)) {
3571
            $userId = api_get_user_id();
3572
            if ($debug) {
3573
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
3574
            }
3575
        }
3576
        if (isset($this->current) && !api_is_invitee()) {
3577
            if ($debug) {
3578
                error_log('Saving current item ('.$this->current.') for later review', 0);
3579
            }
3580
            $sql = "UPDATE $table SET
3581
                        last_item = ".$this->get_current_item_id()."
3582
                    WHERE
3583
                        c_id = $course_id AND
3584
                        lp_id = ".$this->get_id()." AND
3585
                        user_id = ".$userId." ".$session_condition;
3586
3587
            if ($debug) {
3588
                error_log('Saving last item seen : '.$sql, 0);
3589
            }
3590
            Database::query($sql);
3591
        }
3592
3593
        if (!api_is_invitee()) {
3594
            // Save progress.
3595
            [$progress] = $this->get_progress_bar_text('%');
3596
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
3597
            $scoreAsProgress = $this->getUseScoreAsProgress();
3598
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
3599
                if ($debug) {
3600
                    error_log("Return false: Dont save score: $score");
3601
                    error_log("progress: $progress");
3602
                }
3603
3604
                return false;
3605
            }
3606
3607
            if ($scoreAsProgress && $scoreAsProgressSetting) {
3608
                $storedProgress = self::getProgress(
3609
                    $this->get_id(),
3610
                    $userId,
3611
                    $course_id,
3612
                    $this->get_lp_session_id()
3613
                );
3614
3615
                // Check if the stored progress is higher than the new value
3616
                if ($storedProgress >= $progress) {
3617
                    if ($debug) {
3618
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
3619
                    }
3620
3621
                    return false;
3622
                }
3623
            }
3624
            if ($progress >= 0 && $progress <= 100) {
3625
                $progress = (int) $progress;
3626
                $sql = "UPDATE $table SET
3627
                            progress = $progress
3628
                        WHERE
3629
                            c_id = $course_id AND
3630
                            lp_id = ".$this->get_id()." AND
3631
                            user_id = ".$userId." ".$session_condition;
3632
                // Ignore errors as some tables might not have the progress field just yet.
3633
                Database::query($sql);
3634
                $this->progress_db = $progress;
3635
            }
3636
        }
3637
    }
3638
3639
    /**
3640
     * Sets the current item ID (checks if valid and authorized first).
3641
     *
3642
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
3643
     */
3644
    public function set_current_item($item_id = null)
3645
    {
3646
        $debug = $this->debug;
3647
        if ($debug) {
3648
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
3649
        }
3650
        if (empty($item_id)) {
3651
            if ($debug) {
3652
                error_log('No new current item given, ignore...', 0);
3653
            }
3654
            // Do nothing.
3655
        } else {
3656
            if ($debug) {
3657
                error_log('New current item given is '.$item_id.'...', 0);
3658
            }
3659
            if (is_numeric($item_id)) {
3660
                $item_id = (int) $item_id;
3661
                // TODO: Check in database here.
3662
                $this->last = $this->current;
3663
                $this->current = $item_id;
3664
                // TODO: Update $this->index as well.
3665
                foreach ($this->ordered_items as $index => $item) {
3666
                    if ($item == $this->current) {
3667
                        $this->index = $index;
3668
                        break;
3669
                    }
3670
                }
3671
                if ($debug) {
3672
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
3673
                }
3674
            } else {
3675
                if ($debug) {
3676
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
3677
                }
3678
            }
3679
        }
3680
    }
3681
3682
    /**
3683
     * Set index specified prefix terms for all items in this path.
3684
     *
3685
     * @param string $terms_string Comma-separated list of terms
3686
     * @param string $prefix       Xapian term prefix
3687
     *
3688
     * @return bool False on error, true otherwise
3689
     */
3690
    public function set_terms_by_prefix($terms_string, $prefix)
3691
    {
3692
        $course_id = api_get_course_int_id();
3693
        if ('true' !== api_get_setting('search_enabled')) {
3694
            return false;
3695
        }
3696
3697
        if (!extension_loaded('xapian')) {
3698
            return false;
3699
        }
3700
3701
        $terms_string = trim($terms_string);
3702
        $terms = explode(',', $terms_string);
3703
        array_walk($terms, 'trim_value');
3704
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
3705
3706
        // Don't do anything if no change, verify only at DB, not the search engine.
3707
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
3708
            return false;
3709
        }
3710
3711
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
3712
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
3713
3714
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
3715
        // TODO: Make query secure agains XSS : use member attr instead of post var.
3716
        $lp_id = (int) $_POST['lp_id'];
3717
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
3718
        $result = Database::query($sql);
3719
        $di = new ChamiloIndexer();
3720
3721
        while ($lp_item = Database::fetch_array($result)) {
3722
            // Get search_did.
3723
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
3724
            $sql = 'SELECT * FROM %s
3725
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
3726
                    LIMIT 1';
3727
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
3728
3729
            //echo $sql; echo '<br>';
3730
            $res = Database::query($sql);
3731
            if (Database::num_rows($res) > 0) {
3732
                $se_ref = Database::fetch_array($res);
3733
                // Compare terms.
3734
                $doc = $di->get_document($se_ref['search_did']);
3735
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
3736
                $xterms = [];
3737
                foreach ($xapian_terms as $xapian_term) {
3738
                    $xterms[] = substr($xapian_term['name'], 1);
3739
                }
3740
3741
                $dterms = $terms;
3742
                $missing_terms = array_diff($dterms, $xterms);
3743
                $deprecated_terms = array_diff($xterms, $dterms);
3744
3745
                // Save it to search engine.
3746
                foreach ($missing_terms as $term) {
3747
                    $doc->add_term($prefix.$term, 1);
3748
                }
3749
                foreach ($deprecated_terms as $term) {
3750
                    $doc->remove_term($prefix.$term);
3751
                }
3752
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
3753
                $di->getDb()->flush();
3754
            }
3755
        }
3756
3757
        return true;
3758
    }
3759
3760
    /**
3761
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
3762
     *
3763
     * @param int $id DB ID of the item
3764
     */
3765
    public function set_previous_item($id)
3766
    {
3767
        if ($this->debug > 0) {
3768
            error_log('In learnpath::set_previous_item()', 0);
3769
        }
3770
        $this->last = $id;
3771
    }
3772
3773
    /**
3774
     * Sets and saves the expired_on date.
3775
     *
3776
     * @return bool Returns true if author's name is not empty
3777
     */
3778
    public function set_modified_on()
3779
    {
3780
        $this->modified_on = api_get_utc_datetime();
3781
        $table = Database::get_course_table(TABLE_LP_MAIN);
3782
        $lp_id = $this->get_id();
3783
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
3784
                WHERE iid = $lp_id";
3785
        Database::query($sql);
3786
3787
        return true;
3788
    }
3789
3790
    /**
3791
     * Sets the object's error message.
3792
     *
3793
     * @param string $error Error message. If empty, reinits the error string
3794
     */
3795
    public function set_error_msg($error = '')
3796
    {
3797
        if ($this->debug > 0) {
3798
            error_log('In learnpath::set_error_msg()', 0);
3799
        }
3800
        if (empty($error)) {
3801
            $this->error = '';
3802
        } else {
3803
            $this->error .= $error;
3804
        }
3805
    }
3806
3807
    /**
3808
     * Launches the current item if not 'sco'
3809
     * (starts timer and make sure there is a record ready in the DB).
3810
     *
3811
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
3812
     *
3813
     * @return bool
3814
     */
3815
    public function start_current_item($allow_new_attempt = false)
3816
    {
3817
        $debug = $this->debug;
3818
        if ($debug) {
3819
            error_log('In learnpath::start_current_item()');
3820
            error_log('current: '.$this->current);
3821
        }
3822
        if (0 != $this->current && isset($this->items[$this->current]) &&
3823
            is_object($this->items[$this->current])
3824
        ) {
3825
            $type = $this->get_type();
3826
            $item_type = $this->items[$this->current]->get_type();
3827
            if ($debug) {
3828
                error_log('item type: '.$item_type);
3829
                error_log('lp type: '.$type);
3830
            }
3831
            if ((2 == $type && 'sco' !== $item_type) ||
3832
                (3 == $type && 'au' !== $item_type) ||
3833
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
3834
            ) {
3835
                $this->items[$this->current]->open($allow_new_attempt);
3836
                $this->autocomplete_parents($this->current);
3837
                $prereq_check = $this->prerequisites_match($this->current);
3838
                if ($debug) {
3839
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
3840
                }
3841
                $this->items[$this->current]->save(false, $prereq_check);
3842
            }
3843
            // If sco, then it is supposed to have been updated by some other call.
3844
            if ('sco' === $item_type) {
3845
                $this->items[$this->current]->restart();
3846
            }
3847
        }
3848
        if ($debug) {
3849
            error_log('lp_view_session_id');
3850
            error_log($this->lp_view_session_id);
3851
            error_log('api session id');
3852
            error_log(api_get_session_id());
3853
            error_log('End of learnpath::start_current_item()');
3854
        }
3855
3856
        return true;
3857
    }
3858
3859
    /**
3860
     * Stops the processing and counters for the old item (as held in $this->last).
3861
     *
3862
     * @return bool True/False
3863
     */
3864
    public function stop_previous_item()
3865
    {
3866
        $debug = $this->debug;
3867
        if ($debug) {
3868
            error_log('In learnpath::stop_previous_item()', 0);
3869
        }
3870
3871
        if (0 != $this->last && $this->last != $this->current &&
3872
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
3873
        ) {
3874
            if ($debug) {
3875
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
3876
            }
3877
            switch ($this->get_type()) {
3878
                case '3':
3879
                    if ('au' != $this->items[$this->last]->get_type()) {
3880
                        if ($debug) {
3881
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
3882
                        }
3883
                        $this->items[$this->last]->close();
3884
                    } else {
3885
                        if ($debug) {
3886
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
3887
                        }
3888
                    }
3889
                    break;
3890
                case '2':
3891
                    if ('sco' != $this->items[$this->last]->get_type()) {
3892
                        if ($debug) {
3893
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
3894
                        }
3895
                        $this->items[$this->last]->close();
3896
                    } else {
3897
                        if ($debug) {
3898
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
3899
                        }
3900
                    }
3901
                    break;
3902
                case '1':
3903
                default:
3904
                    if ($debug) {
3905
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
3906
                    }
3907
                    $this->items[$this->last]->close();
3908
                    break;
3909
            }
3910
        } else {
3911
            if ($debug) {
3912
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
3913
            }
3914
3915
            return false;
3916
        }
3917
3918
        return true;
3919
    }
3920
3921
    /**
3922
     * Updates the default view mode from fullscreen to embedded and inversely.
3923
     *
3924
     * @return string The current default view mode ('fullscreen' or 'embedded')
3925
     */
3926
    public function update_default_view_mode()
3927
    {
3928
        $table = Database::get_course_table(TABLE_LP_MAIN);
3929
        $sql = "SELECT * FROM $table
3930
                WHERE iid = ".$this->get_id();
3931
        $res = Database::query($sql);
3932
        if (Database::num_rows($res) > 0) {
3933
            $row = Database::fetch_array($res);
3934
            $default_view_mode = $row['default_view_mod'];
3935
            $view_mode = $default_view_mode;
3936
            switch ($default_view_mode) {
3937
                case 'fullscreen': // default with popup
3938
                    $view_mode = 'embedded';
3939
                    break;
3940
                case 'embedded': // default view with left menu
3941
                    $view_mode = 'embedframe';
3942
                    break;
3943
                case 'embedframe': //folded menu
3944
                    $view_mode = 'impress';
3945
                    break;
3946
                case 'impress':
3947
                    $view_mode = 'fullscreen';
3948
                    break;
3949
            }
3950
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
3951
                    WHERE iid = ".$this->get_id();
3952
            Database::query($sql);
3953
            $this->mode = $view_mode;
3954
3955
            return $view_mode;
3956
        }
3957
3958
        return -1;
3959
    }
3960
3961
    /**
3962
     * Updates the default behaviour about auto-commiting SCORM updates.
3963
     *
3964
     * @return bool True if auto-commit has been set to 'on', false otherwise
3965
     */
3966
    public function update_default_scorm_commit()
3967
    {
3968
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3969
        $sql = "SELECT * FROM $lp_table
3970
                WHERE iid = ".$this->get_id();
3971
        $res = Database::query($sql);
3972
        if (Database::num_rows($res) > 0) {
3973
            $row = Database::fetch_array($res);
3974
            $force = $row['force_commit'];
3975
            if (1 == $force) {
3976
                $force = 0;
3977
                $force_return = false;
3978
            } elseif (0 == $force) {
3979
                $force = 1;
3980
                $force_return = true;
3981
            }
3982
            $sql = "UPDATE $lp_table SET force_commit = $force
3983
                    WHERE iid = ".$this->get_id();
3984
            Database::query($sql);
3985
            $this->force_commit = $force_return;
3986
3987
            return $force_return;
3988
        }
3989
3990
        return -1;
3991
    }
3992
3993
    /**
3994
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
3995
     *
3996
     * @return bool True on success, false on failure
3997
     */
3998
    public function update_display_order()
3999
    {
4000
        return;
4001
        $course_id = api_get_course_int_id();
0 ignored issues
show
Unused Code introduced by
$course_id = api_get_course_int_id() 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...
4002
        $table = Database::get_course_table(TABLE_LP_MAIN);
4003
        $sql = "SELECT * FROM $table
4004
                WHERE c_id = $course_id
4005
                ORDER BY display_order";
4006
        $res = Database::query($sql);
4007
        if (false === $res) {
4008
            return false;
4009
        }
4010
4011
        $num = Database::num_rows($res);
4012
        // First check the order is correct, globally (might be wrong because
4013
        // of versions < 1.8.4).
4014
        if ($num > 0) {
4015
            $i = 1;
4016
            while ($row = Database::fetch_array($res)) {
4017
                if ($row['display_order'] != $i) {
4018
                    // If we find a gap in the order, we need to fix it.
4019
                    $sql = "UPDATE $table SET display_order = $i
4020
                            WHERE iid = ".$row['iid'];
4021
                    Database::query($sql);
4022
                }
4023
                $i++;
4024
            }
4025
        }
4026
4027
        return true;
4028
    }
4029
4030
    /**
4031
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4032
     *
4033
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4034
     */
4035
    public function update_reinit()
4036
    {
4037
        $force = $this->entity->getPreventReinit();
4038
        if (1 == $force) {
4039
            $force = 0;
4040
        } elseif (0 == $force) {
4041
            $force = 1;
4042
        }
4043
4044
        $table = Database::get_course_table(TABLE_LP_MAIN);
4045
        $sql = "UPDATE $table SET prevent_reinit = $force
4046
                WHERE iid = ".$this->get_id();
4047
        Database::query($sql);
4048
        $this->prevent_reinit = $force;
4049
4050
        return $force;
4051
    }
4052
4053
    /**
4054
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4055
     *
4056
     * @return string 'single', 'multi' or 'seriousgame'
4057
     *
4058
     * @author ndiechburg <[email protected]>
4059
     */
4060
    public function get_attempt_mode()
4061
    {
4062
        //Set default value for seriousgame_mode
4063
        if (!isset($this->seriousgame_mode)) {
4064
            $this->seriousgame_mode = 0;
4065
        }
4066
        // Set default value for prevent_reinit
4067
        if (!isset($this->prevent_reinit)) {
4068
            $this->prevent_reinit = 1;
4069
        }
4070
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4071
            return 'seriousgame';
4072
        }
4073
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4074
            return 'single';
4075
        }
4076
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4077
            return 'multiple';
4078
        }
4079
4080
        return 'single';
4081
    }
4082
4083
    /**
4084
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4085
     *
4086
     * @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...
4087
     *
4088
     * @return bool
4089
     *
4090
     * @author ndiechburg <[email protected]>
4091
     */
4092
    public function set_attempt_mode($mode)
4093
    {
4094
        switch ($mode) {
4095
            case 'seriousgame':
4096
                $sg_mode = 1;
4097
                $prevent_reinit = 1;
4098
                break;
4099
            case 'single':
4100
                $sg_mode = 0;
4101
                $prevent_reinit = 1;
4102
                break;
4103
            case 'multiple':
4104
                $sg_mode = 0;
4105
                $prevent_reinit = 0;
4106
                break;
4107
            default:
4108
                $sg_mode = 0;
4109
                $prevent_reinit = 0;
4110
                break;
4111
        }
4112
        $this->prevent_reinit = $prevent_reinit;
4113
        $this->seriousgame_mode = $sg_mode;
4114
        $table = Database::get_course_table(TABLE_LP_MAIN);
4115
        $sql = "UPDATE $table SET
4116
                prevent_reinit = $prevent_reinit ,
4117
                seriousgame_mode = $sg_mode
4118
                WHERE iid = ".$this->get_id();
4119
        $res = Database::query($sql);
4120
        if ($res) {
0 ignored issues
show
introduced by
$res is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4121
            return true;
4122
        } else {
4123
            return false;
4124
        }
4125
    }
4126
4127
    /**
4128
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4129
     *
4130
     * @author ndiechburg <[email protected]>
4131
     */
4132
    public function switch_attempt_mode()
4133
    {
4134
        $mode = $this->get_attempt_mode();
4135
        switch ($mode) {
4136
            case 'single':
4137
                $next_mode = 'multiple';
4138
                break;
4139
            case 'multiple':
4140
                $next_mode = 'seriousgame';
4141
                break;
4142
            case 'seriousgame':
4143
            default:
4144
                $next_mode = 'single';
4145
                break;
4146
        }
4147
        $this->set_attempt_mode($next_mode);
4148
    }
4149
4150
    /**
4151
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4152
     * but possibility to do again a completed item.
4153
     *
4154
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4155
     *
4156
     * @author ndiechburg <[email protected]>
4157
     */
4158
    public function set_seriousgame_mode()
4159
    {
4160
        $table = Database::get_course_table(TABLE_LP_MAIN);
4161
        $force = $this->entity->getSeriousgameMode();
4162
        if (1 == $force) {
4163
            $force = 0;
4164
        } elseif (0 == $force) {
4165
            $force = 1;
4166
        }
4167
        $sql = "UPDATE $table SET seriousgame_mode = $force
4168
                WHERE iid = ".$this->get_id();
4169
        Database::query($sql);
4170
        $this->seriousgame_mode = $force;
4171
4172
        return $force;
4173
    }
4174
4175
    /**
4176
     * Updates the "scorm_debug" value that shows or hide the debug window.
4177
     *
4178
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4179
     */
4180
    public function update_scorm_debug()
4181
    {
4182
        $table = Database::get_course_table(TABLE_LP_MAIN);
4183
        $force = $this->entity->getDebug();
4184
        if (1 == $force) {
4185
            $force = 0;
4186
        } elseif (0 == $force) {
4187
            $force = 1;
4188
        }
4189
        $sql = "UPDATE $table SET debug = $force
4190
                WHERE iid = ".$this->get_id();
4191
        Database::query($sql);
4192
        $this->scorm_debug = $force;
4193
4194
        return $force;
4195
    }
4196
4197
    /**
4198
     * Function that creates a html list of learning path items so that we can add audio files to them.
4199
     *
4200
     * @author Kevin Van Den Haute
4201
     *
4202
     * @return string
4203
     */
4204
    public function overview()
4205
    {
4206
        $return = '';
4207
        $update_audio = $_GET['updateaudio'] ?? null;
4208
4209
        // we need to start a form when we want to update all the mp3 files
4210
        if ('true' == $update_audio) {
4211
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4212
                    $_GET['updateaudio']
4213
                ).'&action='.Security::remove_XSS(
4214
                    $_GET['action']
4215
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4216
        }
4217
        $return .= '<div id="message"></div>';
4218
        if (0 == count($this->items)) {
4219
            $return .= Display::return_message(
4220
                get_lang(
4221
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4222
                ),
4223
                'normal'
4224
            );
4225
        } else {
4226
            $return_audio = '<table class="table table-hover table-striped data_table">';
4227
            $return_audio .= '<tr>';
4228
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4229
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4230
            $return_audio .= '</tr>';
4231
4232
            if ('true' != $update_audio) {
4233
                /*$return .= '<div class="col-md-12">';
4234
                $return .= self::return_new_tree($update_audio);
4235
                $return .= '</div>';*/
4236
                $return .= Display::div(
4237
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
4238
                    ['style' => 'float:left; margin-top:15px;width:100%']
4239
                );
4240
            } else {
4241
                //$return_audio .= self::return_new_tree($update_audio);
4242
                $return .= $return_audio.'</table>';
4243
            }
4244
4245
            // We need to close the form when we are updating the mp3 files.
4246
            if ('true' == $update_audio) {
4247
                $return .= '<div class="footer-audio">';
4248
                $return .= Display::button(
4249
                    'save_audio',
4250
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
4251
                    ['class' => 'btn btn-primary', 'type' => 'submit']
4252
                );
4253
                $return .= '</div>';
4254
            }
4255
        }
4256
4257
        // We need to close the form when we are updating the mp3 files.
4258
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
4259
            $return .= '</form>';
4260
        }
4261
4262
        return $return;
4263
    }
4264
4265
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
4266
    {
4267
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
4268
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
4269
4270
        $content = '
4271
        <script>
4272
            /*
4273
            Script to manipulate Learning Path items with Drag and drop
4274
             */
4275
            $(function() {
4276
                function refreshTree() {
4277
                    var params = "&a=get_lp_item_tree";
4278
                    $.get(
4279
                        "'.$ajax_url.'",
4280
                        params,
4281
                        function(result) {
4282
                            serialized = [];
4283
                            $("#lp_item_list").html(result);
4284
                            nestedSortable();
4285
                        }
4286
                    );
4287
                }
4288
4289
                const nestedQuery = ".nested-sortable";
4290
                const identifier = "id";
4291
                const root = document.getElementById("lp_item_list");
4292
4293
                var serialized = [];
4294
                function serialize(sortable) {
4295
                  var children = [].slice.call(sortable.children);
4296
                  for (var i in children) {
4297
                    var nested = children[i].querySelector(nestedQuery);
4298
                    var parentId = $(children[i]).parent().parent().attr("id");
4299
                    var id = children[i].dataset[identifier];
4300
                    if (typeof id === "undefined") {
4301
                        return;
4302
                    }
4303
                    serialized.push({
4304
                      id: children[i].dataset[identifier],
4305
                      parent_id: parentId
4306
                    });
4307
4308
                    if (nested) {
4309
                        serialize(nested);
4310
                    }
4311
                  }
4312
4313
                  return serialized;
4314
                }
4315
4316
                function nestedSortable() {
4317
                    let left = document.getElementsByClassName("nested-sortable");
4318
                    Array.prototype.forEach.call(left, function(resource) {
4319
                        Sortable.create(resource, {
4320
                            group: "nested",
4321
                            put: ["nested-sortable", ".lp_resource", ".nested-source"],
4322
                            animation: 150,
4323
                            //fallbackOnBody: true,
4324
                            swapThreshold: 0.65,
4325
                            dataIdAttr: "data-id",
4326
                            store: {
4327
                                set: function (sortable) {
4328
                                    var order = sortable.toArray();
4329
                                    console.log(order);
4330
                                }
4331
                            },
4332
                            onEnd: function(evt) {
4333
                                console.log("onEnd");
4334
                                let list = serialize(root);
4335
                                let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
4336
                                $.get(
4337
                                    "'.$ajax_url.'",
4338
                                    order,
4339
                                    function(reponse) {
4340
                                        $("#message").html(reponse);
4341
                                        refreshTree();
4342
                                    }
4343
                                );
4344
                            },
4345
                        });
4346
                    });
4347
                }
4348
4349
                nestedSortable();
4350
4351
                let resources = document.getElementsByClassName("lp_resource");
4352
                Array.prototype.forEach.call(resources, function(resource) {
4353
                    Sortable.create(resource, {
4354
                        group: "nested",
4355
                        put: ["nested-sortable"],
4356
                        filter: ".disable_drag",
4357
                        animation: 150,
4358
                        fallbackOnBody: true,
4359
                        swapThreshold: 0.65,
4360
                        dataIdAttr: "data-id",
4361
                        onRemove: function(evt) {
4362
                            console.log("onRemove");
4363
                            var itemEl = evt.item;
4364
                            var newIndex = evt.newIndex;
4365
                            var id = $(itemEl).attr("id");
4366
                            var parent_id = $(itemEl).parent().parent().attr("id");
4367
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
4368
                            var title = $(itemEl).find(".link_with_id").text();
4369
4370
                            let previousId = 0;
4371
                            if (0 !== newIndex) {
4372
                                previousId = $(itemEl).prev().attr("id");
4373
                            }
4374
                            var params = {
4375
                                "a": "add_lp_item",
4376
                                "id": id,
4377
                                "parent_id": parent_id,
4378
                                "previous_id": previousId,
4379
                                "type": type,
4380
                                "title" : title
4381
                            };
4382
                            console.log(params);
4383
                            $.ajax({
4384
                                type: "GET",
4385
                                url: "'.$ajax_url.'",
4386
                                data: params,
4387
                                success: function(itemId) {
4388
                                    $(itemEl).attr("id", itemId);
4389
                                    $(itemEl).attr("data-id", itemId);
4390
                                    let list = serialize(root);
4391
                                    let listInString = JSON.stringify(list);
4392
                                    if (typeof listInString === "undefined") {
4393
                                        listInString = "";
4394
                                    }
4395
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
4396
                                    $.get(
4397
                                        "'.$ajax_url.'",
4398
                                        order,
4399
                                        function(reponse) {
4400
                                            $("#message").html(reponse);
4401
                                            refreshTree();
4402
                                        }
4403
                                    );
4404
                                }
4405
                            });
4406
                        },
4407
                    });
4408
                });
4409
            });
4410
        </script>';
4411
4412
        $content .= "
4413
        <script>
4414
            function confirmation(name) {
4415
                if (confirm('$sureToDelete ' + name)) {
4416
                    return true;
4417
                } else {
4418
                    return false;
4419
                }
4420
            }
4421
            function refreshTree() {
4422
                var params = '&a=get_lp_item_tree';
4423
                $.get(
4424
                    '".$ajax_url."',
4425
                    params,
4426
                    function(result) {
4427
                        $('#lp_item_list').html(result);
4428
                    }
4429
                );
4430
            }
4431
4432
            $(function () {
4433
                //$('.scrollbar-inner').scrollbar();
4434
                /*$('#subtab').on('click', 'a:first', function() {
4435
                    window.location.reload();
4436
                });
4437
                $('#subtab ').on('click', 'a:first', function () {
4438
                    window.location.reload();
4439
                });*/
4440
4441
                expandColumnToggle('#hide_bar_template', {
4442
                    selector: '#lp_sidebar'
4443
                }, {
4444
                    selector: '#doc_form'
4445
                });
4446
4447
                $('.lp-btn-associate-forum').on('click', function (e) {
4448
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
4449
                    if (!associate) {
4450
                        e.preventDefault();
4451
                    }
4452
                });
4453
4454
                $('.lp-btn-dissociate-forum').on('click', function (e) {
4455
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
4456
                    if (!dissociate) {
4457
                        e.preventDefault();
4458
                    }
4459
                });
4460
4461
                // hide the current template list for new documment until it tab clicked
4462
                $('#frmModel').hide();
4463
            });
4464
4465
            // document template for new document tab handler
4466
            /*$(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
4467
                var id = e.target.id;
4468
                if (id == 'subtab2') {
4469
                    $('#frmModel').show();
4470
                } else {
4471
                    $('#frmModel').hide();
4472
                }
4473
            });*/
4474
4475
          function deleteItem(event) {
4476
            var id = $(event).attr('data-id');
4477
            var title = $(event).attr('data-title');
4478
            var params = '&a=delete_item&id=' + id;
4479
            if (confirmation(title)) {
4480
                $.get(
4481
                    '".$ajax_url."',
4482
                    params,
4483
                    function(result) {
4484
                        refreshTree();
4485
                    }
4486
                );
4487
            }
4488
        }
4489
        </script>";
4490
4491
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
4492
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
4493
4494
        $repo = Container::getDocumentRepository();
4495
        $document = $repo->find($documentId);
4496
        if ($document) {
4497
            // Show the template list
4498
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4499
        }
4500
4501
        // Show the template list.
4502
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
4503
            // Show the template list.
4504
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4505
        }
4506
4507
        return $content;
4508
    }
4509
4510
    /**
4511
     * @param bool  $updateAudio
4512
     * @param bool   $dropElement
4513
     *
4514
     * @return string
4515
     */
4516
    public function return_new_tree($updateAudio = false, $dropElement = false)
4517
    {
4518
        $list = $this->getBuildTree(false, $dropElement);
4519
        $return = Display::panelCollapse(
4520
            $this->name,
4521
            $list,
4522
            'scorm-list',
4523
            null,
4524
            'scorm-list-accordion',
4525
            'scorm-list-collapse'
4526
        );
4527
4528
        if ($updateAudio) {
4529
            //$return = $result['return_audio'];
4530
        }
4531
4532
        return $return;
4533
    }
4534
4535
    public function getBuildTree($noWrapper = false, $dropElement = false): string
4536
    {
4537
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
4538
4539
        $upIcon = Display::return_icon(
4540
            'up.png',
4541
            get_lang('Up'),
4542
            [],
4543
            ICON_SIZE_TINY
4544
        );
4545
4546
        $disableUpIcon = Display::return_icon(
4547
            'up_na.png',
4548
            get_lang('Up'),
4549
            [],
4550
            ICON_SIZE_TINY
4551
        );
4552
4553
        $downIcon = Display::return_icon(
4554
            'down.png',
4555
            get_lang('Down'),
4556
            [],
4557
            ICON_SIZE_TINY
4558
        );
4559
4560
        $previewImage = Display::return_icon(
4561
            'preview_view.png',
4562
            get_lang('Preview'),
4563
            [],
4564
            ICON_SIZE_TINY
4565
        );
4566
        $lpItemRepo = Container::getLpItemRepository();
4567
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
4568
4569
        $options = [
4570
            'decorate' => true,
4571
            'rootOpen' => function($tree) use ($noWrapper) {
4572
                if ($tree[0]['lvl'] === 1) {
4573
                    if ($noWrapper) {
4574
                        return '';
4575
                    }
4576
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
4577
                }
4578
4579
                return '<ul class="list-group nested-sortable">';
4580
            },
4581
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
4582
                if ($tree[0]['lvl'] === 1) {
4583
                    if ($dropElement) {
4584
                        //return Display::return_message(get_lang('Drag and drop an element here'));
4585
                        //return $this->getDropElementHtml();
4586
                    }
4587
                    if ($noWrapper) {
4588
                        return '';
4589
                    }
4590
                }
4591
4592
                return '</ul>';
4593
            },
4594
            'childOpen' => function($child) {
4595
                $id = $child['iid'];
4596
                return '<li
4597
                    id="'.$id.'"
4598
                    data-id="'.$id.'"
4599
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
4600
            },
4601
            'childClose' => '',
4602
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
4603
                $fullTitle = $node['title'];
4604
                //$title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
4605
                $title = $fullTitle;
4606
                $itemId = $node['iid'];
4607
                $type = $node['itemType'];
4608
                $lpId = $this->get_id();
4609
4610
                $moveIcon = '';
4611
                if (TOOL_LP_FINAL_ITEM !== $type) {
4612
                    $moveIcon .= '<a class="moved" href="#">';
4613
                    $moveIcon .= Display::return_icon(
4614
                        'move_everywhere.png',
4615
                        get_lang('Move'),
4616
                        [],
4617
                        ICON_SIZE_TINY
4618
                    );
4619
                    $moveIcon .= '</a>';
4620
                }
4621
4622
                $iconName = str_replace(' ', '', $type);
4623
                $icon = Display::return_icon(
4624
                    'lp_'.$iconName.'.png',
4625
                    '',
4626
                    [],
4627
                    ICON_SIZE_TINY
4628
                );
4629
4630
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
4631
                $previewIcon = Display::url(
4632
                    $previewImage,
4633
                    $urlPreviewLink,
4634
                    [
4635
                        'target' => '_blank',
4636
                        'class' => 'btn btn-default',
4637
                        'data-title' => $title,
4638
                        'title' => $title,
4639
                    ]
4640
                );
4641
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
4642
4643
                $preRequisitesIcon = Display::url(
4644
                    Display::return_icon(
4645
                        'accept.png',
4646
                        get_lang('Prerequisites'),
4647
                        [],
4648
                        ICON_SIZE_TINY
4649
                    ),
4650
                    $url.'&action=edit_item_prereq',
4651
                    ['class' => 'btn btn-default']
4652
                );
4653
4654
                $editIcon = '<a
4655
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
4656
                    class="btn btn-default"
4657
                    >';
4658
                $editIcon .= Display::return_icon(
4659
                    'edit.png',
4660
                    get_lang('Edit section description/name'),
4661
                    [],
4662
                    ICON_SIZE_TINY
4663
                );
4664
                $editIcon .= '</a>';
4665
                $orderIcons = '';
4666
                /*if ('final_item' !== $type) {
4667
                    $orderIcons = Display::url(
4668
                        $upIcon,
4669
                        'javascript:void(0)',
4670
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
4671
                    );
4672
                    $orderIcons .= Display::url(
4673
                        $downIcon,
4674
                        'javascript:void(0)',
4675
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
4676
                    );
4677
                }*/
4678
4679
                $deleteIcon = ' <a
4680
                    data-id = '.$itemId.'
4681
                    data-title = \''.addslashes($title).'\'
4682
                    href="javascript:void(0);"
4683
                    onclick="return deleteItem(this);"
4684
                    class="btn btn-default">';
4685
                $deleteIcon .= Display::return_icon(
4686
                    'delete.png',
4687
                    get_lang('Delete section'),
4688
                    [],
4689
                    ICON_SIZE_TINY
4690
                );
4691
                $deleteIcon .= '</a>';
4692
                $extra = '';
4693
4694
                if ('dir' === $type && empty($node['__children'])) {
4695
                    $level = $node['lvl'] + 1;
4696
                    $extra = '<ul class="list-group nested-sortable">
4697
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
4698
                              </ul>';
4699
                }
4700
4701
                $buttons = Display::tag(
4702
                    'div',
4703
                    "<div class=\"btn-group btn-group-sm\">
4704
                                $editIcon
4705
                                $preRequisitesIcon
4706
                                $orderIcons
4707
                                $deleteIcon
4708
                               </div>",
4709
                    ['class' => 'btn-toolbar button_actions']
4710
                );
4711
4712
                return
4713
                    "<div class='flex flex-row'> $moveIcon  $icon <div>$title </div></div>
4714
                    $extra
4715
                    $buttons
4716
                    "
4717
                    ;
4718
            },
4719
        ];
4720
4721
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
4722
4723
        if (empty($tree) && $dropElement) {
4724
            return $this->getDropElementHtml($noWrapper);
4725
        }
4726
4727
        return $tree;
4728
    }
4729
4730
    public function getDropElementHtml($noWrapper = false)
4731
    {
4732
        $li = '<li class="list-group-item">'.
4733
            Display::return_message(get_lang('Drag and drop an element here')).
4734
            '</li>';
4735
        if ($noWrapper) {
4736
            return $li;
4737
        }
4738
4739
        return
4740
            '<ul id="lp_item_list" class="list-group nested-sortable">
4741
            '.$li.'
4742
            </ul>';
4743
    }
4744
4745
    /**
4746
     * This function builds the action menu.
4747
     *
4748
     * @param bool   $returnString           Optional
4749
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
4750
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
4751
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
4752
     * @param string $action
4753
     * @param array  $extraField
4754
     *
4755
     * @return string
4756
     */
4757
    public function build_action_menu(
4758
        $returnString = false,
4759
        $showRequirementButtons = true,
4760
        $isConfigPage = false,
4761
        $allowExpand = true,
4762
        $action = '',
4763
        $extraField = []
4764
    ) {
4765
        $actionsRight = '';
4766
        $lpId = $this->lp_id;
4767
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
4768
            $back = Display::url(
4769
                Display::return_icon(
4770
                    'back.png',
4771
                    get_lang('Back to learning paths'),
4772
                    '',
4773
                    ICON_SIZE_MEDIUM
4774
                ),
4775
                'lp_controller.php?'.api_get_cidreq()
4776
            );
4777
        } else {
4778
            $back = Display::url(
4779
                Display::return_icon(
4780
                    'back.png',
4781
                    get_lang('Back'),
4782
                    '',
4783
                    ICON_SIZE_MEDIUM
4784
                ),
4785
                $extraField['backTo']
4786
            );
4787
        }
4788
4789
        /*if ($backToBuild) {
4790
            $back = Display::url(
4791
                Display::return_icon(
4792
                    'back.png',
4793
                    get_lang('GoBack'),
4794
                    '',
4795
                    ICON_SIZE_MEDIUM
4796
                ),
4797
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
4798
            );
4799
        }*/
4800
4801
        $actionsLeft = $back;
4802
4803
        $actionsLeft .= Display::url(
4804
            Display::return_icon(
4805
                'preview_view.png',
4806
                get_lang('Preview'),
4807
                '',
4808
                ICON_SIZE_MEDIUM
4809
            ),
4810
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4811
                'action' => 'view',
4812
                'lp_id' => $lpId,
4813
                'isStudentView' => 'true',
4814
            ])
4815
        );
4816
4817
        /*$actionsLeft .= Display::url(
4818
            Display::return_icon(
4819
                'upload_audio.png',
4820
                get_lang('Add audio'),
4821
                '',
4822
                ICON_SIZE_MEDIUM
4823
            ),
4824
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4825
                'action' => 'admin_view',
4826
                'lp_id' => $lpId,
4827
                'updateaudio' => 'true',
4828
            ])
4829
        );*/
4830
4831
        $subscriptionSettings = self::getSubscriptionSettings();
4832
4833
        $request = api_request_uri();
4834
        if (false === strpos($request, 'edit')) {
4835
            $actionsLeft .= Display::url(
4836
                Display::return_icon(
4837
                    'settings.png',
4838
                    get_lang('Course settings'),
4839
                    '',
4840
                    ICON_SIZE_MEDIUM
4841
                ),
4842
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4843
                    'action' => 'edit',
4844
                    'lp_id' => $lpId,
4845
                ])
4846
            );
4847
        }
4848
4849
        if ((false === strpos($request, 'build') &&
4850
            false === strpos($request, 'add_item')) ||
4851
            in_array($action, ['add_audio'], true)
4852
        ) {
4853
            $actionsLeft .= Display::url(
4854
                Display::return_icon(
4855
                    'edit.png',
4856
                    get_lang('Edit'),
4857
                    '',
4858
                    ICON_SIZE_MEDIUM
4859
                ),
4860
                'lp_controller.php?'.http_build_query([
4861
                    'action' => 'build',
4862
                    'lp_id' => $lpId,
4863
                ]).'&'.api_get_cidreq()
4864
            );
4865
        }
4866
4867
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
4868
            if (1 == $this->subscribeUsers &&
4869
                $subscriptionSettings['allow_add_users_to_lp']) {
4870
                $actionsLeft .= Display::url(
4871
                    Display::return_icon(
4872
                        'user.png',
4873
                        get_lang('Subscribe users to learning path'),
4874
                        '',
4875
                        ICON_SIZE_MEDIUM
4876
                    ),
4877
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
4878
                );
4879
            }
4880
        }
4881
4882
        if ($allowExpand) {
4883
            /*$actionsLeft .= Display::url(
4884
                Display::return_icon(
4885
                    'expand.png',
4886
                    get_lang('Expand'),
4887
                    ['id' => 'expand'],
4888
                    ICON_SIZE_MEDIUM
4889
                ).
4890
                Display::return_icon(
4891
                    'contract.png',
4892
                    get_lang('Collapse'),
4893
                    ['id' => 'contract', 'class' => 'hide'],
4894
                    ICON_SIZE_MEDIUM
4895
                ),
4896
                '#',
4897
                ['role' => 'button', 'id' => 'hide_bar_template']
4898
            );*/
4899
        }
4900
4901
        if ($showRequirementButtons) {
4902
            $buttons = [
4903
                [
4904
                    'title' => get_lang('Set previous step as prerequisite for each step'),
4905
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4906
                        'action' => 'set_previous_step_as_prerequisite',
4907
                        'lp_id' => $lpId,
4908
                    ]),
4909
                ],
4910
                [
4911
                    'title' => get_lang('Clear all prerequisites'),
4912
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4913
                        'action' => 'clear_prerequisites',
4914
                        'lp_id' => $lpId,
4915
                    ]),
4916
                ],
4917
            ];
4918
            $actionsRight = Display::groupButtonWithDropDown(
4919
                get_lang('Prerequisites options'),
4920
                $buttons,
4921
                true
4922
            );
4923
        }
4924
4925
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
4926
            $actionsLeft .= Display::url(
4927
                Display::return_icon(
4928
                    'add-groups.png',
4929
                    get_lang('Author'),
4930
                    '',
4931
                    ICON_SIZE_MEDIUM
4932
                ),
4933
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4934
                    'action' => 'author_view',
4935
                    'lp_id' => $lpId,
4936
                ])
4937
            );
4938
        }
4939
4940
        $toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]);
4941
4942
        if ($returnString) {
4943
            return $toolbar;
4944
        }
4945
4946
        echo $toolbar;
4947
    }
4948
4949
    /**
4950
     * Creates the default learning path folder.
4951
     *
4952
     * @param array $course
4953
     * @param int   $creatorId
4954
     *
4955
     * @return CDocument
4956
     */
4957
    public static function generate_learning_path_folder($course, $creatorId = 0)
4958
    {
4959
        // Creating learning_path folder
4960
        $dir = 'learning_path';
4961
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
4962
4963
        return create_unexisting_directory(
4964
            $course,
4965
            $creatorId,
4966
            0,
4967
            null,
4968
            0,
4969
            '',
4970
            $dir,
4971
            get_lang('Learning paths'),
4972
            0
4973
        );
4974
    }
4975
4976
    /**
4977
     * @param array  $course
4978
     * @param string $lp_name
4979
     * @param int    $creatorId
4980
     *
4981
     * @return CDocument
4982
     */
4983
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
4984
    {
4985
        $filepath = '';
4986
        $dir = '/learning_path/';
4987
4988
        if (empty($lp_name)) {
4989
            $lp_name = $this->name;
4990
        }
4991
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
4992
        $parent = self::generate_learning_path_folder($course, $creatorId);
4993
4994
        // Limits title size
4995
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
4996
        $dir = $dir.$title;
4997
4998
        // Creating LP folder
4999
        $folder = null;
5000
        if ($parent) {
5001
            $folder = create_unexisting_directory(
5002
                $course,
5003
                $creatorId,
5004
                0,
5005
                0,
5006
                0,
5007
                $filepath,
5008
                $dir,
5009
                $lp_name,
5010
                '',
5011
                false,
5012
                false,
5013
                $parent
5014
            );
5015
        }
5016
5017
        return $folder;
5018
    }
5019
5020
    /**
5021
     * Create a new document //still needs some finetuning.
5022
     *
5023
     * @param array  $courseInfo
5024
     * @param string $content
5025
     * @param string $title
5026
     * @param string $extension
5027
     * @param int    $parentId
5028
     * @param int    $creatorId  creator id
5029
     *
5030
     * @return int
5031
     */
5032
    public function create_document(
5033
        $courseInfo,
5034
        $content = '',
5035
        $title = '',
5036
        $extension = 'html',
5037
        $parentId = 0,
5038
        $creatorId = 0
5039
    ) {
5040
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5041
        $sessionId = api_get_session_id();
5042
5043
        // Generates folder
5044
        $this->generate_lp_folder($courseInfo);
5045
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
5046
        // is already escaped twice when it gets here.
5047
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5048
        if (!empty($title)) {
5049
            $title = api_replace_dangerous_char(stripslashes($title));
5050
        } else {
5051
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5052
        }
5053
5054
        $title = disable_dangerous_file($title);
5055
        $filename = $title;
5056
        $tmp_filename = $filename;
5057
        /*$i = 0;
5058
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
5059
            $tmp_filename = $filename.'_'.++$i;
5060
        }*/
5061
        $filename = $tmp_filename.'.'.$extension;
5062
5063
        if ('html' === $extension) {
5064
            $content = stripslashes($content);
5065
            $content = str_replace(
5066
                api_get_path(WEB_COURSE_PATH),
5067
                api_get_path(REL_PATH).'courses/',
5068
                $content
5069
            );
5070
5071
            // Change the path of mp3 to absolute.
5072
            // The first regexp deals with :// urls.
5073
            /*$content = preg_replace(
5074
                "|(flashvars=\"file=)([^:/]+)/|",
5075
                "$1".api_get_path(
5076
                    REL_COURSE_PATH
5077
                ).$courseInfo['path'].'/document/',
5078
                $content
5079
            );*/
5080
            // The second regexp deals with audio/ urls.
5081
            /*$content = preg_replace(
5082
                "|(flashvars=\"file=)([^/]+)/|",
5083
                "$1".api_get_path(
5084
                    REL_COURSE_PATH
5085
                ).$courseInfo['path'].'/document/$2/',
5086
                $content
5087
            );*/
5088
            // For flv player: To prevent edition problem with firefox,
5089
            // we have to use a strange tip (don't blame me please).
5090
            $content = str_replace(
5091
                '</body>',
5092
                '<style type="text/css">body{}</style></body>',
5093
                $content
5094
            );
5095
        }
5096
5097
        $document = DocumentManager::addDocument(
5098
            $courseInfo,
5099
            null,
5100
            'file',
5101
            '',
5102
            $tmp_filename,
5103
            '',
5104
            0, //readonly
5105
            true,
5106
            null,
5107
            $sessionId,
5108
            $creatorId,
5109
            false,
5110
            $content,
5111
            $parentId
5112
        );
5113
5114
        $document_id = $document->getIid();
5115
        if ($document_id) {
5116
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5117
            $new_title = $originalTitle;
5118
5119
            if ($new_comment || $new_title) {
5120
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
5121
                $ct = '';
5122
                if ($new_comment) {
5123
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
5124
                }
5125
                if ($new_title) {
5126
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
5127
                }
5128
5129
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
5130
                        WHERE iid = $document_id ";
5131
                Database::query($sql);
5132
            }
5133
        }
5134
5135
        return $document_id;
5136
    }
5137
5138
    /**
5139
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
5140
     */
5141
    public function edit_document()
5142
    {
5143
        $repo = Container::getDocumentRepository();
5144
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
5145
            $id = (int) $_REQUEST['document_id'];
5146
            /** @var CDocument $document */
5147
            $document = $repo->find($id);
5148
            if ($document->getResourceNode()->hasEditableTextContent()) {
5149
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
5150
            }
5151
            $document->setTitle($_REQUEST['title']);
5152
            $repo->update($document);
5153
        }
5154
    }
5155
5156
    /**
5157
     * Displays the selected item, with a panel for manipulating the item.
5158
     *
5159
     * @param CLpItem $lpItem
5160
     * @param string  $msg
5161
     * @param bool    $show_actions
5162
     *
5163
     * @return string
5164
     */
5165
    public function display_item($lpItem, $msg = null, $show_actions = true)
5166
    {
5167
        $course_id = api_get_course_int_id();
5168
        $return = '';
5169
5170
        if (null === $lpItem) {
5171
            return '';
5172
        }
5173
        $item_id = $lpItem->getIid();
5174
        $itemType = $lpItem->getItemType();
5175
        $lpId = $lpItem->getLp()->getIid();
5176
        $path = $lpItem->getPath();
5177
5178
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
5179
5180
        // Prevents wrong parent selection for document, see Bug#1251.
5181
        if ('dir' !== $itemType) {
5182
            Session::write('parent_item_id', $lpItem->getParentItemId());
5183
        }
5184
5185
        if ($show_actions) {
5186
            $return .= $this->displayItemMenu($lpItem);
5187
        }
5188
        $return .= '<div style="padding:10px;">';
5189
5190
        if ('' != $msg) {
5191
            $return .= $msg;
5192
        }
5193
5194
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
5195
5196
        switch ($itemType) {
5197
            case TOOL_THREAD:
5198
                $link = $this->rl_get_resource_link_for_learnpath(
5199
                    $course_id,
5200
                    $lpId,
5201
                    $item_id,
5202
                    0
5203
                );
5204
                $return .= Display::url(
5205
                    get_lang('Go to thread'),
5206
                    $link,
5207
                    ['class' => 'btn btn-primary']
5208
                );
5209
                break;
5210
            case TOOL_FORUM:
5211
                $return .= Display::url(
5212
                    get_lang('Go to the forum'),
5213
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
5214
                    ['class' => 'btn btn-primary']
5215
                );
5216
                break;
5217
            case TOOL_QUIZ:
5218
                if (!empty($path)) {
5219
                    $exercise = new Exercise();
5220
                    $exercise->read($path);
5221
                    $return .= $exercise->description.'<br />';
5222
                    $return .= Display::url(
5223
                        get_lang('Go to exercise'),
5224
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
5225
                        ['class' => 'btn btn-primary']
5226
                    );
5227
                }
5228
                break;
5229
            case TOOL_LP_FINAL_ITEM:
5230
                $return .= $this->getSavedFinalItem();
5231
                break;
5232
            case TOOL_DOCUMENT:
5233
            case TOOL_READOUT_TEXT:
5234
                $repo = Container::getDocumentRepository();
5235
                /** @var CDocument $document */
5236
                $document = $repo->find($lpItem->getPath());
5237
                $return .= $this->display_document($document, true, true);
5238
                break;
5239
        }
5240
        $return .= '</div>';
5241
5242
        return $return;
5243
    }
5244
5245
    /**
5246
     * Shows the needed forms for editing a specific item.
5247
     *
5248
     * @param CLpItem $lpItem
5249
     *
5250
     * @throws Exception
5251
     *
5252
     *
5253
     * @return string
5254
     */
5255
    public function display_edit_item($lpItem, $excludeExtraFields = [])
5256
    {
5257
        $return = '';
5258
        if (empty($lpItem)) {
5259
            return '';
5260
        }
5261
        $itemType = $lpItem->getItemType();
5262
        $path = $lpItem->getPath();
5263
5264
        switch ($itemType) {
5265
            case 'dir':
5266
            case 'asset':
5267
            case 'sco':
5268
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
5269
                    $return .= $this->displayItemMenu($lpItem);
5270
                    $return .= $this->display_item_form($lpItem, 'edit');
5271
                } else {
5272
                    $return .= $this->display_item_form($lpItem, 'edit_item');
5273
                }
5274
                break;
5275
            case TOOL_LP_FINAL_ITEM:
5276
            case TOOL_DOCUMENT:
5277
            case TOOL_READOUT_TEXT:
5278
                $return .= $this->displayItemMenu($lpItem);
5279
                $return .= $this->displayDocumentForm('edit', $lpItem);
5280
                break;
5281
            case TOOL_LINK:
5282
                $link = null;
5283
                if (!empty($path)) {
5284
                    $repo = Container::getLinkRepository();
5285
                    $link = $repo->find($path);
5286
                }
5287
                $return .= $this->displayItemMenu($lpItem);
5288
                $return .= $this->display_link_form('edit', $lpItem, $link);
5289
5290
                break;
5291
            case TOOL_QUIZ:
5292
                if (!empty($path)) {
5293
                    $repo = Container::getQuizRepository();
5294
                    $resource = $repo->find($path);
5295
                }
5296
                $return .= $this->displayItemMenu($lpItem);
5297
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
5298
                break;
5299
            case TOOL_STUDENTPUBLICATION:
5300
                if (!empty($path)) {
5301
                    $repo = Container::getStudentPublicationRepository();
5302
                    $resource = $repo->find($path);
5303
                }
5304
                $return .= $this->displayItemMenu($lpItem);
5305
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
5306
                break;
5307
            case TOOL_FORUM:
5308
                if (!empty($path)) {
5309
                    $repo = Container::getForumRepository();
5310
                    $resource = $repo->find($path);
5311
                }
5312
                $return .= $this->displayItemMenu($lpItem);
5313
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
5314
                break;
5315
            case TOOL_THREAD:
5316
                if (!empty($path)) {
5317
                    $repo = Container::getForumPostRepository();
5318
                    $resource = $repo->find($path);
5319
                }
5320
                $return .= $this->displayItemMenu($lpItem);
5321
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
5322
                break;
5323
        }
5324
5325
        return $return;
5326
    }
5327
5328
    /**
5329
     * Function that displays a list with al the resources that
5330
     * could be added to the learning path.
5331
     *
5332
     * @throws Exception
5333
     */
5334
    public function displayResources(): string
5335
    {
5336
        // Get all the docs.
5337
        $documents = $this->get_documents(true);
5338
5339
        // Get all the exercises.
5340
        $exercises = $this->get_exercises();
5341
5342
        // Get all the links.
5343
        $links = $this->get_links();
5344
5345
        // Get all the student publications.
5346
        $works = $this->get_student_publications();
5347
5348
        // Get all the forums.
5349
        $forums = $this->get_forums();
5350
5351
        // Get the final item form (see BT#11048) .
5352
        $finish = $this->getFinalItemForm();
5353
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
5354
        $headers = [
5355
            Display::return_icon('folder_document.png', get_lang('Documents'), [], $size),
5356
            Display::return_icon('quiz.png', get_lang('Tests'), [], $size),
5357
            Display::return_icon('links.png', get_lang('Links'), [], $size),
5358
            Display::return_icon('works.png', get_lang('Assignments'), [], $size),
5359
            Display::return_icon('forum.png', get_lang('Forums'), [], $size),
5360
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], $size),
5361
            Display::return_icon('certificate.png', get_lang('Certificate'), [], $size),
5362
        ];
5363
        $content = '';
5364
        /*$content = Display::return_message(
5365
            get_lang('Click on the [Learner view] button to see your learning path'),
5366
            'normal'
5367
        );*/
5368
        $section = $this->displayNewSectionForm();
5369
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
5370
5371
        return Display::tabs(
5372
            $headers,
5373
            [
5374
                $documents,
5375
                $exercises,
5376
                $links,
5377
                $works,
5378
                $forums,
5379
                $section,
5380
                $finish,
5381
            ],
5382
            'resource_tab',
5383
            [],
5384
            [],
5385
            $selected
5386
        );
5387
    }
5388
5389
    /**
5390
     * Returns the extension of a document.
5391
     *
5392
     * @param string $filename
5393
     *
5394
     * @return string Extension (part after the last dot)
5395
     */
5396
    public function get_extension($filename)
5397
    {
5398
        $explode = explode('.', $filename);
5399
5400
        return $explode[count($explode) - 1];
5401
    }
5402
5403
    /**
5404
     * @return string
5405
     */
5406
    public function getCurrentBuildingModeURL()
5407
    {
5408
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
5409
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
5410
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
5411
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
5412
5413
        $currentUrl = api_get_self().'?'.api_get_cidreq().
5414
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
5415
5416
        return $currentUrl;
5417
    }
5418
5419
    /**
5420
     * Displays a document by id.
5421
     *
5422
     * @param CDocument $document
5423
     * @param bool      $show_title
5424
     * @param bool      $iframe
5425
     * @param bool      $edit_link
5426
     *
5427
     * @return string
5428
     */
5429
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
5430
    {
5431
        $return = '';
5432
        if (!$document) {
5433
            return '';
5434
        }
5435
5436
        $repo = Container::getDocumentRepository();
5437
5438
        // TODO: Add a path filter.
5439
        if ($iframe) {
5440
            $url = $repo->getResourceFileUrl($document);
5441
5442
            $return .= '<iframe
5443
                id="learnpath_preview_frame"
5444
                frameborder="0"
5445
                height="400"
5446
                width="100%"
5447
                scrolling="auto"
5448
                src="'.$url.'"></iframe>';
5449
        } else {
5450
            $return = $repo->getResourceFileContent($document);
5451
        }
5452
5453
        return $return;
5454
    }
5455
5456
    /**
5457
     * Return HTML form to add/edit a link item.
5458
     *
5459
     * @param string  $action (add/edit)
5460
     * @param CLpItem $lpItem
5461
     * @param CLink   $link
5462
     *
5463
     * @throws Exception
5464
     *
5465
     *
5466
     * @return string HTML form
5467
     */
5468
    public function display_link_form($action, $lpItem, $link)
5469
    {
5470
        $item_url = '';
5471
        if ($link) {
5472
            $item_url = stripslashes($link->getUrl());
5473
        }
5474
        $form = new FormValidator(
5475
            'edit_link',
5476
            'POST',
5477
            $this->getCurrentBuildingModeURL()
5478
        );
5479
5480
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5481
5482
        $urlAttributes = ['class' => 'learnpath_item_form'];
5483
        $urlAttributes['disabled'] = 'disabled';
5484
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
5485
        $form->setDefault('url', $item_url);
5486
5487
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5488
5489
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5490
    }
5491
5492
    /**
5493
     * Return HTML form to add/edit a quiz.
5494
     *
5495
     * @param string  $action   Action (add/edit)
5496
     * @param CLpItem $lpItem   Item ID if already exists
5497
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
5498
     *
5499
     * @throws Exception
5500
     *
5501
     * @return string HTML form
5502
     */
5503
    public function display_quiz_form($action, $lpItem, $exercise)
5504
    {
5505
        $form = new FormValidator(
5506
            'quiz_form',
5507
            'POST',
5508
            $this->getCurrentBuildingModeURL()
5509
        );
5510
5511
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5512
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5513
5514
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5515
    }
5516
5517
    /**
5518
     * Return the form to display the forum edit/add option.
5519
     *
5520
     * @param CLpItem $lpItem
5521
     *
5522
     * @throws Exception
5523
     *
5524
     * @return string HTML form
5525
     */
5526
    public function display_forum_form($action, $lpItem, $resource)
5527
    {
5528
        $form = new FormValidator(
5529
            'forum_form',
5530
            'POST',
5531
            $this->getCurrentBuildingModeURL()
5532
        );
5533
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5534
5535
        if ('add' === $action) {
5536
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
5537
        } else {
5538
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
5539
        }
5540
5541
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5542
    }
5543
5544
    /**
5545
     * Return HTML form to add/edit forum threads.
5546
     *
5547
     * @param string  $action
5548
     * @param CLpItem $lpItem
5549
     * @param string  $resource
5550
     *
5551
     * @throws Exception
5552
     *
5553
     * @return string HTML form
5554
     */
5555
    public function display_thread_form($action, $lpItem, $resource)
5556
    {
5557
        $form = new FormValidator(
5558
            'thread_form',
5559
            'POST',
5560
            $this->getCurrentBuildingModeURL()
5561
        );
5562
5563
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5564
5565
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5566
5567
        return $form->returnForm();
5568
    }
5569
5570
    /**
5571
     * Return the HTML form to display an item (generally a dir item).
5572
     *
5573
     * @param CLpItem $lpItem
5574
     * @param string  $action
5575
     *
5576
     * @throws Exception
5577
     *
5578
     *
5579
     * @return string HTML form
5580
     */
5581
    public function display_item_form(
5582
        $lpItem,
5583
        $action = 'add_item'
5584
    ) {
5585
        $item_type = $lpItem->getItemType();
5586
5587
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5588
5589
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5590
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5591
5592
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5593
5594
        return $form->returnForm();
5595
    }
5596
5597
    /**
5598
     * Return HTML form to add/edit a student publication (work).
5599
     *
5600
     * @param string              $action
5601
     * @param CStudentPublication $resource
5602
     *
5603
     * @throws Exception
5604
     *
5605
     * @return string HTML form
5606
     */
5607
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
5608
    {
5609
        $form = new FormValidator('frm_student_publication', 'post', '#');
5610
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5611
5612
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5613
5614
        $return = '<div class="sectioncomment">';
5615
        $return .= $form->returnForm();
5616
        $return .= '</div>';
5617
5618
        return $return;
5619
    }
5620
5621
    public function displayNewSectionForm()
5622
    {
5623
        $action = 'add_item';
5624
        $item_type = 'dir';
5625
5626
        $lpItem = (new CLpItem())
5627
            ->setTitle('')
5628
            ->setItemType('dir')
5629
        ;
5630
5631
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5632
5633
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5634
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
5635
5636
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5637
        $form->addElement('hidden', 'type', 'dir');
5638
5639
        return $form->returnForm();
5640
    }
5641
5642
    /**
5643
     * Returns the form to update or create a document.
5644
     *
5645
     * @param string  $action (add/edit)
5646
     * @param CLpItem $lpItem
5647
     *
5648
     *
5649
     * @throws Exception
5650
     *
5651
     * @return string HTML form
5652
     */
5653
    public function displayDocumentForm($action = 'add', $lpItem = null)
5654
    {
5655
        $courseInfo = api_get_course_info();
5656
5657
        $form = new FormValidator(
5658
            'form',
5659
            'POST',
5660
            $this->getCurrentBuildingModeURL(),
5661
            '',
5662
            ['enctype' => 'multipart/form-data']
5663
        );
5664
5665
        $data = $this->generate_lp_folder($courseInfo);
5666
5667
        if (null !== $lpItem) {
5668
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5669
        }
5670
5671
        switch ($action) {
5672
            case 'add':
5673
                $folders = DocumentManager::get_all_document_folders(
5674
                    $courseInfo,
5675
                    0,
5676
                    true
5677
                );
5678
                DocumentManager::build_directory_selector(
5679
                    $folders,
5680
                    '',
5681
                    [],
5682
                    true,
5683
                    $form,
5684
                    'directory_parent_id'
5685
                );
5686
5687
                if ($data) {
5688
                    $defaults['directory_parent_id'] = $data->getIid();
5689
                }
5690
5691
                break;
5692
        }
5693
5694
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5695
5696
        return $form->returnForm();
5697
    }
5698
5699
    /**
5700
     * @param array  $courseInfo
5701
     * @param string $content
5702
     * @param string $title
5703
     * @param int    $parentId
5704
     *
5705
     * @return int
5706
     */
5707
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
5708
    {
5709
        $creatorId = api_get_user_id();
5710
        $sessionId = api_get_session_id();
5711
5712
        // Generates folder
5713
        $result = $this->generate_lp_folder($courseInfo);
5714
        $dir = $result['dir'];
5715
5716
        if (empty($parentId) || '/' === $parentId) {
5717
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
5718
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
5719
5720
            if ('/' === $parentId) {
5721
                $dir = '/';
5722
            }
5723
5724
            // Please, do not modify this dirname formatting.
5725
            if (strstr($dir, '..')) {
5726
                $dir = '/';
5727
            }
5728
5729
            if (!empty($dir[0]) && '.' == $dir[0]) {
5730
                $dir = substr($dir, 1);
5731
            }
5732
            if (!empty($dir[0]) && '/' != $dir[0]) {
5733
                $dir = '/'.$dir;
5734
            }
5735
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
5736
                $dir .= '/';
5737
            }
5738
        } else {
5739
            $parentInfo = DocumentManager::get_document_data_by_id(
5740
                $parentId,
5741
                $courseInfo['code']
5742
            );
5743
            if (!empty($parentInfo)) {
5744
                $dir = $parentInfo['path'].'/';
5745
            }
5746
        }
5747
5748
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5749
5750
        if (!is_dir($filepath)) {
5751
            $dir = '/';
5752
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5753
        }
5754
5755
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5756
5757
        if (!empty($title)) {
5758
            $title = api_replace_dangerous_char(stripslashes($title));
5759
        } else {
5760
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5761
        }
5762
5763
        $title = disable_dangerous_file($title);
5764
        $filename = $title;
5765
        $content = !empty($content) ? $content : $_POST['content_lp'];
5766
        $tmpFileName = $filename;
5767
5768
        $i = 0;
5769
        while (file_exists($filepath.$tmpFileName.'.html')) {
5770
            $tmpFileName = $filename.'_'.++$i;
5771
        }
5772
5773
        $filename = $tmpFileName.'.html';
5774
        $content = stripslashes($content);
5775
5776
        if (file_exists($filepath.$filename)) {
5777
            return 0;
5778
        }
5779
5780
        $putContent = file_put_contents($filepath.$filename, $content);
5781
5782
        if (false === $putContent) {
5783
            return 0;
5784
        }
5785
5786
        $fileSize = filesize($filepath.$filename);
5787
        $saveFilePath = $dir.$filename;
5788
5789
        $document = DocumentManager::addDocument(
5790
            $courseInfo,
5791
            $saveFilePath,
5792
            'file',
5793
            $fileSize,
5794
            $tmpFileName,
5795
            '',
5796
            0, //readonly
5797
            true,
5798
            null,
5799
            $sessionId,
5800
            $creatorId
5801
        );
5802
5803
        $documentId = $document->getIid();
5804
5805
        if (!$document) {
5806
            return 0;
5807
        }
5808
5809
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5810
        $newTitle = $originalTitle;
5811
5812
        if ($newComment || $newTitle) {
5813
            $em = Database::getManager();
5814
5815
            if ($newComment) {
5816
                $document->setComment($newComment);
5817
            }
5818
5819
            if ($newTitle) {
5820
                $document->setTitle($newTitle);
5821
            }
5822
5823
            $em->persist($document);
5824
            $em->flush();
5825
        }
5826
5827
        return $documentId;
5828
    }
5829
5830
    /**
5831
     * Displays the menu for manipulating a step.
5832
     *
5833
     * @return string
5834
     */
5835
    public function displayItemMenu(CLpItem $lpItem)
5836
    {
5837
        $item_id = $lpItem->getIid();
5838
        $audio = $lpItem->getAudio();
5839
        $itemType = $lpItem->getItemType();
5840
        $path = $lpItem->getPath();
5841
5842
        $return = '';
5843
        $audio_player = null;
5844
        // We display an audio player if needed.
5845
        if (!empty($audio)) {
5846
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
5847
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
5848
                .'<audio src="'.$webAudioPath.'" controls>'
5849
                .'</div><br>';*/
5850
        }
5851
5852
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
5853
5854
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
5855
            $return .= Display::url(
5856
                Display::return_icon(
5857
                    'edit.png',
5858
                    get_lang('Edit'),
5859
                    [],
5860
                    ICON_SIZE_SMALL
5861
                ),
5862
                $url.'&action=edit_item&path_item='.$path
5863
            );
5864
5865
            /*$return .= Display::url(
5866
                Display::return_icon(
5867
                    'move.png',
5868
                    get_lang('Move'),
5869
                    [],
5870
                    ICON_SIZE_SMALL
5871
                ),
5872
                $url.'&action=move_item'
5873
            );*/
5874
        }
5875
5876
        // Commented for now as prerequisites cannot be added to chapters.
5877
        if ('dir' !== $itemType) {
5878
            $return .= Display::url(
5879
                Display::return_icon(
5880
                    'accept.png',
5881
                    get_lang('Prerequisites'),
5882
                    [],
5883
                    ICON_SIZE_SMALL
5884
                ),
5885
                $url.'&action=edit_item_prereq'
5886
            );
5887
        }
5888
        $return .= Display::url(
5889
            Display::return_icon(
5890
                'delete.png',
5891
                get_lang('Delete'),
5892
                [],
5893
                ICON_SIZE_SMALL
5894
            ),
5895
            $url.'&action=delete_item'
5896
        );
5897
5898
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
5899
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
5900
            if (empty($documentData)) {
5901
                // Try with iid
5902
                $table = Database::get_course_table(TABLE_DOCUMENT);
5903
                $sql = "SELECT path FROM $table
5904
                        WHERE
5905
                              c_id = ".api_get_course_int_id()." AND
5906
                              iid = ".$path." AND
5907
                              path NOT LIKE '%_DELETED_%'";
5908
                $result = Database::query($sql);
5909
                $documentData = Database::fetch_array($result);
5910
                if ($documentData) {
5911
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
5912
                }
5913
            }
5914
            if (isset($documentData['absolute_path_from_document'])) {
5915
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
5916
            }
5917
        }*/
5918
5919
        if (!empty($audio_player)) {
5920
            $return .= $audio_player;
5921
        }
5922
5923
        return Display::toolbarAction('lp_item', [$return]);
5924
    }
5925
5926
    /**
5927
     * Creates the javascript needed for filling up the checkboxes without page reload.
5928
     *
5929
     * @return string
5930
     */
5931
    public function get_js_dropdown_array()
5932
    {
5933
        $return = 'var child_name = new Array();'."\n";
5934
        $return .= 'var child_value = new Array();'."\n\n";
5935
        $return .= 'child_name[0] = new Array();'."\n";
5936
        $return .= 'child_value[0] = new Array();'."\n\n";
5937
5938
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
5939
        $sql = "SELECT * FROM ".$tbl_lp_item."
5940
                WHERE
5941
                    lp_id = ".$this->lp_id." AND
5942
                    parent_item_id = 0
5943
                ORDER BY display_order ASC";
5944
        Database::query($sql);
5945
        $i = 0;
5946
5947
        $list = $this->getItemsForForm(true);
5948
5949
        foreach ($list as $row_zero) {
5950
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
5951
                if (TOOL_QUIZ == $row_zero['item_type']) {
5952
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
5953
                }
5954
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
5955
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
5956
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
5957
            }
5958
        }
5959
5960
        $return .= "\n";
5961
        $sql = "SELECT * FROM $tbl_lp_item
5962
                WHERE lp_id = ".$this->lp_id;
5963
        $res = Database::query($sql);
5964
        while ($row = Database::fetch_array($res)) {
5965
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
5966
                           WHERE
5967
                                parent_item_id = ".$row['iid']."
5968
                           ORDER BY display_order ASC";
5969
            $res_parent = Database::query($sql_parent);
5970
            $i = 0;
5971
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
5972
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
5973
5974
            while ($row_parent = Database::fetch_array($res_parent)) {
5975
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
5976
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
5977
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
5978
            }
5979
            $return .= "\n";
5980
        }
5981
5982
        $return .= "
5983
            function load_cbo(id) {
5984
                if (!id) {
5985
                    return false;
5986
                }
5987
5988
                var cbo = document.getElementById('previous');
5989
                if (cbo) {
5990
                    for(var i = cbo.length - 1; i > 0; i--) {
5991
                        cbo.options[i] = null;
5992
                    }
5993
                    var k=0;
5994
                    for (var i = 1; i <= child_name[id].length; i++){
5995
                        var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
5996
                        option.style.paddingLeft = '40px';
5997
                        cbo.options[i] = option;
5998
                        k = i;
5999
                    }
6000
                    cbo.options[k].selected = true;
6001
                }
6002
6003
                //$('#previous').selectpicker('refresh');
6004
            }";
6005
6006
        return $return;
6007
    }
6008
6009
    /**
6010
     * Display the form to allow moving an item.
6011
     *
6012
     * @param CLpItem $lpItem
6013
     *
6014
     * @throws Exception
6015
     *
6016
     *
6017
     * @return string HTML form
6018
     */
6019
    public function display_move_item($lpItem)
6020
    {
6021
        $return = '';
6022
        $path = $lpItem->getPath();
6023
6024
        if ($lpItem) {
6025
            $itemType = $lpItem->getItemType();
6026
            switch ($itemType) {
6027
                case 'dir':
6028
                case 'asset':
6029
                    $return .= $this->displayItemMenu($lpItem);
6030
                    $return .= $this->display_item_form(
6031
                        $lpItem,
6032
                        get_lang('Move the current section'),
6033
                        'move',
6034
                        $row
6035
                    );
6036
                    break;
6037
                case TOOL_DOCUMENT:
6038
                    $return .= $this->displayItemMenu($lpItem);
6039
                    $return .= $this->displayDocumentForm('move', $lpItem);
6040
                    break;
6041
                case TOOL_LINK:
6042
                    $link = null;
6043
                    if (!empty($path)) {
6044
                        $repo = Container::getLinkRepository();
6045
                        $link = $repo->find($path);
6046
                    }
6047
                    $return .= $this->displayItemMenu($lpItem);
6048
                    $return .= $this->display_link_form('move', $lpItem, $link);
6049
                    break;
6050
                case TOOL_HOTPOTATOES:
6051
                    $return .= $this->displayItemMenu($lpItem);
6052
                    $return .= $this->display_link_form('move', $lpItem, $row);
6053
                    break;
6054
                case TOOL_QUIZ:
6055
                    $return .= $this->displayItemMenu($lpItem);
6056
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
6057
                    break;
6058
                case TOOL_STUDENTPUBLICATION:
6059
                    $return .= $this->displayItemMenu($lpItem);
6060
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
6061
                    break;
6062
                case TOOL_FORUM:
6063
                    $return .= $this->displayItemMenu($lpItem);
6064
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6065
                    break;
6066
                case TOOL_THREAD:
6067
                    $return .= $this->displayItemMenu($lpItem);
6068
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6069
                    break;
6070
            }
6071
        }
6072
6073
        return $return;
6074
    }
6075
6076
    /**
6077
     * Return HTML form to allow prerequisites selection.
6078
     *
6079
     * @todo use FormValidator
6080
     *
6081
     * @return string HTML form
6082
     */
6083
    public function display_item_prerequisites_form(CLpItem $lpItem)
6084
    {
6085
        $course_id = api_get_course_int_id();
6086
        $preRequisiteId = $lpItem->getPrerequisite();
6087
        $itemId = $lpItem->getIid();
6088
6089
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
6090
6091
        $return .= '<form method="POST">';
6092
        $return .= '<div class="table-responsive">';
6093
        $return .= '<table class="table table-hover">';
6094
        $return .= '<thead>';
6095
        $return .= '<tr>';
6096
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
6097
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
6098
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
6099
        $return .= '</tr>';
6100
        $return .= '</thead>';
6101
6102
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
6103
        $return .= '<tbody>';
6104
        $return .= '<tr>';
6105
        $return .= '<td colspan="3">';
6106
        $return .= '<div class="radio learnpath"><label for="idnone">';
6107
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
6108
        $return .= get_lang('none').'</label>';
6109
        $return .= '</div>';
6110
        $return .= '</tr>';
6111
6112
        // @todo use entitites
6113
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6114
        $sql = "SELECT * FROM $tbl_lp_item
6115
                WHERE lp_id = ".$this->lp_id;
6116
        $result = Database::query($sql);
6117
6118
        $selectedMinScore = [];
6119
        $selectedMaxScore = [];
6120
        $masteryScore = [];
6121
        while ($row = Database::fetch_array($result)) {
6122
            if ($row['iid'] == $itemId) {
6123
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
6124
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
6125
            }
6126
            $masteryScore[$row['iid']] = $row['mastery_score'];
6127
        }
6128
6129
        $displayOrder = $lpItem->getDisplayOrder();
6130
        $lpItemRepo = Container::getLpItemRepository();
6131
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
6132
        $em = Database::getManager();
6133
6134
        $currentItemId = $itemId;
6135
        $options = [
6136
            'decorate' => true,
6137
            'rootOpen' => function() {
6138
                return '';
6139
            },
6140
            'rootClose' => function() {
6141
                return '';
6142
            },
6143
            'childOpen' => function() {
6144
                return '';
6145
            },
6146
            'childClose' => '',
6147
            'nodeDecorator' => function ($item) use (
6148
                $currentItemId,
6149
                $preRequisiteId,
6150
                $course_id,
6151
                $selectedMaxScore,
6152
                $selectedMinScore,
6153
                $displayOrder,
6154
                $lpItemRepo,
6155
                $em
6156
            ) {
6157
                $mainUrl = '';
6158
                $fullTitle = $item['title'];
6159
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
6160
                $itemId = $item['iid'];
6161
                $type = $item['itemType'];
6162
                $lpId = $this->get_id();
6163
                $iconName = str_replace(' ', '', $type);
6164
                $icon = Display::return_icon(
6165
                    'lp_'.$iconName.'.png',
6166
                    '',
6167
                    [],
6168
                    ICON_SIZE_TINY
6169
                );
6170
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
6171
6172
                if ($itemId == $currentItemId) {
6173
                    return '';
6174
                }
6175
6176
                if ($displayOrder < $item['displayOrder']) {
6177
                    return '';
6178
                }
6179
6180
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
6181
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
6182
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
6183
6184
                $return = '<tr>';
6185
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
6186
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
6187
                $return .= '<label for="id'.$itemId.'">';
6188
6189
                $checked = '';
6190
                if (null !== $preRequisiteId) {
6191
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
6192
                }
6193
6194
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
6195
6196
                $return .= '<input
6197
                    '.$checked.' '.$disabled.'
6198
                    id="id'.$itemId.'"
6199
                    name="prerequisites"
6200
                    type="radio"
6201
                    value="'.$itemId.'" />';
6202
6203
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
6204
                $return .= '</div>';
6205
                $return .= '</td>';
6206
6207
                if (TOOL_QUIZ == $type) {
6208
                    // lets update max_score Tests information depending of the Tests Advanced properties
6209
                    $exercise = new Exercise($course_id);
6210
                    /** @var CLpItem $itemEntity */
6211
                    $itemEntity = $lpItemRepo->find($itemId);
6212
                    $exercise->read($item['path']);
6213
                    $itemEntity->setMaxScore($exercise->get_max_score());
6214
                    $em->persist($itemEntity);
6215
                    $em->flush($itemEntity);
6216
6217
                    $item['maxScore'] = $exercise->get_max_score();
6218
6219
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
6220
                        // Backwards compatibility with 1.9.x use mastery_score as min value
6221
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
6222
                    }
6223
                    $return .= '<td>';
6224
                    $return .= '<input
6225
                        class="form-control"
6226
                        size="4" maxlength="3"
6227
                        name="min_'.$itemId.'"
6228
                        type="number"
6229
                        min="0"
6230
                        step="any"
6231
                        max="'.$item['maxScore'].'"
6232
                        value="'.$selectedMinScoreValue.'"
6233
                    />';
6234
                    $return .= '</td>';
6235
                    $return .= '<td>';
6236
                    $return .= '<input
6237
                        class="form-control"
6238
                        size="4"
6239
                        maxlength="3"
6240
                        name="max_'.$itemId.'"
6241
                        type="number"
6242
                        min="0"
6243
                        step="any"
6244
                        max="'.$item['maxScore'].'"
6245
                        value="'.$selectedMaxScoreValue.'"
6246
                    />';
6247
                        $return .= '</td>';
6248
                    }
6249
6250
                if (TOOL_HOTPOTATOES == $type) {
6251
                    $return .= '<td>';
6252
                    $return .= '<input
6253
                        size="4"
6254
                        maxlength="3"
6255
                        name="min_'.$itemId.'"
6256
                        type="number"
6257
                        min="0"
6258
                        step="any"
6259
                        max="'.$item['maxScore'].'"
6260
                        value="'.$selectedMinScoreValue.'"
6261
                    />';
6262
                        $return .= '</td>';
6263
                        $return .= '<td>';
6264
                        $return .= '<input
6265
                        size="4"
6266
                        maxlength="3"
6267
                        name="max_'.$itemId.'"
6268
                        type="number"
6269
                        min="0"
6270
                        step="any"
6271
                        max="'.$item['maxScore'].'"
6272
                        value="'.$selectedMaxScoreValue.'"
6273
                    />';
6274
                    $return .= '</td>';
6275
                }
6276
                $return .= '</tr>';
6277
6278
                return $return;
6279
            },
6280
        ];
6281
6282
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6283
        $return .= $tree;
6284
        $return .= '</tbody>';
6285
        $return .= '</table>';
6286
        $return .= '</div>';
6287
        $return .= '<div class="form-group">';
6288
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
6289
            get_lang('Save prerequisites settings').'</button>';
6290
        $return .= '</form>';
6291
6292
        return $return;
6293
    }
6294
6295
    /**
6296
     * Return HTML list to allow prerequisites selection for lp.
6297
     */
6298
    public function display_lp_prerequisites_list(FormValidator $form)
6299
    {
6300
        $lp_id = $this->lp_id;
6301
        $lp = api_get_lp_entity($lp_id);
6302
        $prerequisiteId = $lp->getPrerequisite();
6303
6304
        $repo = Container::getLpRepository();
6305
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6306
        /** @var CLp[] $lps */
6307
        $lps = $qb->getQuery()->getResult();
6308
6309
        //$session_id = api_get_session_id();
6310
        /*$session_condition = api_get_session_condition($session_id, true, true);
6311
        $sql = "SELECT * FROM $tbl_lp
6312
                WHERE c_id = $course_id $session_condition
6313
                ORDER BY display_order ";
6314
        $rs = Database::query($sql);*/
6315
6316
        $items = [get_lang('none')];
6317
        foreach ($lps as $lp) {
6318
            $myLpId = $lp->getIid();
6319
            if ($myLpId == $lp_id) {
6320
                continue;
6321
            }
6322
            $items[$myLpId] = $lp->getName();
6323
            /*$return .= '<option
6324
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6325
                $lp->getName().
6326
                '</option>';*/
6327
        }
6328
6329
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6330
        $select->setSelected($prerequisiteId);
6331
    }
6332
6333
    /**
6334
     * Creates a list with all the documents in it.
6335
     *
6336
     * @param bool $showInvisibleFiles
6337
     *
6338
     * @throws Exception
6339
     *
6340
     *
6341
     * @return string
6342
     */
6343
    public function get_documents($showInvisibleFiles = false)
6344
    {
6345
        $sessionId = api_get_session_id();
6346
        $documentTree = DocumentManager::get_document_preview(
6347
            api_get_course_entity(),
6348
            $this->lp_id,
6349
            null,
6350
            $sessionId,
6351
            true,
6352
            null,
6353
            null,
6354
            $showInvisibleFiles,
6355
            true
6356
        );
6357
6358
        $form = new FormValidator(
6359
            'form_upload',
6360
            'POST',
6361
            $this->getCurrentBuildingModeURL(),
6362
            '',
6363
            ['enctype' => 'multipart/form-data']
6364
        );
6365
6366
        $folders = DocumentManager::get_all_document_folders(
6367
            api_get_course_info(),
6368
            0,
6369
            true
6370
        );
6371
6372
        $folder = $this->generate_lp_folder(api_get_course_info());
6373
6374
        DocumentManager::build_directory_selector(
6375
            $folders,
6376
            $folder->getIid(),
6377
            [],
6378
            true,
6379
            $form,
6380
            'directory_parent_id'
6381
        );
6382
6383
        $group = [
6384
            $form->createElement(
6385
                'radio',
6386
                'if_exists',
6387
                get_lang('If file exists:'),
6388
                get_lang('Do nothing'),
6389
                'nothing'
6390
            ),
6391
            $form->createElement(
6392
                'radio',
6393
                'if_exists',
6394
                null,
6395
                get_lang('Overwrite the existing file'),
6396
                'overwrite'
6397
            ),
6398
            $form->createElement(
6399
                'radio',
6400
                'if_exists',
6401
                null,
6402
                get_lang('Rename the uploaded file if it exists'),
6403
                'rename'
6404
            ),
6405
        ];
6406
        $form->addGroup($group, null, get_lang('If file exists:'));
6407
6408
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
6409
        $defaultFileExistsOption = 'rename';
6410
        if (!empty($fileExistsOption)) {
6411
            $defaultFileExistsOption = $fileExistsOption;
6412
        }
6413
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
6414
6415
        // Check box options
6416
        $form->addCheckBox(
6417
            'unzip',
6418
            get_lang('Options'),
6419
            get_lang('Uncompress zip')
6420
        );
6421
6422
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
6423
        $form->addMultipleUpload($url);
6424
6425
        $lpItem = (new CLpItem())
6426
            ->setTitle('')
6427
            ->setItemType(TOOL_DOCUMENT)
6428
        ;
6429
        $new = $this->displayDocumentForm('add', $lpItem);
6430
6431
        /*$lpItem = new CLpItem();
6432
        $lpItem->setItemType(TOOL_READOUT_TEXT);
6433
        $frmReadOutText = $this->displayDocumentForm('add');*/
6434
6435
        $headers = [
6436
            get_lang('Files'),
6437
            get_lang('Create a new document'),
6438
            //get_lang('Create read-out text'),
6439
            get_lang('Upload'),
6440
        ];
6441
6442
        return Display::tabs(
6443
            $headers,
6444
            [$documentTree, $new, $form->returnForm()],
6445
            'subtab'
6446
        );
6447
    }
6448
6449
    /**
6450
     * Creates a list with all the exercises (quiz) in it.
6451
     *
6452
     * @return string
6453
     */
6454
    public function get_exercises()
6455
    {
6456
        $course_id = api_get_course_int_id();
6457
        $session_id = api_get_session_id();
6458
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
6459
6460
        //$activeCondition = ' active <> -1 ';
6461
        $active = 2;
6462
        if ($setting) {
6463
            $active = 1;
6464
            //$activeCondition = ' active = 1 ';
6465
        }
6466
6467
        $categoryCondition = '';
6468
6469
        $keyword = $_REQUEST['keyword'] ?? null;
6470
        $categoryId = $_REQUEST['category_id'] ?? null;
6471
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
6472
            $categoryCondition = " AND exercise_category_id = $categoryId ";
6473
        }
6474
6475
        $keywordCondition = '';
6476
6477
        if (!empty($keyword)) {
6478
            $keyword = Database::escape_string($keyword);
6479
            $keywordCondition = " AND title LIKE '%$keyword%' ";
6480
        }
6481
        */
6482
        $course = api_get_course_entity($course_id);
6483
        $session = api_get_session_entity($session_id);
6484
6485
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
6486
        /** @var CQuiz[] $exercises */
6487
        $exercises = $qb->getQuery()->getResult();
6488
6489
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
6490
                     WHERE
6491
                            c_id = $course_id AND
6492
                            $activeCondition
6493
                            $condition_session
6494
                            $categoryCondition
6495
                            $keywordCondition
6496
                     ORDER BY title ASC";
6497
        $res_quiz = Database::query($sql_quiz);*/
6498
6499
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
6500
6501
        // Create a search-box
6502
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
6503
        $form->addHidden('action', 'add_item');
6504
        $form->addHidden('type', 'step');
6505
        $form->addHidden('lp_id', $this->lp_id);
6506
        $form->addHidden('lp_build_selected', '2');
6507
6508
        $form->addCourseHiddenParams();
6509
        $form->addText(
6510
            'keyword',
6511
            get_lang('Search'),
6512
            false,
6513
            [
6514
                'aria-label' => get_lang('Search'),
6515
            ]
6516
        );
6517
6518
        if (api_get_configuration_value('allow_exercise_categories')) {
6519
            $manager = new ExerciseCategoryManager();
6520
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
6521
            if (!empty($options)) {
6522
                $form->addSelect(
6523
                    'category_id',
6524
                    get_lang('Category'),
6525
                    $options,
6526
                    ['placeholder' => get_lang('Please select an option')]
6527
                );
6528
            }
6529
        }
6530
6531
        $form->addButtonSearch(get_lang('Search'));
6532
        $return = $form->returnForm();*/
6533
6534
        $return = '<ul class = "list-group lp_resource">';
6535
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6536
        $return .= Display::return_icon('new_exercice.png');
6537
        $return .= '<a
6538
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
6539
            get_lang('New test').'</a>';
6540
        $return .= '</li>';
6541
6542
        $previewIcon = Display::return_icon(
6543
            'preview_view.png',
6544
            get_lang('Preview')
6545
        );
6546
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
6547
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
6548
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
6549
        foreach ($exercises as $exercise) {
6550
            $exerciseId = $exercise->getIid();
6551
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
6552
            $visibility = $exercise->isVisible($course, $session);
6553
6554
            $link = Display::url(
6555
                $previewIcon,
6556
                $exerciseUrl.'&exerciseId='.$exerciseId,
6557
                ['target' => '_blank']
6558
            );
6559
            $return .= '<li
6560
                class="list-group-item lp_resource_element"
6561
                id="'.$exerciseId.'"
6562
                data-id="'.$exerciseId.'"
6563
                title="'.$title.'">';
6564
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
6565
            $return .= $quizIcon;
6566
            $sessionStar = '';
6567
            /*$sessionStar = api_get_session_image(
6568
                $row_quiz['session_id'],
6569
                $userInfo['status']
6570
            );*/
6571
            $return .= Display::url(
6572
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
6573
                api_get_self().'?'.
6574
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
6575
                [
6576
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
6577
                    'data_type' => 'quiz',
6578
                    'data-id' => $exerciseId,
6579
                ]
6580
            );
6581
            $return .= '</li>';
6582
        }
6583
6584
        $return .= '</ul>';
6585
6586
        return $return;
6587
    }
6588
6589
    /**
6590
     * Creates a list with all the links in it.
6591
     *
6592
     * @return string
6593
     */
6594
    public function get_links()
6595
    {
6596
        $sessionId = api_get_session_id();
6597
        $repo = Container::getLinkRepository();
6598
6599
        $course = api_get_course_entity();
6600
        $session = api_get_session_entity($sessionId);
6601
        $qb = $repo->getResourcesByCourse($course, $session);
6602
        /** @var CLink[] $links */
6603
        $links = $qb->getQuery()->getResult();
6604
6605
        $selfUrl = api_get_self();
6606
        $courseIdReq = api_get_cidreq();
6607
        $userInfo = api_get_user_info();
6608
6609
        $moveEverywhereIcon = Display::return_icon(
6610
            'move_everywhere.png',
6611
            get_lang('Move'),
6612
            [],
6613
            ICON_SIZE_TINY
6614
        );
6615
6616
        $categorizedLinks = [];
6617
        $categories = [];
6618
6619
        foreach ($links as $link) {
6620
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
6621
            if (empty($categoryId)) {
6622
                $categories[0] = get_lang('Uncategorized');
6623
            } else {
6624
                $category = $link->getCategory();
6625
                $categories[$categoryId] = $category->getCategoryTitle();
6626
            }
6627
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
6628
        }
6629
6630
        $linksHtmlCode =
6631
            '<script>
6632
            function toggle_tool(tool, id) {
6633
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
6634
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
6635
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6636
                } else {
6637
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
6638
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6639
                }
6640
            }
6641
        </script>
6642
6643
        <ul class="list-group lp_resource">
6644
            <li class="list-group-item lp_resource_element disable_drag ">
6645
                '.Display::return_icon('linksnew.gif').'
6646
                <a
6647
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
6648
                title="'.get_lang('Add a link').'">'.
6649
                get_lang('Add a link').'
6650
                </a>
6651
            </li>';
6652
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
6653
        foreach ($categorizedLinks as $categoryId => $links) {
6654
            $linkNodes = null;
6655
            /** @var CLink $link */
6656
            foreach ($links as $key => $link) {
6657
                $title = $link->getTitle();
6658
                $id = $link->getIid();
6659
                $linkUrl = Display::url(
6660
                    Display::return_icon('preview_view.png', get_lang('Preview')),
6661
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
6662
                    ['target' => '_blank']
6663
                );
6664
6665
                if ($link->isVisible($course, $session)) {
6666
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
6667
                    $sessionStar = '';
6668
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
6669
                    $link = Display::url(
6670
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
6671
                        $url,
6672
                        [
6673
                            'class' => 'moved link_with_id',
6674
                            'data-id' => $key,
6675
                            'data_type' => TOOL_LINK,
6676
                            'title' => $title,
6677
                        ]
6678
                    );
6679
                    $linkNodes .=
6680
                        "<li
6681
                            class='list-group-item lp_resource_element'
6682
                            id= $id
6683
                            data-id= $id
6684
                            >
6685
                         <a class='moved' href='#'>
6686
                            $moveEverywhereIcon
6687
                        </a>
6688
                        $linkIcon $link
6689
                        </li>";
6690
                }
6691
            }
6692
            $linksHtmlCode .=
6693
                '<li class="list-group-item disable_drag">
6694
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
6695
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
6696
                        align="absbottom" />
6697
                    </a>
6698
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
6699
                </li>
6700
            '.
6701
                $linkNodes.
6702
            '';
6703
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
6704
        }
6705
        $linksHtmlCode .= '</ul>';
6706
6707
        return $linksHtmlCode;
6708
    }
6709
6710
    /**
6711
     * Creates a list with all the student publications in it.
6712
     *
6713
     * @return string
6714
     */
6715
    public function get_student_publications()
6716
    {
6717
        $return = '<ul class="list-group lp_resource">';
6718
        $return .= '<li class="list-group-item lp_resource_element">';
6719
        $works = getWorkListTeacher(0, 100, null, null, null);
6720
        if (!empty($works)) {
6721
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
6722
            foreach ($works as $work) {
6723
                $workId = $work['iid'];
6724
                $link = Display::url(
6725
                    Display::return_icon('preview_view.png', get_lang('Preview')),
6726
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
6727
                    ['target' => '_blank']
6728
                );
6729
6730
                $return .= '<li
6731
                    class="list-group-item lp_resource_element"
6732
                    id="'.$workId.'"
6733
                    data-id="'.$workId.'"
6734
                    >';
6735
                $return .= '<a class="moved" href="#">';
6736
                $return .= Display::return_icon(
6737
                    'move_everywhere.png',
6738
                    get_lang('Move'),
6739
                    [],
6740
                    ICON_SIZE_TINY
6741
                );
6742
                $return .= '</a> ';
6743
6744
                $return .= $icon;
6745
                $return .= Display::url(
6746
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
6747
                    api_get_self().'?'.
6748
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
6749
                    [
6750
                        'class' => 'moved link_with_id',
6751
                        'data-id' => $work['iid'],
6752
                        'data_type' => TOOL_STUDENTPUBLICATION,
6753
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
6754
                    ]
6755
                );
6756
                $return .= '</li>';
6757
            }
6758
        }
6759
6760
        $return .= '</ul>';
6761
6762
        return $return;
6763
    }
6764
6765
    /**
6766
     * Creates a list with all the forums in it.
6767
     *
6768
     * @return string
6769
     */
6770
    public function get_forums()
6771
    {
6772
        $forumCategories = get_forum_categories();
6773
        $forumsInNoCategory = get_forums_in_category(0);
6774
        if (!empty($forumsInNoCategory)) {
6775
            $forumCategories = array_merge(
6776
                $forumCategories,
6777
                [
6778
                    [
6779
                        'cat_id' => 0,
6780
                        'session_id' => 0,
6781
                        'visibility' => 1,
6782
                        'cat_comment' => null,
6783
                    ],
6784
                ]
6785
            );
6786
        }
6787
6788
        $a_forums = [];
6789
        $courseEntity = api_get_course_entity(api_get_course_int_id());
6790
        $sessionEntity = api_get_session_entity(api_get_session_id());
6791
6792
        foreach ($forumCategories as $forumCategory) {
6793
            // The forums in this category.
6794
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
6795
            if (!empty($forumsInCategory)) {
6796
                foreach ($forumsInCategory as $forum) {
6797
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
6798
                        $a_forums[] = $forum;
6799
                    }
6800
                }
6801
            }
6802
        }
6803
6804
        $return = '<ul class="list-group lp_resource">';
6805
6806
        // First add link
6807
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6808
        $return .= Display::return_icon('new_forum.png');
6809
        $return .= Display::url(
6810
            get_lang('Create a new forum'),
6811
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
6812
                'action' => 'add',
6813
                'content' => 'forum',
6814
                'lp_id' => $this->lp_id,
6815
            ]),
6816
            ['title' => get_lang('Create a new forum')]
6817
        );
6818
        $return .= '</li>';
6819
6820
        $return .= '<script>
6821
            function toggle_forum(forum_id) {
6822
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
6823
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
6824
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6825
                } else {
6826
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
6827
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6828
                }
6829
            }
6830
        </script>';
6831
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
6832
        foreach ($a_forums as $forum) {
6833
            $forumId = $forum->getIid();
6834
            $title = Security::remove_XSS($forum->getForumTitle());
6835
            $link = Display::url(
6836
                Display::return_icon('preview_view.png', get_lang('Preview')),
6837
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
6838
                ['target' => '_blank']
6839
            );
6840
6841
            $return .= '<li
6842
                    class="list-group-item lp_resource_element"
6843
                    id="'.$forumId.'"
6844
                    data-id="'.$forumId.'"
6845
                    >';
6846
            $return .= '<a class="moved" href="#">';
6847
            $return .= $moveIcon;
6848
            $return .= ' </a>';
6849
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
6850
6851
            $moveLink = Display::url(
6852
                $title.' '.$link,
6853
                api_get_self().'?'.
6854
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
6855
                [
6856
                    'class' => 'moved link_with_id',
6857
                    'data-id' => $forumId,
6858
                    'data_type' => TOOL_FORUM,
6859
                    'title' => $title,
6860
                    'style' => 'vertical-align:middle',
6861
                ]
6862
            );
6863
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
6864
                            <img
6865
                                src="'.Display::returnIconPath('add.png').'"
6866
                                id="forum_'.$forumId.'_opener" align="absbottom"
6867
                             />
6868
                        </a>
6869
                        '.$moveLink;
6870
            $return .= '</li>';
6871
6872
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
6873
            $threads = get_threads($forumId);
6874
            if (is_array($threads)) {
6875
                foreach ($threads as $thread) {
6876
                    $threadId = $thread->getIid();
6877
                    $link = Display::url(
6878
                        Display::return_icon('preview_view.png', get_lang('Preview')),
6879
                        api_get_path(WEB_CODE_PATH).
6880
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
6881
                        ['target' => '_blank']
6882
                    );
6883
6884
                    $return .= '<li
6885
                        class="list-group-item lp_resource_element"
6886
                      id="'.$threadId.'"
6887
                        data-id="'.$threadId.'"
6888
                    >';
6889
                    $return .= '&nbsp;<a class="moved" href="#">';
6890
                    $return .= $moveIcon;
6891
                    $return .= ' </a>';
6892
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
6893
                    $return .= '<a
6894
                        class="moved link_with_id"
6895
                        data-id="'.$threadId.'"
6896
                        data_type="'.TOOL_THREAD.'"
6897
                        title="'.$thread->getThreadTitle().'"
6898
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
6899
                        >'.
6900
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
6901
                    $return .= '</li>';
6902
                }
6903
            }
6904
            $return .= '</div>';
6905
        }
6906
        $return .= '</ul>';
6907
6908
        return $return;
6909
    }
6910
6911
    /**
6912
     * Temp function to be moved in main_api or the best place around for this.
6913
     * Creates a file path if it doesn't exist.
6914
     *
6915
     * @param string $path
6916
     */
6917
    public function create_path($path)
6918
    {
6919
        $path_bits = explode('/', dirname($path));
6920
6921
        // IS_WINDOWS_OS has been defined in main_api.lib.php
6922
        $path_built = IS_WINDOWS_OS ? '' : '/';
6923
        foreach ($path_bits as $bit) {
6924
            if (!empty($bit)) {
6925
                $new_path = $path_built.$bit;
6926
                if (is_dir($new_path)) {
6927
                    $path_built = $new_path.'/';
6928
                } else {
6929
                    mkdir($new_path, api_get_permissions_for_new_directories());
6930
                    $path_built = $new_path.'/';
6931
                }
6932
            }
6933
        }
6934
    }
6935
6936
    /**
6937
     * @param int    $lp_id
6938
     * @param string $status
6939
     */
6940
    public function set_autolaunch($lp_id, $status)
6941
    {
6942
        $course_id = api_get_course_int_id();
6943
        $lp_id = (int) $lp_id;
6944
        $status = (int) $status;
6945
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6946
6947
        // Setting everything to autolaunch = 0
6948
        $attributes['autolaunch'] = 0;
6949
        $where = [
6950
            'session_id = ? AND c_id = ? ' => [
6951
                api_get_session_id(),
6952
                $course_id,
6953
            ],
6954
        ];
6955
        Database::update($lp_table, $attributes, $where);
6956
        if (1 == $status) {
6957
            //Setting my lp_id to autolaunch = 1
6958
            $attributes['autolaunch'] = 1;
6959
            $where = [
6960
                'iid = ? AND session_id = ? AND c_id = ?' => [
6961
                    $lp_id,
6962
                    api_get_session_id(),
6963
                    $course_id,
6964
                ],
6965
            ];
6966
            Database::update($lp_table, $attributes, $where);
6967
        }
6968
    }
6969
6970
    /**
6971
     * Gets previous_item_id for the next element of the lp_item table.
6972
     *
6973
     * @author Isaac flores paz
6974
     *
6975
     * @return int Previous item ID
6976
     */
6977
    public function select_previous_item_id()
6978
    {
6979
        $course_id = api_get_course_int_id();
6980
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6981
6982
        // Get the max order of the items
6983
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
6984
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6985
        $rs_max_order = Database::query($sql);
6986
        $row_max_order = Database::fetch_object($rs_max_order);
6987
        $max_order = $row_max_order->display_order;
6988
        // Get the previous item ID
6989
        $sql = "SELECT iid as previous FROM $table_lp_item
6990
                WHERE
6991
                    c_id = $course_id AND
6992
                    lp_id = ".$this->lp_id." AND
6993
                    display_order = '$max_order' ";
6994
        $rs_max = Database::query($sql);
6995
        $row_max = Database::fetch_object($rs_max);
6996
6997
        // Return the previous item ID
6998
        return $row_max->previous;
6999
    }
7000
7001
    /**
7002
     * Copies an LP.
7003
     */
7004
    public function copy()
7005
    {
7006
        // Course builder
7007
        $cb = new CourseBuilder();
7008
7009
        //Setting tools that will be copied
7010
        $cb->set_tools_to_build(['learnpaths']);
7011
7012
        //Setting elements that will be copied
7013
        $cb->set_tools_specific_id_list(
7014
            ['learnpaths' => [$this->lp_id]]
7015
        );
7016
7017
        $course = $cb->build();
7018
7019
        //Course restorer
7020
        $course_restorer = new CourseRestorer($course);
7021
        $course_restorer->set_add_text_in_items(true);
7022
        $course_restorer->set_tool_copy_settings(
7023
            ['learnpaths' => ['reset_dates' => true]]
7024
        );
7025
        $course_restorer->restore(
7026
            api_get_course_id(),
7027
            api_get_session_id(),
7028
            false,
7029
            false
7030
        );
7031
    }
7032
7033
    /**
7034
     * Verify document size.
7035
     *
7036
     * @param string $s
7037
     *
7038
     * @return bool
7039
     */
7040
    public static function verify_document_size($s)
7041
    {
7042
        $post_max = ini_get('post_max_size');
7043
        if ('M' == substr($post_max, -1, 1)) {
7044
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7045
        } elseif ('G' == substr($post_max, -1, 1)) {
7046
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7047
        }
7048
        $upl_max = ini_get('upload_max_filesize');
7049
        if ('M' == substr($upl_max, -1, 1)) {
7050
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7051
        } elseif ('G' == substr($upl_max, -1, 1)) {
7052
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7053
        }
7054
7055
        $repo = Container::getDocumentRepository();
7056
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7057
7058
        $course_max_space = DocumentManager::get_course_quota();
7059
        $total_size = filesize($s) + $documents_total_space;
7060
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7061
            return true;
7062
        }
7063
7064
        return false;
7065
    }
7066
7067
    /**
7068
     * Clear LP prerequisites.
7069
     */
7070
    public function clearPrerequisites()
7071
    {
7072
        $course_id = $this->get_course_int_id();
7073
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7074
        $lp_id = $this->get_id();
7075
        // Cleaning prerequisites
7076
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7077
                WHERE lp_id = $lp_id";
7078
        Database::query($sql);
7079
7080
        // Cleaning mastery score for exercises
7081
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7082
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7083
        Database::query($sql);
7084
    }
7085
7086
    public function set_previous_step_as_prerequisite_for_all_items()
7087
    {
7088
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7089
        $course_id = $this->get_course_int_id();
7090
        $lp_id = $this->get_id();
7091
7092
        if (!empty($this->items)) {
7093
            $previous_item_id = null;
7094
            $previous_item_max = 0;
7095
            $previous_item_type = null;
7096
            $last_item_not_dir = null;
7097
            $last_item_not_dir_type = null;
7098
            $last_item_not_dir_max = null;
7099
7100
            foreach ($this->ordered_items as $itemId) {
7101
                $item = $this->getItem($itemId);
7102
                // if there was a previous item... (otherwise jump to set it)
7103
                if (!empty($previous_item_id)) {
7104
                    $current_item_id = $item->get_id(); //save current id
7105
                    if ('dir' != $item->get_type()) {
7106
                        // Current item is not a folder, so it qualifies to get a prerequisites
7107
                        if ('quiz' == $last_item_not_dir_type) {
7108
                            // if previous is quiz, mark its max score as default score to be achieved
7109
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7110
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7111
                            Database::query($sql);
7112
                        }
7113
                        // now simply update the prerequisite to set it to the last non-chapter item
7114
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7115
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7116
                        Database::query($sql);
7117
                        // record item as 'non-chapter' reference
7118
                        $last_item_not_dir = $item->get_id();
7119
                        $last_item_not_dir_type = $item->get_type();
7120
                        $last_item_not_dir_max = $item->get_max();
7121
                    }
7122
                } else {
7123
                    if ('dir' != $item->get_type()) {
7124
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7125
                        $last_item_not_dir = $item->get_id();
7126
                        $last_item_not_dir_type = $item->get_type();
7127
                        $last_item_not_dir_max = $item->get_max();
7128
                    }
7129
                }
7130
                // Saving the item as "previous item" for the next loop
7131
                $previous_item_id = $item->get_id();
7132
                $previous_item_max = $item->get_max();
7133
                $previous_item_type = $item->get_type();
7134
            }
7135
        }
7136
    }
7137
7138
    /**
7139
     * @param array $params
7140
     *
7141
     * @return int
7142
     */
7143
    public static function createCategory($params)
7144
    {
7145
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7146
7147
        $item = new CLpCategory();
7148
        $item
7149
            ->setName($params['name'])
7150
            ->setParent($courseEntity)
7151
            ->addCourseLink($courseEntity, api_get_session_entity())
7152
        ;
7153
7154
        $repo = Container::getLpCategoryRepository();
7155
        $repo->create($item);
7156
7157
        return $item->getIid();
7158
    }
7159
7160
    /**
7161
     * @param array $params
7162
     */
7163
    public static function updateCategory($params)
7164
    {
7165
        $em = Database::getManager();
7166
        /** @var CLpCategory $item */
7167
        $item = $em->find(CLpCategory::class, $params['id']);
7168
        if ($item) {
7169
            $item->setName($params['name']);
7170
            $em->persist($item);
7171
            $em->flush();
7172
        }
7173
    }
7174
7175
    /**
7176
     * @param int $id
7177
     */
7178
    public static function moveUpCategory($id)
7179
    {
7180
        $id = (int) $id;
7181
        $em = Database::getManager();
7182
        /** @var CLpCategory $item */
7183
        $item = $em->find(CLpCategory::class, $id);
7184
        if ($item) {
7185
            $position = $item->getPosition() - 1;
7186
            $item->setPosition($position);
7187
            $em->persist($item);
7188
            $em->flush();
7189
        }
7190
    }
7191
7192
    /**
7193
     * @param int $id
7194
     */
7195
    public static function moveDownCategory($id)
7196
    {
7197
        $id = (int) $id;
7198
        $em = Database::getManager();
7199
        /** @var CLpCategory $item */
7200
        $item = $em->find(CLpCategory::class, $id);
7201
        if ($item) {
7202
            $position = $item->getPosition() + 1;
7203
            $item->setPosition($position);
7204
            $em->persist($item);
7205
            $em->flush();
7206
        }
7207
    }
7208
7209
    /**
7210
     * @param int $courseId
7211
     *
7212
     * @return int
7213
     */
7214
    public static function getCountCategories($courseId)
7215
    {
7216
        if (empty($courseId)) {
7217
            return 0;
7218
        }
7219
        $repo = Container::getLpCategoryRepository();
7220
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7221
        $qb->addSelect('count(resource)');
7222
7223
        return (int) $qb->getQuery()->getSingleScalarResult();
7224
    }
7225
7226
    /**
7227
     * @param int $courseId
7228
     *
7229
     * @return CLpCategory[]
7230
     */
7231
    public static function getCategories($courseId)
7232
    {
7233
        // Using doctrine extensions
7234
        $repo = Container::getLpCategoryRepository();
7235
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7236
7237
        return $qb->getQuery()->getResult();
7238
    }
7239
7240
    public static function getCategorySessionId($id)
7241
    {
7242
        if (false === api_get_configuration_value('allow_session_lp_category')) {
7243
            return 0;
7244
        }
7245
7246
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
7247
        $id = (int) $id;
7248
7249
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
7250
        $result = Database::query($sql);
7251
        $result = Database::fetch_array($result, 'ASSOC');
7252
7253
        if ($result) {
7254
            return (int) $result['session_id'];
7255
        }
7256
7257
        return 0;
7258
    }
7259
7260
    /**
7261
     * @param int $id
7262
     */
7263
    public static function deleteCategory($id): bool
7264
    {
7265
        $repo = Container::getLpCategoryRepository();
7266
        /** @var CLpCategory $category */
7267
        $category = $repo->find($id);
7268
        if ($category) {
7269
            $em = Database::getManager();
7270
            $lps = $category->getLps();
7271
7272
            foreach ($lps as $lp) {
7273
                $lp->setCategory(null);
7274
                $em->persist($lp);
7275
            }
7276
7277
            // Removing category.
7278
            $em->remove($category);
7279
            $em->flush();
7280
7281
            return true;
7282
        }
7283
7284
        return false;
7285
    }
7286
7287
    /**
7288
     * @param int  $courseId
7289
     * @param bool $addSelectOption
7290
     *
7291
     * @return array
7292
     */
7293
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7294
    {
7295
        $repo = Container::getLpCategoryRepository();
7296
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7297
        $items = $qb->getQuery()->getResult();
7298
7299
        $cats = [];
7300
        if ($addSelectOption) {
7301
            $cats = [get_lang('Select a category')];
7302
        }
7303
7304
        if (!empty($items)) {
7305
            foreach ($items as $cat) {
7306
                $cats[$cat->getIid()] = $cat->getName();
7307
            }
7308
        }
7309
7310
        return $cats;
7311
    }
7312
7313
    /**
7314
     * @param string $courseCode
7315
     * @param int    $lpId
7316
     * @param int    $user_id
7317
     *
7318
     * @return learnpath
7319
     */
7320
    public static function getLpFromSession($courseCode, $lpId, $user_id)
7321
    {
7322
        $debug = 0;
7323
        $learnPath = null;
7324
        $lpObject = Session::read('lpobject');
7325
7326
        $repo = Container::getLpRepository();
7327
        $lp = $repo->find($lpId);
7328
        if (null !== $lpObject) {
7329
            /** @var learnpath $learnPath */
7330
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7331
            $learnPath->entity = $lp;
7332
            if ($debug) {
7333
                error_log('getLpFromSession: unserialize');
7334
                error_log('------getLpFromSession------');
7335
                error_log('------unserialize------');
7336
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7337
                error_log("api_get_sessionid: ".api_get_session_id());
7338
            }
7339
        }
7340
7341
        if (!is_object($learnPath)) {
7342
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
7343
            if ($debug) {
7344
                error_log('------getLpFromSession------');
7345
                error_log('getLpFromSession: create new learnpath');
7346
                error_log("create new LP with $courseCode - $lpId - $user_id");
7347
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7348
                error_log("api_get_sessionid: ".api_get_session_id());
7349
            }
7350
        }
7351
7352
        return $learnPath;
7353
    }
7354
7355
    /**
7356
     * @param int $itemId
7357
     *
7358
     * @return learnpathItem|false
7359
     */
7360
    public function getItem($itemId)
7361
    {
7362
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7363
            return $this->items[$itemId];
7364
        }
7365
7366
        return false;
7367
    }
7368
7369
    /**
7370
     * @return int
7371
     */
7372
    public function getCurrentAttempt()
7373
    {
7374
        $attempt = $this->getItem($this->get_current_item_id());
7375
        if ($attempt) {
7376
            return $attempt->get_attempt_id();
7377
        }
7378
7379
        return 0;
7380
    }
7381
7382
    /**
7383
     * @return int
7384
     */
7385
    public function getCategoryId()
7386
    {
7387
        return (int) $this->categoryId;
7388
    }
7389
7390
    /**
7391
     * Get whether this is a learning path with the possibility to subscribe
7392
     * users or not.
7393
     *
7394
     * @return int
7395
     */
7396
    public function getSubscribeUsers()
7397
    {
7398
        return $this->subscribeUsers;
7399
    }
7400
7401
    /**
7402
     * Calculate the count of stars for a user in this LP
7403
     * This calculation is based on the following rules:
7404
     * - the student gets one star when he gets to 50% of the learning path
7405
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
7406
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
7407
     * - the student gets the final star when the score for the *last* test is >= 80%.
7408
     *
7409
     * @param int $sessionId Optional. The session ID
7410
     *
7411
     * @return int The count of stars
7412
     */
7413
    public function getCalculateStars($sessionId = 0)
7414
    {
7415
        $stars = 0;
7416
        $progress = self::getProgress(
7417
            $this->lp_id,
7418
            $this->user_id,
7419
            $this->course_int_id,
7420
            $sessionId
7421
        );
7422
7423
        if ($progress >= 50) {
7424
            $stars++;
7425
        }
7426
7427
        // Calculate stars chapters evaluation
7428
        $exercisesItems = $this->getExercisesItems();
7429
7430
        if (!empty($exercisesItems)) {
7431
            $totalResult = 0;
7432
7433
            foreach ($exercisesItems as $exerciseItem) {
7434
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7435
                    $this->user_id,
7436
                    $exerciseItem->path,
7437
                    $this->course_int_id,
7438
                    $sessionId,
7439
                    $this->lp_id,
7440
                    $exerciseItem->db_id
7441
                );
7442
7443
                $exerciseResultInfo = end($exerciseResultInfo);
7444
7445
                if (!$exerciseResultInfo) {
7446
                    continue;
7447
                }
7448
7449
                if (!empty($exerciseResultInfo['max_score'])) {
7450
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
7451
                } else {
7452
                    $exerciseResult = 0;
7453
                }
7454
                $totalResult += $exerciseResult;
7455
            }
7456
7457
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
7458
7459
            if ($totalExerciseAverage >= 50) {
7460
                $stars++;
7461
            }
7462
7463
            if ($totalExerciseAverage >= 80) {
7464
                $stars++;
7465
            }
7466
        }
7467
7468
        // Calculate star for final evaluation
7469
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7470
7471
        if (!empty($finalEvaluationItem)) {
7472
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7473
                $this->user_id,
7474
                $finalEvaluationItem->path,
7475
                $this->course_int_id,
7476
                $sessionId,
7477
                $this->lp_id,
7478
                $finalEvaluationItem->db_id
7479
            );
7480
7481
            $evaluationResultInfo = end($evaluationResultInfo);
7482
7483
            if ($evaluationResultInfo) {
7484
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
7485
                if ($evaluationResult >= 80) {
7486
                    $stars++;
7487
                }
7488
            }
7489
        }
7490
7491
        return $stars;
7492
    }
7493
7494
    /**
7495
     * Get the items of exercise type.
7496
     *
7497
     * @return array The items. Otherwise return false
7498
     */
7499
    public function getExercisesItems()
7500
    {
7501
        $exercises = [];
7502
        foreach ($this->items as $item) {
7503
            if ('quiz' !== $item->type) {
7504
                continue;
7505
            }
7506
            $exercises[] = $item;
7507
        }
7508
7509
        array_pop($exercises);
7510
7511
        return $exercises;
7512
    }
7513
7514
    /**
7515
     * Get the item of exercise type (evaluation type).
7516
     *
7517
     * @return array The final evaluation. Otherwise return false
7518
     */
7519
    public function getFinalEvaluationItem()
7520
    {
7521
        $exercises = [];
7522
        foreach ($this->items as $item) {
7523
            if (TOOL_QUIZ !== $item->type) {
7524
                continue;
7525
            }
7526
7527
            $exercises[] = $item;
7528
        }
7529
7530
        return array_pop($exercises);
7531
    }
7532
7533
    /**
7534
     * Calculate the total points achieved for the current user in this learning path.
7535
     *
7536
     * @param int $sessionId Optional. The session Id
7537
     *
7538
     * @return int
7539
     */
7540
    public function getCalculateScore($sessionId = 0)
7541
    {
7542
        // Calculate stars chapters evaluation
7543
        $exercisesItems = $this->getExercisesItems();
7544
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7545
        $totalExercisesResult = 0;
7546
        $totalEvaluationResult = 0;
7547
7548
        if (false !== $exercisesItems) {
7549
            foreach ($exercisesItems as $exerciseItem) {
7550
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7551
                    $this->user_id,
7552
                    $exerciseItem->path,
7553
                    $this->course_int_id,
7554
                    $sessionId,
7555
                    $this->lp_id,
7556
                    $exerciseItem->db_id
7557
                );
7558
7559
                $exerciseResultInfo = end($exerciseResultInfo);
7560
7561
                if (!$exerciseResultInfo) {
7562
                    continue;
7563
                }
7564
7565
                $totalExercisesResult += $exerciseResultInfo['score'];
7566
            }
7567
        }
7568
7569
        if (!empty($finalEvaluationItem)) {
7570
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7571
                $this->user_id,
7572
                $finalEvaluationItem->path,
7573
                $this->course_int_id,
7574
                $sessionId,
7575
                $this->lp_id,
7576
                $finalEvaluationItem->db_id
7577
            );
7578
7579
            $evaluationResultInfo = end($evaluationResultInfo);
7580
7581
            if ($evaluationResultInfo) {
7582
                $totalEvaluationResult += $evaluationResultInfo['score'];
7583
            }
7584
        }
7585
7586
        return $totalExercisesResult + $totalEvaluationResult;
7587
    }
7588
7589
    /**
7590
     * Check if URL is not allowed to be show in a iframe.
7591
     *
7592
     * @param string $src
7593
     *
7594
     * @return string
7595
     */
7596
    public function fixBlockedLinks($src)
7597
    {
7598
        $urlInfo = parse_url($src);
7599
7600
        $platformProtocol = 'https';
7601
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
7602
            $platformProtocol = 'http';
7603
        }
7604
7605
        $protocolFixApplied = false;
7606
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
7607
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
7608
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
7609
7610
        if ($platformProtocol != $scheme) {
7611
            Session::write('x_frame_source', $src);
7612
            $src = 'blank.php?error=x_frames_options';
7613
            $protocolFixApplied = true;
7614
        }
7615
7616
        if (false == $protocolFixApplied) {
7617
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
7618
                // Check X-Frame-Options
7619
                $ch = curl_init();
7620
                $options = [
7621
                    CURLOPT_URL => $src,
7622
                    CURLOPT_RETURNTRANSFER => true,
7623
                    CURLOPT_HEADER => true,
7624
                    CURLOPT_FOLLOWLOCATION => true,
7625
                    CURLOPT_ENCODING => "",
7626
                    CURLOPT_AUTOREFERER => true,
7627
                    CURLOPT_CONNECTTIMEOUT => 120,
7628
                    CURLOPT_TIMEOUT => 120,
7629
                    CURLOPT_MAXREDIRS => 10,
7630
                ];
7631
7632
                $proxySettings = api_get_configuration_value('proxy_settings');
7633
                if (!empty($proxySettings) &&
7634
                    isset($proxySettings['curl_setopt_array'])
7635
                ) {
7636
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
7637
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
7638
                }
7639
7640
                curl_setopt_array($ch, $options);
7641
                $response = curl_exec($ch);
7642
                $httpCode = curl_getinfo($ch);
7643
                $headers = substr($response, 0, $httpCode['header_size']);
7644
7645
                $error = false;
7646
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
7647
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
7648
                ) {
7649
                    $error = true;
7650
                }
7651
7652
                if ($error) {
7653
                    Session::write('x_frame_source', $src);
7654
                    $src = 'blank.php?error=x_frames_options';
7655
                }
7656
            }
7657
        }
7658
7659
        return $src;
7660
    }
7661
7662
    /**
7663
     * Check if this LP has a created forum in the basis course.
7664
     *
7665
     * @deprecated
7666
     *
7667
     * @return bool
7668
     */
7669
    public function lpHasForum()
7670
    {
7671
        $forumTable = Database::get_course_table(TABLE_FORUM);
7672
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
7673
7674
        $fakeFrom = "
7675
            $forumTable f
7676
            INNER JOIN $itemProperty ip
7677
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
7678
        ";
7679
7680
        $resultData = Database::select(
7681
            'COUNT(f.iid) AS qty',
7682
            $fakeFrom,
7683
            [
7684
                'where' => [
7685
                    'ip.visibility != ? AND ' => 2,
7686
                    'ip.tool = ? AND ' => TOOL_FORUM,
7687
                    'f.c_id = ? AND ' => intval($this->course_int_id),
7688
                    'f.lp_id = ?' => intval($this->lp_id),
7689
                ],
7690
            ],
7691
            'first'
7692
        );
7693
7694
        return $resultData['qty'] > 0;
7695
    }
7696
7697
    /**
7698
     * Get the forum for this learning path.
7699
     *
7700
     * @param int $sessionId
7701
     *
7702
     * @return array
7703
     */
7704
    public function getForum($sessionId = 0)
7705
    {
7706
        $repo = Container::getForumRepository();
7707
7708
        $course = api_get_course_entity();
7709
        $session = api_get_session_entity($sessionId);
7710
        $qb = $repo->getResourcesByCourse($course, $session);
7711
7712
        return $qb->getQuery()->getResult();
7713
    }
7714
7715
    /**
7716
     * Get the LP Final Item form.
7717
     *
7718
     * @throws Exception
7719
     *
7720
     *
7721
     * @return string
7722
     */
7723
    public function getFinalItemForm()
7724
    {
7725
        $finalItem = $this->getFinalItem();
7726
        $title = '';
7727
7728
        if ($finalItem) {
7729
            $title = $finalItem->get_title();
7730
            $buttonText = get_lang('Save');
7731
            $content = $this->getSavedFinalItem();
7732
        } else {
7733
            $buttonText = get_lang('Add this document to the course');
7734
            $content = $this->getFinalItemTemplate();
7735
        }
7736
7737
        $editorConfig = [
7738
            'ToolbarSet' => 'LearningPathDocuments',
7739
            'Width' => '100%',
7740
            'Height' => '500',
7741
            'FullPage' => true,
7742
        ];
7743
7744
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
7745
            'type' => 'document',
7746
            'lp_id' => $this->lp_id,
7747
        ]);
7748
7749
        $form = new FormValidator('final_item', 'POST', $url);
7750
        $form->addText('title', get_lang('Title'));
7751
        $form->addButtonSave($buttonText);
7752
        $form->addHtml(
7753
            Display::return_message(
7754
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
7755
                'normal',
7756
                false
7757
            )
7758
        );
7759
7760
        $renderer = $form->defaultRenderer();
7761
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
7762
7763
        $form->addHtmlEditor(
7764
            'content_lp_certificate',
7765
            null,
7766
            true,
7767
            false,
7768
            $editorConfig
7769
        );
7770
        $form->addHidden('action', 'add_final_item');
7771
        $form->addHidden('path', Session::read('pathItem'));
7772
        $form->addHidden('previous', $this->get_last());
7773
        $form->setDefaults(
7774
            ['title' => $title, 'content_lp_certificate' => $content]
7775
        );
7776
7777
        if ($form->validate()) {
7778
            $values = $form->exportValues();
7779
            $lastItemId = $this->getLastInFirstLevel();
7780
7781
            if (!$finalItem) {
7782
                $documentId = $this->create_document(
7783
                    $this->course_info,
7784
                    $values['content_lp_certificate'],
7785
                    $values['title']
7786
                );
7787
                $this->add_item(
7788
                    0,
7789
                    $lastItemId,
7790
                    'final_item',
7791
                    $documentId,
7792
                    $values['title'],
7793
                );
7794
7795
                Display::addFlash(
7796
                    Display::return_message(get_lang('Added'))
7797
                );
7798
            } else {
7799
                $this->edit_document();
7800
            }
7801
        }
7802
7803
        return $form->returnForm();
7804
    }
7805
7806
    /**
7807
     * Check if the current lp item is first, both, last or none from lp list.
7808
     *
7809
     * @param int $currentItemId
7810
     *
7811
     * @return string
7812
     */
7813
    public function isFirstOrLastItem($currentItemId)
7814
    {
7815
        $lpItemId = [];
7816
        $typeListNotToVerify = self::getChapterTypes();
7817
7818
        // Using get_toc() function instead $this->items because returns the correct order of the items
7819
        foreach ($this->get_toc() as $item) {
7820
            if (!in_array($item['type'], $typeListNotToVerify)) {
7821
                $lpItemId[] = $item['id'];
7822
            }
7823
        }
7824
7825
        $lastLpItemIndex = count($lpItemId) - 1;
7826
        $position = array_search($currentItemId, $lpItemId);
7827
7828
        switch ($position) {
7829
            case 0:
7830
                if (!$lastLpItemIndex) {
7831
                    $answer = 'both';
7832
                    break;
7833
                }
7834
7835
                $answer = 'first';
7836
                break;
7837
            case $lastLpItemIndex:
7838
                $answer = 'last';
7839
                break;
7840
            default:
7841
                $answer = 'none';
7842
        }
7843
7844
        return $answer;
7845
    }
7846
7847
    /**
7848
     * Get whether this is a learning path with the accumulated SCORM time or not.
7849
     *
7850
     * @return int
7851
     */
7852
    public function getAccumulateScormTime()
7853
    {
7854
        return $this->accumulateScormTime;
7855
    }
7856
7857
    /**
7858
     * Returns an HTML-formatted link to a resource, to incorporate directly into
7859
     * the new learning path tool.
7860
     *
7861
     * The function is a big switch on tool type.
7862
     * In each case, we query the corresponding table for information and build the link
7863
     * with that information.
7864
     *
7865
     * @author Yannick Warnier <[email protected]> - rebranding based on
7866
     * previous work (display_addedresource_link_in_learnpath())
7867
     *
7868
     * @param int $course_id      Course code
7869
     * @param int $learningPathId The learning path ID (in lp table)
7870
     * @param int $id_in_path     the unique index in the items table
7871
     * @param int $lpViewId
7872
     *
7873
     * @return string
7874
     */
7875
    public static function rl_get_resource_link_for_learnpath(
7876
        $course_id,
7877
        $learningPathId,
7878
        $id_in_path,
7879
        $lpViewId
7880
    ) {
7881
        $session_id = api_get_session_id();
7882
7883
        $learningPathId = (int) $learningPathId;
7884
        $id_in_path = (int) $id_in_path;
7885
        $lpViewId = (int) $lpViewId;
7886
7887
        $em = Database::getManager();
7888
        $lpItemRepo = $em->getRepository(CLpItem::class);
7889
7890
        /** @var CLpItem $rowItem */
7891
        $rowItem = $lpItemRepo->findOneBy([
7892
            'lp' => $learningPathId,
7893
            'iid' => $id_in_path,
7894
        ]);
7895
        $type = $rowItem->getItemType();
7896
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
7897
        $main_dir_path = api_get_path(WEB_CODE_PATH);
7898
        $link = '';
7899
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
7900
7901
        switch ($type) {
7902
            case 'dir':
7903
                return $main_dir_path.'lp/blank.php';
7904
            case TOOL_CALENDAR_EVENT:
7905
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
7906
            case TOOL_ANNOUNCEMENT:
7907
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
7908
            case TOOL_LINK:
7909
                $linkInfo = Link::getLinkInfo($id);
7910
                if (isset($linkInfo['url'])) {
7911
                    return $linkInfo['url'];
7912
                }
7913
7914
                return '';
7915
            case TOOL_QUIZ:
7916
                if (empty($id)) {
7917
                    return '';
7918
                }
7919
7920
                // Get the lp_item_view with the highest view_count.
7921
                $learnpathItemViewResult = $em
7922
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
7923
                    ->findBy(
7924
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
7925
                        ['viewCount' => 'DESC'],
7926
                        1
7927
                    );
7928
                /** @var CLpItemView $learnpathItemViewData */
7929
                $learnpathItemViewData = current($learnpathItemViewResult);
7930
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
7931
7932
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
7933
                    .http_build_query([
7934
                        'lp_init' => 1,
7935
                        'learnpath_item_view_id' => $learnpathItemViewId,
7936
                        'learnpath_id' => $learningPathId,
7937
                        'learnpath_item_id' => $id_in_path,
7938
                        'exerciseId' => $id,
7939
                    ]);
7940
            case TOOL_HOTPOTATOES:
7941
                return '';
7942
            case TOOL_FORUM:
7943
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
7944
            case TOOL_THREAD:
7945
                // forum post
7946
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
7947
                if (empty($id)) {
7948
                    return '';
7949
                }
7950
                $sql = "SELECT * FROM $tbl_topics WHERE iid=$id";
7951
                $result = Database::query($sql);
7952
                $row = Database::fetch_array($result);
7953
7954
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$row['forum_id'].'&lp=true&'
7955
                    .$extraParams;
7956
            case TOOL_POST:
7957
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
7958
                $result = Database::query("SELECT * FROM $tbl_post WHERE post_id=$id");
7959
                $row = Database::fetch_array($result);
7960
7961
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$row['thread_id'].'&forum='
7962
                    .$row['forum_id'].'&lp=true&'.$extraParams;
7963
            case TOOL_READOUT_TEXT:
7964
                return api_get_path(WEB_CODE_PATH).
7965
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
7966
            case TOOL_DOCUMENT:
7967
                $repo = Container::getDocumentRepository();
7968
                $document = $repo->find($rowItem->getPath());
7969
                if ($document) {
7970
                    $params = [
7971
                        'cid' => $course_id,
7972
                        'sid' => $session_id,
7973
                    ];
7974
7975
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
7976
                }
7977
7978
                return null;
7979
            case TOOL_LP_FINAL_ITEM:
7980
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
7981
                    .$extraParams;
7982
            case 'assignments':
7983
                return $main_dir_path.'work/work.php?'.$extraParams;
7984
            case TOOL_DROPBOX:
7985
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
7986
            case 'introduction_text': //DEPRECATED
7987
                return '';
7988
            case TOOL_COURSE_DESCRIPTION:
7989
                return $main_dir_path.'course_description?'.$extraParams;
7990
            case TOOL_GROUP:
7991
                return $main_dir_path.'group/group.php?'.$extraParams;
7992
            case TOOL_USER:
7993
                return $main_dir_path.'user/user.php?'.$extraParams;
7994
            case TOOL_STUDENTPUBLICATION:
7995
                if (!empty($rowItem->getPath())) {
7996
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
7997
                }
7998
7999
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
8000
        }
8001
8002
        return $link;
8003
    }
8004
8005
    /**
8006
     * Gets the name of a resource (generally used in learnpath when no name is provided).
8007
     *
8008
     * @author Yannick Warnier <[email protected]>
8009
     *
8010
     * @param string $course_code    Course code
8011
     * @param int    $learningPathId
8012
     * @param int    $id_in_path     The resource ID
8013
     *
8014
     * @return string
8015
     */
8016
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
8017
    {
8018
        $_course = api_get_course_info($course_code);
8019
        if (empty($_course)) {
8020
            return '';
8021
        }
8022
        $course_id = $_course['real_id'];
8023
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8024
        $learningPathId = (int) $learningPathId;
8025
        $id_in_path = (int) $id_in_path;
8026
8027
        $sql = "SELECT item_type, title, ref
8028
                FROM $tbl_lp_item
8029
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
8030
        $res_item = Database::query($sql);
8031
8032
        if (Database::num_rows($res_item) < 1) {
8033
            return '';
8034
        }
8035
        $row_item = Database::fetch_array($res_item);
8036
        $type = strtolower($row_item['item_type']);
8037
        $id = $row_item['ref'];
8038
        $output = '';
8039
8040
        switch ($type) {
8041
            case TOOL_CALENDAR_EVENT:
8042
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8043
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8044
                $myrow = Database::fetch_array($result);
8045
                $output = $myrow['title'];
8046
                break;
8047
            case TOOL_ANNOUNCEMENT:
8048
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8049
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8050
                $myrow = Database::fetch_array($result);
8051
                $output = $myrow['title'];
8052
                break;
8053
            case TOOL_LINK:
8054
                // Doesn't take $target into account.
8055
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8056
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8057
                $myrow = Database::fetch_array($result);
8058
                $output = $myrow['title'];
8059
                break;
8060
            case TOOL_QUIZ:
8061
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8062
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8063
                $myrow = Database::fetch_array($result);
8064
                $output = $myrow['title'];
8065
                break;
8066
            case TOOL_FORUM:
8067
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8068
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8069
                $myrow = Database::fetch_array($result);
8070
                $output = $myrow['forum_name'];
8071
                break;
8072
            case TOOL_THREAD:
8073
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8074
                // Grabbing the title of the post.
8075
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8076
                $result_title = Database::query($sql_title);
8077
                $myrow_title = Database::fetch_array($result_title);
8078
                $output = $myrow_title['post_title'];
8079
                break;
8080
            case TOOL_POST:
8081
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8082
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8083
                $result = Database::query($sql);
8084
                $post = Database::fetch_array($result);
8085
                $output = $post['post_title'];
8086
                break;
8087
            case 'dir':
8088
            case TOOL_DOCUMENT:
8089
                $title = $row_item['title'];
8090
                $output = '-';
8091
                if (!empty($title)) {
8092
                    $output = $title;
8093
                }
8094
                break;
8095
            case 'hotpotatoes':
8096
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8097
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8098
                $myrow = Database::fetch_array($result);
8099
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8100
                $last = count($pathname) - 1; // Making a correct name for the link.
8101
                $filename = $pathname[$last]; // Making a correct name for the link.
8102
                $myrow['path'] = rawurlencode($myrow['path']);
8103
                $output = $filename;
8104
                break;
8105
        }
8106
8107
        return stripslashes($output);
8108
    }
8109
8110
    /**
8111
     * Get the parent names for the current item.
8112
     *
8113
     * @param int $newItemId Optional. The item ID
8114
     *
8115
     * @return array
8116
     */
8117
    public function getCurrentItemParentNames($newItemId = 0)
8118
    {
8119
        $newItemId = $newItemId ?: $this->get_current_item_id();
8120
        $return = [];
8121
        $item = $this->getItem($newItemId);
8122
        $parent = $this->getItem($item->get_parent());
8123
8124
        while ($parent) {
8125
            $return[] = $parent->get_title();
8126
            $parent = $this->getItem($parent->get_parent());
8127
        }
8128
8129
        return array_reverse($return);
8130
    }
8131
8132
    /**
8133
     * Reads and process "lp_subscription_settings" setting.
8134
     *
8135
     * @return array
8136
     */
8137
    public static function getSubscriptionSettings()
8138
    {
8139
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
8140
        if (empty($subscriptionSettings)) {
8141
            // By default allow both settings
8142
            $subscriptionSettings = [
8143
                'allow_add_users_to_lp' => true,
8144
                'allow_add_users_to_lp_category' => true,
8145
            ];
8146
        } else {
8147
            $subscriptionSettings = $subscriptionSettings['options'];
8148
        }
8149
8150
        return $subscriptionSettings;
8151
    }
8152
8153
    /**
8154
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8155
     */
8156
    public function exportToCourseBuildFormat()
8157
    {
8158
        if (!api_is_allowed_to_edit()) {
8159
            return false;
8160
        }
8161
8162
        $courseBuilder = new CourseBuilder();
8163
        $itemList = [];
8164
        /** @var learnpathItem $item */
8165
        foreach ($this->items as $item) {
8166
            $itemList[$item->get_type()][] = $item->get_path();
8167
        }
8168
8169
        if (empty($itemList)) {
8170
            return false;
8171
        }
8172
8173
        if (isset($itemList['document'])) {
8174
            // Get parents
8175
            foreach ($itemList['document'] as $documentId) {
8176
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8177
                if (!empty($documentInfo['parents'])) {
8178
                    foreach ($documentInfo['parents'] as $parentInfo) {
8179
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8180
                            continue;
8181
                        }
8182
                        $itemList['document'][] = $parentInfo['iid'];
8183
                    }
8184
                }
8185
            }
8186
8187
            $courseInfo = api_get_course_info();
8188
            foreach ($itemList['document'] as $documentId) {
8189
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8190
                $items = DocumentManager::get_resources_from_source_html(
8191
                    $documentInfo['absolute_path'],
8192
                    true,
8193
                    TOOL_DOCUMENT
8194
                );
8195
8196
                if (!empty($items)) {
8197
                    foreach ($items as $item) {
8198
                        // Get information about source url
8199
                        $url = $item[0]; // url
8200
                        $scope = $item[1]; // scope (local, remote)
8201
                        $type = $item[2]; // type (rel, abs, url)
8202
8203
                        $origParseUrl = parse_url($url);
8204
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8205
8206
                        if ('local' === $scope) {
8207
                            if ('abs' === $type || 'rel' === $type) {
8208
                                $documentFile = strstr($realOrigPath, 'document');
8209
                                if (false !== strpos($realOrigPath, $documentFile)) {
8210
                                    $documentFile = str_replace('document', '', $documentFile);
8211
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8212
                                    // Document found! Add it to the list
8213
                                    if ($itemDocumentId) {
8214
                                        $itemList['document'][] = $itemDocumentId;
8215
                                    }
8216
                                }
8217
                            }
8218
                        }
8219
                    }
8220
                }
8221
            }
8222
8223
            $courseBuilder->build_documents(
8224
                api_get_session_id(),
8225
                $this->get_course_int_id(),
8226
                true,
8227
                $itemList['document']
8228
            );
8229
        }
8230
8231
        if (isset($itemList['quiz'])) {
8232
            $courseBuilder->build_quizzes(
8233
                api_get_session_id(),
8234
                $this->get_course_int_id(),
8235
                true,
8236
                $itemList['quiz']
8237
            );
8238
        }
8239
8240
        if (!empty($itemList['thread'])) {
8241
            $threadList = [];
8242
            $repo = Container::getForumThreadRepository();
8243
            foreach ($itemList['thread'] as $threadId) {
8244
                /** @var CForumThread $thread */
8245
                $thread = $repo->find($threadId);
8246
                if ($thread) {
8247
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8248
                    $threadList[] = $thread->getIid();
8249
                }
8250
            }
8251
8252
            if (!empty($threadList)) {
8253
                $courseBuilder->build_forum_topics(
8254
                    api_get_session_id(),
8255
                    $this->get_course_int_id(),
8256
                    null,
8257
                    $threadList
8258
                );
8259
            }
8260
        }
8261
8262
        $forumCategoryList = [];
8263
        if (isset($itemList['forum'])) {
8264
            foreach ($itemList['forum'] as $forumId) {
8265
                $forumInfo = get_forums($forumId);
8266
                $forumCategoryList[] = $forumInfo['forum_category'];
8267
            }
8268
        }
8269
8270
        if (!empty($forumCategoryList)) {
8271
            $courseBuilder->build_forum_category(
8272
                api_get_session_id(),
8273
                $this->get_course_int_id(),
8274
                true,
8275
                $forumCategoryList
8276
            );
8277
        }
8278
8279
        if (!empty($itemList['forum'])) {
8280
            $courseBuilder->build_forums(
8281
                api_get_session_id(),
8282
                $this->get_course_int_id(),
8283
                true,
8284
                $itemList['forum']
8285
            );
8286
        }
8287
8288
        if (isset($itemList['link'])) {
8289
            $courseBuilder->build_links(
8290
                api_get_session_id(),
8291
                $this->get_course_int_id(),
8292
                true,
8293
                $itemList['link']
8294
            );
8295
        }
8296
8297
        if (!empty($itemList['student_publication'])) {
8298
            $courseBuilder->build_works(
8299
                api_get_session_id(),
8300
                $this->get_course_int_id(),
8301
                true,
8302
                $itemList['student_publication']
8303
            );
8304
        }
8305
8306
        $courseBuilder->build_learnpaths(
8307
            api_get_session_id(),
8308
            $this->get_course_int_id(),
8309
            true,
8310
            [$this->get_id()],
8311
            false
8312
        );
8313
8314
        $courseBuilder->restoreDocumentsFromList();
8315
8316
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
8317
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
8318
        $result = DocumentManager::file_send_for_download(
8319
            $zipPath,
8320
            true,
8321
            $this->get_name().'.zip'
8322
        );
8323
8324
        if ($result) {
8325
            api_not_allowed();
8326
        }
8327
8328
        return true;
8329
    }
8330
8331
    /**
8332
     * Get whether this is a learning path with the accumulated work time or not.
8333
     *
8334
     * @return int
8335
     */
8336
    public function getAccumulateWorkTime()
8337
    {
8338
        return (int) $this->accumulateWorkTime;
8339
    }
8340
8341
    /**
8342
     * Get whether this is a learning path with the accumulated work time or not.
8343
     *
8344
     * @return int
8345
     */
8346
    public function getAccumulateWorkTimeTotalCourse()
8347
    {
8348
        $table = Database::get_course_table(TABLE_LP_MAIN);
8349
        $sql = "SELECT SUM(accumulate_work_time) AS total
8350
                FROM $table
8351
                WHERE c_id = ".$this->course_int_id;
8352
        $result = Database::query($sql);
8353
        $row = Database::fetch_array($result);
8354
8355
        return (int) $row['total'];
8356
    }
8357
8358
    /**
8359
     * @param int $lpId
8360
     * @param int $courseId
8361
     *
8362
     * @return mixed
8363
     */
8364
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
8365
    {
8366
        $lpId = (int) $lpId;
8367
        $table = Database::get_course_table(TABLE_LP_MAIN);
8368
        $sql = "SELECT accumulate_work_time
8369
                FROM $table
8370
                WHERE iid = $lpId";
8371
        $result = Database::query($sql);
8372
        $row = Database::fetch_array($result);
8373
8374
        return $row['accumulate_work_time'];
8375
    }
8376
8377
    /**
8378
     * @param int $courseId
8379
     *
8380
     * @return int
8381
     */
8382
    public static function getAccumulateWorkTimeTotal($courseId)
8383
    {
8384
        $table = Database::get_course_table(TABLE_LP_MAIN);
8385
        $courseId = (int) $courseId;
8386
        $sql = "SELECT SUM(accumulate_work_time) AS total
8387
                FROM $table
8388
                WHERE c_id = $courseId";
8389
        $result = Database::query($sql);
8390
        $row = Database::fetch_array($result);
8391
8392
        return (int) $row['total'];
8393
    }
8394
8395
    /**
8396
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
8397
     * and put the images in.
8398
     *
8399
     * @return array
8400
     */
8401
    public static function getIconSelect()
8402
    {
8403
        $theme = api_get_visual_theme();
8404
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
8405
        $icons = ['' => get_lang('Please select an option')];
8406
8407
        if (is_dir($path)) {
8408
            $finder = new Finder();
8409
            $finder->files()->in($path);
8410
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
8411
            /** @var SplFileInfo $file */
8412
            foreach ($finder as $file) {
8413
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
8414
                    $icons[$file->getFilename()] = $file->getFilename();
8415
                }
8416
            }
8417
        }
8418
8419
        return $icons;
8420
    }
8421
8422
    /**
8423
     * @param int $lpId
8424
     *
8425
     * @return string
8426
     */
8427
    public static function getSelectedIcon($lpId)
8428
    {
8429
        $extraFieldValue = new ExtraFieldValue('lp');
8430
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
8431
        $icon = '';
8432
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
8433
            $icon = $lpIcon['value'];
8434
        }
8435
8436
        return $icon;
8437
    }
8438
8439
    /**
8440
     * @param int $lpId
8441
     *
8442
     * @return string
8443
     */
8444
    public static function getSelectedIconHtml($lpId)
8445
    {
8446
        $icon = self::getSelectedIcon($lpId);
8447
8448
        if (empty($icon)) {
8449
            return '';
8450
        }
8451
8452
        $theme = api_get_visual_theme();
8453
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
8454
8455
        return Display::img($path);
8456
    }
8457
8458
    /**
8459
     * @param string $value
8460
     *
8461
     * @return string
8462
     */
8463
    public function cleanItemTitle($value)
8464
    {
8465
        $value = Security::remove_XSS(strip_tags($value));
8466
8467
        return $value;
8468
    }
8469
8470
    public function setItemTitle(FormValidator $form)
8471
    {
8472
        if (api_get_configuration_value('save_titles_as_html')) {
8473
            $form->addHtmlEditor(
8474
                'title',
8475
                get_lang('Title'),
8476
                true,
8477
                false,
8478
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
8479
            );
8480
        } else {
8481
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
8482
            $form->applyFilter('title', 'trim');
8483
            $form->applyFilter('title', 'html_filter');
8484
        }
8485
    }
8486
8487
    /**
8488
     * @return array
8489
     */
8490
    public function getItemsForForm($addParentCondition = false)
8491
    {
8492
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8493
8494
        $sql = "SELECT * FROM $tbl_lp_item
8495
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
8496
8497
        if ($addParentCondition) {
8498
            $sql .= ' AND parent_item_id IS NULL ';
8499
        }
8500
        $sql .= ' ORDER BY display_order ASC';
8501
8502
        $result = Database::query($sql);
8503
        $arrLP = [];
8504
        while ($row = Database::fetch_array($result)) {
8505
            $arrLP[] = [
8506
                'iid' => $row['iid'],
8507
                'id' => $row['iid'],
8508
                'item_type' => $row['item_type'],
8509
                'title' => $this->cleanItemTitle($row['title']),
8510
                'title_raw' => $row['title'],
8511
                'path' => $row['path'],
8512
                'description' => Security::remove_XSS($row['description']),
8513
                'parent_item_id' => $row['parent_item_id'],
8514
                'previous_item_id' => $row['previous_item_id'],
8515
                'next_item_id' => $row['next_item_id'],
8516
                'display_order' => $row['display_order'],
8517
                'max_score' => $row['max_score'],
8518
                'min_score' => $row['min_score'],
8519
                'mastery_score' => $row['mastery_score'],
8520
                'prerequisite' => $row['prerequisite'],
8521
                'max_time_allowed' => $row['max_time_allowed'],
8522
                'prerequisite_min_score' => $row['prerequisite_min_score'],
8523
                'prerequisite_max_score' => $row['prerequisite_max_score'],
8524
            ];
8525
        }
8526
8527
        return $arrLP;
8528
    }
8529
8530
    /**
8531
     * Gets whether this SCORM learning path has been marked to use the score
8532
     * as progress. Takes into account whether the learnpath matches (SCORM
8533
     * content + less than 2 items).
8534
     *
8535
     * @return bool True if the score should be used as progress, false otherwise
8536
     */
8537
    public function getUseScoreAsProgress()
8538
    {
8539
        // If not a SCORM, we don't care about the setting
8540
        if (2 != $this->get_type()) {
8541
            return false;
8542
        }
8543
        // If more than one step in the SCORM, we don't care about the setting
8544
        if ($this->get_total_items_count() > 1) {
8545
            return false;
8546
        }
8547
        $extraFieldValue = new ExtraFieldValue('lp');
8548
        $doUseScore = false;
8549
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
8550
            $this->get_id(),
8551
            'use_score_as_progress'
8552
        );
8553
        if (!empty($useScore) && isset($useScore['value'])) {
8554
            $doUseScore = $useScore['value'];
8555
        }
8556
8557
        return $doUseScore;
8558
    }
8559
8560
    /**
8561
     * Get the user identifier (user_id or username
8562
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
8563
     *
8564
     * @return string User ID or username, depending on configuration setting
8565
     */
8566
    public static function getUserIdentifierForExternalServices()
8567
    {
8568
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
8569
            return api_get_user_info(api_get_user_id())['username'];
8570
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
8571
            $extraFieldValue = new ExtraFieldValue('user');
8572
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
8573
                api_get_user_id(),
8574
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
8575
            );
8576
8577
            return $extrafield['value'];
8578
        } else {
8579
            return api_get_user_id();
8580
        }
8581
    }
8582
8583
    /**
8584
     * Save the new order for learning path items.
8585
     *
8586
     * @param array $orderList A associative array with id and parent_id keys.
8587
     */
8588
    public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true)
8589
    {
8590
        if (empty($orderList)) {
8591
            return true;
8592
        }
8593
        $lpItemRepo = Container::getLpItemRepository();
8594
        $em = Database::getManager();
8595
        $counter = 2;
8596
        $rootItem->setDisplayOrder(1);
8597
        $rootItem->setPreviousItemId(null);
8598
        $em->persist($rootItem);
8599
        if ($flush) {
8600
            $em->flush();
8601
        }
8602
8603
        foreach ($orderList as $item) {
8604
            $itemId = $item->id ?? 0;
8605
            if (empty($itemId)) {
8606
                continue;
8607
            }
8608
            $parentId = $item->parent_id ?? 0;
8609
            $parent = $rootItem;
8610
            if (!empty($parentId)) {
8611
                $parentExists = $lpItemRepo->find($parentId);
8612
                if (null !== $parentExists) {
8613
                    $parent = $parentExists;
8614
                }
8615
            }
8616
8617
            /** @var CLpItem $itemEntity */
8618
            $itemEntity = $lpItemRepo->find($itemId);
8619
            $itemEntity->setParent($parent);
8620
            $previousId = (int) $itemEntity->getPreviousItemId();
8621
            //if (0 === $previousId) {
8622
                $itemEntity->setPreviousItemId(null);
8623
            //}
8624
8625
            $nextId = (int) $itemEntity->getNextItemId();
8626
            //if (0 === $nextId) {
8627
                $itemEntity->setNextItemId(null);
8628
            //}
8629
8630
            $itemEntity->setDisplayOrder($counter);
8631
            $em->persist($itemEntity);
8632
            if ($flush) {
8633
                $em->flush();
8634
            }
8635
            $counter++;
8636
        }
8637
8638
        $em->flush();
8639
        $lpItemRepo->recoverNode($rootItem, 'displayOrder');
8640
        $em->persist($rootItem);
8641
        if ($flush) {
8642
            $em->flush();
8643
        }
8644
8645
        return true;
8646
    }
8647
8648
    /**
8649
     * Get the depth level of LP item.
8650
     *
8651
     * @param array $items
8652
     * @param int   $currentItemId
8653
     *
8654
     * @return int
8655
     */
8656
    private static function get_level_for_item($items, $currentItemId)
8657
    {
8658
        $parentItemId = 0;
8659
        if (isset($items[$currentItemId])) {
8660
            $parentItemId = $items[$currentItemId]->parent;
8661
        }
8662
8663
        if (0 == $parentItemId) {
8664
            return 0;
8665
        }
8666
8667
        return self::get_level_for_item($items, $parentItemId) + 1;
8668
    }
8669
8670
    /**
8671
     * Generate the link for a learnpath category as course tool.
8672
     *
8673
     * @param int $categoryId
8674
     *
8675
     * @return string
8676
     */
8677
    private static function getCategoryLinkForTool($categoryId)
8678
    {
8679
        $categoryId = (int) $categoryId;
8680
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
8681
            .http_build_query(
8682
                [
8683
                    'action' => 'view_category',
8684
                    'id' => $categoryId,
8685
                ]
8686
            );
8687
    }
8688
8689
    /**
8690
     * Check and obtain the lp final item if exist.
8691
     *
8692
     * @return learnpathItem
8693
     */
8694
    private function getFinalItem()
8695
    {
8696
        if (empty($this->items)) {
8697
            return null;
8698
        }
8699
8700
        foreach ($this->items as $item) {
8701
            if ('final_item' !== $item->type) {
8702
                continue;
8703
            }
8704
8705
            return $item;
8706
        }
8707
    }
8708
8709
    /**
8710
     * Get the LP Final Item Template.
8711
     *
8712
     * @return string
8713
     */
8714
    private function getFinalItemTemplate()
8715
    {
8716
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
8717
    }
8718
8719
    /**
8720
     * Get the LP Final Item Url.
8721
     *
8722
     * @return string
8723
     */
8724
    private function getSavedFinalItem()
8725
    {
8726
        $finalItem = $this->getFinalItem();
8727
8728
        $repo = Container::getDocumentRepository();
8729
        /** @var CDocument $document */
8730
        $document = $repo->find($finalItem->path);
8731
8732
        if ($document && $document->getResourceNode()->hasResourceFile()) {
8733
            return $repo->getResourceFileContent($document);
8734
        }
8735
8736
        return '';
8737
    }
8738
}
8739