learnpathItem::get_min()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * Class learnpathItem
7
 * lp_item defines items belonging to a learnpath. Each item has a name,
8
 * a score, a use time and additional information that enables tracking a user's
9
 * progress in a learning path.
10
 *
11
 * @author  Yannick Warnier <[email protected]>
12
 */
13
class learnpathItem
14
{
15
    public const DEBUG = 0; // Logging parameter.
16
    public $iId;
17
    public $attempt_id; // Also called "objectives" SCORM-wise.
18
    public $audio; // The path to an audio file (stored in document/audio/).
19
    public $children = []; // Contains the ids of children items.
20
    public $condition; // If this item has a special condition embedded.
21
    public $current_score;
22
    public $current_start_time;
23
    public $current_stop_time;
24
    public $current_data = '';
25
    public $db_id;
26
    public $db_item_view_id = '';
27
    public $description = '';
28
    public $file;
29
    /**
30
     * At the moment, interactions are just an array of arrays with a structure
31
     * of 8 text fields: id(0), type(1), time(2), weighting(3),
32
     * correct_responses(4), student_response(5), result(6), latency(7).
33
     */
34
    public $interactions = [];
35
    public $interactions_count = 0;
36
    public $objectives = [];
37
    public $objectives_count = 0;
38
    public $launch_data = '';
39
    public $lesson_location = '';
40
    public $level = 0;
41
    public $core_exit = '';
42
    public $lp_id;
43
    public $max_score;
44
    public $mastery_score;
45
    public $min_score;
46
    public $max_time_allowed = '';
47
    public $name;
48
    public $next;
49
    public $parent;
50
    public $path; // In some cases the exo_id = exercise_id in courseDb exercices table.
51
    public $possible_status = [
52
        'not attempted',
53
        'incomplete',
54
        'completed',
55
        'passed',
56
        'failed',
57
        'browsed',
58
    ];
59
    public $prereq_string = '';
60
    public $prereq_alert = '';
61
    public $prereqs = [];
62
    public $previous;
63
    public $prevent_reinit = 1; // 0 =  multiple attempts   1 = one attempt
64
    public $seriousgame_mode;
65
    public $ref;
66
    public $save_on_close = true;
67
    public $search_did = null;
68
    public $status;
69
    public $title;
70
    /**
71
     * Type attribute can contain one of
72
     * link|student_publication|dir|quiz|document|forum|thread.
73
     */
74
    public $type;
75
    public $view_id;
76
    public $oldTotalTime;
77
    public $view_max_score;
78
    public $courseInfo;
79
    public $courseId;
80
    //var used if absolute session time mode is used
81
    private $last_scorm_session_time = 0;
82
    private $prerequisiteMaxScore;
83
    private $prerequisiteMinScore;
84
85
    /**
86
     * Prepares the learning path item for later launch.
87
     * Don't forget to use set_lp_view() if applicable after creating the item.
88
     * Setting an lp_view will finalise the item_view data collection.
89
     *
90
     * @param int        $id           Learning path item ID
91
     * @param int        $user_id      User ID
92
     * @param int        $courseId     Course int id
93
     * @param array|null $item_content An array with the contents of the item
94
     */
95
    public function __construct(
96
        $id,
97
        $user_id = 0,
98
        $courseId = 0,
99
        $item_content = null
100
    ) {
101
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
102
        $id = (int) $id;
103
        $this->courseId = $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
104
        $this->courseInfo = api_get_course_info_by_id($this->courseId);
105
106
        if (empty($item_content)) {
107
            $sql = "SELECT * FROM $items_table
108
                    WHERE iid = $id";
109
            $res = Database::query($sql);
110
            if (Database::num_rows($res) < 1) {
111
                $this->error = 'Could not find given learnpath item in learnpath_item table';
112
            }
113
            $row = Database::fetch_array($res);
114
        } else {
115
            $row = $item_content;
116
        }
117
118
        $this->lp_id = $row['lp_id'];
119
        $this->iId = $row['iid'];
120
        $this->max_score = $row['max_score'];
121
        $this->min_score = $row['min_score'];
122
        $this->name = $row['title'];
123
        $this->type = $row['item_type'];
124
        $this->ref = $row['ref'];
125
        $this->title = $row['title'];
126
        $this->description = $row['description'];
127
        $this->path = $row['path'];
128
        $this->mastery_score = $row['mastery_score'];
129
        $this->parent = $row['parent_item_id'];
130
        $this->next = $row['next_item_id'];
131
        $this->previous = $row['previous_item_id'];
132
        $this->display_order = $row['display_order'];
133
        $this->prereq_string = $row['prerequisite'];
134
        $this->max_time_allowed = $row['max_time_allowed'];
135
        $this->setPrerequisiteMaxScore($row['prerequisite_max_score']);
136
        $this->setPrerequisiteMinScore($row['prerequisite_min_score']);
137
        $this->oldTotalTime = 0;
138
        $this->view_max_score = 0;
139
        $this->seriousgame_mode = 0;
140
        $this->audio = self::fixAudio($row['audio']);
141
        $this->launch_data = $row['launch_data'];
142
        $this->save_on_close = true;
143
        $this->db_id = $id;
144
145
        // Load children list
146
        if (!empty($this->lp_id)) {
147
            $sql = "SELECT iid FROM $items_table
148
                    WHERE
149
                        c_id = $courseId AND
150
                        lp_id = ".$this->lp_id." AND
151
                        parent_item_id = $id";
152
            $res = Database::query($sql);
153
            if (Database::num_rows($res) > 0) {
154
                while ($row = Database::fetch_assoc($res)) {
155
                    $this->children[] = $row['iid'];
156
                }
157
            }
158
159
            // Get search_did.
160
            if ('true' === api_get_setting('search_enabled')) {
161
                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
162
                $sql = 'SELECT *
163
                        FROM %s
164
                        WHERE
165
                            course_code=\'%s\' AND
166
                            tool_id=\'%s\' AND
167
                            ref_id_high_level=%s AND
168
                            ref_id_second_level=%d
169
                        LIMIT 1';
170
                // TODO: Verify if it's possible to assume the actual course instead
171
                // of getting it from db.
172
                $sql = sprintf(
173
                    $sql,
174
                    $tbl_se_ref,
175
                    api_get_course_id(),
176
                    TOOL_LEARNPATH,
177
                    $this->lp_id,
178
                    $id
179
                );
180
                $res = Database::query($sql);
181
                if (Database::num_rows($res) > 0) {
182
                    $se_ref = Database::fetch_array($res);
183
                    $this->search_did = (int) $se_ref['search_did'];
184
                }
185
            }
186
        }
187
    }
188
189
    public static function fixAudio($audio)
190
    {
191
        $courseInfo = api_get_course_info();
192
193
        if (empty($audio) || empty($courseInfo)) {
194
            return '';
195
        }
196
197
        // Old structure
198
        $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
199
        if (file_exists($file)) {
200
            $audio = '/audio/'.$audio;
201
            $audio = str_replace('//', '/', $audio);
202
203
            return $audio;
204
        }
205
206
        $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
207
208
        if (file_exists($file)) {
209
            return $audio;
210
        }
211
212
        return '';
213
    }
214
215
    /**
216
     * Adds an interaction to the current item.
217
     *
218
     * @param int   $index  Index (order ID) of the interaction inside this item
219
     * @param array $params Array of parameters:
220
     *                      id(0), type(1), time(2), weighting(3), correct_responses(4),
221
     *                      student_response(5), result(6), latency(7)
222
     */
223
    public function add_interaction($index, $params)
224
    {
225
        $this->interactions[$index] = $params;
226
        // Take the current maximum index to generate the interactions_count.
227
        if (($index + 1) > $this->interactions_count) {
228
            $this->interactions_count = $index + 1;
229
        }
230
    }
231
232
    /**
233
     * Adds an objective to the current item.
234
     *
235
     * @param    array    Array of parameters:
236
     * id(0), status(1), score_raw(2), score_max(3), score_min(4)
237
     */
238
    public function add_objective($index, $params)
239
    {
240
        if (empty($params[0])) {
241
            return null;
242
        }
243
        $this->objectives[$index] = $params;
244
        // Take the current maximum index to generate the objectives_count.
245
        if ((count($this->objectives) + 1) > $this->objectives_count) {
246
            $this->objectives_count = (count($this->objectives) + 1);
247
        }
248
    }
249
250
    /**
251
     * Closes/stops the item viewing. Finalises runtime values.
252
     * If required, save to DB.
253
     *
254
     * @param bool $prerequisitesCheck Needed to check if asset can be set as completed or not
255
     *
256
     * @return bool True on success, false otherwise
257
     */
258
    public function close()
259
    {
260
        $debug = self::DEBUG;
261
        $this->current_stop_time = time();
262
        $type = $this->get_type();
263
        if ($debug) {
264
            error_log('Start - learnpathItem:close');
265
            error_log("Type: ".$type);
266
            error_log("get_id: ".$this->get_id());
267
        }
268
        if ($type !== 'sco') {
269
            if ($type == TOOL_QUIZ || $type == TOOL_HOTPOTATOES || $type == TOOL_H5P) {
270
                $this->get_status(
271
                    true,
272
                    true
273
                );
274
            } else {
275
                /*$this->status = $this->possible_status[2];
276
                if (self::DEBUG) {
277
                    error_log("STATUS changed to: ".$this->status);
278
                }*/
279
            }
280
        }
281
        if ($this->save_on_close) {
282
            if ($debug) {
283
                error_log('save_on_close');
284
            }
285
            $this->save();
286
        }
287
288
        if ($debug) {
289
            error_log('End - learnpathItem:close');
290
        }
291
292
        return true;
293
    }
294
295
    /**
296
     * Deletes all traces of this item in the database.
297
     *
298
     * @return bool true. Doesn't check for errors yet.
299
     */
300
    public function delete()
301
    {
302
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
303
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
304
        $courseId = $this->courseId;
305
306
        $sql = "DELETE FROM $lp_item_view
307
                WHERE c_id = $courseId AND lp_item_id = ".$this->db_id;
308
        Database::query($sql);
309
310
        $sql = "SELECT * FROM $lp_item
311
                WHERE iid = ".$this->db_id;
312
        $res_sel = Database::query($sql);
313
        if (Database::num_rows($res_sel) < 1) {
314
            return false;
315
        }
316
317
        $sql = "DELETE FROM $lp_item
318
                WHERE iid = ".$this->db_id;
319
        Database::query($sql);
320
321
        if (api_get_setting('search_enabled') === 'true') {
322
            if (!is_null($this->search_did)) {
323
                $di = new ChamiloIndexer();
324
                $di->remove_document($this->search_did);
325
            }
326
        }
327
328
        return true;
329
    }
330
331
    /**
332
     * Gets the current attempt_id for this user on this item.
333
     *
334
     * @return int attempt_id for this item view by this user or 1 if none defined
335
     */
336
    public function get_attempt_id()
337
    {
338
        $res = 1;
339
        if (!empty($this->attempt_id)) {
340
            $res = (int) $this->attempt_id;
341
        }
342
343
        return $res;
344
    }
345
346
    /**
347
     * Gets a list of the item's children.
348
     *
349
     * @return array Array of children items IDs
350
     */
351
    public function get_children()
352
    {
353
        $list = [];
354
        foreach ($this->children as $child) {
355
            if (!empty($child)) {
356
                $list[] = $child;
357
            }
358
        }
359
360
        return $list;
361
    }
362
363
    /**
364
     * Gets the core_exit value from the database.
365
     */
366
    public function get_core_exit()
367
    {
368
        return $this->core_exit;
369
    }
370
371
    /**
372
     * Gets the credit information (rather scorm-stuff) based on current status
373
     * and reinit autorization. Credit tells the sco(content) if Chamilo will
374
     * record the data it is sent (credit) or not (no-credit).
375
     *
376
     * @return string 'credit' or 'no-credit'. Defaults to 'credit'
377
     *                Because if we don't know enough about this item, it's probably because
378
     *                it was never used before.
379
     */
380
    public function get_credit()
381
    {
382
        if (self::DEBUG > 1) {
383
            error_log('learnpathItem::get_credit()', 0);
384
        }
385
        $credit = 'credit';
386
        // Now check the value of prevent_reinit (if it's 0, return credit as
387
        // the default was).
388
        // If prevent_reinit == 1 (or more).
389
        if ($this->get_prevent_reinit() != 0) {
390
            // If status is not attempted or incomplete, credit anyway.
391
            // Otherwise:
392
            // Check the status in the database rather than in the object, as
393
            // checking in the object would always return "no-credit" when we
394
            // want to set it to completed.
395
            $status = $this->get_status(true);
396
            if (self::DEBUG > 2) {
397
                error_log(
398
                    'learnpathItem::get_credit() - get_prevent_reinit!=0 and '.
399
                    'status is '.$status,
400
                    0
401
                );
402
            }
403
            //0=not attempted - 1 = incomplete
404
            if ($status != $this->possible_status[0] &&
405
                $status != $this->possible_status[1]
406
            ) {
407
                $credit = 'no-credit';
408
            }
409
        }
410
        if (self::DEBUG > 1) {
411
            error_log("learnpathItem::get_credit() returns: $credit");
412
        }
413
414
        return $credit;
415
    }
416
417
    /**
418
     * Gets the current start time property.
419
     *
420
     * @return int Current start time, or current time if none
421
     */
422
    public function get_current_start_time()
423
    {
424
        if (empty($this->current_start_time)) {
425
            return time();
426
        }
427
428
        return $this->current_start_time;
429
    }
430
431
    /**
432
     * Gets the item's description.
433
     *
434
     * @return string Description
435
     */
436
    public function get_description()
437
    {
438
        if (empty($this->description)) {
439
            return '';
440
        }
441
442
        return $this->description;
443
    }
444
445
    /**
446
     * Gets the file path from the course's root directory, no matter what
447
     * tool it is from.
448
     *
449
     * @param string $path_to_scorm_dir
450
     *
451
     * @return string The file path, or an empty string if there is no file
452
     *                attached, or '-1' if the file must be replaced by an error page
453
     */
454
    public function get_file_path($path_to_scorm_dir = '')
455
    {
456
        $courseId = $this->courseId;
457
        $path = $this->get_path();
458
        $type = $this->get_type();
459
460
        if (empty($path)) {
461
            if ($type === 'dir') {
462
                return '';
463
            } else {
464
                return '-1';
465
            }
466
        } elseif ($path == strval(intval($path))) {
467
            // The path is numeric, so it is a reference to a Chamilo object.
468
            switch ($type) {
469
                case 'dir':
470
                    return '';
471
                case TOOL_DOCUMENT:
472
                    $table_doc = Database::get_course_table(TABLE_DOCUMENT);
473
                    $sql = 'SELECT path
474
                            FROM '.$table_doc.'
475
                            WHERE
476
                                c_id = '.$courseId.' AND
477
                                iid = '.$path;
478
                    $res = Database::query($sql);
479
                    $row = Database::fetch_array($res);
480
                    $real_path = 'document'.$row['path'];
481
482
                    return $real_path;
483
                case TOOL_STUDENTPUBLICATION:
484
                case TOOL_QUIZ:
485
                case TOOL_FORUM:
486
                case TOOL_THREAD:
487
                case TOOL_LINK:
488
                default:
489
                    return '-1';
490
            }
491
        } else {
492
            if (!empty($path_to_scorm_dir)) {
493
                $path = $path_to_scorm_dir.$path;
494
            }
495
496
            return $path;
497
        }
498
    }
499
500
    /**
501
     * Gets the DB ID.
502
     *
503
     * @return int Database ID for the current item
504
     */
505
    public function get_id()
506
    {
507
        if (!empty($this->db_id)) {
508
            return $this->db_id;
509
        }
510
        // TODO: Check this return value is valid for children classes (SCORM?).
511
        return 0;
512
    }
513
514
    /**
515
     * Loads the interactions into the item object, from the database.
516
     * If object interactions exist, they will be overwritten by this function,
517
     * using the database elements only.
518
     */
519
    public function load_interactions()
520
    {
521
        $this->interactions = [];
522
        $courseId = $this->courseId;
523
        $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
524
        $sql = "SELECT id FROM $tbl
525
                WHERE
526
                    c_id = $courseId AND
527
                    lp_item_id = ".$this->db_id." AND
528
                    lp_view_id = ".$this->view_id." AND
529
                    view_count = ".$this->get_view_count();
530
        $res = Database::query($sql);
531
        if (Database::num_rows($res) > 0) {
532
            $row = Database::fetch_array($res);
533
            $lp_iv_id = $row[0];
534
            $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
535
            $sql = "SELECT * FROM $iva_table
536
                    WHERE c_id = $courseId AND lp_iv_id = $lp_iv_id ";
537
            $res_sql = Database::query($sql);
538
            while ($row = Database::fetch_array($res_sql)) {
539
                $this->interactions[$row['interaction_id']] = [
540
                    $row['interaction_id'],
541
                    $row['interaction_type'],
542
                    $row['weighting'],
543
                    $row['completion_time'],
544
                    $row['correct_responses'],
545
                    $row['student_responses'],
546
                    $row['result'],
547
                    $row['latency'],
548
                ];
549
            }
550
        }
551
    }
552
553
    /**
554
     * Gets the current count of interactions recorded in the database.
555
     *
556
     * @param bool $checkdb Whether to count from database or not (defaults to no)
557
     *
558
     * @return int The current number of interactions recorder
559
     */
560
    public function get_interactions_count($checkdb = false)
561
    {
562
        $return = 0;
563
        if (api_is_invitee()) {
564
            // If the user is an invitee, we consider there's no interaction
565
            return 0;
566
        }
567
        $courseId = $this->courseId;
568
569
        if ($checkdb) {
570
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
571
            $sql = "SELECT iid FROM $tbl
572
                    WHERE
573
                        c_id = $courseId AND
574
                        lp_item_id = ".$this->db_id." AND
575
                        lp_view_id = ".$this->view_id." AND
576
                        view_count = ".$this->get_attempt_id();
577
            $res = Database::query($sql);
578
            if (Database::num_rows($res) > 0) {
579
                $row = Database::fetch_array($res);
580
                $lp_iv_id = $row[0];
581
                $iva_table = Database::get_course_table(
582
                    TABLE_LP_IV_INTERACTION
583
                );
584
                $sql = "SELECT count(id) as mycount
585
                        FROM $iva_table
586
                        WHERE c_id = $courseId AND lp_iv_id = $lp_iv_id ";
587
                $res_sql = Database::query($sql);
588
                if (Database::num_rows($res_sql) > 0) {
589
                    $row = Database::fetch_array($res_sql);
590
                    $return = $row['mycount'];
591
                }
592
            }
593
        } else {
594
            if (!empty($this->interactions_count)) {
595
                $return = $this->interactions_count;
596
            }
597
        }
598
599
        return $return;
600
    }
601
602
    /**
603
     * Gets the JavaScript array content to fill the interactions array.
604
     *
605
     * @param bool $checkdb Whether to check directly into the database (default no)
606
     *
607
     * @return string An empty string if no interaction, a JS array definition otherwise
608
     */
609
    public function get_interactions_js_array($checkdb = false)
610
    {
611
        $return = '';
612
        if ($checkdb) {
613
            $this->load_interactions(true);
614
        }
615
        foreach ($this->interactions as $id => $in) {
616
            $return .= "[
617
                '$id',
618
                '".$in[1]."',
619
                '".$in[2]."',
620
                '".$in[3]."',
621
                '".$in[4]."',
622
                '".$in[5]."',
623
                '".$in[6]."',
624
                '".$in[7]."'],";
625
        }
626
        if (!empty($return)) {
627
            $return = substr($return, 0, -1);
628
        }
629
630
        return $return;
631
    }
632
633
    /**
634
     * Gets the current count of objectives recorded in the database.
635
     *
636
     * @return int The current number of objectives recorder
637
     */
638
    public function get_objectives_count()
639
    {
640
        $res = 0;
641
        if (!empty($this->objectives_count)) {
642
            $res = $this->objectives_count;
643
        }
644
645
        return $res;
646
    }
647
648
    /**
649
     * Gets the launch_data field found in imsmanifests (this is SCORM- or
650
     * AICC-related, really).
651
     *
652
     * @return string Launch data as found in imsmanifest and stored in
653
     *                Chamilo (read only). Defaults to ''.
654
     */
655
    public function get_launch_data()
656
    {
657
        if (!empty($this->launch_data)) {
658
            return str_replace(
659
                ["\r", "\n", "'"],
660
                ['\r', '\n', "\\'"],
661
                $this->launch_data
662
            );
663
        }
664
665
        return '';
666
    }
667
668
    /**
669
     * Gets the lesson location.
670
     *
671
     * @return string lesson location as recorded by the SCORM and AICC
672
     *                elements. Defaults to ''
673
     */
674
    public function get_lesson_location()
675
    {
676
        if (!empty($this->lesson_location)) {
677
            return str_replace(
678
                ["\r", "\n", "'"],
679
                ['\r', '\n', "\\'"],
680
                $this->lesson_location
681
            );
682
        }
683
684
        return '';
685
    }
686
687
    /**
688
     * Gets the lesson_mode (scorm feature, but might be used by aicc as well
689
     * as chamilo paths).
690
     *
691
     * The "browse" mode is not supported yet (because there is no such way of
692
     * seeing a sco in Chamilo)
693
     *
694
     * @return string 'browse','normal' or 'review'. Defaults to 'normal'
695
     */
696
    public function get_lesson_mode()
697
    {
698
        $mode = 'normal';
699
        if ($this->get_prevent_reinit() != 0) {
700
            // If prevent_reinit == 0
701
            $my_status = $this->get_status();
702
            if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) {
703
                $mode = 'review';
704
            }
705
        }
706
707
        return $mode;
708
    }
709
710
    /**
711
     * Gets the depth level.
712
     *
713
     * @return int Level. Defaults to 0
714
     */
715
    public function get_level()
716
    {
717
        if (empty($this->level)) {
718
            return 0;
719
        }
720
721
        return $this->level;
722
    }
723
724
    /**
725
     * Gets the mastery score.
726
     */
727
    public function get_mastery_score()
728
    {
729
        if (isset($this->mastery_score)) {
730
            return $this->mastery_score;
731
        }
732
733
        return -1;
734
    }
735
736
    /**
737
     * Gets the maximum (score).
738
     *
739
     * @return int Maximum score. Defaults to 100 if nothing else is defined
740
     */
741
    public function get_max()
742
    {
743
        if ($this->type === 'sco') {
744
            if (!empty($this->view_max_score) && $this->view_max_score > 0) {
745
                return $this->view_max_score;
746
            } else {
747
                if (!empty($this->max_score)) {
748
                    return $this->max_score;
749
                }
750
751
                return 100;
752
            }
753
        } else {
754
            if (!empty($this->max_score)) {
755
                return $this->max_score;
756
            }
757
758
            return 100;
759
        }
760
    }
761
762
    /**
763
     * Gets the maximum time allowed for this user in this attempt on this item.
764
     *
765
     * @return string Time string in SCORM format
766
     *                (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS)
767
     */
768
    public function get_max_time_allowed()
769
    {
770
        if (!empty($this->max_time_allowed)) {
771
            return $this->max_time_allowed;
772
        }
773
774
        return '';
775
    }
776
777
    /**
778
     * Gets the minimum (score).
779
     *
780
     * @return int Minimum score. Defaults to 0
781
     */
782
    public function get_min()
783
    {
784
        if (!empty($this->min_score)) {
785
            return $this->min_score;
786
        }
787
788
        return 0;
789
    }
790
791
    /**
792
     * Gets the parent ID.
793
     *
794
     * @return int Parent ID. Defaults to null
795
     */
796
    public function get_parent()
797
    {
798
        if (!empty($this->parent)) {
799
            return $this->parent;
800
        }
801
        // TODO: Check this return value is valid for children classes (SCORM?).
802
        return null;
803
    }
804
805
    /**
806
     * Gets the path attribute.
807
     *
808
     * @return string Path. Defaults to ''
809
     */
810
    public function get_path()
811
    {
812
        if (empty($this->path)) {
813
            return '';
814
        }
815
816
        return $this->path;
817
    }
818
819
    /**
820
     * Gets the prerequisites string.
821
     *
822
     * @return string empty string or prerequisites string if defined
823
     */
824
    public function get_prereq_string()
825
    {
826
        if (!empty($this->prereq_string)) {
827
            return $this->prereq_string;
828
        }
829
830
        return '';
831
    }
832
833
    /**
834
     * Gets the prevent_reinit attribute value (and sets it if not set already).
835
     *
836
     * @return int 1 or 0 (defaults to 1)
837
     */
838
    public function get_prevent_reinit()
839
    {
840
        if (self::DEBUG > 2) {
841
            error_log('learnpathItem::get_prevent_reinit()', 0);
842
        }
843
        if (!isset($this->prevent_reinit)) {
844
            if (!empty($this->lp_id)) {
845
                $table = Database::get_course_table(TABLE_LP_MAIN);
846
                $sql = "SELECT prevent_reinit
847
                        FROM $table
848
                        WHERE iid = ".$this->lp_id;
849
                $res = Database::query($sql);
850
                if (Database::num_rows($res) < 1) {
851
                    $this->error = 'Could not find parent learnpath in lp table';
852
                    if (self::DEBUG > 2) {
853
                        error_log(
854
                            'LearnpathItem::get_prevent_reinit() - Returning false',
855
                            0
856
                        );
857
                    }
858
859
                    return false;
860
                } else {
861
                    $row = Database::fetch_array($res);
862
                    $this->prevent_reinit = $row['prevent_reinit'];
863
                }
864
            } else {
865
                // Prevent reinit is always 1 by default - see learnpath.class.php
866
                $this->prevent_reinit = 1;
867
            }
868
        }
869
        if (self::DEBUG > 2) {
870
            error_log('End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit);
871
        }
872
873
        return $this->prevent_reinit;
874
    }
875
876
    /**
877
     * Returns 1 if seriousgame_mode is activated, 0 otherwise.
878
     *
879
     * @return int (0 or 1)
880
     *
881
     * @deprecated seriousgame_mode seems not to be used
882
     *
883
     * @author ndiechburg <[email protected]>
884
     */
885
    public function get_seriousgame_mode()
886
    {
887
        if (!isset($this->seriousgame_mode)) {
888
            if (!empty($this->lp_id)) {
889
                $table = Database::get_course_table(TABLE_LP_MAIN);
890
                $sql = "SELECT seriousgame_mode
891
                        FROM $table
892
                        WHERE iid = ".$this->lp_id;
893
                $res = Database::query($sql);
894
                if (Database::num_rows($res) < 1) {
895
                    $this->error = 'Could not find parent learnpath in learnpath table';
896
897
                    return false;
898
                } else {
899
                    $row = Database::fetch_array($res);
900
                    $this->seriousgame_mode = isset($row['seriousgame_mode']) ? $row['seriousgame_mode'] : 0;
901
                }
902
            } else {
903
                $this->seriousgame_mode = 0; //SeriousGame mode is always off by default
904
            }
905
        }
906
907
        return $this->seriousgame_mode;
908
    }
909
910
    /**
911
     * Gets the item's reference column.
912
     *
913
     * @return string The item's reference field (generally used for SCORM identifiers)
914
     */
915
    public function get_ref()
916
    {
917
        return $this->ref;
918
    }
919
920
    /**
921
     * Gets the list of included resources as a list of absolute or relative
922
     * paths of resources included in the current item. This allows for a
923
     * better SCORM export. The list will generally include pictures, flash
924
     * objects, java applets, or any other stuff included in the source of the
925
     * current item. The current item is expected to be an HTML file. If it
926
     * is not, then the function will return and empty list.
927
     *
928
     * @param string $type        (one of the Chamilo tools) - optional (otherwise takes the current item's type)
929
     * @param string $abs_path    absolute file path - optional (otherwise takes the current item's path)
930
     * @param int    $recursivity level of recursivity we're in
931
     *
932
     * @return array List of file paths.
933
     *               An additional field containing 'local' or 'remote' helps determine if
934
     *               the file should be copied into the zip or just linked
935
     */
936
    public function get_resources_from_source(
937
        $type = null,
938
        $abs_path = null,
939
        $recursivity = 1
940
    ) {
941
        $max = 5;
942
        if ($recursivity > $max) {
943
            return [];
944
        }
945
946
        $type = empty($type) ? $this->get_type() : $type;
947
948
        if (!isset($abs_path)) {
949
            $path = $this->get_file_path();
950
            $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path;
951
        }
952
953
        $files_list = [];
954
        switch ($type) {
955
            case TOOL_DOCUMENT:
956
            case TOOL_QUIZ:
957
            case TOOL_H5P:
958
            case 'sco':
959
                // Get the document and, if HTML, open it.
960
                if (!is_file($abs_path)) {
961
                    // The file could not be found.
962
                    return false;
963
                }
964
965
                // for now, read the whole file in one go (that's gonna be
966
                // a problem when the file is too big).
967
                $info = pathinfo($abs_path);
968
                $ext = $info['extension'];
969
970
                switch (strtolower($ext)) {
971
                    case 'html':
972
                    case 'htm':
973
                    case 'shtml':
974
                    case 'css':
975
                        $wantedAttributes = [
976
                            'src',
977
                            'url',
978
                            '@import',
979
                            'href',
980
                            'value',
981
                        ];
982
983
                        // Parse it for included resources.
984
                        $fileContent = file_get_contents($abs_path);
985
                        // Get an array of attributes from the HTML source.
986
                        $attributes = DocumentManager::parse_HTML_attributes(
987
                            $fileContent,
988
                            $wantedAttributes
989
                        );
990
991
                        // Look at 'src' attributes in this file
992
                        foreach ($wantedAttributes as $attr) {
993
                            if (isset($attributes[$attr])) {
994
                                // Find which kind of path these are (local or remote).
995
                                $sources = $attributes[$attr];
996
997
                                foreach ($sources as $source) {
998
                                    // Skip what is obviously not a resource.
999
                                    if (strpos($source, "+this.")) {
1000
                                        continue;
1001
                                    } // javascript code - will still work unaltered.
1002
                                    if (strpos($source, '.') === false) {
1003
                                        continue;
1004
                                    } // No dot, should not be an external file anyway.
1005
                                    if (strpos($source, 'mailto:')) {
1006
                                        continue;
1007
                                    } // mailto link.
1008
                                    if (strpos($source, ';') &&
1009
                                        !strpos($source, '&amp;')
1010
                                    ) {
1011
                                        continue;
1012
                                    } // Avoid code - that should help.
1013
1014
                                    if ($attr == 'value') {
1015
                                        if (strpos($source, 'mp3file')) {
1016
                                            $files_list[] = [
1017
                                                substr(
1018
                                                    $source,
1019
                                                    0,
1020
                                                    strpos(
1021
                                                        $source,
1022
                                                        '.swf'
1023
                                                    ) + 4
1024
                                                ),
1025
                                                'local',
1026
                                                'abs',
1027
                                            ];
1028
                                            $mp3file = substr(
1029
                                                $source,
1030
                                                strpos(
1031
                                                    $source,
1032
                                                    'mp3file='
1033
                                                ) + 8
1034
                                            );
1035
                                            if (substr($mp3file, 0, 1) == '/') {
1036
                                                $files_list[] = [
1037
                                                    $mp3file,
1038
                                                    'local',
1039
                                                    'abs',
1040
                                                ];
1041
                                            } else {
1042
                                                $files_list[] = [
1043
                                                    $mp3file,
1044
                                                    'local',
1045
                                                    'rel',
1046
                                                ];
1047
                                            }
1048
                                        } elseif (strpos($source, 'flv=') === 0) {
1049
                                            $source = substr($source, 4);
1050
                                            if (strpos($source, '&') > 0) {
1051
                                                $source = substr(
1052
                                                    $source,
1053
                                                    0,
1054
                                                    strpos($source, '&')
1055
                                                );
1056
                                            }
1057
                                            if (strpos($source, '://') > 0) {
1058
                                                if (strpos($source, api_get_path(WEB_PATH)) !== false) {
1059
                                                    // We found the current portal url.
1060
                                                    $files_list[] = [
1061
                                                        $source,
1062
                                                        'local',
1063
                                                        'url',
1064
                                                    ];
1065
                                                } else {
1066
                                                    // We didn't find any trace of current portal.
1067
                                                    $files_list[] = [
1068
                                                        $source,
1069
                                                        'remote',
1070
                                                        'url',
1071
                                                    ];
1072
                                                }
1073
                                            } else {
1074
                                                $files_list[] = [
1075
                                                    $source,
1076
                                                    'local',
1077
                                                    'abs',
1078
                                                ];
1079
                                            }
1080
                                            continue; // Skipping anything else to avoid two entries
1081
                                            //(while the others can have sub-files in their url, flv's can't).
1082
                                        }
1083
                                    }
1084
1085
                                    if (strpos($source, '://') > 0) {
1086
                                        // Cut at '?' in a URL with params.
1087
                                        if (strpos($source, '?') > 0) {
1088
                                            $second_part = substr(
1089
                                                $source,
1090
                                                strpos($source, '?')
1091
                                            );
1092
                                            if (strpos($second_part, '://') > 0) {
1093
                                                // If the second part of the url contains a url too,
1094
                                                // treat the second one before cutting.
1095
                                                $pos1 = strpos(
1096
                                                    $second_part,
1097
                                                    '='
1098
                                                );
1099
                                                $pos2 = strpos(
1100
                                                    $second_part,
1101
                                                    '&'
1102
                                                );
1103
                                                $second_part = substr(
1104
                                                    $second_part,
1105
                                                    $pos1 + 1,
1106
                                                    $pos2 - ($pos1 + 1)
1107
                                                );
1108
                                                if (strpos($second_part, api_get_path(WEB_PATH)) !== false) {
1109
                                                    // We found the current portal url.
1110
                                                    $files_list[] = [
1111
                                                        $second_part,
1112
                                                        'local',
1113
                                                        'url',
1114
                                                    ];
1115
                                                    $in_files_list[] = self::get_resources_from_source(
1116
                                                        TOOL_DOCUMENT,
1117
                                                        $second_part,
1118
                                                        $recursivity + 1
1119
                                                    );
1120
                                                    if (count($in_files_list) > 0) {
1121
                                                        $files_list = array_merge(
1122
                                                            $files_list,
1123
                                                            $in_files_list
1124
                                                        );
1125
                                                    }
1126
                                                } else {
1127
                                                    // We didn't find any trace of current portal.
1128
                                                    $files_list[] = [
1129
                                                        $second_part,
1130
                                                        'remote',
1131
                                                        'url',
1132
                                                    ];
1133
                                                }
1134
                                            } elseif (strpos($second_part, '=') > 0) {
1135
                                                if (substr($second_part, 0, 1) === '/') {
1136
                                                    // Link starts with a /,
1137
                                                    // making it absolute (relative to DocumentRoot).
1138
                                                    $files_list[] = [
1139
                                                        $second_part,
1140
                                                        'local',
1141
                                                        'abs',
1142
                                                    ];
1143
                                                    $in_files_list[] = self::get_resources_from_source(
1144
                                                        TOOL_DOCUMENT,
1145
                                                        $second_part,
1146
                                                        $recursivity + 1
1147
                                                    );
1148
                                                    if (count($in_files_list) > 0) {
1149
                                                        $files_list = array_merge(
1150
                                                            $files_list,
1151
                                                            $in_files_list
1152
                                                        );
1153
                                                    }
1154
                                                } elseif (strstr($second_part, '..') === 0) {
1155
                                                    // Link is relative but going back in the hierarchy.
1156
                                                    $files_list[] = [
1157
                                                        $second_part,
1158
                                                        'local',
1159
                                                        'rel',
1160
                                                    ];
1161
                                                    $dir = dirname(
1162
                                                        $abs_path
1163
                                                    );
1164
                                                    $new_abs_path = realpath(
1165
                                                        $dir.'/'.$second_part
1166
                                                    );
1167
                                                    $in_files_list[] = self::get_resources_from_source(
1168
                                                        TOOL_DOCUMENT,
1169
                                                        $new_abs_path,
1170
                                                        $recursivity + 1
1171
                                                    );
1172
                                                    if (count($in_files_list) > 0) {
1173
                                                        $files_list = array_merge(
1174
                                                            $files_list,
1175
                                                            $in_files_list
1176
                                                        );
1177
                                                    }
1178
                                                } else {
1179
                                                    // No starting '/', making it relative to current document's path.
1180
                                                    if (substr($second_part, 0, 2) == './') {
1181
                                                        $second_part = substr(
1182
                                                            $second_part,
1183
                                                            2
1184
                                                        );
1185
                                                    }
1186
                                                    $files_list[] = [
1187
                                                        $second_part,
1188
                                                        'local',
1189
                                                        'rel',
1190
                                                    ];
1191
                                                    $dir = dirname(
1192
                                                        $abs_path
1193
                                                    );
1194
                                                    $new_abs_path = realpath(
1195
                                                        $dir.'/'.$second_part
1196
                                                    );
1197
                                                    $in_files_list[] = self::get_resources_from_source(
1198
                                                        TOOL_DOCUMENT,
1199
                                                        $new_abs_path,
1200
                                                        $recursivity + 1
1201
                                                    );
1202
                                                    if (count($in_files_list) > 0) {
1203
                                                        $files_list = array_merge(
1204
                                                            $files_list,
1205
                                                            $in_files_list
1206
                                                        );
1207
                                                    }
1208
                                                }
1209
                                            }
1210
                                            // Leave that second part behind now.
1211
                                            $source = substr(
1212
                                                $source,
1213
                                                0,
1214
                                                strpos($source, '?')
1215
                                            );
1216
                                            if (strpos($source, '://') > 0) {
1217
                                                if (strpos($source, api_get_path(WEB_PATH)) !== false) {
1218
                                                    // We found the current portal url.
1219
                                                    $files_list[] = [
1220
                                                        $source,
1221
                                                        'local',
1222
                                                        'url',
1223
                                                    ];
1224
                                                    $in_files_list[] = self::get_resources_from_source(
1225
                                                        TOOL_DOCUMENT,
1226
                                                        $source,
1227
                                                        $recursivity + 1
1228
                                                    );
1229
                                                    if (count($in_files_list) > 0) {
1230
                                                        $files_list = array_merge(
1231
                                                            $files_list,
1232
                                                            $in_files_list
1233
                                                        );
1234
                                                    }
1235
                                                } else {
1236
                                                    // We didn't find any trace of current portal.
1237
                                                    $files_list[] = [
1238
                                                        $source,
1239
                                                        'remote',
1240
                                                        'url',
1241
                                                    ];
1242
                                                }
1243
                                            } else {
1244
                                                // No protocol found, make link local.
1245
                                                if (substr($source, 0, 1) === '/') {
1246
                                                    // Link starts with a /, making it absolute (relative to DocumentRoot).
1247
                                                    $files_list[] = [
1248
                                                        $source,
1249
                                                        'local',
1250
                                                        'abs',
1251
                                                    ];
1252
                                                    $in_files_list[] = self::get_resources_from_source(
1253
                                                        TOOL_DOCUMENT,
1254
                                                        $source,
1255
                                                        $recursivity + 1
1256
                                                    );
1257
                                                    if (count($in_files_list) > 0) {
1258
                                                        $files_list = array_merge(
1259
                                                            $files_list,
1260
                                                            $in_files_list
1261
                                                        );
1262
                                                    }
1263
                                                } elseif (strstr($source, '..') === 0) {
1264
                                                    // Link is relative but going back in the hierarchy.
1265
                                                    $files_list[] = [
1266
                                                        $source,
1267
                                                        'local',
1268
                                                        'rel',
1269
                                                    ];
1270
                                                    $dir = dirname(
1271
                                                        $abs_path
1272
                                                    );
1273
                                                    $new_abs_path = realpath(
1274
                                                        $dir.'/'.$source
1275
                                                    );
1276
                                                    $in_files_list[] = self::get_resources_from_source(
1277
                                                        TOOL_DOCUMENT,
1278
                                                        $new_abs_path,
1279
                                                        $recursivity + 1
1280
                                                    );
1281
                                                    if (count($in_files_list) > 0) {
1282
                                                        $files_list = array_merge(
1283
                                                            $files_list,
1284
                                                            $in_files_list
1285
                                                        );
1286
                                                    }
1287
                                                } else {
1288
                                                    // No starting '/', making it relative to current document's path.
1289
                                                    if (substr($source, 0, 2) == './') {
1290
                                                        $source = substr(
1291
                                                            $source,
1292
                                                            2
1293
                                                        );
1294
                                                    }
1295
                                                    $files_list[] = [
1296
                                                        $source,
1297
                                                        'local',
1298
                                                        'rel',
1299
                                                    ];
1300
                                                    $dir = dirname(
1301
                                                        $abs_path
1302
                                                    );
1303
                                                    $new_abs_path = realpath(
1304
                                                        $dir.'/'.$source
1305
                                                    );
1306
                                                    $in_files_list[] = self::get_resources_from_source(
1307
                                                        TOOL_DOCUMENT,
1308
                                                        $new_abs_path,
1309
                                                        $recursivity + 1
1310
                                                    );
1311
                                                    if (count($in_files_list) > 0) {
1312
                                                        $files_list = array_merge(
1313
                                                            $files_list,
1314
                                                            $in_files_list
1315
                                                        );
1316
                                                    }
1317
                                                }
1318
                                            }
1319
                                        }
1320
1321
                                        // Found some protocol there.
1322
                                        if (strpos($source, api_get_path(WEB_PATH)) !== false) {
1323
                                            // We found the current portal url.
1324
                                            $files_list[] = [
1325
                                                $source,
1326
                                                'local',
1327
                                                'url',
1328
                                            ];
1329
                                            $in_files_list[] = self::get_resources_from_source(
1330
                                                TOOL_DOCUMENT,
1331
                                                $source,
1332
                                                $recursivity + 1
1333
                                            );
1334
                                            if (count($in_files_list) > 0) {
1335
                                                $files_list = array_merge(
1336
                                                    $files_list,
1337
                                                    $in_files_list
1338
                                                );
1339
                                            }
1340
                                        } else {
1341
                                            // We didn't find any trace of current portal.
1342
                                            $files_list[] = [
1343
                                                $source,
1344
                                                'remote',
1345
                                                'url',
1346
                                            ];
1347
                                        }
1348
                                    } else {
1349
                                        // No protocol found, make link local.
1350
                                        if (substr($source, 0, 1) === '/') {
1351
                                            // Link starts with a /, making it absolute (relative to DocumentRoot).
1352
                                            $files_list[] = [
1353
                                                $source,
1354
                                                'local',
1355
                                                'abs',
1356
                                            ];
1357
                                            $in_files_list[] = self::get_resources_from_source(
1358
                                                TOOL_DOCUMENT,
1359
                                                $source,
1360
                                                $recursivity + 1
1361
                                            );
1362
                                            if (count($in_files_list) > 0) {
1363
                                                $files_list = array_merge(
1364
                                                    $files_list,
1365
                                                    $in_files_list
1366
                                                );
1367
                                            }
1368
                                        } elseif (strstr($source, '..') === 0) {
1369
                                            // Link is relative but going back in the hierarchy.
1370
                                            $files_list[] = [
1371
                                                $source,
1372
                                                'local',
1373
                                                'rel',
1374
                                            ];
1375
                                            $dir = dirname($abs_path);
1376
                                            $new_abs_path = realpath(
1377
                                                $dir.'/'.$source
1378
                                            );
1379
                                            $in_files_list[] = self::get_resources_from_source(
1380
                                                TOOL_DOCUMENT,
1381
                                                $new_abs_path,
1382
                                                $recursivity + 1
1383
                                            );
1384
                                            if (count($in_files_list) > 0) {
1385
                                                $files_list = array_merge(
1386
                                                    $files_list,
1387
                                                    $in_files_list
1388
                                                );
1389
                                            }
1390
                                        } else {
1391
                                            // No starting '/', making it relative to current document's path.
1392
                                            if (strpos($source, 'width=') ||
1393
                                                strpos($source, 'autostart=')
1394
                                            ) {
1395
                                                continue;
1396
                                            }
1397
1398
                                            if (substr($source, 0, 2) == './') {
1399
                                                $source = substr(
1400
                                                    $source,
1401
                                                    2
1402
                                                );
1403
                                            }
1404
                                            $files_list[] = [
1405
                                                $source,
1406
                                                'local',
1407
                                                'rel',
1408
                                            ];
1409
                                            $dir = dirname($abs_path);
1410
                                            $new_abs_path = realpath(
1411
                                                $dir.'/'.$source
1412
                                            );
1413
                                            $in_files_list[] = self::get_resources_from_source(
1414
                                                TOOL_DOCUMENT,
1415
                                                $new_abs_path,
1416
                                                $recursivity + 1
1417
                                            );
1418
                                            if (count($in_files_list) > 0) {
1419
                                                $files_list = array_merge(
1420
                                                    $files_list,
1421
                                                    $in_files_list
1422
                                                );
1423
                                            }
1424
                                        }
1425
                                    }
1426
                                }
1427
                            }
1428
                        }
1429
                        break;
1430
                    default:
1431
                        break;
1432
                }
1433
1434
                break;
1435
            default: // Ignore.
1436
                break;
1437
        }
1438
1439
        $checked_files_list = [];
1440
        $checked_array_list = [];
1441
        foreach ($files_list as $idx => $file) {
1442
            if (!empty($file[0])) {
1443
                if (!in_array($file[0], $checked_files_list)) {
1444
                    $checked_files_list[] = $files_list[$idx][0];
1445
                    $checked_array_list[] = $files_list[$idx];
1446
                }
1447
            }
1448
        }
1449
1450
        return $checked_array_list;
1451
    }
1452
1453
    /**
1454
     * Gets the score.
1455
     *
1456
     * @return float The current score or 0 if no score set yet
1457
     */
1458
    public function get_score()
1459
    {
1460
        $res = 0;
1461
        if (!empty($this->current_score)) {
1462
            $res = $this->current_score;
1463
        }
1464
1465
        return $res;
1466
    }
1467
1468
    /**
1469
     * Gets the item status.
1470
     *
1471
     * @param bool $check_db     Do or don't check into the database for the
1472
     *                           latest value. Optional. Default is true
1473
     * @param bool $update_local Do or don't update the local attribute
1474
     *                           value with what's been found in DB
1475
     *
1476
     * @return string Current status or 'Not attempted' if no status set yet
1477
     */
1478
    public function get_status($check_db = true, $update_local = false)
1479
    {
1480
        $courseId = $this->courseId;
1481
        $debug = self::DEBUG;
1482
        if ($debug) {
1483
            error_log('learnpathItem::get_status() on item '.$this->db_id);
1484
        }
1485
        if ($check_db) {
1486
            if ($debug) {
1487
                error_log('learnpathItem::get_status(): checking db');
1488
            }
1489
            if (!empty($this->db_item_view_id) && !empty($courseId)) {
1490
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1491
                $sql = "SELECT status FROM $table
1492
                        WHERE
1493
                            c_id = $courseId AND
1494
                            iid = '".$this->db_item_view_id."' AND
1495
                            view_count = '".$this->get_attempt_id()."'";
1496
                $res = Database::query($sql);
1497
                if (Database::num_rows($res) == 1) {
1498
                    $row = Database::fetch_array($res);
1499
                    if ($update_local) {
1500
                        if ($debug) {
1501
                            error_log('Status to be updated to: '.$row['status']);
1502
                        }
1503
                        $this->set_status($row['status']);
1504
                    }
1505
1506
                    if ($debug) {
1507
                        error_log('Return of $row[status]: '.$row['status']);
1508
                    }
1509
1510
                    return $row['status'];
1511
                }
1512
            }
1513
        } else {
1514
            if ($debug) {
1515
                error_log('Status from this->status: '.$this->status);
1516
            }
1517
            if (!empty($this->status)) {
1518
                return $this->status;
1519
            }
1520
        }
1521
1522
        if ($debug) {
1523
            error_log('Return default: '.$this->possible_status[0]);
1524
        }
1525
1526
        return $this->possible_status[0];
1527
    }
1528
1529
    /**
1530
     * Gets the suspend data.
1531
     */
1532
    public function get_suspend_data()
1533
    {
1534
        // TODO: Improve cleaning of breaklines ... it works but is it really
1535
        // a beautiful way to do it ?
1536
        if (!empty($this->current_data)) {
1537
            return str_replace(
1538
                ["\r", "\n", "'"],
1539
                ['\r', '\n', "\\'"],
1540
                $this->current_data
1541
            );
1542
        }
1543
1544
        return '';
1545
    }
1546
1547
    /**
1548
     * @param string $origin
1549
     * @param string $time
1550
     *
1551
     * @return string
1552
     */
1553
    public static function getScormTimeFromParameter(
1554
        $origin = 'php',
1555
        $time = null
1556
    ) {
1557
        $h = get_lang('h');
1558
        if (!isset($time)) {
1559
            if ($origin == 'js') {
1560
                return '00 : 00: 00';
1561
            }
1562
1563
            return '00 '.$h.' 00 \' 00"';
1564
        }
1565
1566
        return api_format_time($time, $origin);
1567
    }
1568
1569
    /**
1570
     * Gets the total time spent on this item view so far.
1571
     *
1572
     * @param string   $origin     Origin of the request. If coming from PHP,
1573
     *                             send formatted as xxhxx'xx", otherwise use scorm format 00:00:00
1574
     * @param int|null $given_time Given time is a default time to return formatted
1575
     * @param bool     $query_db   Whether to get the value from db or from memory
1576
     *
1577
     * @return string A string with the time in SCORM format
1578
     */
1579
    public function get_scorm_time(
1580
        $origin = 'php',
1581
        $given_time = null,
1582
        $query_db = false
1583
    ) {
1584
        $time = null;
1585
        $courseId = $this->courseId;
1586
        if (empty($courseId)) {
1587
            $courseId = api_get_course_int_id();
1588
        }
1589
1590
        $courseId = (int) $courseId;
1591
1592
        if (!isset($given_time)) {
1593
            if (self::DEBUG > 2) {
1594
                error_log(
1595
                    'learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time,
1596
                    0
1597
                );
1598
            }
1599
            if ($query_db === true) {
1600
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1601
                $sql = "SELECT start_time, total_time
1602
                        FROM $table
1603
                        WHERE
1604
                            c_id = $courseId AND
1605
                            iid = '".$this->db_item_view_id."' AND
1606
                            view_count = '".$this->get_attempt_id()."'";
1607
                $res = Database::query($sql);
1608
                $row = Database::fetch_array($res);
1609
                $start = $row['start_time'];
1610
                $stop = $start + $row['total_time'];
1611
            } else {
1612
                $start = $this->current_start_time;
1613
                $stop = $this->current_stop_time;
1614
            }
1615
            if (!empty($start)) {
1616
                if (!empty($stop)) {
1617
                    $time = $stop - $start;
1618
                } else {
1619
                    $time = time() - $start;
1620
                }
1621
            }
1622
        } else {
1623
            $time = $given_time;
1624
        }
1625
        if (self::DEBUG > 2) {
1626
            error_log(
1627
                'learnpathItem::get_scorm_time(): intermediate = '.$time,
1628
                0
1629
            );
1630
        }
1631
        $time = api_format_time($time, $origin);
1632
1633
        return $time;
1634
    }
1635
1636
    /**
1637
     * Get the extra terms (tags) that identify this item.
1638
     *
1639
     * @return mixed
1640
     */
1641
    public function get_terms()
1642
    {
1643
        $table = Database::get_course_table(TABLE_LP_ITEM);
1644
        $sql = "SELECT * FROM $table
1645
                WHERE iid = ".intval($this->db_id);
1646
        $res = Database::query($sql);
1647
        $row = Database::fetch_array($res);
1648
1649
        return $row['terms'];
1650
    }
1651
1652
    /**
1653
     * Returns the item's title.
1654
     *
1655
     * @return string Title
1656
     */
1657
    public function get_title()
1658
    {
1659
        if (empty($this->title)) {
1660
            return '';
1661
        }
1662
1663
        return $this->title;
1664
    }
1665
1666
    /**
1667
     * Returns the total time used to see that item.
1668
     *
1669
     * @return int Total time
1670
     */
1671
    public function get_total_time()
1672
    {
1673
        $debug = self::DEBUG;
1674
        if ($debug) {
1675
            error_log(
1676
                'learnpathItem::get_total_time() for item '.$this->db_id.
1677
                ' - Initially, current_start_time = '.$this->current_start_time.
1678
                ' and current_stop_time = '.$this->current_stop_time,
1679
                0
1680
            );
1681
        }
1682
        if ($this->current_start_time == 0) {
1683
            // Shouldn't be necessary thanks to the open() method.
1684
            if ($debug) {
1685
                error_log(
1686
                    'learnpathItem::get_total_time() - Current start time was empty',
1687
                    0
1688
                );
1689
            }
1690
            $this->current_start_time = time();
1691
        }
1692
1693
        if (time() < $this->current_stop_time ||
1694
            $this->current_stop_time == 0
1695
        ) {
1696
            if ($debug) {
1697
                error_log(
1698
                    'learnpathItem::get_total_time() - Current stop time was '
1699
                    .'greater than the current time or was empty',
1700
                    0
1701
                );
1702
            }
1703
            // If this case occurs, then we risk to write huge time data in db.
1704
            // In theory, stop time should be *always* updated here, but it
1705
            // might be used in some unknown goal.
1706
            $this->current_stop_time = time();
1707
        }
1708
1709
        $time = $this->current_stop_time - $this->current_start_time;
1710
1711
        if ($time < 0) {
1712
            if ($debug) {
1713
                error_log(
1714
                    'learnpathItem::get_total_time() - Time smaller than 0. Returning 0',
1715
                    0
1716
                );
1717
            }
1718
1719
            return 0;
1720
        } else {
1721
            $time = $this->fixAbusiveTime($time);
1722
            if ($debug) {
1723
                error_log(
1724
                    'Current start time = '.$this->current_start_time.', current stop time = '.
1725
                    $this->current_stop_time.' Returning '.$time."-----------\n"
1726
                );
1727
            }
1728
1729
            return $time;
1730
        }
1731
    }
1732
1733
    /**
1734
     * Sometimes time recorded for a learning path item is superior to the maximum allowed duration of the session.
1735
     * In this case, this session resets the time for that particular learning path item to 5 minutes
1736
     * (something more realistic, that is also used when leaving the portal without closing one's session).
1737
     *
1738
     * @param int $time
1739
     *
1740
     * @return int
1741
     */
1742
    public function fixAbusiveTime($time)
1743
    {
1744
        // Code based from Event::courseLogout
1745
        $sessionLifetime = api_get_configuration_value('session_lifetime');
1746
        // If session life time too big use 1 hour
1747
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
1748
            $sessionLifetime = 3600;
1749
        }
1750
1751
        if (!Tracking::minimumTimeAvailable(api_get_session_id(), api_get_course_int_id())) {
1752
            $fixedAddedMinute = 5 * 60; // Add only 5 minutes
1753
            $defaultTimeAdded = api_get_configuration_value('time_to_be_registered_for_abusiveTime');
1754
            if (!empty($defaultTimeAdded)) {
1755
                $fixedAddedMinute = $defaultTimeAdded;
1756
            }
1757
            if ($time > $sessionLifetime) {
1758
                if (api_get_setting('server_type') === 'test') {
1759
                    error_log("fixAbusiveTime: Total time is too big: $time replaced with: $fixedAddedMinute");
1760
                    error_log("item_id : ".$this->db_id." lp_item_view.iid: ".$this->db_item_view_id);
1761
                }
1762
                $time = $fixedAddedMinute;
1763
            }
1764
1765
            return $time;
1766
        } else {
1767
            // Calulate minimum and accumulated time
1768
            $user_id = api_get_user_id();
1769
            $myLP = learnpath::getLpFromSession(api_get_course_id(), $this->lp_id, $user_id);
1770
            $timeLp = $myLP->getAccumulateWorkTime();
1771
            $timeTotalCourse = $myLP->getAccumulateWorkTimeTotalCourse();
1772
            /*
1773
            $timeLp = $_SESSION['oLP']->getAccumulateWorkTime();
1774
            $timeTotalCourse = $_SESSION['oLP']->getAccumulateWorkTimeTotalCourse();
1775
            */
1776
            // Minimum connection percentage
1777
            $perc = 100;
1778
            // Time from the course
1779
            $tc = $timeTotalCourse;
1780
            /*if (!empty($sessionId) && $sessionId != 0) {
1781
                $sql = "SELECT hours, perc FROM plugin_licences_course_session WHERE session_id = $sessionId";
1782
                $res = Database::query($sql);
1783
                if (Database::num_rows($res) > 0) {
1784
                    $aux = Database::fetch_assoc($res);
1785
                    $perc = $aux['perc'];
1786
                    $tc = $aux['hours'] * 60;
1787
                }
1788
            }*/
1789
            // Percentage of the learning paths
1790
            $pl = 0;
1791
            if (!empty($timeTotalCourse)) {
1792
                $pl = $timeLp / $timeTotalCourse;
1793
            }
1794
1795
            // Minimum time for each learning path
1796
            $accumulateWorkTime = ($pl * $tc * $perc / 100);
1797
            $time_seg = intval($accumulateWorkTime * 60);
1798
1799
            if ($time_seg < $sessionLifetime) {
1800
                $sessionLifetime = $time_seg;
1801
            }
1802
1803
            if ($time > $sessionLifetime) {
1804
                $fixedAddedMinute = $time_seg + mt_rand(0, 300);
1805
                if (self::DEBUG > 2) {
1806
                    error_log("Total time is too big: $time replaced with: $fixedAddedMinute");
1807
                }
1808
                $time = $fixedAddedMinute;
1809
            }
1810
1811
            return $time;
1812
        }
1813
    }
1814
1815
    /**
1816
     * Gets the item type.
1817
     *
1818
     * @return string The item type (can be doc, dir, sco, asset)
1819
     */
1820
    public function get_type()
1821
    {
1822
        $res = 'asset';
1823
        if (!empty($this->type)) {
1824
            $res = $this->type;
1825
        }
1826
1827
        return $res;
1828
    }
1829
1830
    /**
1831
     * Gets the view count for this item.
1832
     *
1833
     * @return int Number of attempts or 0
1834
     */
1835
    public function get_view_count()
1836
    {
1837
        if (!empty($this->attempt_id)) {
1838
            return $this->attempt_id;
1839
        }
1840
1841
        return 0;
1842
    }
1843
1844
    /**
1845
     * Tells if an item is done ('completed','passed','succeeded') or not.
1846
     *
1847
     * @return bool True if the item is done ('completed','passed','succeeded'),
1848
     *              false otherwise
1849
     */
1850
    public function is_done()
1851
    {
1852
        $completedStatusList = [
1853
            'completed',
1854
            'passed',
1855
            'succeeded',
1856
            'failed',
1857
        ];
1858
1859
        if ($this->status_is($completedStatusList)) {
1860
            if (self::DEBUG > 2) {
1861
                error_log(
1862
                    'learnpath::is_done() - Item '.$this->get_id(
1863
                    ).' is complete',
1864
                    0
1865
                );
1866
            }
1867
1868
            return true;
1869
        } else {
1870
            if (self::DEBUG > 2) {
1871
                error_log(
1872
                    'learnpath::is_done() - Item '.$this->get_id(
1873
                    ).' is not complete',
1874
                    0
1875
                );
1876
            }
1877
1878
            return false;
1879
        }
1880
    }
1881
1882
    /**
1883
     * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status).
1884
     *
1885
     * @return int -1 if retaking the sco another time for credit is not allowed,
1886
     *             0 if it is not allowed but the item has to be finished
1887
     *             1 if it is allowed. Defaults to 1
1888
     */
1889
    public function isRestartAllowed()
1890
    {
1891
        $restart = 1;
1892
        $mystatus = $this->get_status(true);
1893
        if ($this->get_prevent_reinit() > 0) {
1894
            // If prevent_reinit == 1 (or more)
1895
            // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise:
1896
            if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) {
1897
                $restart = -1;
1898
            } else { //status incompleted or not attempted
1899
                $restart = 0;
1900
            }
1901
        } else {
1902
            if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) {
1903
                $restart = -1;
1904
            }
1905
        }
1906
        if (self::DEBUG > 2) {
1907
            error_log(
1908
                'New LP - End of learnpathItem::isRestartAllowed() - Returning '.$restart,
1909
                0
1910
            );
1911
        }
1912
1913
        return $restart;
1914
    }
1915
1916
    /**
1917
     * Opens/launches the item. Initialises runtime values.
1918
     *
1919
     * @param bool $allow_new_attempt
1920
     *
1921
     * @return bool true on success, false on failure
1922
     */
1923
    public function open($allow_new_attempt = false)
1924
    {
1925
        if (self::DEBUG > 0) {
1926
            error_log('learnpathItem::open()', 0);
1927
        }
1928
        if ($this->prevent_reinit == 0) {
1929
            $this->current_score = 0;
1930
            $this->current_start_time = time();
1931
            // In this case, as we are opening the item, what is important to us
1932
            // is the database status, in order to know if this item has already
1933
            // been used in the past (rather than just loaded and modified by
1934
            // some javascript but not written in the database).
1935
            // If the database status is different from 'not attempted', we can
1936
            // consider this item has already been used, and as such we can
1937
            // open a new attempt. Otherwise, we'll just reuse the current
1938
            // attempt, which is generally created the first time the item is
1939
            // loaded (for example as part of the table of contents).
1940
            $stat = $this->get_status(true);
1941
            if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) {
1942
                $this->attempt_id = $this->attempt_id + 1; // Open a new attempt.
1943
            }
1944
            $this->status = $this->possible_status[1];
1945
        } else {
1946
            /*if ($this->current_start_time == 0) {
1947
                // Small exception for start time, to avoid amazing values.
1948
                $this->current_start_time = time();
1949
            }*/
1950
            // If we don't init start time here, the time is sometimes calculated from the last start time.
1951
            $this->current_start_time = time();
1952
        }
1953
    }
1954
1955
    /**
1956
     * Outputs the item contents.
1957
     *
1958
     * @return string HTML file (displayable in an <iframe>) or empty string if no path defined
1959
     */
1960
    public function output()
1961
    {
1962
        if (!empty($this->path) and is_file($this->path)) {
1963
            $output = '';
1964
            $output .= file_get_contents($this->path);
1965
1966
            return $output;
1967
        }
1968
1969
        return '';
1970
    }
1971
1972
    /**
1973
     * Parses the prerequisites string with the AICC logic language.
1974
     *
1975
     * @param string $prereqs_string The prerequisites string as it figures in imsmanifest.xml
1976
     * @param array  $items          Array of items in the current learnpath object.
1977
     *                               Although we're in the learnpathItem object, it's necessary to have
1978
     *                               a list of all items to be able to check the current item's prerequisites
1979
     * @param array  $refs_list      list of references
1980
     *                               (the "ref" column in the lp_item table) that are strings used in the
1981
     *                               expression of prerequisites
1982
     * @param int    $user_id        The user ID. In some cases like Chamilo quizzes,
1983
     *                               it's necessary to have the user ID to query other tables (like the results of quizzes)
1984
     *
1985
     * @return bool True if the list of prerequisites given is entirely satisfied, false otherwise
1986
     */
1987
    public function parse_prereq($prereqs_string, $items, $refs_list, $user_id)
1988
    {
1989
        $debug = self::DEBUG;
1990
        if ($debug > 0) {
1991
            error_log(
1992
                'learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string,
1993
                0
1994
            );
1995
        }
1996
1997
        $courseId = $this->courseId;
1998
        $sessionId = api_get_session_id();
1999
2000
        // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order.
2001
        $this->prereq_alert = '';
2002
2003
        // First parse all parenthesis by using a sequential loop
2004
        //  (looking for less-inclusives first).
2005
        if ($prereqs_string == '_true_') {
2006
            return true;
2007
        }
2008
2009
        if ($prereqs_string == '_false_') {
2010
            if (empty($this->prereq_alert)) {
2011
                $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2012
            }
2013
2014
            return false;
2015
        }
2016
2017
        while (strpos($prereqs_string, '(') !== false) {
2018
            // Remove any () set and replace with its value.
2019
            $matches = [];
2020
            $res = preg_match_all(
2021
                '/(\(([^\(\)]*)\))/',
2022
                $prereqs_string,
2023
                $matches
2024
            );
2025
            if ($res) {
2026
                foreach ($matches[2] as $id => $match) {
2027
                    $str_res = $this->parse_prereq(
2028
                        $match,
2029
                        $items,
2030
                        $refs_list,
2031
                        $user_id
2032
                    );
2033
                    if ($str_res) {
2034
                        $prereqs_string = str_replace(
2035
                            $matches[1][$id],
2036
                            '_true_',
2037
                            $prereqs_string
2038
                        );
2039
                    } else {
2040
                        $prereqs_string = str_replace(
2041
                            $matches[1][$id],
2042
                            '_false_',
2043
                            $prereqs_string
2044
                        );
2045
                    }
2046
                }
2047
            }
2048
        }
2049
2050
        // Parenthesis removed, now look for ORs as it is the lesser-priority
2051
        //  binary operator (= always uses one text operand).
2052
        if (strpos($prereqs_string, '|') === false) {
2053
            if ($debug) {
2054
                error_log('New LP - Didnt find any OR, looking for AND', 0);
2055
            }
2056
            if (strpos($prereqs_string, '&') !== false) {
2057
                $list = explode('&', $prereqs_string);
2058
                if (count($list) > 1) {
2059
                    $andstatus = true;
2060
                    foreach ($list as $condition) {
2061
                        $andstatus = $andstatus && $this->parse_prereq(
2062
                            $condition,
2063
                            $items,
2064
                            $refs_list,
2065
                            $user_id
2066
                        );
2067
2068
                        if (!$andstatus) {
2069
                            if ($debug) {
2070
                                error_log(
2071
                                    'New LP - One condition in AND was false, short-circuit',
2072
                                    0
2073
                                );
2074
                            }
2075
                            break;
2076
                        }
2077
                    }
2078
2079
                    if (empty($this->prereq_alert) && !$andstatus) {
2080
                        $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2081
                    }
2082
2083
                    return $andstatus;
2084
                } else {
2085
                    if (isset($items[$refs_list[$list[0]]])) {
2086
                        $status = $items[$refs_list[$list[0]]]->get_status(true);
2087
                        $returnstatus = ($status == $this->possible_status[2]) || ($status == $this->possible_status[3]);
2088
                        if (empty($this->prereq_alert) && !$returnstatus) {
2089
                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2090
                        }
2091
2092
                        return $returnstatus;
2093
                    }
2094
                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2095
2096
                    return false;
2097
                }
2098
            } else {
2099
                // No ORs found, now look for ANDs.
2100
                if ($debug) {
2101
                    error_log('New LP - Didnt find any AND, looking for =', 0);
2102
                }
2103
2104
                if (strpos($prereqs_string, '=') !== false) {
2105
                    if ($debug) {
2106
                        error_log('New LP - Found =, looking into it', 0);
2107
                    }
2108
                    // We assume '=' signs only appear when there's nothing else around.
2109
                    $params = explode('=', $prereqs_string);
2110
                    if (count($params) == 2) {
2111
                        // Right number of operands.
2112
                        if (isset($items[$refs_list[$params[0]]])) {
2113
                            $status = $items[$refs_list[$params[0]]]->get_status(true);
2114
                            $returnstatus = $status == $params[1];
2115
                            if (empty($this->prereq_alert) && !$returnstatus) {
2116
                                $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2117
                            }
2118
2119
                            return $returnstatus;
2120
                        }
2121
                        $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2122
2123
                        return false;
2124
                    }
2125
                } else {
2126
                    // No ANDs found, look for <>
2127
                    if ($debug) {
2128
                        error_log(
2129
                            'New LP - Didnt find any =, looking for <>',
2130
                            0
2131
                        );
2132
                    }
2133
2134
                    if (strpos($prereqs_string, '<>') !== false) {
2135
                        if ($debug) {
2136
                            error_log('New LP - Found <>, looking into it', 0);
2137
                        }
2138
                        // We assume '<>' signs only appear when there's nothing else around.
2139
                        $params = explode('<>', $prereqs_string);
2140
                        if (count($params) == 2) {
2141
                            // Right number of operands.
2142
                            if (isset($items[$refs_list[$params[0]]])) {
2143
                                $status = $items[$refs_list[$params[0]]]->get_status(true);
2144
                                $returnstatus = $status != $params[1];
2145
                                if (empty($this->prereq_alert) && !$returnstatus) {
2146
                                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2147
                                }
2148
2149
                                return $returnstatus;
2150
                            }
2151
                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2152
2153
                            return false;
2154
                        }
2155
                    } else {
2156
                        // No <> found, look for ~ (unary)
2157
                        if ($debug) {
2158
                            error_log(
2159
                                'New LP - Didnt find any =, looking for ~',
2160
                                0
2161
                            );
2162
                        }
2163
                        // Only remains: ~ and X*{}
2164
                        if (strpos($prereqs_string, '~') !== false) {
2165
                            // Found NOT.
2166
                            if ($debug) {
2167
                                error_log(
2168
                                    'New LP - Found ~, looking into it',
2169
                                    0
2170
                                );
2171
                            }
2172
                            $list = [];
2173
                            $myres = preg_match(
2174
                                '/~([^(\d+\*)\{]*)/',
2175
                                $prereqs_string,
2176
                                $list
2177
                            );
2178
                            if ($myres) {
2179
                                $returnstatus = !$this->parse_prereq(
2180
                                    $list[1],
2181
                                    $items,
2182
                                    $refs_list,
2183
                                    $user_id
2184
                                );
2185
                                if (empty($this->prereq_alert) && !$returnstatus) {
2186
                                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2187
                                }
2188
2189
                                return $returnstatus;
2190
                            } else {
2191
                                // Strange...
2192
                                if ($debug) {
2193
                                    error_log(
2194
                                        'New LP - Found ~ but strange string: '.$prereqs_string,
2195
                                        0
2196
                                    );
2197
                                }
2198
                            }
2199
                        } else {
2200
                            // Finally, look for sets/groups
2201
                            if ($debug) {
2202
                                error_log(
2203
                                    'New LP - Didnt find any ~, looking for groups',
2204
                                    0
2205
                                );
2206
                            }
2207
                            // Only groups here.
2208
                            $groups = [];
2209
                            $groups_there = preg_match_all(
2210
                                '/((\d+\*)?\{([^\}]+)\}+)/',
2211
                                $prereqs_string,
2212
                                $groups
2213
                            );
2214
2215
                            if ($groups_there) {
2216
                                foreach ($groups[1] as $gr) {
2217
                                    // Only take the results that correspond to
2218
                                    //  the big brackets-enclosed condition.
2219
                                    if ($debug) {
2220
                                        error_log(
2221
                                            'New LP - Dealing with group '.$gr,
2222
                                            0
2223
                                        );
2224
                                    }
2225
                                    $multi = [];
2226
                                    $mycond = false;
2227
                                    if (preg_match(
2228
                                        '/(\d+)\*\{([^\}]+)\}/',
2229
                                        $gr,
2230
                                        $multi
2231
                                    )
2232
                                    ) {
2233
                                        if ($debug) {
2234
                                            error_log(
2235
                                                'New LP - Found multiplier '.$multi[0],
2236
                                                0
2237
                                            );
2238
                                        }
2239
                                        $count = $multi[1];
2240
                                        $list = explode(',', $multi[2]);
2241
                                        $mytrue = 0;
2242
                                        foreach ($list as $cond) {
2243
                                            if (isset($items[$refs_list[$cond]])) {
2244
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2245
                                                if ($status == $this->possible_status[2] ||
2246
                                                    $status == $this->possible_status[3]
2247
                                                ) {
2248
                                                    $mytrue++;
2249
                                                    if ($debug) {
2250
                                                        error_log(
2251
                                                            'New LP - Found true item, counting.. ('.($mytrue).')',
2252
                                                            0
2253
                                                        );
2254
                                                    }
2255
                                                }
2256
                                            } else {
2257
                                                if ($debug) {
2258
                                                    error_log(
2259
                                                        'New LP - item '.$cond.' does not exist in items list',
2260
                                                        0
2261
                                                    );
2262
                                                }
2263
                                            }
2264
                                        }
2265
                                        if ($mytrue >= $count) {
2266
                                            if ($debug) {
2267
                                                error_log(
2268
                                                    'New LP - Got enough true results, return true',
2269
                                                    0
2270
                                                );
2271
                                            }
2272
                                            $mycond = true;
2273
                                        } else {
2274
                                            if ($debug) {
2275
                                                error_log(
2276
                                                    'New LP - Not enough true results',
2277
                                                    0
2278
                                                );
2279
                                            }
2280
                                        }
2281
                                    } else {
2282
                                        if ($debug) {
2283
                                            error_log(
2284
                                                'New LP - No multiplier',
2285
                                                0
2286
                                            );
2287
                                        }
2288
                                        $list = explode(',', $gr);
2289
                                        $mycond = true;
2290
                                        foreach ($list as $cond) {
2291
                                            if (isset($items[$refs_list[$cond]])) {
2292
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2293
                                                if ($status == $this->possible_status[2] ||
2294
                                                    $status == $this->possible_status[3]
2295
                                                ) {
2296
                                                    $mycond = true;
2297
                                                    if ($debug) {
2298
                                                        error_log(
2299
                                                            'New LP - Found true item',
2300
                                                            0
2301
                                                        );
2302
                                                    }
2303
                                                } else {
2304
                                                    if ($debug) {
2305
                                                        error_log(
2306
                                                            'New LP - '.
2307
                                                            ' Found false item, the set is not true, return false',
2308
                                                            0
2309
                                                        );
2310
                                                    }
2311
                                                    $mycond = false;
2312
                                                    break;
2313
                                                }
2314
                                            } else {
2315
                                                if ($debug) {
2316
                                                    error_log(
2317
                                                        'New LP - item '.$cond.' does not exist in items list',
2318
                                                        0
2319
                                                    );
2320
                                                }
2321
                                                if ($debug) {
2322
                                                    error_log(
2323
                                                        'New LP - Found false item, the set is not true, return false',
2324
                                                        0
2325
                                                    );
2326
                                                }
2327
                                                $mycond = false;
2328
                                                break;
2329
                                            }
2330
                                        }
2331
                                    }
2332
                                    if (!$mycond && empty($this->prereq_alert)) {
2333
                                        $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2334
                                    }
2335
2336
                                    return $mycond;
2337
                                }
2338
                            } else {
2339
                                // Nothing found there either. Now return the
2340
                                // value of the corresponding resource completion status.
2341
                                if (isset($refs_list[$prereqs_string]) &&
2342
                                    isset($items[$refs_list[$prereqs_string]])
2343
                                ) {
2344
                                    /** @var learnpathItem $itemToCheck */
2345
                                    $itemToCheck = $items[$refs_list[$prereqs_string]];
2346
2347
                                    if ($itemToCheck->type === 'quiz' || $itemToCheck->type === 'h5p') {
2348
                                        // 1. Checking the status in current items.
2349
                                        $status = $itemToCheck->get_status(true);
2350
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2351
2352
                                        // Allow learnpath prerequisite on quiz to unblock if maximum attempt is reached
2353
                                        if (true === api_get_configuration_value('lp_prerequisit_on_quiz_unblock_if_max_attempt_reached')) {
2354
                                            $isQuizMaxAttemptReached = $this->isQuizMaxAttemptReached($items[$refs_list[$prereqs_string]]->path, $user_id, $courseId, $this->lp_id, $prereqs_string);
2355
                                            if ($isQuizMaxAttemptReached) {
2356
                                                $returnstatus = true;
2357
                                            }
2358
                                        }
2359
                                        if (!$returnstatus) {
2360
                                            $explanation = sprintf(
2361
                                                get_lang('ItemXBlocksThisElement'),
2362
                                                $itemToCheck->get_title()
2363
                                            );
2364
                                            $this->prereq_alert = $explanation;
2365
                                        }
2366
2367
                                        // For one and first attempt.
2368
                                        if ($this->prevent_reinit == 1) {
2369
                                            // 2. If is completed we check the results in the DB of the quiz.
2370
                                            if ($returnstatus) {
2371
                                                $checkLastScoreAttempt = api_get_configuration_value('lp_prerequisite_use_last_attempt_only');
2372
                                                $orderBy = ($checkLastScoreAttempt ? 'ORDER BY exe_date DESC' : 'ORDER BY (exe_result/exe_weighting) DESC');
2373
                                                $sql = 'SELECT exe_result, exe_weighting
2374
                                                        FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2375
                                                        WHERE
2376
                                                            exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2377
                                                            exe_user_id = '.$user_id.' AND
2378
                                                            orig_lp_id = '.$this->lp_id.' AND
2379
                                                            orig_lp_item_id = '.$prereqs_string.' AND
2380
                                                            status <> "incomplete" AND
2381
                                                            c_id = '.$courseId.'
2382
                                                        '.$orderBy.'
2383
                                                        LIMIT 0, 1';
2384
                                                $rs_quiz = Database::query($sql);
2385
                                                if ($quiz = Database::fetch_array($rs_quiz)) {
2386
                                                    /** @var learnpathItem $myItemToCheck */
2387
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2388
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2389
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2390
2391
                                                    if (isset($minScore) && isset($minScore)) {
2392
                                                        // Taking min/max prerequisites values see BT#5776
2393
                                                        if ($quiz['exe_result'] >= $minScore &&
2394
                                                            $quiz['exe_result'] <= $maxScore
2395
                                                        ) {
2396
                                                            $returnstatus = true;
2397
                                                        } else {
2398
                                                            $explanation = sprintf(
2399
                                                                get_lang('YourResultAtXBlocksThisElement'),
2400
                                                                $itemToCheck->get_title()
2401
                                                            );
2402
                                                            $this->prereq_alert = $explanation;
2403
                                                            $returnstatus = false;
2404
                                                        }
2405
                                                    } else {
2406
                                                        // Classic way
2407
                                                        if ($quiz['exe_result'] >=
2408
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2409
                                                        ) {
2410
                                                            $returnstatus = true;
2411
                                                        } else {
2412
                                                            $explanation = sprintf(
2413
                                                                get_lang('YourResultAtXBlocksThisElement'),
2414
                                                                $itemToCheck->get_title()
2415
                                                            );
2416
                                                            $this->prereq_alert = $explanation;
2417
                                                            $returnstatus = false;
2418
                                                        }
2419
                                                    }
2420
                                                } else {
2421
                                                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2422
                                                    $returnstatus = false;
2423
                                                }
2424
                                            }
2425
                                        } else {
2426
                                            // 3. For multiple attempts we check that there are minimum 1 item completed
2427
                                            // Checking in the database.
2428
                                            $sql = 'SELECT exe_result, exe_weighting
2429
                                                    FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2430
                                                    WHERE
2431
                                                        c_id = '.$courseId.' AND
2432
                                                        exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2433
                                                        exe_user_id = '.$user_id.' AND
2434
                                                        orig_lp_id = '.$this->lp_id.' AND
2435
                                                        orig_lp_item_id = '.$prereqs_string;
2436
2437
                                            $rs_quiz = Database::query($sql);
2438
                                            if (Database::num_rows($rs_quiz) > 0) {
2439
                                                while ($quiz = Database::fetch_array($rs_quiz)) {
2440
                                                    /** @var learnpathItem $myItemToCheck */
2441
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2442
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2443
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2444
2445
                                                    if (empty($minScore)) {
2446
                                                        // Try with mastery_score
2447
                                                        $masteryScoreAsMin = $myItemToCheck->get_mastery_score();
2448
                                                        if (!empty($masteryScoreAsMin)) {
2449
                                                            $minScore = $masteryScoreAsMin;
2450
                                                        }
2451
                                                    }
2452
                                                    if (isset($minScore) && isset($minScore)) {
2453
                                                        // Taking min/max prerequisites values see BT#5776
2454
                                                        if ($quiz['exe_result'] >= $minScore &&
2455
                                                            $quiz['exe_result'] <= $maxScore
2456
                                                        ) {
2457
                                                            $returnstatus = true;
2458
                                                            break;
2459
                                                        } else {
2460
                                                            $explanation = sprintf(
2461
                                                                get_lang('YourResultAtXBlocksThisElement'),
2462
                                                                $itemToCheck->get_title()
2463
                                                            );
2464
                                                            $this->prereq_alert = $explanation;
2465
                                                            $returnstatus = false;
2466
                                                        }
2467
                                                    } else {
2468
                                                        if ($quiz['exe_result'] >=
2469
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2470
                                                        ) {
2471
                                                            $returnstatus = true;
2472
                                                            break;
2473
                                                        } else {
2474
                                                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2475
                                                            $returnstatus = false;
2476
                                                        }
2477
                                                    }
2478
                                                }
2479
                                            } else {
2480
                                                $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2481
                                                $returnstatus = false;
2482
                                            }
2483
                                        }
2484
2485
                                        if ($returnstatus === false) {
2486
                                            // Check results from another sessions.
2487
                                            $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
2488
                                            if ($checkOtherSessions) {
2489
                                                $returnstatus = $this->getStatusFromOtherSessions(
2490
                                                    $user_id,
2491
                                                    $prereqs_string,
2492
                                                    $refs_list
2493
                                                );
2494
                                            }
2495
                                            // Allow learnpath prerequisite on quiz to unblock if maximum attempt is reached
2496
                                            if (true === api_get_configuration_value('lp_prerequisit_on_quiz_unblock_if_max_attempt_reached')) {
2497
                                                $isQuizMaxAttemptReached = $this->isQuizMaxAttemptReached($items[$refs_list[$prereqs_string]]->path, $user_id, $courseId, $this->lp_id, $prereqs_string);
2498
                                                if ($isQuizMaxAttemptReached) {
2499
                                                    $returnstatus = true;
2500
                                                }
2501
                                            }
2502
                                        }
2503
2504
                                        return $returnstatus;
2505
                                    } elseif ($itemToCheck->type === 'student_publication') {
2506
                                        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
2507
                                        $workId = $items[$refs_list[$prereqs_string]]->path;
2508
                                        $count = get_work_count_by_student($user_id, $workId);
2509
                                        if ($count >= 1) {
2510
                                            $returnstatus = true;
2511
                                        } else {
2512
                                            $returnstatus = false;
2513
                                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2514
                                        }
2515
2516
                                        return $returnstatus;
2517
                                    } else {
2518
                                        $status = $itemToCheck->get_status(true);
2519
                                        if (self::DEBUG) {
2520
                                            error_log('Status:'.$status);
2521
                                        }
2522
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2523
2524
                                        // Check results from another sessions.
2525
                                        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
2526
                                        if ($checkOtherSessions && !$returnstatus) {
2527
                                            $returnstatus = $this->getStatusFromOtherSessions(
2528
                                                $user_id,
2529
                                                $prereqs_string,
2530
                                                $refs_list
2531
                                            );
2532
                                        }
2533
2534
                                        if (!$returnstatus) {
2535
                                            $explanation = sprintf(
2536
                                                get_lang('ItemXBlocksThisElement'),
2537
                                                $itemToCheck->get_title()
2538
                                            );
2539
                                            $this->prereq_alert = $explanation;
2540
                                        }
2541
2542
                                        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2543
                                        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
2544
2545
                                        if ($returnstatus && $this->prevent_reinit == 1) {
2546
                                            $sql = "SELECT iid FROM $lp_view
2547
                                                    WHERE
2548
                                                        c_id = $courseId AND
2549
                                                        user_id = $user_id  AND
2550
                                                        lp_id = $this->lp_id AND
2551
                                                        session_id = $sessionId
2552
                                                    LIMIT 0, 1";
2553
                                            $rs_lp = Database::query($sql);
2554
                                            if (Database::num_rows($rs_lp)) {
2555
                                                $lp_id = Database::fetch_row($rs_lp);
2556
                                                $my_lp_id = $lp_id[0];
2557
2558
                                                $sql = "SELECT status FROM $lp_item_view
2559
                                                        WHERE
2560
                                                            c_id = $courseId AND
2561
                                                            lp_view_id = $my_lp_id AND
2562
                                                            lp_item_id = $refs_list[$prereqs_string]
2563
                                                        LIMIT 0, 1";
2564
                                                $rs_lp = Database::query($sql);
2565
                                                $status_array = Database::fetch_row($rs_lp);
2566
                                                $status = $status_array[0];
2567
2568
                                                $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2569
                                                if (!$returnstatus && empty($this->prereq_alert)) {
2570
                                                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2571
                                                }
2572
                                            }
2573
2574
                                            if ($checkOtherSessions && $returnstatus === false) {
2575
                                                $returnstatus = $returnstatus = $this->getStatusFromOtherSessions(
2576
                                                    $user_id,
2577
                                                    $prereqs_string,
2578
                                                    $refs_list
2579
                                                );
2580
                                            }
2581
                                        }
2582
2583
                                        return $returnstatus;
2584
                                    }
2585
                                }
2586
                            }
2587
                        }
2588
                    }
2589
                }
2590
            }
2591
        } else {
2592
            $list = explode("\|", $prereqs_string);
2593
            if (count($list) > 1) {
2594
                if (self::DEBUG > 1) {
2595
                    error_log('New LP - Found OR, looking into it', 0);
2596
                }
2597
                $orstatus = false;
2598
                foreach ($list as $condition) {
2599
                    if (self::DEBUG) {
2600
                        error_log(
2601
                            'New LP - Found OR, adding it ('.$condition.')',
2602
                            0
2603
                        );
2604
                    }
2605
                    $orstatus = $orstatus || $this->parse_prereq(
2606
                        $condition,
2607
                        $items,
2608
                        $refs_list,
2609
                        $user_id
2610
                    );
2611
                    if ($orstatus) {
2612
                        // Shortcircuit OR.
2613
                        if (self::DEBUG > 1) {
2614
                            error_log(
2615
                                'New LP - One condition in OR was true, short-circuit',
2616
                                0
2617
                            );
2618
                        }
2619
                        break;
2620
                    }
2621
                }
2622
                if (!$orstatus && empty($this->prereq_alert)) {
2623
                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2624
                }
2625
2626
                return $orstatus;
2627
            } else {
2628
                if (self::DEBUG > 1) {
2629
                    error_log(
2630
                        'New LP - OR was found but only one elem present !?',
2631
                        0
2632
                    );
2633
                }
2634
                if (isset($items[$refs_list[$list[0]]])) {
2635
                    $status = $items[$refs_list[$list[0]]]->get_status(true);
2636
                    $returnstatus = $status == 'completed' || $status == 'passed';
2637
                    if (!$returnstatus && empty($this->prereq_alert)) {
2638
                        $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2639
                    }
2640
2641
                    return $returnstatus;
2642
                }
2643
            }
2644
        }
2645
        if (empty($this->prereq_alert)) {
2646
            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2647
        }
2648
2649
        if (self::DEBUG > 1) {
2650
            error_log(
2651
                'New LP - End of parse_prereq. Error code is now '.$this->prereq_alert,
2652
                0
2653
            );
2654
        }
2655
2656
        return false;
2657
    }
2658
2659
    /**
2660
     * Check if max quiz attempt is reached.
2661
     *
2662
     * @param $exerciseId
2663
     * @param $userId
2664
     * @param $courseId
2665
     * @param $lpId
2666
     * @param $lpItemId
2667
     *
2668
     * @return bool
2669
     */
2670
    public function isQuizMaxAttemptReached($exerciseId, $userId, $courseId, $lpId, $lpItemId)
2671
    {
2672
        $objExercise = new Exercise();
2673
        $objExercise->read($exerciseId);
2674
        $nbAttempts = $objExercise->selectAttempts();
2675
        $countAttempts = Tracking::count_student_exercise_attempts(
2676
            $userId,
2677
            $courseId,
2678
            $exerciseId,
2679
            $lpId,
2680
            $lpItemId,
2681
            api_get_session_id()
2682
        );
2683
2684
        $isMaxAttemptReached = ($nbAttempts > 0 && $countAttempts >= $nbAttempts);
2685
2686
        return $isMaxAttemptReached;
2687
    }
2688
2689
    /**
2690
     * Reinits all local values as the learnpath is restarted.
2691
     *
2692
     * @return bool True on success, false otherwise
2693
     */
2694
    public function restart()
2695
    {
2696
        if (self::DEBUG > 0) {
2697
            error_log('learnpathItem::restart()', 0);
2698
        }
2699
        $seriousGame = $this->get_seriousgame_mode();
2700
        //For serious game  : We reuse same attempt_id
2701
        if ($seriousGame == 1 && $this->type == 'sco') {
2702
            // If this is a sco, Chamilo can't update the time without an
2703
            //  explicit scorm call
2704
            $this->current_start_time = 0;
2705
            $this->current_stop_time = 0; //Those 0 value have this effect
2706
            $this->last_scorm_session_time = 0;
2707
            $this->save();
2708
2709
            return true;
2710
        }
2711
2712
        $this->save();
2713
2714
        $allowed = $this->isRestartAllowed();
2715
        if ($allowed === -1) {
2716
            // Nothing allowed, do nothing.
2717
        } elseif ($allowed === 1) {
2718
            // Restart as new attempt is allowed, record a new attempt.
2719
            $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id.
2720
            $this->current_score = 0;
2721
            $this->current_start_time = 0;
2722
            $this->current_stop_time = 0;
2723
            $this->current_data = '';
2724
            $this->status = $this->possible_status[0];
2725
            $this->interactions_count = 0;
2726
            $this->interactions = [];
2727
            $this->objectives_count = 0;
2728
            $this->objectives = [];
2729
            $this->lesson_location = '';
2730
            if ($this->type != TOOL_QUIZ || $this->type != TOOL_H5P) {
2731
                $this->write_to_db();
2732
            }
2733
        } else {
2734
            // Restart current element is allowed (because it's not finished yet),
2735
            // reinit current.
2736
            //$this->current_score = 0;
2737
            $this->current_start_time = 0;
2738
            $this->current_stop_time = 0;
2739
            $this->interactions_count = $this->get_interactions_count(true);
2740
        }
2741
2742
        return true;
2743
    }
2744
2745
    /**
2746
     * Saves data in the database.
2747
     *
2748
     * @param bool $from_outside     Save from URL params (1) or from object attributes (0)
2749
     * @param bool $prereqs_complete The results of a check on prerequisites for this item.
2750
     *                               True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au
2751
     *
2752
     * @return bool True on success, false on failure
2753
     */
2754
    public function save($from_outside = true, $prereqs_complete = false)
2755
    {
2756
        $debug = self::DEBUG;
2757
        if ($debug) {
2758
            error_log('learnpathItem::save()');
2759
        }
2760
        // First check if parameters passed via GET can be saved here
2761
        // in case it's a SCORM, we should get:
2762
        if ($this->type == 'sco' || $this->type == 'au') {
2763
            $status = $this->get_status(true);
2764
            if ($debug) {
2765
                error_log(
2766
                    'learnpathItem::save() - SCORM save request received',
2767
                    0
2768
                );
2769
            }
2770
            // Get all new settings from the URL
2771
            if ($from_outside) {
2772
                if ($debug) {
2773
                    error_log(
2774
                        'learnpathItem::save() - Getting item data from outside',
2775
                        0
2776
                    );
2777
                }
2778
                foreach ($_GET as $param => $value) {
2779
                    switch ($param) {
2780
                        case 'score':
2781
                            $this->set_score($value);
2782
                            if ($debug) {
2783
                                error_log(
2784
                                    'learnpathItem::save() - setting score to '.$value,
2785
                                    0
2786
                                );
2787
                            }
2788
                            break;
2789
                        case 'max':
2790
                            $this->set_max_score($value);
2791
                            if ($debug) {
2792
                                error_log(
2793
                                    'learnpathItem::save() - setting view_max_score to '.$value,
2794
                                    0
2795
                                );
2796
                            }
2797
                            break;
2798
                        case 'min':
2799
                            $this->min_score = $value;
2800
                            if ($debug) {
2801
                                error_log(
2802
                                    'learnpathItem::save() - setting min_score to '.$value,
2803
                                    0
2804
                                );
2805
                            }
2806
                            break;
2807
                        case 'lesson_status':
2808
                            if (!empty($value)) {
2809
                                if ($this->prevent_reinit == 1 &&
2810
                                    $status != $this->possible_status[0] && // not attempted
2811
                                    $status != $this->possible_status[1]    // incomplete
2812
                                ) {
2813
                                    // do nothing: status was already completed or similar and we don't want to allow the SCO to reinitialize
2814
                                } else {
2815
                                    $this->set_status($value);
2816
                                    if ($debug) {
2817
                                        error_log(
2818
                                            'learnpathItem::save() - setting status to '.$value,
2819
                                            0
2820
                                        );
2821
                                    }
2822
                                }
2823
                            }
2824
                            break;
2825
                        case 'time':
2826
                            $this->set_time($value);
2827
                            if ($debug) {
2828
                                error_log(
2829
                                    'learnpathItem::save() - setting time to '.$value,
2830
                                    0
2831
                                );
2832
                            }
2833
                            break;
2834
                        case 'suspend_data':
2835
                            $this->current_data = $value;
2836
                            if ($debug) {
2837
                                error_log(
2838
                                    'learnpathItem::save() - setting suspend_data to '.$value,
2839
                                    0
2840
                                );
2841
                            }
2842
                            break;
2843
                        case 'lesson_location':
2844
                            $this->set_lesson_location($value);
2845
                            if ($debug) {
2846
                                error_log(
2847
                                    'learnpathItem::save() - setting lesson_location to '.$value,
2848
                                    0
2849
                                );
2850
                            }
2851
                            break;
2852
                        case 'core_exit':
2853
                            $this->set_core_exit($value);
2854
                            if ($debug) {
2855
                                error_log(
2856
                                    'learnpathItem::save() - setting core_exit to '.$value,
2857
                                    0
2858
                                );
2859
                            }
2860
                            break;
2861
                        case 'interactions':
2862
                        case 'objectives':
2863
                            break;
2864
                        default:
2865
                            // Ignore.
2866
                            break;
2867
                    }
2868
                }
2869
            } else {
2870
                if ($debug) {
2871
                    error_log(
2872
                        'learnpathItem::save() - Using inside item status',
2873
                        0
2874
                    );
2875
                }
2876
                // Do nothing, just let the local attributes be used.
2877
            }
2878
        } else {
2879
            // If not SCO, such messages should not be expected.
2880
            $type = strtolower($this->type);
2881
            if ($debug) {
2882
                error_log("type: $type");
2883
            }
2884
2885
            if (!WhispeakAuthPlugin::isAllowedToSaveLpItem($this->iId)) {
2886
                return false;
2887
            }
2888
2889
            switch ($type) {
2890
                case 'asset':
2891
                    if ($prereqs_complete) {
2892
                        $this->set_status($this->possible_status[2]);
2893
                    }
2894
                    break;
2895
                case TOOL_HOTPOTATOES:
2896
                    break;
2897
                case TOOL_H5P:
2898
                case TOOL_QUIZ:
2899
                    return false;
2900
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2901
                default:
2902
                    // For now, everything that is not sco and not asset is set to
2903
                    // completed when saved.
2904
                    if ($prereqs_complete) {
2905
                        $this->set_status($this->possible_status[2]);
2906
                    }
2907
                    break;
2908
            }
2909
        }
2910
2911
        if ($debug) {
2912
            error_log('End of learnpathItem::save() - Calling write_to_db() now');
2913
        }
2914
2915
        return $this->write_to_db();
2916
    }
2917
2918
    /**
2919
     * Sets the number of attempt_id to a given value.
2920
     *
2921
     * @param int $num The given value to set attempt_id to
2922
     *
2923
     * @return bool TRUE on success, FALSE otherwise
2924
     */
2925
    public function set_attempt_id($num)
2926
    {
2927
        if ($num == strval(intval($num)) && $num >= 0) {
2928
            $this->attempt_id = $num;
2929
2930
            return true;
2931
        }
2932
2933
        return false;
2934
    }
2935
2936
    /**
2937
     * Sets the core_exit value to the one given.
2938
     *
2939
     * @return bool $value  True (always)
2940
     */
2941
    public function set_core_exit($value)
2942
    {
2943
        switch ($value) {
2944
            case '':
2945
                $this->core_exit = '';
2946
                break;
2947
            case 'suspend':
2948
                $this->core_exit = 'suspend';
2949
                break;
2950
            default:
2951
                $this->core_exit = 'none';
2952
                break;
2953
        }
2954
2955
        return true;
2956
    }
2957
2958
    /**
2959
     * Sets the item's description.
2960
     *
2961
     * @param string $string Description
2962
     */
2963
    public function set_description($string = '')
2964
    {
2965
        if (!empty($string)) {
2966
            $this->description = $string;
2967
        }
2968
    }
2969
2970
    /**
2971
     * Sets the lesson_location value.
2972
     *
2973
     * @param string $location lesson_location as provided by the SCO
2974
     *
2975
     * @return bool True on success, false otherwise
2976
     */
2977
    public function set_lesson_location($location)
2978
    {
2979
        if (isset($location)) {
2980
            $this->lesson_location = $location;
2981
2982
            return true;
2983
        }
2984
2985
        return false;
2986
    }
2987
2988
    /**
2989
     * Sets the item's depth level in the LP tree (0 is at root).
2990
     *
2991
     * @param int $int Level
2992
     */
2993
    public function set_level($int = 0)
2994
    {
2995
        $this->level = (int) $int;
2996
    }
2997
2998
    /**
2999
     * Sets the lp_view id this item view is registered to.
3000
     *
3001
     * @param int $lp_view_id lp_view DB ID
3002
     * @param int $courseId
3003
     *
3004
     * @return bool
3005
     *
3006
     * @todo //todo insert into lp_item_view if lp_view not exists
3007
     */
3008
    public function set_lp_view($lp_view_id, $courseId = null)
3009
    {
3010
        $lp_view_id = (int) $lp_view_id;
3011
        $courseId = (int) $courseId;
3012
3013
        if (empty($courseId)) {
3014
            $courseId = api_get_course_int_id();
3015
        }
3016
3017
        $lpItemId = $this->get_id();
3018
3019
        if (empty($lpItemId)) {
3020
            return false;
3021
        }
3022
3023
        if (empty($lp_view_id)) {
3024
            return false;
3025
        }
3026
3027
        if (self::DEBUG > 0) {
3028
            error_log('learnpathItem::set_lp_view('.$lp_view_id.')', 0);
3029
        }
3030
3031
        $this->view_id = $lp_view_id;
3032
3033
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3034
        // Get the lp_item_view with the highest view_count.
3035
        $sql = "SELECT * FROM $item_view_table
3036
                WHERE
3037
                    c_id = $courseId AND
3038
                    lp_item_id = $lpItemId AND
3039
                    lp_view_id = $lp_view_id
3040
                ORDER BY view_count DESC";
3041
3042
        if (self::DEBUG > 2) {
3043
            error_log(
3044
                'learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql,
3045
                0
3046
            );
3047
        }
3048
        $res = Database::query($sql);
3049
        if (Database::num_rows($res) > 0) {
3050
            $row = Database::fetch_array($res);
3051
            $this->db_item_view_id = $row['iid'];
3052
            $this->attempt_id = $row['view_count'];
3053
            $this->current_score = $row['score'];
3054
            $this->current_data = $row['suspend_data'];
3055
            $this->view_max_score = $row['max_score'];
3056
            $this->status = $row['status'];
3057
            $this->current_start_time = $row['start_time'];
3058
            $this->current_stop_time = $this->current_start_time + $row['total_time'];
3059
            $this->lesson_location = $row['lesson_location'];
3060
            $this->core_exit = $row['core_exit'];
3061
3062
            if (self::DEBUG > 2) {
3063
                error_log(
3064
                    'learnpathItem::set_lp_view() - Updated item object with database values',
3065
                    0
3066
                );
3067
            }
3068
3069
            // Now get the number of interactions for this little guy.
3070
            $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3071
            $sql = "SELECT * FROM $table
3072
                    WHERE
3073
                        c_id = $courseId AND
3074
                        lp_iv_id = '".$this->db_item_view_id."'";
3075
3076
            $res = Database::query($sql);
3077
            if ($res !== false) {
3078
                $this->interactions_count = Database::num_rows($res);
3079
            } else {
3080
                $this->interactions_count = 0;
3081
            }
3082
            // Now get the number of objectives for this little guy.
3083
            $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3084
            $sql = "SELECT * FROM $table
3085
                    WHERE
3086
                        c_id = $courseId AND
3087
                        lp_iv_id = '".$this->db_item_view_id."'";
3088
3089
            $this->objectives_count = 0;
3090
            $res = Database::query($sql);
3091
            if ($res !== false) {
3092
                $this->objectives_count = Database::num_rows($res);
3093
            }
3094
        }
3095
3096
        return true;
3097
    }
3098
3099
    /**
3100
     * Sets the path.
3101
     *
3102
     * @param string $string Path
3103
     */
3104
    public function set_path($string = '')
3105
    {
3106
        if (!empty($string)) {
3107
            $this->path = $string;
3108
        }
3109
    }
3110
3111
    /**
3112
     * Sets the prevent_reinit attribute.
3113
     * This is based on the LP value and is set at creation time for
3114
     * each learnpathItem. It is a (bad?) way of avoiding
3115
     * a reference to the LP when saving an item.
3116
     *
3117
     * @param int 1 for "prevent", 0 for "don't prevent"
0 ignored issues
show
Documentation Bug introduced by
The doc comment 1 at position 0 could not be parsed: Unknown type name '1' at position 0 in 1.
Loading history...
3118
     * saving freshened values (new "not attempted" status etc)
3119
     */
3120
    public function set_prevent_reinit($prevent)
3121
    {
3122
        $this->prevent_reinit = 0;
3123
        if ($prevent) {
3124
            $this->prevent_reinit = 1;
3125
        }
3126
    }
3127
3128
    /**
3129
     * Sets the score value. If the mastery_score is set and the score reaches
3130
     * it, then set the status to 'passed'.
3131
     *
3132
     * @param float $score Score
3133
     *
3134
     * @return bool True on success, false otherwise
3135
     */
3136
    public function set_score($score)
3137
    {
3138
        $debug = self::DEBUG;
3139
        if ($debug > 0) {
3140
            error_log('learnpathItem::set_score('.$score.')', 0);
3141
        }
3142
        if (($this->max_score <= 0 || $score <= $this->max_score) && ($score >= $this->min_score)) {
3143
            $this->current_score = $score;
3144
            $masteryScore = $this->get_mastery_score();
3145
            $current_status = $this->get_status(false);
3146
3147
            // Fixes bug when SCORM doesn't send a mastery score even if they sent a score!
3148
            if ($masteryScore == -1) {
3149
                $masteryScore = $this->max_score;
3150
            }
3151
3152
            if ($debug > 0) {
3153
                error_log('get_mastery_score: '.$masteryScore);
3154
                error_log('current_status: '.$current_status);
3155
                error_log('current score : '.$this->current_score);
3156
            }
3157
3158
            // If mastery_score is set AND the current score reaches the mastery
3159
            //  score AND the current status is different from 'completed', then
3160
            //  set it to 'passed'.
3161
            /*
3162
            if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) {
3163
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[3]);
3164
                $this->set_status($this->possible_status[3]); //passed
3165
            } elseif ($master != -1 && $this->current_score < $master) {
3166
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[4]);
3167
                $this->set_status($this->possible_status[4]); //failed
3168
            }*/
3169
            return true;
3170
        }
3171
3172
        return false;
3173
    }
3174
3175
    /**
3176
     * Sets the maximum score for this item.
3177
     *
3178
     * @param int $score Maximum score - must be a decimal or an empty string
3179
     *
3180
     * @return bool True on success, false on error
3181
     */
3182
    public function set_max_score($score)
3183
    {
3184
        if (is_int($score) || $score == '') {
3185
            $this->view_max_score = $score;
3186
3187
            return true;
3188
        }
3189
3190
        return false;
3191
    }
3192
3193
    /**
3194
     * Sets the status for this item.
3195
     *
3196
     * @param string $status Status - must be one of the values defined in $this->possible_status
3197
     *                       (this affects the status setting)
3198
     *
3199
     * @return bool True on success, false on error
3200
     */
3201
    public function set_status($status)
3202
    {
3203
        if (self::DEBUG) {
3204
            error_log('learnpathItem::set_status('.$status.')');
3205
        }
3206
3207
        $found = false;
3208
        foreach ($this->possible_status as $possible) {
3209
            if (preg_match('/^'.$possible.'$/i', $status)) {
3210
                $found = true;
3211
            }
3212
        }
3213
3214
        if ($found) {
3215
            $this->status = $status;
3216
            if (self::DEBUG) {
3217
                error_log(
3218
                    'learnpathItem::set_status() - '.
3219
                    'Updated object status of item '.$this->db_id.
3220
                    ' to '.$this->status
3221
                );
3222
            }
3223
3224
            return true;
3225
        }
3226
3227
        if (self::DEBUG) {
3228
            error_log('Setting status: '.$this->possible_status[0]);
3229
        }
3230
3231
        $this->status = $this->possible_status[0];
3232
3233
        return false;
3234
    }
3235
3236
    /**
3237
     * Set the (indexing) terms for this learnpath item.
3238
     *
3239
     * @param string $terms Terms, as a comma-split list
3240
     *
3241
     * @return bool Always return true
3242
     */
3243
    public function set_terms($terms)
3244
    {
3245
        global $charset;
3246
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
3247
        $a_terms = preg_split('/,/', $terms);
3248
        $i_terms = preg_split('/,/', $this->get_terms());
3249
        foreach ($i_terms as $term) {
3250
            if (!in_array($term, $a_terms)) {
3251
                array_push($a_terms, $term);
3252
            }
3253
        }
3254
        $new_terms = $a_terms;
3255
        $new_terms_string = implode(',', $new_terms);
3256
3257
        // TODO: Validate csv string.
3258
        $terms = Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset));
3259
        $sql = "UPDATE $lp_item
3260
                SET terms = '$terms'
3261
                WHERE iid=".$this->get_id();
3262
        Database::query($sql);
3263
        // Save it to search engine.
3264
        if (api_get_setting('search_enabled') == 'true') {
3265
            $di = new ChamiloIndexer();
3266
            $di->update_terms($this->get_search_did(), $new_terms, 'T');
3267
        }
3268
3269
        return true;
3270
    }
3271
3272
    /**
3273
     * Get the document ID from inside the text index database.
3274
     *
3275
     * @return int Search index database document ID
3276
     */
3277
    public function get_search_did()
3278
    {
3279
        return $this->search_did;
3280
    }
3281
3282
    /**
3283
     * Sets the item viewing time in a usable form, given that SCORM packages
3284
     * often give it as 00:00:00.0000.
3285
     *
3286
     * @param    string    Time as given by SCORM
3287
     * @param string $format
3288
     */
3289
    public function set_time($scorm_time, $format = 'scorm')
3290
    {
3291
        $debug = self::DEBUG;
3292
        if ($debug) {
3293
            error_log("learnpathItem::set_time($scorm_time, $format)");
3294
            error_log("this->type: ".$this->type);
3295
            error_log("this->current_start_time: ".$this->current_start_time);
3296
        }
3297
3298
        if ($scorm_time == '0' &&
3299
            $this->type != 'sco' &&
3300
            $this->current_start_time != 0
3301
        ) {
3302
            $myTime = time() - $this->current_start_time;
3303
            if ($myTime > 0) {
3304
                $this->update_time($myTime);
3305
                if ($debug) {
3306
                    error_log('found asset - set time to '.$myTime);
3307
                }
3308
            } else {
3309
                if ($debug) {
3310
                    error_log('Time not set');
3311
                }
3312
            }
3313
        } else {
3314
            switch ($format) {
3315
                case 'scorm':
3316
                    $res = [];
3317
                    if (preg_match(
3318
                        '/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/',
3319
                        $scorm_time,
3320
                        $res
3321
                    )
3322
                    ) {
3323
                        $hour = $res[1];
3324
                        $min = $res[2];
3325
                        $sec = $res[3];
3326
                        // Getting total number of seconds spent.
3327
                        $totalSec = $hour * 3600 + $min * 60 + $sec;
3328
                        if ($debug) {
3329
                            error_log("totalSec : $totalSec");
3330
                            error_log("Now calling to scorm_update_time()");
3331
                        }
3332
                        $this->scorm_update_time($totalSec);
3333
                    }
3334
                    break;
3335
                case 'int':
3336
                    if ($debug) {
3337
                        error_log("scorm_time = $scorm_time");
3338
                        error_log("Now calling to scorm_update_time()");
3339
                    }
3340
                    $this->scorm_update_time($scorm_time);
3341
                    break;
3342
            }
3343
        }
3344
    }
3345
3346
    /**
3347
     * Sets the item's title.
3348
     *
3349
     * @param string $string Title
3350
     */
3351
    public function set_title($string = '')
3352
    {
3353
        if (!empty($string)) {
3354
            $this->title = $string;
3355
        }
3356
    }
3357
3358
    /**
3359
     * Sets the item's type.
3360
     *
3361
     * @param string $string Type
3362
     */
3363
    public function set_type($string = '')
3364
    {
3365
        if (!empty($string)) {
3366
            $this->type = $string;
3367
        }
3368
    }
3369
3370
    /**
3371
     * Checks if the current status is part of the list of status given.
3372
     *
3373
     * @param array $list An array of status to check for.
3374
     *                    If the current status is one of the strings, return true
3375
     *
3376
     * @return bool True if the status was one of the given strings,
3377
     *              false otherwise
3378
     */
3379
    public function status_is($list = [])
3380
    {
3381
        if (self::DEBUG > 1) {
3382
            error_log(
3383
                'learnpathItem::status_is('.print_r(
3384
                    $list,
3385
                    true
3386
                ).') on item '.$this->db_id,
3387
                0
3388
            );
3389
        }
3390
        $currentStatus = $this->get_status(true);
3391
        if (empty($currentStatus)) {
3392
            return false;
3393
        }
3394
        $found = false;
3395
        foreach ($list as $status) {
3396
            if (preg_match('/^'.$status.'$/i', $currentStatus)) {
3397
                if (self::DEBUG > 2) {
3398
                    error_log(
3399
                        'New LP - learnpathItem::status_is() - Found status '.
3400
                            $status.' corresponding to current status',
3401
                        0
3402
                    );
3403
                }
3404
                $found = true;
3405
3406
                return $found;
3407
            }
3408
        }
3409
        if (self::DEBUG > 2) {
3410
            error_log(
3411
                'New LP - learnpathItem::status_is() - Status '.
3412
                    $currentStatus.' did not match request',
3413
                0
3414
            );
3415
        }
3416
3417
        return $found;
3418
    }
3419
3420
    /**
3421
     * Updates the time info according to the given session_time.
3422
     *
3423
     * @param int $totalSec Time in seconds
3424
     */
3425
    public function update_time($totalSec = 0)
3426
    {
3427
        if (self::DEBUG > 0) {
3428
            error_log('learnpathItem::update_time('.$totalSec.')');
3429
        }
3430
        if ($totalSec >= 0) {
3431
            // Getting start time from finish time. The only problem in the calculation is it might be
3432
            // modified by the scripts processing time.
3433
            $now = time();
3434
            $start = $now - $totalSec;
3435
            $this->current_start_time = $start;
3436
            $this->current_stop_time = $now;
3437
        }
3438
    }
3439
3440
    /**
3441
     * Special scorm update time function. This function will update time
3442
     * directly into db for scorm objects.
3443
     *
3444
     * @param int $total_sec Total number of seconds
3445
     */
3446
    public function scorm_update_time($total_sec = 0)
3447
    {
3448
        $debug = self::DEBUG;
3449
        if ($debug) {
3450
            error_log('learnpathItem::scorm_update_time()');
3451
            error_log("total_sec: $total_sec");
3452
        }
3453
3454
        // Step 1 : get actual total time stored in db
3455
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3456
        $courseId = $this->courseId;
3457
3458
        $sql = 'SELECT total_time, status
3459
                FROM '.$item_view_table.'
3460
                WHERE
3461
                    c_id = '.$courseId.' AND
3462
                    lp_item_id = "'.$this->db_id.'" AND
3463
                    lp_view_id = "'.$this->view_id.'" AND
3464
                    view_count = "'.$this->get_attempt_id().'"';
3465
        $result = Database::query($sql);
3466
        $row = Database::fetch_array($result);
3467
3468
        if (!isset($row['total_time'])) {
3469
            $total_time = 0;
3470
        } else {
3471
            $total_time = $row['total_time'];
3472
        }
3473
        if ($debug) {
3474
            error_log("Original total_time: $total_time");
3475
        }
3476
3477
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3478
        $lp_id = (int) $this->lp_id;
3479
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
3480
        $res = Database::query($sql);
3481
        $accumulateScormTime = 'false';
3482
        if (Database::num_rows($res) > 0) {
3483
            $row = Database::fetch_assoc($res);
3484
            $accumulateScormTime = $row['accumulate_scorm_time'];
3485
        }
3486
3487
        // Step 2.1 : if normal mode total_time = total_time + total_sec
3488
        if ($this->type == 'sco' && $accumulateScormTime != 0) {
3489
            if ($debug) {
3490
                error_log("accumulateScormTime is on. total_time modified: $total_time + $total_sec");
3491
            }
3492
            $total_time += $total_sec;
3493
        } else {
3494
            // Step 2.2 : if not cumulative mode total_time = total_time - last_update + total_sec
3495
            $total_sec = $this->fixAbusiveTime($total_sec);
3496
            if ($debug) {
3497
                error_log("after fix abusive: $total_sec");
3498
                error_log("total_time: $total_time");
3499
                error_log("this->last_scorm_session_time: ".$this->last_scorm_session_time);
3500
            }
3501
3502
            $total_time = $total_time - $this->last_scorm_session_time + $total_sec;
3503
            $this->last_scorm_session_time = $total_sec;
3504
3505
            if ($total_time < 0) {
3506
                $total_time = $total_sec;
3507
            }
3508
        }
3509
3510
        if ($debug) {
3511
            error_log("accumulate_scorm_time: $accumulateScormTime");
3512
            error_log("total_time modified: $total_time");
3513
        }
3514
3515
        // Step 3 update db only if status != completed, passed, browsed or seriousgamemode not activated
3516
        // @todo complete
3517
        $case_completed = [
3518
            'completed',
3519
            'passed',
3520
            'browsed',
3521
            'failed',
3522
        ];
3523
3524
        if ($this->seriousgame_mode != 1 ||
3525
            !in_array($row['status'], $case_completed)
3526
        ) {
3527
            $sql = "UPDATE $item_view_table
3528
                      SET total_time = '$total_time'
3529
                    WHERE
3530
                        c_id = $courseId AND
3531
                        lp_item_id = {$this->db_id} AND
3532
                        lp_view_id = {$this->view_id} AND
3533
                        view_count = {$this->get_attempt_id()}";
3534
            if ($debug) {
3535
                error_log('-------------total_time updated ------------------------');
3536
                error_log($sql);
3537
                error_log('-------------------------------------');
3538
            }
3539
            Database::query($sql);
3540
        }
3541
    }
3542
3543
    /**
3544
     * Write objectives to DB. This method is separate from write_to_db() because otherwise
3545
     * objectives are lost as a side effect to AJAX and session concurrent access.
3546
     *
3547
     * @return bool True or false on error
3548
     */
3549
    public function write_objectives_to_db()
3550
    {
3551
        if (self::DEBUG > 0) {
3552
            error_log('learnpathItem::write_objectives_to_db()', 0);
3553
        }
3554
        if (api_is_invitee()) {
3555
            // If the user is an invitee, we don't write anything to DB
3556
            return true;
3557
        }
3558
        $courseId = api_get_course_int_id();
3559
        if (is_array($this->objectives) && count($this->objectives) > 0) {
3560
            // Save objectives.
3561
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3562
            $sql = "SELECT iid
3563
                    FROM $tbl
3564
                    WHERE
3565
                        c_id = $courseId AND
3566
                        lp_item_id = ".$this->db_id." AND
3567
                        lp_view_id = ".$this->view_id." AND
3568
                        view_count = ".$this->attempt_id;
3569
            $res = Database::query($sql);
3570
            if (Database::num_rows($res) > 0) {
3571
                $row = Database::fetch_array($res);
3572
                $lp_iv_id = $row[0];
3573
                if (self::DEBUG > 2) {
3574
                    error_log(
3575
                        'learnpathItem::write_to_db() - Got item_view_id '.
3576
                            $lp_iv_id.', now checking objectives ',
3577
                        0
3578
                    );
3579
                }
3580
                foreach ($this->objectives as $index => $objective) {
3581
                    $iva_table = Database::get_course_table(
3582
                        TABLE_LP_IV_OBJECTIVE
3583
                    );
3584
                    $iva_sql = "SELECT iid FROM $iva_table
3585
                                WHERE
3586
                                    c_id = $courseId AND
3587
                                    lp_iv_id = $lp_iv_id AND
3588
                                    objective_id = '".Database::escape_string($objective[0])."'";
3589
                    $iva_res = Database::query($iva_sql);
3590
                    // id(0), type(1), time(2), weighting(3),
3591
                    // correct_responses(4), student_response(5),
3592
                    // result(6), latency(7)
3593
                    if (Database::num_rows($iva_res) > 0) {
3594
                        // Update (or don't).
3595
                        $iva_row = Database::fetch_array($iva_res);
3596
                        $iva_id = $iva_row[0];
3597
                        $ivau_sql = "UPDATE $iva_table ".
3598
                            "SET objective_id = '".Database::escape_string($objective[0])."',".
3599
                            "status = '".Database::escape_string($objective[1])."',".
3600
                            "score_raw = '".Database::escape_string($objective[2])."',".
3601
                            "score_min = '".Database::escape_string($objective[4])."',".
3602
                            "score_max = '".Database::escape_string($objective[3])."' ".
3603
                            "WHERE c_id = $courseId AND iid = $iva_id";
3604
                        Database::query($ivau_sql);
3605
                    } else {
3606
                        // Insert new one.
3607
                        $params = [
3608
                            'c_id' => $courseId,
3609
                            'lp_iv_id' => $lp_iv_id,
3610
                            'order_id' => $index,
3611
                            'objective_id' => $objective[0],
3612
                            'status' => $objective[1],
3613
                            'score_raw' => $objective[2],
3614
                            'score_min' => $objective[4],
3615
                            'score_max' => $objective[3],
3616
                        ];
3617
3618
                        $insertId = Database::insert($iva_table, $params);
3619
                        if ($insertId) {
3620
                            $sql = "UPDATE $iva_table SET id = iid
3621
                                    WHERE iid = $insertId";
3622
                            Database::query($sql);
3623
                        }
3624
                    }
3625
                }
3626
            }
3627
        }
3628
    }
3629
3630
    public function isLpItemsCompleted()
3631
    {
3632
        $lp = new Learnpath(api_get_course_id(), $this->lp_id, api_get_user_id());
3633
        $count = $lp->getTotalItemsCountWithoutDirs([TOOL_LP_FINAL_ITEM]);
3634
        $excludeFailedStatus = !(true === api_get_configuration_value('lp_prerequisit_on_quiz_unblock_if_max_attempt_reached'));
3635
        $completed = $lp->get_complete_items_count($excludeFailedStatus, [TOOL_LP_FINAL_ITEM]);
3636
        $isCompleted = ($count - $completed == 0);
3637
3638
        return $isCompleted;
3639
    }
3640
3641
    public function getLpFinalItem()
3642
    {
3643
        $lp = new Learnpath(api_get_course_id(), $this->lp_id, api_get_user_id());
3644
3645
        return $lp->getFinalItem();
3646
    }
3647
3648
    /**
3649
     * Writes the current data to the database.
3650
     *
3651
     * @return bool Query result
3652
     */
3653
    public function write_to_db()
3654
    {
3655
        $debug = self::DEBUG;
3656
        if ($debug) {
3657
            error_log('------------------------');
3658
            error_log('learnpathItem::write_to_db()');
3659
        }
3660
3661
        // Check the session visibility.
3662
        if (!api_is_allowed_to_session_edit()) {
3663
            if ($debug) {
3664
                error_log('return false api_is_allowed_to_session_edit');
3665
            }
3666
3667
            return false;
3668
        }
3669
3670
        if (api_is_invitee()) {
3671
            if ($debug) {
3672
                error_log('api_is_invitee');
3673
            }
3674
            // If the user is an invitee, we don't write anything to DB
3675
            return true;
3676
        }
3677
3678
        // Final item is checked when previous are completed.
3679
        if ($this->type == 'final_item') {
3680
            if ($debug) {
3681
                error_log('learnpathItem::write_to_db() , final item, so not updated.', 0);
3682
            }
3683
3684
            return false;
3685
        }
3686
3687
        $courseId = api_get_course_int_id();
3688
        $mode = $this->get_lesson_mode();
3689
        $credit = $this->get_credit();
3690
        $total_time = ' ';
3691
        $my_status = ' ';
3692
3693
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3694
        $sql = 'SELECT status, total_time
3695
                FROM '.$item_view_table.'
3696
                WHERE
3697
                    c_id = '.$courseId.' AND
3698
                    lp_item_id="'.$this->db_id.'" AND
3699
                    lp_view_id="'.$this->view_id.'" AND
3700
                    view_count="'.$this->get_attempt_id().'" ';
3701
        $rs_verified = Database::query($sql);
3702
        $row_verified = Database::fetch_array($rs_verified);
3703
3704
        $oldTotalTime = $row_verified['total_time'];
3705
        $this->oldTotalTime = $oldTotalTime;
3706
        $inserted = false;
3707
3708
        if (
3709
            $this->type === 'sco' &&
3710
            ($credit === 'no-credit' || $mode === 'review' || $mode === 'browse') &&
3711
            $this->seriousgame_mode != 1
3712
        ) {
3713
            if ($debug) {
3714
                error_log(
3715
                    "This info shouldn't be saved as the credit or lesson mode info prevent it"
3716
                );
3717
                error_log(
3718
                    'learnpathItem::write_to_db() - credit('.$credit.') or'.
3719
                    ' lesson_mode('.$mode.') prevent recording!',
3720
                    0
3721
                );
3722
            }
3723
        } else {
3724
            // Check the row exists.
3725
            // This a special case for multiple attempts and Chamilo exercises.
3726
            if (($this->type === 'quiz' || $this->type === 'h5p') &&
3727
                $this->get_prevent_reinit() == 0 &&
3728
                $this->get_status() === 'completed'
3729
            ) {
3730
                // We force the item to be restarted.
3731
                $this->restart();
3732
                $params = [
3733
                    "c_id" => $courseId,
3734
                    "total_time" => $this->get_total_time(),
3735
                    "start_time" => $this->current_start_time,
3736
                    "score" => $this->get_score(),
3737
                    "status" => $this->get_status(false),
3738
                    "max_score" => $this->get_max(),
3739
                    "lp_item_id" => $this->db_id,
3740
                    "lp_view_id" => $this->view_id,
3741
                    "view_count" => $this->get_attempt_id(),
3742
                    "suspend_data" => $this->current_data,
3743
                    //"max_time_allowed" => ,
3744
                    "lesson_location" => $this->lesson_location,
3745
                ];
3746
                if ($debug) {
3747
                    error_log(
3748
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3749
                        0
3750
                    );
3751
                }
3752
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3753
                if ($this->db_item_view_id) {
3754
                    $sql = "UPDATE $item_view_table SET id = iid
3755
                            WHERE iid = ".$this->db_item_view_id;
3756
                    Database::query($sql);
3757
                    $inserted = true;
3758
                }
3759
            }
3760
3761
            $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3762
            $sql = "SELECT * FROM $item_view_table
3763
                    WHERE
3764
                        c_id = $courseId AND
3765
                        lp_item_id = ".$this->db_id." AND
3766
                        lp_view_id = ".$this->view_id." AND
3767
                        view_count = ".$this->get_attempt_id();
3768
            if ($debug) {
3769
                error_log('learnpathItem::write_to_db() - Querying item_view: '.$sql);
3770
            }
3771
3772
            $check_res = Database::query($sql);
3773
            // Depending on what we want (really), we'll update or insert a new row
3774
            // now save into DB.
3775
            if (!$inserted && Database::num_rows($check_res) < 1) {
3776
                $params = [
3777
                    "c_id" => $courseId,
3778
                    "total_time" => $this->get_total_time(),
3779
                    "start_time" => $this->current_start_time,
3780
                    "score" => $this->get_score(),
3781
                    "status" => $this->get_status(false),
3782
                    "max_score" => $this->get_max(),
3783
                    "lp_item_id" => $this->db_id,
3784
                    "lp_view_id" => $this->view_id,
3785
                    "view_count" => $this->get_attempt_id(),
3786
                    "suspend_data" => $this->current_data,
3787
                    //"max_time_allowed" => ,$this->get_max_time_allowed()
3788
                    "lesson_location" => $this->lesson_location,
3789
                ];
3790
3791
                if ($debug) {
3792
                    error_log(
3793
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3794
                        0
3795
                    );
3796
                }
3797
3798
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3799
                if ($this->db_item_view_id) {
3800
                    $sql = "UPDATE $item_view_table SET id = iid
3801
                            WHERE iid = ".$this->db_item_view_id;
3802
                    Database::query($sql);
3803
                }
3804
            } else {
3805
                if ($debug) {
3806
                    error_log('item is: '.$this->type);
3807
                }
3808
                if ($this->type === 'hotpotatoes') {
3809
                    $params = [
3810
                        'total_time' => $this->get_total_time(),
3811
                        'start_time' => $this->get_current_start_time(),
3812
                        'score' => $this->get_score(),
3813
                        'status' => $this->get_status(false),
3814
                        'max_score' => $this->get_max(),
3815
                        'suspend_data' => $this->current_data,
3816
                        'lesson_location' => $this->lesson_location,
3817
                    ];
3818
                    $where = [
3819
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' => [
3820
                            $courseId,
3821
                            $this->db_id,
3822
                            $this->view_id,
3823
                            $this->get_attempt_id(),
3824
                        ],
3825
                    ];
3826
                    Database::update($item_view_table, $params, $where);
3827
                } else {
3828
                    // For all other content types...
3829
                    if ($this->type === 'quiz' || $this->type === 'h5p') {
3830
                        $my_status = ' ';
3831
                        $total_time = ' ';
3832
                        if (!empty($_REQUEST['exeId'])) {
3833
                            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3834
                            $exeId = (int) $_REQUEST['exeId'];
3835
                            $sql = "SELECT exe_duration
3836
                                    FROM $table
3837
                                    WHERE exe_id = $exeId";
3838
                            $res = Database::query($sql);
3839
                            $exeRow = Database::fetch_array($res);
3840
                            $duration = $exeRow['exe_duration'];
3841
                            $total_time = " total_time = ".$duration.", ";
3842
                            if ($debug) {
3843
                                error_log("quiz: $total_time");
3844
                            }
3845
                        }
3846
                    } else {
3847
                        $my_type_lp = learnpath::get_type_static($this->lp_id);
3848
                        if ($debug) {
3849
                            error_log('get_type_static: '.$my_type_lp);
3850
                        }
3851
3852
                        // This is an array containing values equivalent to a finished state
3853
                        $case_completed = [
3854
                            'completed',
3855
                            'passed',
3856
                            'browsed',
3857
                            'failed',
3858
                        ];
3859
3860
                        // Is not multiple attempts
3861
                        if ($this->seriousgame_mode == 1 && $this->type === 'sco') {
3862
                            $total_time = " total_time = total_time +".$this->get_total_time().", ";
3863
                            $my_status = " status = '".$this->get_status(false)."' ,";
3864
                        } elseif ($this->get_prevent_reinit() == 1) {
3865
                            // Process of status verified into data base.
3866
                            $sql = 'SELECT status FROM '.$item_view_table.'
3867
                                    WHERE
3868
                                        c_id = '.$courseId.' AND
3869
                                        lp_item_id="'.$this->db_id.'" AND
3870
                                        lp_view_id="'.$this->view_id.'" AND
3871
                                        view_count="'.$this->get_attempt_id().'"
3872
                                    ';
3873
                            $rs_verified = Database::query($sql);
3874
                            $row_verified = Database::fetch_array($rs_verified, 'ASSOC');
3875
3876
                            if ($debug) {
3877
                                error_log("Query: $sql");
3878
                                error_log("With result: ".print_r($row_verified, 1));
3879
                            }
3880
3881
                            // Get type lp: 1 = Chamilo and 2 = Scorm.
3882
                            // If not is completed or passed or browsed and learning path is scorm.
3883
                            if (!in_array($this->get_status(false), $case_completed) &&
3884
                                2 == $my_type_lp
3885
                            ) {
3886
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3887
                                $my_status = " status = '".$this->get_status(false)."' ,";
3888
                                if ($debug) {
3889
                                    error_log("get_prevent_reinit = 1 time changed: $total_time");
3890
                                }
3891
                            } else {
3892
                                // Verified into database.
3893
                                if (2 == $my_type_lp &&
3894
                                    !in_array($row_verified['status'], $case_completed)
3895
                                ) {
3896
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3897
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3898
                                    if ($debug) {
3899
                                        error_log("total_time time changed case 1: $total_time");
3900
                                    }
3901
                                } elseif (2 == $my_type_lp &&
3902
                                    $this->type != 'sco' &&
3903
                                    in_array($row_verified['status'], $case_completed)
3904
                                ) {
3905
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3906
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3907
                                    if ($debug) {
3908
                                        error_log("total_time time changed case 2: $total_time");
3909
                                    }
3910
                                } else {
3911
                                    if (($my_type_lp == 3 && $this->type == 'au') ||
3912
                                        ($my_type_lp == 1 && $this->type != 'dir')) {
3913
                                        // Is AICC or Chamilo LP
3914
                                        $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3915
                                        $my_status = " status = '".$this->get_status(false)."' ,";
3916
                                        if ($debug) {
3917
                                            error_log("total_time time changed case 3: $total_time");
3918
                                        }
3919
                                    }
3920
                                }
3921
                            }
3922
                        } else {
3923
                            // Multiple attempts are allowed.
3924
                            if (in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) {
3925
                                // Reset zero new attempt ?
3926
                                $my_status = " status = '".$this->get_status(false)."' ,";
3927
                                if ($debug) {
3928
                                    error_log("total_time time changed Multiple attempt case 1: $total_time");
3929
                                }
3930
                            } elseif (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) {
3931
                                $total_time = " total_time = ".$this->get_total_time().", ";
3932
                                $my_status = " status = '".$this->get_status(false)."' ,";
3933
                                if ($debug) {
3934
                                    error_log("total_time time changed Multiple attempt case 2: $total_time");
3935
                                }
3936
                            } else {
3937
                                // It is chamilo LP.
3938
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3939
                                $my_status = " status = '".$this->get_status(false)."' ,";
3940
                                if ($debug) {
3941
                                    error_log("total_time time changed Multiple attempt case 3: $total_time");
3942
                                }
3943
                            }
3944
3945
                            // This code line fixes the problem of wrong status.
3946
                            if ($my_type_lp == 2) {
3947
                                // Verify current status in multiples attempts.
3948
                                $sql = 'SELECT status FROM '.$item_view_table.'
3949
                                        WHERE
3950
                                            c_id = '.$courseId.' AND
3951
                                            lp_item_id="'.$this->db_id.'" AND
3952
                                            lp_view_id="'.$this->view_id.'" AND
3953
                                            view_count="'.$this->get_attempt_id().'" ';
3954
                                $rs_status = Database::query($sql);
3955
                                if ($debug) {
3956
                                    error_log("Query: $sql");
3957
                                    error_log("With result: ".print_r($rs_status, 1));
3958
                                }
3959
3960
                                $current_status = Database::result($rs_status, 0, 'status');
3961
                                if (in_array($current_status, $case_completed)) {
3962
                                    $my_status = '';
3963
                                    $total_time = '';
3964
                                } else {
3965
                                    $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3966
                                }
3967
3968
                                if ($debug) {
3969
                                    error_log("total_time time my_type_lp: $total_time");
3970
                                }
3971
                            }
3972
                        }
3973
                    }
3974
3975
                    if ($this->type === 'sco') {
3976
                        //IF scorm scorm_update_time has already updated total_time in db
3977
                        //" . //start_time = ".$this->get_current_start_time().", " . //scorm_init_time does it
3978
                        ////" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3979
                        $sql = "UPDATE $item_view_table SET
3980
                                    score = ".$this->get_score().",
3981
                                    $my_status
3982
                                    max_score = '".$this->get_max()."',
3983
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3984
                                    lesson_location = '".$this->lesson_location."'
3985
                                WHERE
3986
                                    c_id = $courseId AND
3987
                                    lp_item_id = ".$this->db_id." AND
3988
                                    lp_view_id = ".$this->view_id."  AND
3989
                                    view_count = ".$this->get_attempt_id();
3990
                    } else {
3991
                        //" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3992
                        $sql = "UPDATE $item_view_table SET
3993
                                    $total_time
3994
                                    start_time = ".$this->get_current_start_time().",
3995
                                    score = ".$this->get_score().",
3996
                                    $my_status
3997
                                    max_score = '".$this->get_max()."',
3998
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3999
                                    lesson_location = '".$this->lesson_location."'
4000
                                WHERE
4001
                                    c_id = $courseId AND
4002
                                    lp_item_id = ".$this->db_id." AND
4003
                                    lp_view_id = ".$this->view_id." AND
4004
                                    view_count = ".$this->get_attempt_id();
4005
                    }
4006
                    $this->current_start_time = time();
4007
                }
4008
                if ($debug) {
4009
                    error_log('-------------------------------------------');
4010
                    error_log('learnpathItem::write_to_db() - Updating item_view:');
4011
                    error_log($sql);
4012
                    error_log('-------------------------------------------');
4013
                }
4014
                Database::query($sql);
4015
            }
4016
4017
            if (is_array($this->interactions) &&
4018
                count($this->interactions) > 0
4019
            ) {
4020
                // Save interactions.
4021
                $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4022
                $sql = "SELECT iid FROM $tbl
4023
                        WHERE
4024
                            c_id = $courseId AND
4025
                            lp_item_id = ".$this->db_id." AND
4026
                            lp_view_id = ".$this->view_id." AND
4027
                            view_count = ".$this->get_attempt_id();
4028
                $res = Database::query($sql);
4029
                if (Database::num_rows($res) > 0) {
4030
                    $row = Database::fetch_array($res);
4031
                    $lp_iv_id = $row[0];
4032
                    if ($debug) {
4033
                        error_log(
4034
                            'learnpathItem::write_to_db() - Got item_view_id '.
4035
                            $lp_iv_id.', now checking interactions ',
4036
                            0
4037
                        );
4038
                    }
4039
                    foreach ($this->interactions as $index => $interaction) {
4040
                        $correct_resp = '';
4041
                        if (is_array($interaction[4]) && !empty($interaction[4][0])) {
4042
                            foreach ($interaction[4] as $resp) {
4043
                                $correct_resp .= $resp.',';
4044
                            }
4045
                            $correct_resp = substr(
4046
                                $correct_resp,
4047
                                0,
4048
                                strlen($correct_resp) - 1
4049
                            );
4050
                        }
4051
                        $iva_table = Database::get_course_table(
4052
                            TABLE_LP_IV_INTERACTION
4053
                        );
4054
4055
                        //also check for the interaction ID as it must be unique for this SCO view
4056
                        $iva_sql = "SELECT iid FROM $iva_table
4057
                                    WHERE
4058
                                        c_id = $courseId AND
4059
                                        lp_iv_id = $lp_iv_id AND
4060
                                        (
4061
                                            order_id = $index OR
4062
                                            interaction_id = '".Database::escape_string($interaction[0])."'
4063
                                        )
4064
                                    ";
4065
                        $iva_res = Database::query($iva_sql);
4066
4067
                        $interaction[0] = isset($interaction[0]) ? $interaction[0] : '';
4068
                        $interaction[1] = isset($interaction[1]) ? $interaction[1] : '';
4069
                        $interaction[2] = isset($interaction[2]) ? $interaction[2] : '';
4070
                        $interaction[3] = isset($interaction[3]) ? $interaction[3] : '';
4071
                        $interaction[4] = isset($interaction[4]) ? $interaction[4] : '';
4072
                        $interaction[5] = isset($interaction[5]) ? $interaction[5] : '';
4073
                        $interaction[6] = isset($interaction[6]) ? $interaction[6] : '';
4074
                        $interaction[7] = isset($interaction[7]) ? $interaction[7] : '';
4075
4076
                        // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
4077
                        if (Database::num_rows($iva_res) > 0) {
4078
                            // Update (or don't).
4079
                            $iva_row = Database::fetch_array($iva_res);
4080
                            $iva_id = $iva_row[0];
4081
                            // Insert new one.
4082
                            $params = [
4083
                                'interaction_id' => $interaction[0],
4084
                                'interaction_type' => $interaction[1],
4085
                                'weighting' => $interaction[3],
4086
                                'completion_time' => $interaction[2],
4087
                                'correct_responses' => $correct_resp,
4088
                                'student_response' => $interaction[5],
4089
                                'result' => $interaction[6],
4090
                                'latency' => $interaction[7],
4091
                            ];
4092
                            Database::update(
4093
                                $iva_table,
4094
                                $params,
4095
                                [
4096
                                    'c_id = ? AND iid = ?' => [
4097
                                        $courseId,
4098
                                        $iva_id,
4099
                                    ],
4100
                                ]
4101
                            );
4102
                        } else {
4103
                            // Insert new one.
4104
                            $params = [
4105
                                'c_id' => $courseId,
4106
                                'order_id' => $index,
4107
                                'lp_iv_id' => $lp_iv_id,
4108
                                'interaction_id' => $interaction[0],
4109
                                'interaction_type' => $interaction[1],
4110
                                'weighting' => $interaction[3],
4111
                                'completion_time' => $interaction[2],
4112
                                'correct_responses' => $correct_resp,
4113
                                'student_response' => $interaction[5],
4114
                                'result' => $interaction[6],
4115
                                'latency' => $interaction[7],
4116
                            ];
4117
4118
                            $insertId = Database::insert($iva_table, $params);
4119
                            if ($insertId) {
4120
                                $sql = "UPDATE $iva_table SET id = iid
4121
                                        WHERE iid = $insertId";
4122
                                Database::query($sql);
4123
                            }
4124
                        }
4125
                    }
4126
                }
4127
            }
4128
        }
4129
4130
        // It updates the last progress only in case.
4131
        if (is_object($_SESSION['oLP'])) {
4132
            $_SESSION['oLP']->updateLpProgress();
4133
        }
4134
4135
        if ($debug) {
4136
            error_log('End of learnpathItem::write_to_db()', 0);
4137
        }
4138
4139
        // Check if lp is completed to validate the final item
4140
        if ($this->isLpItemsCompleted()) {
4141
            $lpFinalItem = $this->getLpFinalItem();
4142
            if ($lpFinalItem) {
0 ignored issues
show
introduced by
$lpFinalItem is of type learnpathItem, thus it always evaluated to true.
Loading history...
4143
                $sql = "SELECT iid
4144
                        FROM $item_view_table
4145
                        WHERE
4146
                            c_id = $courseId AND
4147
                            lp_item_id = {$lpFinalItem->get_id()} AND
4148
                            lp_view_id = {$this->view_id} AND
4149
                            status != 'completed'";
4150
                $rs = Database::query($sql);
4151
                if (Database::num_rows($rs) > 0) {
4152
                    $params = [
4153
                        'total_time' => $this->get_total_time(),
4154
                        'start_time' => $this->get_current_start_time(),
4155
                        'score' => $this->get_score(),
4156
                        'status' => 'completed',
4157
                        'max_score' => $this->get_max(),
4158
                        'suspend_data' => $this->current_data,
4159
                        'lesson_location' => $this->lesson_location,
4160
                    ];
4161
                    $where = [
4162
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' => [
4163
                            $courseId,
4164
                            $lpFinalItem->get_id(),
4165
                            $this->view_id,
4166
                            $this->get_attempt_id(),
4167
                        ],
4168
                    ];
4169
                    Database::update($item_view_table, $params, $where);
4170
                }
4171
            }
4172
        }
4173
4174
        return true;
4175
    }
4176
4177
    /**
4178
     * Adds an audio file attached to the current item (store on disk and in db).
4179
     *
4180
     * @return bool|string|null
4181
     */
4182
    public function addAudio()
4183
    {
4184
        $course_info = api_get_course_info();
4185
        $filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/';
4186
4187
        if (!is_dir($filepath.'audio')) {
4188
            mkdir(
4189
                $filepath.'audio',
4190
                api_get_permissions_for_new_directories()
4191
            );
4192
            $audio_id = add_document(
4193
                $course_info,
4194
                '/audio',
4195
                'folder',
4196
                0,
4197
                'audio'
4198
            );
4199
            api_item_property_update(
4200
                $course_info,
4201
                TOOL_DOCUMENT,
4202
                $audio_id,
4203
                'FolderCreated',
4204
                api_get_user_id(),
4205
                null,
4206
                null,
4207
                null,
4208
                null,
4209
                api_get_session_id()
4210
            );
4211
            api_item_property_update(
4212
                $course_info,
4213
                TOOL_DOCUMENT,
4214
                $audio_id,
4215
                'invisible',
4216
                api_get_user_id(),
4217
                null,
4218
                null,
4219
                null,
4220
                null,
4221
                api_get_session_id()
4222
            );
4223
        }
4224
4225
        $key = 'file';
4226
        if (!isset($_FILES[$key]['name']) || !isset($_FILES[$key]['tmp_name'])) {
4227
            return false;
4228
        }
4229
        $result = DocumentManager::upload_document(
4230
            $_FILES,
4231
            '/audio',
4232
            null,
4233
            null,
4234
            0,
4235
            'rename',
4236
            false,
4237
            false
4238
        );
4239
        $file_path = null;
4240
4241
        if ($result) {
4242
            $file_path = $result['path'];
4243
            // Store the mp3 file in the lp_item table.
4244
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4245
            $sql = "UPDATE $tbl_lp_item SET
4246
                        audio = '".Database::escape_string($file_path)."'
4247
                    WHERE iid = ".intval($this->db_id);
4248
            Database::query($sql);
4249
        }
4250
4251
        return $file_path;
4252
    }
4253
4254
    /**
4255
     * Removes the relation between the current item and an audio file. The file
4256
     * is only removed from the lp_item table, but remains in the document table
4257
     * and directory.
4258
     *
4259
     * @return bool
4260
     */
4261
    public function removeAudio()
4262
    {
4263
        $courseInfo = api_get_course_info();
4264
4265
        if (empty($this->db_id) || empty($courseInfo)) {
4266
            return false;
4267
        }
4268
4269
        $table = Database::get_course_table(TABLE_LP_ITEM);
4270
        $sql = "UPDATE $table SET
4271
                audio = ''
4272
                WHERE iid = ".$this->db_id;
4273
        Database::query($sql);
4274
    }
4275
4276
    /**
4277
     * Adds an audio file to the current item, using a file already in documents.
4278
     *
4279
     * @param int $documentId
4280
     *
4281
     * @return string
4282
     */
4283
    public function add_audio_from_documents($documentId)
4284
    {
4285
        $courseInfo = api_get_course_info();
4286
        $documentData = DocumentManager::get_document_data_by_id($documentId, $courseInfo['code']);
4287
4288
        $path = '';
4289
        if (!empty($documentData)) {
4290
            $path = $documentData['path'];
4291
            // Store the mp3 file in the lp_item table.
4292
            $table = Database::get_course_table(TABLE_LP_ITEM);
4293
            $sql = "UPDATE $table SET
4294
                        audio = '".Database::escape_string($path)."'
4295
                    WHERE iid = ".$this->db_id;
4296
            Database::query($sql);
4297
        }
4298
4299
        return $path;
4300
    }
4301
4302
    /**
4303
     * Transform the SCORM status to a string that can be translated by Chamilo
4304
     * in different user languages.
4305
     *
4306
     * @param $status
4307
     * @param bool   $decorate
4308
     * @param string $type     classic|simple
4309
     *
4310
     * @return array|string
4311
     */
4312
    public static function humanize_status($status, $decorate = true, $type = 'classic')
4313
    {
4314
        $statusList = [
4315
            'completed' => 'ScormCompstatus',
4316
            'incomplete' => 'ScormIncomplete',
4317
            'failed' => 'ScormFailed',
4318
            'passed' => 'ScormPassed',
4319
            'browsed' => 'ScormBrowsed',
4320
            'not attempted' => 'ScormNotAttempted',
4321
        ];
4322
4323
        $myLessonStatus = get_lang($statusList[$status]);
4324
4325
        switch ($status) {
4326
            case 'completed':
4327
            case 'browsed':
4328
                $classStatus = 'info';
4329
                break;
4330
            case 'incomplete':
4331
                $classStatus = 'warning';
4332
                break;
4333
            case 'passed':
4334
                $classStatus = 'success';
4335
                break;
4336
            case 'failed':
4337
                $classStatus = 'important';
4338
                break;
4339
            default:
4340
                $classStatus = 'default';
4341
                break;
4342
        }
4343
4344
        if ($type === 'simple') {
4345
            if (in_array($status, ['failed', 'passed', 'browsed'])) {
4346
                $myLessonStatus = get_lang('ScormIncomplete');
4347
4348
                $classStatus = 'warning';
4349
            }
4350
        }
4351
4352
        if ($decorate) {
4353
            return Display::label($myLessonStatus, $classStatus);
4354
        }
4355
4356
        return $myLessonStatus;
4357
    }
4358
4359
    /**
4360
     * @return float
4361
     */
4362
    public function getPrerequisiteMaxScore()
4363
    {
4364
        return $this->prerequisiteMaxScore;
4365
    }
4366
4367
    /**
4368
     * @param float $prerequisiteMaxScore
4369
     */
4370
    public function setPrerequisiteMaxScore($prerequisiteMaxScore)
4371
    {
4372
        $this->prerequisiteMaxScore = $prerequisiteMaxScore;
4373
    }
4374
4375
    /**
4376
     * @return float
4377
     */
4378
    public function getPrerequisiteMinScore()
4379
    {
4380
        return $this->prerequisiteMinScore;
4381
    }
4382
4383
    /**
4384
     * @param float $prerequisiteMinScore
4385
     */
4386
    public function setPrerequisiteMinScore($prerequisiteMinScore)
4387
    {
4388
        $this->prerequisiteMinScore = $prerequisiteMinScore;
4389
    }
4390
4391
    /**
4392
     * Check if this LP item has a created thread in the basis course from the forum of its LP.
4393
     *
4394
     * @param int $lpCourseId The course ID
4395
     *
4396
     * @return bool
4397
     */
4398
    public function lpItemHasThread($lpCourseId)
4399
    {
4400
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4401
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4402
4403
        $fakeFrom = "
4404
            $forumThreadTable ft
4405
            INNER JOIN $itemProperty ip
4406
            ON (ft.thread_id = ip.ref AND ft.c_id = ip.c_id)
4407
        ";
4408
4409
        $resultData = Database::select(
4410
            'COUNT(ft.iid) AS qty',
4411
            $fakeFrom,
4412
            [
4413
                'where' => [
4414
                    'ip.visibility != ? AND ' => 2,
4415
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4416
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4417
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4418
                        intval($this->db_id),
4419
                        "{$this->title} - {$this->db_id}",
4420
                        intval($this->db_id),
4421
                    ],
4422
                ],
4423
            ],
4424
            'first'
4425
        );
4426
4427
        if ($resultData['qty'] > 0) {
4428
            return true;
4429
        }
4430
4431
        return false;
4432
    }
4433
4434
    /**
4435
     * Get the forum thread info.
4436
     *
4437
     * @param int $lpCourseId  The course ID from the learning path
4438
     * @param int $lpSessionId Optional. The session ID from the learning path
4439
     *
4440
     * @return bool
4441
     */
4442
    public function getForumThread($lpCourseId, $lpSessionId = 0)
4443
    {
4444
        $lpSessionId = (int) $lpSessionId;
4445
        $lpCourseId = (int) $lpCourseId;
4446
4447
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4448
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4449
4450
        $fakeFrom = "$forumThreadTable ft INNER JOIN $itemProperty ip ";
4451
4452
        if ($lpSessionId == 0) {
4453
            $fakeFrom .= "
4454
                ON (
4455
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND (
4456
                        ft.session_id = ip.session_id OR ip.session_id IS NULL
4457
                    )
4458
                )
4459
            ";
4460
        } else {
4461
            $fakeFrom .= "
4462
                ON (
4463
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND ft.session_id = ip.session_id
4464
                )
4465
            ";
4466
        }
4467
4468
        $resultData = Database::select(
4469
            'ft.*',
4470
            $fakeFrom,
4471
            [
4472
                'where' => [
4473
                    'ip.visibility != ? AND ' => 2,
4474
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4475
                    'ft.session_id = ? AND ' => $lpSessionId,
4476
                    'ft.c_id = ? AND ' => $lpCourseId,
4477
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4478
                        intval($this->db_id),
4479
                        "{$this->title} - {$this->db_id}",
4480
                        intval($this->db_id),
4481
                    ],
4482
                ],
4483
            ],
4484
            'first'
4485
        );
4486
4487
        if (empty($resultData)) {
4488
            return false;
4489
        }
4490
4491
        return $resultData;
4492
    }
4493
4494
    /**
4495
     * Create a forum thread for this learning path item.
4496
     *
4497
     * @param int $currentForumId The forum ID to add the new thread
4498
     *
4499
     * @return int The forum thread if was created. Otherwise return false
4500
     */
4501
    public function createForumThread($currentForumId)
4502
    {
4503
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
4504
4505
        $currentForumId = (int) $currentForumId;
4506
4507
        $em = Database::getManager();
4508
        $threadRepo = $em->getRepository('ChamiloCourseBundle:CForumThread');
4509
        $forumThread = $threadRepo->findOneBy([
4510
            'threadTitle' => "{$this->title} - {$this->db_id}",
4511
            'forumId' => $currentForumId,
4512
        ]);
4513
4514
        if (!$forumThread) {
4515
            $forumInfo = get_forum_information($currentForumId);
4516
4517
            store_thread(
4518
                $forumInfo,
4519
                [
4520
                    'forum_id' => $currentForumId,
4521
                    'thread_id' => 0,
4522
                    'gradebook' => 0,
4523
                    'post_title' => "{$this->name} - {$this->db_id}",
4524
                    'post_text' => $this->description,
4525
                    'category_id' => 1,
4526
                    'numeric_calification' => 0,
4527
                    'calification_notebook_title' => 0,
4528
                    'weight_calification' => 0.00,
4529
                    'thread_peer_qualify' => 0,
4530
                    'lp_item_id' => $this->db_id,
4531
                ],
4532
                [],
4533
                false
4534
            );
4535
4536
            return;
4537
        }
4538
4539
        $forumThread->setLpItemId($this->db_id);
4540
4541
        $em->persist($forumThread);
4542
        $em->flush();
4543
    }
4544
4545
    /**
4546
     * Allow dissociate a forum to this LP item.
4547
     *
4548
     * @param int $threadIid The thread id
4549
     *
4550
     * @return bool
4551
     */
4552
    public function dissociateForumThread($threadIid)
4553
    {
4554
        $threadIid = (int) $threadIid;
4555
        $em = Database::getManager();
4556
4557
        $forumThread = $em->find('ChamiloCourseBundle:CForumThread', $threadIid);
4558
4559
        if (!$forumThread) {
4560
            return false;
4561
        }
4562
4563
        $forumThread->setThreadTitle("{$this->get_title()} - {$this->db_id}");
4564
        $forumThread->setLpItemId(0);
4565
4566
        $em->persist($forumThread);
4567
        $em->flush();
4568
4569
        return true;
4570
    }
4571
4572
    /**
4573
     * @return int
4574
     */
4575
    public function getLastScormSessionTime()
4576
    {
4577
        return $this->last_scorm_session_time;
4578
    }
4579
4580
    /**
4581
     * @return int
4582
     */
4583
    public function getIid()
4584
    {
4585
        return $this->iId;
4586
    }
4587
4588
    /**
4589
     * @param int    $user_id
4590
     * @param string $prereqs_string
4591
     * @param array  $refs_list
4592
     *
4593
     * @return bool
4594
     */
4595
    public function getStatusFromOtherSessions($user_id, $prereqs_string, $refs_list)
4596
    {
4597
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4598
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
4599
        $courseId = api_get_course_int_id();
4600
        $user_id = (int) $user_id;
4601
4602
        // Check results from another sessions:
4603
        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
4604
        if ($checkOtherSessions) {
4605
            // Check items
4606
            $sql = "SELECT iid FROM $lp_view
4607
                    WHERE
4608
                        c_id = $courseId AND
4609
                        user_id = $user_id  AND
4610
                        lp_id = $this->lp_id AND
4611
                        session_id <> 0
4612
                    ";
4613
            $result = Database::query($sql);
4614
            $resultFromOtherSessions = false;
4615
            while ($row = Database::fetch_array($result)) {
4616
                $lpIid = $row['iid'];
4617
                $sql = "SELECT status FROM $lp_item_view
4618
                        WHERE
4619
                            c_id = $courseId AND
4620
                            lp_view_id = $lpIid AND
4621
                            lp_item_id = $refs_list[$prereqs_string]
4622
                        LIMIT 1";
4623
                $resultRow = Database::query($sql);
4624
                if (Database::num_rows($resultRow)) {
4625
                    $statusResult = Database::fetch_array($resultRow);
4626
                    $status = $statusResult['status'];
4627
                    $checked = $status == $this->possible_status[2] || $status == $this->possible_status[3];
4628
                    if ($checked) {
4629
                        $resultFromOtherSessions = true;
4630
                        break;
4631
                    }
4632
                }
4633
            }
4634
4635
            return $resultFromOtherSessions;
4636
        }
4637
    }
4638
4639
    public static function isLpItemAutoComplete($lpItemId): bool
4640
    {
4641
        $extraFieldValue = new ExtraFieldValue('lp_item');
4642
        $saveAutomatic = $extraFieldValue->get_values_by_handler_and_field_variable(
4643
            $lpItemId,
4644
            'no_automatic_validation'
4645
        );
4646
4647
        if (false !== $saveAutomatic && is_array($saveAutomatic) && isset($saveAutomatic['value'])) {
4648
            if (1 === (int) $saveAutomatic['value']) {
4649
                return false;
4650
            }
4651
        }
4652
4653
        return true;
4654
    }
4655
}
4656