Passed
Push — master ( 45bdfa...db3c2b )
by Julito
10:09
created

learnpathItem::get_interactions_js_array()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 16
nc 8
nop 1
dl 0
loc 22
rs 9.7333
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Framework\Container;
5
6
/**
7
 * Class learnpathItem
8
 * lp_item defines items belonging to a learnpath. Each item has a name,
9
 * a score, a use time and additional information that enables tracking a user's
10
 * progress in a learning path.
11
 *
12
 * @author  Yannick Warnier <[email protected]>
13
 */
14
class learnpathItem
15
{
16
    const DEBUG = 0; // Logging parameter.
17
    public $iId;
18
    public $attempt_id; // Also called "objectives" SCORM-wise.
19
    public $audio; // The path to an audio file (stored in document/audio/).
20
    public $children = []; // Contains the ids of children items.
21
    public $condition; // If this item has a special condition embedded.
22
    public $current_score;
23
    public $current_start_time;
24
    public $current_stop_time;
25
    public $current_data = '';
26
    public $db_id;
27
    public $db_item_view_id = '';
28
    public $description = '';
29
    public $file;
30
    /**
31
     * At the moment, interactions are just an array of arrays with a structure
32
     * of 8 text fields: id(0), type(1), time(2), weighting(3),
33
     * correct_responses(4), student_response(5), result(6), latency(7).
34
     */
35
    public $interactions = [];
36
    public $interactions_count = 0;
37
    public $objectives = [];
38
    public $objectives_count = 0;
39
    public $launch_data = '';
40
    public $lesson_location = '';
41
    public $level = 0;
42
    public $core_exit = '';
43
    //var $location; // Only set this for SCORM?
44
    public $lp_id;
45
    public $max_score;
46
    public $mastery_score;
47
    public $min_score;
48
    public $max_time_allowed = '';
49
    public $name;
50
    public $next;
51
    public $parent;
52
    public $path; // In some cases the exo_id = exercise_id in courseDb exercices table.
53
    public $possible_status = [
54
        'not attempted',
55
        'incomplete',
56
        'completed',
57
        'passed',
58
        'failed',
59
        'browsed',
60
    ];
61
    public $prereq_string = '';
62
    public $prereq_alert = '';
63
    public $prereqs = [];
64
    public $previous;
65
    public $prevent_reinit = 1; // 0 =  multiple attempts   1 = one attempt
66
    public $seriousgame_mode;
67
    public $ref;
68
    public $save_on_close = true;
69
    public $search_did = null;
70
    public $status;
71
    public $title;
72
    /**
73
     * Type attribute can contain one of
74
     * link|student_publication|dir|quiz|document|forum|thread.
75
     */
76
    public $type;
77
    public $view_id;
78
    public $oldTotalTime;
79
    //var used if absolute session time mode is used
80
    private $last_scorm_session_time = 0;
81
    private $prerequisiteMaxScore;
82
    private $prerequisiteMinScore;
83
84
    /**
85
     * Prepares the learning path item for later launch.
86
     * Don't forget to use set_lp_view() if applicable after creating the item.
87
     * Setting an lp_view will finalise the item_view data collection.
88
     *
89
     * @param int        $id           Learning path item ID
90
     * @param int        $user_id      User ID
91
     * @param int        $course_id    Course int id
92
     * @param array|null $item_content An array with the contents of the item
93
     */
94
    public function __construct(
95
        $id,
96
        $user_id = 0,
97
        $course_id = 0,
98
        $item_content = null
99
    ) {
100
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
101
102
        // Get items table.
103
        if (!isset($user_id)) {
104
            $user_id = api_get_user_id();
105
        }
106
        $id = (int) $id;
107
        if (empty($item_content)) {
108
            if (empty($course_id)) {
109
                $course_id = api_get_course_int_id();
110
            } else {
111
                $course_id = (int) $course_id;
112
            }
113
            $sql = "SELECT * FROM $items_table
114
                    WHERE iid = $id";
115
            $res = Database::query($sql);
116
            if (Database::num_rows($res) < 1) {
117
                $this->error = 'Could not find given learnpath item in learnpath_item table';
118
            }
119
            $row = Database::fetch_array($res);
120
        } else {
121
            $row = $item_content;
122
        }
123
124
        $this->lp_id = $row['lp_id'];
125
        $this->iId = $row['iid'];
126
        $this->max_score = $row['max_score'];
127
        $this->min_score = $row['min_score'];
128
        $this->name = $row['title'];
129
        $this->type = $row['item_type'];
130
        $this->ref = $row['ref'];
131
        $this->title = $row['title'];
132
        $this->description = $row['description'];
133
        $this->path = $row['path'];
134
        $this->mastery_score = $row['mastery_score'];
135
        $this->parent = $row['parent_item_id'];
136
        $this->next = $row['next_item_id'];
137
        $this->previous = $row['previous_item_id'];
138
        $this->display_order = $row['display_order'];
139
        $this->prereq_string = $row['prerequisite'];
140
        $this->max_time_allowed = $row['max_time_allowed'];
141
        $this->setPrerequisiteMaxScore($row['prerequisite_max_score']);
142
        $this->setPrerequisiteMinScore($row['prerequisite_min_score']);
143
        $this->oldTotalTime = 0;
144
145
        if (isset($row['launch_data'])) {
146
            $this->launch_data = $row['launch_data'];
147
        }
148
        $this->save_on_close = true;
149
        $this->db_id = $id;
150
151
        // Load children list
152
        if (!empty($this->lp_id)) {
153
            $sql = "SELECT iid FROM $items_table
154
                    WHERE
155
                        c_id = $course_id AND
156
                        lp_id = ".$this->lp_id." AND
157
                        parent_item_id = $id";
158
            $res = Database::query($sql);
159
            if (Database::num_rows($res) > 0) {
160
                while ($row = Database::fetch_assoc($res)) {
161
                    $this->children[] = $row['iid'];
162
                }
163
            }
164
165
            // Get search_did.
166
            if ('true' == api_get_setting('search_enabled')) {
167
                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
168
                $sql = 'SELECT *
169
                        FROM %s
170
                        WHERE
171
                            course_code=\'%s\' AND
172
                            tool_id=\'%s\' AND
173
                            ref_id_high_level=%s AND
174
                            ref_id_second_level=%d
175
                        LIMIT 1';
176
                // TODO: Verify if it's possible to assume the actual course instead
177
                // of getting it from db.
178
                $sql = sprintf(
179
                    $sql,
180
                    $tbl_se_ref,
181
                    api_get_course_id(),
182
                    TOOL_LEARNPATH,
183
                    $this->lp_id,
184
                    $id
185
                );
186
                $res = Database::query($sql);
187
                if (Database::num_rows($res) > 0) {
188
                    $se_ref = Database::fetch_array($res);
189
                    $this->search_did = (int) $se_ref['search_did'];
190
                }
191
            }
192
        }
193
        $this->seriousgame_mode = 0;
194
        //$this->audio = $row['audio'];
195
    }
196
197
    /**
198
     * Adds a child to the current item.
199
     *
200
     * @param int $item The child item ID
201
     */
202
    public function add_child($item)
203
    {
204
        if (!empty($item)) {
205
            // Do not check in DB as we expect the call to come from the
206
            // learnpath class which should be aware of any fake.
207
            $this->children[] = $item;
208
        }
209
    }
210
211
    /**
212
     * Adds an interaction to the current item.
213
     *
214
     * @param int   $index  Index (order ID) of the interaction inside this item
215
     * @param array $params Array of parameters:
216
     *                      id(0), type(1), time(2), weighting(3), correct_responses(4),
217
     *                      student_response(5), result(6), latency(7)
218
     */
219
    public function add_interaction($index, $params)
220
    {
221
        $this->interactions[$index] = $params;
222
        // Take the current maximum index to generate the interactions_count.
223
        if (($index + 1) > $this->interactions_count) {
224
            $this->interactions_count = $index + 1;
225
        }
226
    }
227
228
    /**
229
     * Adds an objective to the current item.
230
     *
231
     * @param    array    Array of parameters:
232
     * id(0), status(1), score_raw(2), score_max(3), score_min(4)
233
     */
234
    public function add_objective($index, $params)
235
    {
236
        if (empty($params[0])) {
237
            return null;
238
        }
239
        $this->objectives[$index] = $params;
240
        // Take the current maximum index to generate the objectives_count.
241
        if ((count($this->objectives) + 1) > $this->objectives_count) {
242
            $this->objectives_count = (count($this->objectives) + 1);
243
        }
244
    }
245
246
    /**
247
     * Closes/stops the item viewing. Finalises runtime values.
248
     * If required, save to DB.
249
     *
250
     * @return bool True on success, false otherwise
251
     */
252
    public function close()
253
    {
254
        $this->current_stop_time = time();
255
        $type = $this->get_type();
256
        if ('sco' != $type) {
257
            if (TOOL_QUIZ == $type || TOOL_HOTPOTATOES == $type) {
258
                $this->get_status(
259
                    true,
260
                    true
261
                ); // Update status (second option forces the update).
262
            } else {
263
                $this->status = $this->possible_status[2];
264
            }
265
        }
266
        if ($this->save_on_close) {
267
            $this->save();
268
        }
269
270
        return true;
271
    }
272
273
    /**
274
     * Deletes all traces of this item in the database.
275
     *
276
     * @return bool true. Doesn't check for errors yet.
277
     */
278
    public function delete()
279
    {
280
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
281
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
282
        $course_id = api_get_course_int_id();
283
284
        $sql = "DELETE FROM $lp_item_view
285
                WHERE c_id = $course_id AND lp_item_id = ".$this->db_id;
286
        Database::query($sql);
287
288
        $sql = "SELECT * FROM $lp_item
289
                WHERE iid = ".$this->db_id;
290
        $res_sel = Database::query($sql);
291
        if (Database::num_rows($res_sel) < 1) {
292
            return false;
293
        }
294
295
        $sql = "DELETE FROM $lp_item
296
                WHERE iid = ".$this->db_id;
297
        Database::query($sql);
298
299
        if ('true' == api_get_setting('search_enabled')) {
300
            if (!is_null($this->search_did)) {
301
                $di = new ChamiloIndexer();
302
                $di->remove_document($this->search_did);
303
            }
304
        }
305
306
        return true;
307
    }
308
309
    /**
310
     * Drops a child from the children array.
311
     *
312
     * @param string $item index of child item to drop
313
     */
314
    public function drop_child($item)
315
    {
316
        if (!empty($item)) {
317
            foreach ($this->children as $index => $child) {
318
                if ($child == $item) {
319
                    $this->children[$index] = null;
320
                }
321
            }
322
        }
323
    }
324
325
    /**
326
     * Gets the current attempt_id for this user on this item.
327
     *
328
     * @return int attempt_id for this item view by this user or 1 if none defined
329
     */
330
    public function get_attempt_id()
331
    {
332
        $res = 1;
333
        if (!empty($this->attempt_id)) {
334
            $res = (int) $this->attempt_id;
335
        }
336
337
        return $res;
338
    }
339
340
    /**
341
     * Gets a list of the item's children.
342
     *
343
     * @return array Array of children items IDs
344
     */
345
    public function get_children()
346
    {
347
        $list = [];
348
        foreach ($this->children as $child) {
349
            if (!empty($child)) {
350
                $list[] = $child;
351
            }
352
        }
353
354
        return $list;
355
    }
356
357
    /**
358
     * Gets the core_exit value from the database.
359
     */
360
    public function get_core_exit()
361
    {
362
        return $this->core_exit;
363
    }
364
365
    /**
366
     * Gets the credit information (rather scorm-stuff) based on current status
367
     * and reinit autorization. Credit tells the sco(content) if Chamilo will
368
     * record the data it is sent (credit) or not (no-credit).
369
     *
370
     * @return string 'credit' or 'no-credit'. Defaults to 'credit'
371
     *                Because if we don't know enough about this item, it's probably because
372
     *                it was never used before.
373
     */
374
    public function get_credit()
375
    {
376
        if (self::DEBUG > 1) {
377
            error_log('learnpathItem::get_credit()', 0);
378
        }
379
        $credit = 'credit';
380
        // Now check the value of prevent_reinit (if it's 0, return credit as
381
        // the default was).
382
        // If prevent_reinit == 1 (or more).
383
        if (0 != $this->get_prevent_reinit()) {
384
            // If status is not attempted or incomplete, credit anyway.
385
            // Otherwise:
386
            // Check the status in the database rather than in the object, as
387
            // checking in the object would always return "no-credit" when we
388
            // want to set it to completed.
389
            $status = $this->get_status(true);
390
            if (self::DEBUG > 2) {
391
                error_log(
392
                    'learnpathItem::get_credit() - get_prevent_reinit!=0 and '.
393
                    'status is '.$status,
394
                    0
395
                );
396
            }
397
            //0=not attempted - 1 = incomplete
398
            if ($status != $this->possible_status[0] &&
399
                $status != $this->possible_status[1]
400
            ) {
401
                $credit = 'no-credit';
402
            }
403
        }
404
        if (self::DEBUG > 1) {
405
            error_log("learnpathItem::get_credit() returns: $credit");
406
        }
407
408
        return $credit;
409
    }
410
411
    /**
412
     * Gets the current start time property.
413
     *
414
     * @return int Current start time, or current time if none
415
     */
416
    public function get_current_start_time()
417
    {
418
        if (empty($this->current_start_time)) {
419
            return time();
420
        }
421
422
        return $this->current_start_time;
423
    }
424
425
    /**
426
     * Gets the item's description.
427
     *
428
     * @return string Description
429
     */
430
    public function get_description()
431
    {
432
        if (empty($this->description)) {
433
            return '';
434
        }
435
436
        return $this->description;
437
    }
438
439
    /**
440
     * Gets the file path from the course's root directory, no matter what
441
     * tool it is from.
442
     *
443
     * @param string $path_to_scorm_dir
444
     *
445
     * @return string The file path, or an empty string if there is no file
446
     *                attached, or '-1' if the file must be replaced by an error page
447
     */
448
    public function get_file_path($path_to_scorm_dir = '')
449
    {
450
        $course_id = api_get_course_int_id();
451
        $path = $this->get_path();
452
        $type = $this->get_type();
453
454
        if (empty($path)) {
455
            if ('dir' == $type) {
456
                return '';
457
            } else {
458
                return '-1';
459
            }
460
        } elseif ($path == strval(intval($path))) {
461
            // The path is numeric, so it is a reference to a Chamilo object.
462
            switch ($type) {
463
                case 'dir':
464
                    return '';
465
                case TOOL_DOCUMENT:
466
                    $table_doc = Database::get_course_table(TABLE_DOCUMENT);
467
                    $sql = 'SELECT path
468
                            FROM '.$table_doc.'
469
                            WHERE
470
                                c_id = '.$course_id.' AND
471
                                iid = '.$path;
472
                    $res = Database::query($sql);
473
                    $row = Database::fetch_array($res);
474
                    $real_path = 'document'.$row['path'];
475
476
                    return $real_path;
477
                case TOOL_STUDENTPUBLICATION:
478
                case TOOL_QUIZ:
479
                case TOOL_FORUM:
480
                case TOOL_THREAD:
481
                case TOOL_LINK:
482
                default:
483
                    return '-1';
484
            }
485
        } else {
486
            if (!empty($path_to_scorm_dir)) {
487
                $path = $path_to_scorm_dir.$path;
488
            }
489
490
            return $path;
491
        }
492
    }
493
494
    /**
495
     * Gets the DB ID.
496
     *
497
     * @return int Database ID for the current item
498
     */
499
    public function get_id()
500
    {
501
        if (!empty($this->db_id)) {
502
            return $this->db_id;
503
        }
504
        // TODO: Check this return value is valid for children classes (SCORM?).
505
        return 0;
506
    }
507
508
    /**
509
     * Loads the interactions into the item object, from the database.
510
     * If object interactions exist, they will be overwritten by this function,
511
     * using the database elements only.
512
     */
513
    public function load_interactions()
514
    {
515
        $this->interactions = [];
516
        $course_id = api_get_course_int_id();
517
        $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
518
        $sql = "SELECT id FROM $tbl
519
                WHERE
520
                    c_id = $course_id AND
521
                    lp_item_id = ".$this->db_id." AND
522
                    lp_view_id = ".$this->view_id." AND
523
                    view_count = ".$this->get_view_count();
524
        $res = Database::query($sql);
525
        if (Database::num_rows($res) > 0) {
526
            $row = Database::fetch_array($res);
527
            $lp_iv_id = $row[0];
528
            $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
529
            $sql = "SELECT * FROM $iva_table
530
                    WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id ";
531
            $res_sql = Database::query($sql);
532
            while ($row = Database::fetch_array($res_sql)) {
533
                $this->interactions[$row['interaction_id']] = [
534
                    $row['interaction_id'],
535
                    $row['interaction_type'],
536
                    $row['weighting'],
537
                    $row['completion_time'],
538
                    $row['correct_responses'],
539
                    $row['student_responses'],
540
                    $row['result'],
541
                    $row['latency'],
542
                ];
543
            }
544
        }
545
    }
546
547
    /**
548
     * Gets the current count of interactions recorded in the database.
549
     *
550
     * @param bool $checkdb Whether to count from database or not (defaults to no)
551
     *
552
     * @return int The current number of interactions recorder
553
     */
554
    public function get_interactions_count($checkdb = false)
555
    {
556
        $return = 0;
557
        if (api_is_invitee()) {
558
            // If the user is an invitee, we consider there's no interaction
559
            return 0;
560
        }
561
        $course_id = api_get_course_int_id();
562
563
        if ($checkdb) {
564
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
565
            $sql = "SELECT iid FROM $tbl
566
                    WHERE
567
                        c_id = $course_id AND
568
                        lp_item_id = ".$this->db_id." AND
569
                        lp_view_id = ".$this->view_id." AND
570
                        view_count = ".$this->get_attempt_id();
571
            $res = Database::query($sql);
572
            if (Database::num_rows($res) > 0) {
573
                $row = Database::fetch_array($res);
574
                $lp_iv_id = $row[0];
575
                $iva_table = Database::get_course_table(
576
                    TABLE_LP_IV_INTERACTION
577
                );
578
                $sql = "SELECT count(id) as mycount
579
                        FROM $iva_table
580
                        WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id ";
581
                $res_sql = Database::query($sql);
582
                if (Database::num_rows($res_sql) > 0) {
583
                    $row = Database::fetch_array($res_sql);
584
                    $return = $row['mycount'];
585
                }
586
            }
587
        } else {
588
            if (!empty($this->interactions_count)) {
589
                $return = $this->interactions_count;
590
            }
591
        }
592
593
        return $return;
594
    }
595
596
    /**
597
     * Gets the JavaScript array content to fill the interactions array.
598
     *
599
     * @param bool $checkdb Whether to check directly into the database (default no)
600
     *
601
     * @return string An empty string if no interaction, a JS array definition otherwise
602
     */
603
    public function get_interactions_js_array($checkdb = false)
604
    {
605
        $return = '';
606
        if ($checkdb) {
607
            $this->load_interactions(true);
608
        }
609
        foreach ($this->interactions as $id => $in) {
610
            $return .= "[
611
                '$id',
612
                '".$in[1]."',
613
                '".$in[2]."',
614
                '".$in[3]."',
615
                '".$in[4]."',
616
                '".$in[5]."',
617
                '".$in[6]."',
618
                '".$in[7]."'],";
619
        }
620
        if (!empty($return)) {
621
            $return = substr($return, 0, -1);
622
        }
623
624
        return $return;
625
    }
626
627
    /**
628
     * Gets the current count of objectives recorded in the database.
629
     *
630
     * @return int The current number of objectives recorder
631
     */
632
    public function get_objectives_count()
633
    {
634
        $res = 0;
635
        if (!empty($this->objectives_count)) {
636
            $res = $this->objectives_count;
637
        }
638
639
        return $res;
640
    }
641
642
    /**
643
     * Gets the launch_data field found in imsmanifests (this is SCORM- or
644
     * AICC-related, really).
645
     *
646
     * @return string Launch data as found in imsmanifest and stored in
647
     *                Chamilo (read only). Defaults to ''.
648
     */
649
    public function get_launch_data()
650
    {
651
        if (!empty($this->launch_data)) {
652
            return str_replace(
653
                ["\r", "\n", "'"],
654
                ['\r', '\n', "\\'"],
655
                $this->launch_data
656
            );
657
        }
658
659
        return '';
660
    }
661
662
    /**
663
     * Gets the lesson location.
664
     *
665
     * @return string lesson location as recorded by the SCORM and AICC
666
     *                elements. Defaults to ''
667
     */
668
    public function get_lesson_location()
669
    {
670
        if (!empty($this->lesson_location)) {
671
            return str_replace(
672
                ["\r", "\n", "'"],
673
                ['\r', '\n', "\\'"],
674
                $this->lesson_location
675
            );
676
        }
677
678
        return '';
679
    }
680
681
    /**
682
     * Gets the lesson_mode (scorm feature, but might be used by aicc as well
683
     * as chamilo paths).
684
     *
685
     * The "browse" mode is not supported yet (because there is no such way of
686
     * seeing a sco in Chamilo)
687
     *
688
     * @return string 'browse','normal' or 'review'. Defaults to 'normal'
689
     */
690
    public function get_lesson_mode()
691
    {
692
        $mode = 'normal';
693
        if (0 != $this->get_prevent_reinit()) {
694
            // If prevent_reinit == 0
695
            $my_status = $this->get_status();
696
            if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) {
697
                $mode = 'review';
698
            }
699
        }
700
701
        return $mode;
702
    }
703
704
    /**
705
     * Gets the depth level.
706
     *
707
     * @return int Level. Defaults to 0
708
     */
709
    public function get_level()
710
    {
711
        if (empty($this->level)) {
712
            return 0;
713
        }
714
715
        return $this->level;
716
    }
717
718
    /**
719
     * Gets the mastery score.
720
     */
721
    public function get_mastery_score()
722
    {
723
        if (isset($this->mastery_score)) {
724
            return $this->mastery_score;
725
        }
726
727
        return -1;
728
    }
729
730
    /**
731
     * Gets the maximum (score).
732
     *
733
     * @return int Maximum score. Defaults to 100 if nothing else is defined
734
     */
735
    public function get_max()
736
    {
737
        if ('sco' == $this->type) {
738
            if (isset($this->view_max_score) &&
739
                !empty($this->view_max_score) &&
740
                $this->view_max_score > 0
741
            ) {
742
                return $this->view_max_score;
743
            } elseif (isset($this->view_max_score) &&
744
                '' === $this->view_max_score
745
            ) {
746
                return $this->view_max_score;
747
            } else {
748
                if (!empty($this->max_score)) {
749
                    return $this->max_score;
750
                } else {
751
                    return 100;
752
                }
753
            }
754
        } else {
755
            if (!empty($this->max_score)) {
756
                return $this->max_score;
757
            } else {
758
                return 100;
759
            }
760
        }
761
    }
762
763
    /**
764
     * Gets the maximum time allowed for this user in this attempt on this item.
765
     *
766
     * @return string Time string in SCORM format
767
     *                (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS)
768
     */
769
    public function get_max_time_allowed()
770
    {
771
        if (!empty($this->max_time_allowed)) {
772
            return $this->max_time_allowed;
773
        }
774
775
        return '';
776
    }
777
778
    /**
779
     * Gets the minimum (score).
780
     *
781
     * @return int Minimum score. Defaults to 0
782
     */
783
    public function get_min()
784
    {
785
        if (!empty($this->min_score)) {
786
            return $this->min_score;
787
        }
788
789
        return 0;
790
    }
791
792
    /**
793
     * Gets the parent ID.
794
     *
795
     * @return int Parent ID. Defaults to null
796
     */
797
    public function get_parent()
798
    {
799
        if (!empty($this->parent)) {
800
            return $this->parent;
801
        }
802
        // TODO: Check this return value is valid for children classes (SCORM?).
803
        return null;
804
    }
805
806
    /**
807
     * Gets the path attribute.
808
     *
809
     * @return string Path. Defaults to ''
810
     */
811
    public function get_path()
812
    {
813
        if (empty($this->path)) {
814
            return '';
815
        }
816
817
        return $this->path;
818
    }
819
820
    /**
821
     * Gets the prerequisites string.
822
     *
823
     * @return string empty string or prerequisites string if defined
824
     */
825
    public function get_prereq_string()
826
    {
827
        if (!empty($this->prereq_string)) {
828
            return $this->prereq_string;
829
        }
830
831
        return '';
832
    }
833
834
    /**
835
     * Gets the prevent_reinit attribute value (and sets it if not set already).
836
     *
837
     * @return int 1 or 0 (defaults to 1)
838
     */
839
    public function get_prevent_reinit()
840
    {
841
        if (self::DEBUG > 2) {
842
            error_log('learnpathItem::get_prevent_reinit()', 0);
843
        }
844
        if (!isset($this->prevent_reinit)) {
845
            if (!empty($this->lp_id)) {
846
                $table = Database::get_course_table(TABLE_LP_MAIN);
847
                $sql = "SELECT prevent_reinit
848
                        FROM $table
849
                        WHERE iid = ".$this->lp_id;
850
                $res = Database::query($sql);
851
                if (Database::num_rows($res) < 1) {
852
                    $this->error = "Could not find parent learnpath in lp table";
853
                    if (self::DEBUG > 2) {
854
                        error_log(
855
                            'New LP - End of learnpathItem::get_prevent_reinit() - Returning false',
856
                            0
857
                        );
858
                    }
859
860
                    return false;
861
                } else {
862
                    $row = Database::fetch_array($res);
863
                    $this->prevent_reinit = $row['prevent_reinit'];
864
                }
865
            } else {
866
                // Prevent reinit is always 1 by default - see learnpath.class.php
867
                $this->prevent_reinit = 1;
868
            }
869
        }
870
        if (self::DEBUG > 2) {
871
            error_log(
872
                'New LP - End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit,
873
                0
874
            );
875
        }
876
877
        return $this->prevent_reinit;
878
    }
879
880
    /**
881
     * Returns 1 if seriousgame_mode is activated, 0 otherwise.
882
     *
883
     * @return int (0 or 1)
884
     *
885
     * @deprecated seriousgame_mode seems not to be used
886
     *
887
     * @author ndiechburg <[email protected]>
888
     */
889
    public function get_seriousgame_mode()
890
    {
891
        if (!isset($this->seriousgame_mode)) {
892
            if (!empty($this->lp_id)) {
893
                $table = Database::get_course_table(TABLE_LP_MAIN);
894
                $sql = "SELECT seriousgame_mode
895
                        FROM $table
896
                        WHERE iid = ".$this->lp_id;
897
                $res = Database::query($sql);
898
                if (Database::num_rows($res) < 1) {
899
                    $this->error = 'Could not find parent learnpath in learnpath table';
900
901
                    return false;
902
                } else {
903
                    $row = Database::fetch_array($res);
904
                    $this->seriousgame_mode = isset($row['seriousgame_mode']) ? $row['seriousgame_mode'] : 0;
905
                }
906
            } else {
907
                $this->seriousgame_mode = 0; //SeriousGame mode is always off by default
908
            }
909
        }
910
911
        return $this->seriousgame_mode;
912
    }
913
914
    /**
915
     * Gets the item's reference column.
916
     *
917
     * @return string The item's reference field (generally used for SCORM identifiers)
918
     */
919
    public function get_ref()
920
    {
921
        return $this->ref;
922
    }
923
924
    /**
925
     * Gets the list of included resources as a list of absolute or relative
926
     * paths of resources included in the current item. This allows for a
927
     * better SCORM export. The list will generally include pictures, flash
928
     * objects, java applets, or any other stuff included in the source of the
929
     * current item. The current item is expected to be an HTML file. If it
930
     * is not, then the function will return and empty list.
931
     *
932
     * @param string $type        (one of the Chamilo tools) - optional (otherwise takes the current item's type)
933
     * @param string $abs_path    absolute file path - optional (otherwise takes the current item's path)
934
     * @param int    $recursivity level of recursivity we're in
935
     *
936
     * @return array List of file paths.
937
     *               An additional field containing 'local' or 'remote' helps determine if
938
     *               the file should be copied into the zip or just linked
939
     */
940
    public function get_resources_from_source(
941
        $type = null,
942
        $abs_path = null,
943
        $recursivity = 1
944
    ) {
945
        $max = 5;
946
        if ($recursivity > $max) {
947
            return [];
948
        }
949
950
        $type = empty($type) ? $this->get_type() : $type;
951
952
        if (!isset($abs_path)) {
953
            $path = $this->get_file_path();
954
            $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path;
955
        }
956
957
        $files_list = [];
958
        switch ($type) {
959
            case TOOL_DOCUMENT:
960
            case TOOL_QUIZ:
961
            case 'sco':
962
                // Get the document and, if HTML, open it.
963
                if (!is_file($abs_path)) {
964
                    // The file could not be found.
965
                    return false;
966
                }
967
968
                // for now, read the whole file in one go (that's gonna be
969
                // a problem when the file is too big).
970
                $info = pathinfo($abs_path);
971
                $ext = $info['extension'];
972
973
                switch (strtolower($ext)) {
974
                    case 'html':
975
                    case 'htm':
976
                    case 'shtml':
977
                    case 'css':
978
                        $wantedAttributes = [
979
                            'src',
980
                            'url',
981
                            '@import',
982
                            'href',
983
                            'value',
984
                        ];
985
986
                        // Parse it for included resources.
987
                        $fileContent = file_get_contents($abs_path);
988
                        // Get an array of attributes from the HTML source.
989
                        $attributes = DocumentManager::parse_HTML_attributes(
990
                            $fileContent,
991
                            $wantedAttributes
992
                        );
993
994
                        // Look at 'src' attributes in this file
995
                        foreach ($wantedAttributes as $attr) {
996
                            if (isset($attributes[$attr])) {
997
                                // Find which kind of path these are (local or remote).
998
                                $sources = $attributes[$attr];
999
1000
                                foreach ($sources as $source) {
1001
                                    // Skip what is obviously not a resource.
1002
                                    if (strpos($source, "+this.")) {
1003
                                        continue;
1004
                                    } // javascript code - will still work unaltered.
1005
                                    if (false === strpos($source, '.')) {
1006
                                        continue;
1007
                                    } // No dot, should not be an external file anyway.
1008
                                    if (strpos($source, 'mailto:')) {
1009
                                        continue;
1010
                                    } // mailto link.
1011
                                    if (strpos($source, ';') &&
1012
                                        !strpos($source, '&amp;')
1013
                                    ) {
1014
                                        continue;
1015
                                    } // Avoid code - that should help.
1016
1017
                                    if ('value' == $attr) {
1018
                                        if (strpos($source, 'mp3file')) {
1019
                                            $files_list[] = [
1020
                                                substr(
1021
                                                    $source,
1022
                                                    0,
1023
                                                    strpos(
1024
                                                        $source,
1025
                                                        '.swf'
1026
                                                    ) + 4
1027
                                                ),
1028
                                                'local',
1029
                                                'abs',
1030
                                            ];
1031
                                            $mp3file = substr(
1032
                                                $source,
1033
                                                strpos(
1034
                                                    $source,
1035
                                                    'mp3file='
1036
                                                ) + 8
1037
                                            );
1038
                                            if ('/' == substr($mp3file, 0, 1)) {
1039
                                                $files_list[] = [
1040
                                                    $mp3file,
1041
                                                    'local',
1042
                                                    'abs',
1043
                                                ];
1044
                                            } else {
1045
                                                $files_list[] = [
1046
                                                    $mp3file,
1047
                                                    'local',
1048
                                                    'rel',
1049
                                                ];
1050
                                            }
1051
                                        } elseif (0 === strpos($source, 'flv=')) {
1052
                                            $source = substr($source, 4);
1053
                                            if (strpos($source, '&') > 0) {
1054
                                                $source = substr(
1055
                                                    $source,
1056
                                                    0,
1057
                                                    strpos($source, '&')
1058
                                                );
1059
                                            }
1060
                                            if (strpos($source, '://') > 0) {
1061
                                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1062
                                                    // We found the current portal url.
1063
                                                    $files_list[] = [
1064
                                                        $source,
1065
                                                        'local',
1066
                                                        'url',
1067
                                                    ];
1068
                                                } else {
1069
                                                    // We didn't find any trace of current portal.
1070
                                                    $files_list[] = [
1071
                                                        $source,
1072
                                                        'remote',
1073
                                                        'url',
1074
                                                    ];
1075
                                                }
1076
                                            } else {
1077
                                                $files_list[] = [
1078
                                                    $source,
1079
                                                    'local',
1080
                                                    'abs',
1081
                                                ];
1082
                                            }
1083
                                            continue; // Skipping anything else to avoid two entries
1084
                                            //(while the others can have sub-files in their url, flv's can't).
1085
                                        }
1086
                                    }
1087
1088
                                    if (strpos($source, '://') > 0) {
1089
                                        // Cut at '?' in a URL with params.
1090
                                        if (strpos($source, '?') > 0) {
1091
                                            $second_part = substr(
1092
                                                $source,
1093
                                                strpos($source, '?')
1094
                                            );
1095
                                            if (strpos($second_part, '://') > 0) {
1096
                                                // If the second part of the url contains a url too,
1097
                                                // treat the second one before cutting.
1098
                                                $pos1 = strpos(
1099
                                                    $second_part,
1100
                                                    '='
1101
                                                );
1102
                                                $pos2 = strpos(
1103
                                                    $second_part,
1104
                                                    '&'
1105
                                                );
1106
                                                $second_part = substr(
1107
                                                    $second_part,
1108
                                                    $pos1 + 1,
1109
                                                    $pos2 - ($pos1 + 1)
1110
                                                );
1111
                                                if (false !== strpos($second_part, api_get_path(WEB_PATH))) {
1112
                                                    // We found the current portal url.
1113
                                                    $files_list[] = [
1114
                                                        $second_part,
1115
                                                        'local',
1116
                                                        'url',
1117
                                                    ];
1118
                                                    $in_files_list[] = self::get_resources_from_source(
1119
                                                        TOOL_DOCUMENT,
1120
                                                        $second_part,
1121
                                                        $recursivity + 1
1122
                                                    );
1123
                                                    if (count($in_files_list) > 0) {
1124
                                                        $files_list = array_merge(
1125
                                                            $files_list,
1126
                                                            $in_files_list
1127
                                                        );
1128
                                                    }
1129
                                                } else {
1130
                                                    // We didn't find any trace of current portal.
1131
                                                    $files_list[] = [
1132
                                                        $second_part,
1133
                                                        'remote',
1134
                                                        'url',
1135
                                                    ];
1136
                                                }
1137
                                            } elseif (strpos($second_part, '=') > 0) {
1138
                                                if ('/' === substr($second_part, 0, 1)) {
1139
                                                    // Link starts with a /,
1140
                                                    // making it absolute (relative to DocumentRoot).
1141
                                                    $files_list[] = [
1142
                                                        $second_part,
1143
                                                        'local',
1144
                                                        'abs',
1145
                                                    ];
1146
                                                    $in_files_list[] = self::get_resources_from_source(
1147
                                                        TOOL_DOCUMENT,
1148
                                                        $second_part,
1149
                                                        $recursivity + 1
1150
                                                    );
1151
                                                    if (count($in_files_list) > 0) {
1152
                                                        $files_list = array_merge(
1153
                                                            $files_list,
1154
                                                            $in_files_list
1155
                                                        );
1156
                                                    }
1157
                                                } elseif (0 === strstr($second_part, '..')) {
1158
                                                    // Link is relative but going back in the hierarchy.
1159
                                                    $files_list[] = [
1160
                                                        $second_part,
1161
                                                        'local',
1162
                                                        'rel',
1163
                                                    ];
1164
                                                    $dir = dirname(
1165
                                                        $abs_path
1166
                                                    );
1167
                                                    $new_abs_path = realpath(
1168
                                                        $dir.'/'.$second_part
1169
                                                    );
1170
                                                    $in_files_list[] = self::get_resources_from_source(
1171
                                                        TOOL_DOCUMENT,
1172
                                                        $new_abs_path,
1173
                                                        $recursivity + 1
1174
                                                    );
1175
                                                    if (count($in_files_list) > 0) {
1176
                                                        $files_list = array_merge(
1177
                                                            $files_list,
1178
                                                            $in_files_list
1179
                                                        );
1180
                                                    }
1181
                                                } else {
1182
                                                    // No starting '/', making it relative to current document's path.
1183
                                                    if ('./' == substr($second_part, 0, 2)) {
1184
                                                        $second_part = substr(
1185
                                                            $second_part,
1186
                                                            2
1187
                                                        );
1188
                                                    }
1189
                                                    $files_list[] = [
1190
                                                        $second_part,
1191
                                                        'local',
1192
                                                        'rel',
1193
                                                    ];
1194
                                                    $dir = dirname(
1195
                                                        $abs_path
1196
                                                    );
1197
                                                    $new_abs_path = realpath(
1198
                                                        $dir.'/'.$second_part
1199
                                                    );
1200
                                                    $in_files_list[] = self::get_resources_from_source(
1201
                                                        TOOL_DOCUMENT,
1202
                                                        $new_abs_path,
1203
                                                        $recursivity + 1
1204
                                                    );
1205
                                                    if (count($in_files_list) > 0) {
1206
                                                        $files_list = array_merge(
1207
                                                            $files_list,
1208
                                                            $in_files_list
1209
                                                        );
1210
                                                    }
1211
                                                }
1212
                                            }
1213
                                            // Leave that second part behind now.
1214
                                            $source = substr(
1215
                                                $source,
1216
                                                0,
1217
                                                strpos($source, '?')
1218
                                            );
1219
                                            if (strpos($source, '://') > 0) {
1220
                                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1221
                                                    // We found the current portal url.
1222
                                                    $files_list[] = [
1223
                                                        $source,
1224
                                                        'local',
1225
                                                        'url',
1226
                                                    ];
1227
                                                    $in_files_list[] = self::get_resources_from_source(
1228
                                                        TOOL_DOCUMENT,
1229
                                                        $source,
1230
                                                        $recursivity + 1
1231
                                                    );
1232
                                                    if (count($in_files_list) > 0) {
1233
                                                        $files_list = array_merge(
1234
                                                            $files_list,
1235
                                                            $in_files_list
1236
                                                        );
1237
                                                    }
1238
                                                } else {
1239
                                                    // We didn't find any trace of current portal.
1240
                                                    $files_list[] = [
1241
                                                        $source,
1242
                                                        'remote',
1243
                                                        'url',
1244
                                                    ];
1245
                                                }
1246
                                            } else {
1247
                                                // No protocol found, make link local.
1248
                                                if ('/' === substr($source, 0, 1)) {
1249
                                                    // Link starts with a /, making it absolute (relative to DocumentRoot).
1250
                                                    $files_list[] = [
1251
                                                        $source,
1252
                                                        'local',
1253
                                                        'abs',
1254
                                                    ];
1255
                                                    $in_files_list[] = self::get_resources_from_source(
1256
                                                        TOOL_DOCUMENT,
1257
                                                        $source,
1258
                                                        $recursivity + 1
1259
                                                    );
1260
                                                    if (count($in_files_list) > 0) {
1261
                                                        $files_list = array_merge(
1262
                                                            $files_list,
1263
                                                            $in_files_list
1264
                                                        );
1265
                                                    }
1266
                                                } elseif (0 === strstr($source, '..')) {
1267
                                                    // Link is relative but going back in the hierarchy.
1268
                                                    $files_list[] = [
1269
                                                        $source,
1270
                                                        'local',
1271
                                                        'rel',
1272
                                                    ];
1273
                                                    $dir = dirname(
1274
                                                        $abs_path
1275
                                                    );
1276
                                                    $new_abs_path = realpath(
1277
                                                        $dir.'/'.$source
1278
                                                    );
1279
                                                    $in_files_list[] = self::get_resources_from_source(
1280
                                                        TOOL_DOCUMENT,
1281
                                                        $new_abs_path,
1282
                                                        $recursivity + 1
1283
                                                    );
1284
                                                    if (count($in_files_list) > 0) {
1285
                                                        $files_list = array_merge(
1286
                                                            $files_list,
1287
                                                            $in_files_list
1288
                                                        );
1289
                                                    }
1290
                                                } else {
1291
                                                    // No starting '/', making it relative to current document's path.
1292
                                                    if ('./' == substr($source, 0, 2)) {
1293
                                                        $source = substr(
1294
                                                            $source,
1295
                                                            2
1296
                                                        );
1297
                                                    }
1298
                                                    $files_list[] = [
1299
                                                        $source,
1300
                                                        'local',
1301
                                                        'rel',
1302
                                                    ];
1303
                                                    $dir = dirname(
1304
                                                        $abs_path
1305
                                                    );
1306
                                                    $new_abs_path = realpath(
1307
                                                        $dir.'/'.$source
1308
                                                    );
1309
                                                    $in_files_list[] = self::get_resources_from_source(
1310
                                                        TOOL_DOCUMENT,
1311
                                                        $new_abs_path,
1312
                                                        $recursivity + 1
1313
                                                    );
1314
                                                    if (count($in_files_list) > 0) {
1315
                                                        $files_list = array_merge(
1316
                                                            $files_list,
1317
                                                            $in_files_list
1318
                                                        );
1319
                                                    }
1320
                                                }
1321
                                            }
1322
                                        }
1323
1324
                                        // Found some protocol there.
1325
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1326
                                            // We found the current portal url.
1327
                                            $files_list[] = [
1328
                                                $source,
1329
                                                'local',
1330
                                                'url',
1331
                                            ];
1332
                                            $in_files_list[] = self::get_resources_from_source(
1333
                                                TOOL_DOCUMENT,
1334
                                                $source,
1335
                                                $recursivity + 1
1336
                                            );
1337
                                            if (count($in_files_list) > 0) {
1338
                                                $files_list = array_merge(
1339
                                                    $files_list,
1340
                                                    $in_files_list
1341
                                                );
1342
                                            }
1343
                                        } else {
1344
                                            // We didn't find any trace of current portal.
1345
                                            $files_list[] = [
1346
                                                $source,
1347
                                                'remote',
1348
                                                'url',
1349
                                            ];
1350
                                        }
1351
                                    } else {
1352
                                        // No protocol found, make link local.
1353
                                        if ('/' === substr($source, 0, 1)) {
1354
                                            // Link starts with a /, making it absolute (relative to DocumentRoot).
1355
                                            $files_list[] = [
1356
                                                $source,
1357
                                                'local',
1358
                                                'abs',
1359
                                            ];
1360
                                            $in_files_list[] = self::get_resources_from_source(
1361
                                                TOOL_DOCUMENT,
1362
                                                $source,
1363
                                                $recursivity + 1
1364
                                            );
1365
                                            if (count($in_files_list) > 0) {
1366
                                                $files_list = array_merge(
1367
                                                    $files_list,
1368
                                                    $in_files_list
1369
                                                );
1370
                                            }
1371
                                        } elseif (0 === strstr($source, '..')) {
1372
                                            // Link is relative but going back in the hierarchy.
1373
                                            $files_list[] = [
1374
                                                $source,
1375
                                                'local',
1376
                                                'rel',
1377
                                            ];
1378
                                            $dir = dirname($abs_path);
1379
                                            $new_abs_path = realpath(
1380
                                                $dir.'/'.$source
1381
                                            );
1382
                                            $in_files_list[] = self::get_resources_from_source(
1383
                                                TOOL_DOCUMENT,
1384
                                                $new_abs_path,
1385
                                                $recursivity + 1
1386
                                            );
1387
                                            if (count($in_files_list) > 0) {
1388
                                                $files_list = array_merge(
1389
                                                    $files_list,
1390
                                                    $in_files_list
1391
                                                );
1392
                                            }
1393
                                        } else {
1394
                                            // No starting '/', making it relative to current document's path.
1395
                                            if (strpos($source, 'width=') ||
1396
                                                strpos($source, 'autostart=')
1397
                                            ) {
1398
                                                continue;
1399
                                            }
1400
1401
                                            if ('./' == substr($source, 0, 2)) {
1402
                                                $source = substr(
1403
                                                    $source,
1404
                                                    2
1405
                                                );
1406
                                            }
1407
                                            $files_list[] = [
1408
                                                $source,
1409
                                                'local',
1410
                                                'rel',
1411
                                            ];
1412
                                            $dir = dirname($abs_path);
1413
                                            $new_abs_path = realpath(
1414
                                                $dir.'/'.$source
1415
                                            );
1416
                                            $in_files_list[] = self::get_resources_from_source(
1417
                                                TOOL_DOCUMENT,
1418
                                                $new_abs_path,
1419
                                                $recursivity + 1
1420
                                            );
1421
                                            if (count($in_files_list) > 0) {
1422
                                                $files_list = array_merge(
1423
                                                    $files_list,
1424
                                                    $in_files_list
1425
                                                );
1426
                                            }
1427
                                        }
1428
                                    }
1429
                                }
1430
                            }
1431
                        }
1432
                        break;
1433
                    default:
1434
                        break;
1435
                }
1436
1437
                break;
1438
            default: // Ignore.
1439
                break;
1440
        }
1441
1442
        $checked_files_list = [];
1443
        $checked_array_list = [];
1444
        foreach ($files_list as $idx => $file) {
1445
            if (!empty($file[0])) {
1446
                if (!in_array($file[0], $checked_files_list)) {
1447
                    $checked_files_list[] = $files_list[$idx][0];
1448
                    $checked_array_list[] = $files_list[$idx];
1449
                }
1450
            }
1451
        }
1452
1453
        return $checked_array_list;
1454
    }
1455
1456
    /**
1457
     * Gets the score.
1458
     *
1459
     * @return float The current score or 0 if no score set yet
1460
     */
1461
    public function get_score()
1462
    {
1463
        $res = 0;
1464
        if (!empty($this->current_score)) {
1465
            $res = $this->current_score;
1466
        }
1467
1468
        return $res;
1469
    }
1470
1471
    /**
1472
     * Gets the item status.
1473
     *
1474
     * @param bool $check_db     Do or don't check into the database for the
1475
     *                           latest value. Optional. Default is true
1476
     * @param bool $update_local Do or don't update the local attribute
1477
     *                           value with what's been found in DB
1478
     *
1479
     * @return string Current status or 'Not attempted' if no status set yet
1480
     */
1481
    public function get_status($check_db = true, $update_local = false)
1482
    {
1483
        $course_id = api_get_course_int_id();
1484
        $debug = self::DEBUG;
1485
        if ($debug > 0) {
1486
            error_log('learnpathItem::get_status() on item '.$this->db_id, 0);
1487
        }
1488
        if ($check_db) {
1489
            if ($debug > 2) {
1490
                error_log('learnpathItem::get_status(): checking db', 0);
1491
            }
1492
            if (!empty($this->db_item_view_id) && !empty($course_id)) {
1493
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1494
                $sql = "SELECT status FROM $table
1495
                        WHERE
1496
                            c_id = $course_id AND
1497
                            iid = '".$this->db_item_view_id."' AND
1498
                            view_count = '".$this->get_attempt_id()."'";
1499
                $res = Database::query($sql);
1500
                if (1 == Database::num_rows($res)) {
1501
                    $row = Database::fetch_array($res);
1502
                    if ($update_local) {
1503
                        $this->set_status($row['status']);
1504
                    }
1505
1506
                    return $row['status'];
1507
                }
1508
            }
1509
        } else {
1510
            if (!empty($this->status)) {
1511
                return $this->status;
1512
            }
1513
        }
1514
1515
        return $this->possible_status[0];
1516
    }
1517
1518
    /**
1519
     * Gets the suspend data.
1520
     */
1521
    public function get_suspend_data()
1522
    {
1523
        // TODO: Improve cleaning of breaklines ... it works but is it really
1524
        // a beautiful way to do it ?
1525
        if (!empty($this->current_data)) {
1526
            return str_replace(
1527
                ["\r", "\n", "'"],
1528
                ['\r', '\n', "\\'"],
1529
                $this->current_data
1530
            );
1531
        }
1532
1533
        return '';
1534
    }
1535
1536
    /**
1537
     * @param string $origin
1538
     * @param string $time
1539
     *
1540
     * @return string
1541
     */
1542
    public static function getScormTimeFromParameter(
1543
        $origin = 'php',
1544
        $time = null
1545
    ) {
1546
        $h = get_lang('h');
1547
        if (!isset($time)) {
1548
            if ('js' == $origin) {
1549
                return '00 : 00: 00';
1550
            }
1551
1552
            return '00 '.$h.' 00 \' 00"';
1553
        }
1554
1555
        return api_format_time($time, $origin);
1556
    }
1557
1558
    /**
1559
     * Gets the total time spent on this item view so far.
1560
     *
1561
     * @param string   $origin     Origin of the request. If coming from PHP,
1562
     *                             send formatted as xxhxx'xx", otherwise use scorm format 00:00:00
1563
     * @param int|null $given_time Given time is a default time to return formatted
1564
     * @param bool     $query_db   Whether to get the value from db or from memory
1565
     *
1566
     * @return string A string with the time in SCORM format
1567
     */
1568
    public function get_scorm_time(
1569
        $origin = 'php',
1570
        $given_time = null,
1571
        $query_db = false
1572
    ) {
1573
        $time = null;
1574
        $course_id = api_get_course_int_id();
1575
        if (!isset($given_time)) {
1576
            if (self::DEBUG > 2) {
1577
                error_log(
1578
                    'learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time,
1579
                    0
1580
                );
1581
            }
1582
            if (true === $query_db) {
1583
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1584
                $sql = "SELECT start_time, total_time
1585
                        FROM $table
1586
                        WHERE
1587
                            c_id = $course_id AND
1588
                            iid = '".$this->db_item_view_id."' AND
1589
                            view_count = '".$this->get_attempt_id()."'";
1590
                $res = Database::query($sql);
1591
                $row = Database::fetch_array($res);
1592
                $start = $row['start_time'];
1593
                $stop = $start + $row['total_time'];
1594
            } else {
1595
                $start = $this->current_start_time;
1596
                $stop = $this->current_stop_time;
1597
            }
1598
            if (!empty($start)) {
1599
                if (!empty($stop)) {
1600
                    $time = $stop - $start;
1601
                } else {
1602
                    $time = time() - $start;
1603
                }
1604
            }
1605
        } else {
1606
            $time = $given_time;
1607
        }
1608
        if (self::DEBUG > 2) {
1609
            error_log(
1610
                'learnpathItem::get_scorm_time(): intermediate = '.$time,
1611
                0
1612
            );
1613
        }
1614
        $time = api_format_time($time, $origin);
1615
1616
        return $time;
1617
    }
1618
1619
    /**
1620
     * Get the extra terms (tags) that identify this item.
1621
     *
1622
     * @return mixed
1623
     */
1624
    public function get_terms()
1625
    {
1626
        $table = Database::get_course_table(TABLE_LP_ITEM);
1627
        $sql = "SELECT * FROM $table
1628
                WHERE iid = ".intval($this->db_id);
1629
        $res = Database::query($sql);
1630
        $row = Database::fetch_array($res);
1631
1632
        return $row['terms'];
1633
    }
1634
1635
    /**
1636
     * Returns the item's title.
1637
     *
1638
     * @return string Title
1639
     */
1640
    public function get_title()
1641
    {
1642
        if (empty($this->title)) {
1643
            return '';
1644
        }
1645
1646
        return $this->title;
1647
    }
1648
1649
    /**
1650
     * Returns the total time used to see that item.
1651
     *
1652
     * @return int Total time
1653
     */
1654
    public function get_total_time()
1655
    {
1656
        $debug = self::DEBUG;
1657
        if ($debug) {
1658
            error_log(
1659
                'learnpathItem::get_total_time() for item '.$this->db_id.
1660
                ' - Initially, current_start_time = '.$this->current_start_time.
1661
                ' and current_stop_time = '.$this->current_stop_time,
1662
                0
1663
            );
1664
        }
1665
        if (0 == $this->current_start_time) {
1666
            // Shouldn't be necessary thanks to the open() method.
1667
            if ($debug) {
1668
                error_log(
1669
                    'learnpathItem::get_total_time() - Current start time was empty',
1670
                    0
1671
                );
1672
            }
1673
            $this->current_start_time = time();
1674
        }
1675
1676
        if (time() < $this->current_stop_time ||
1677
            0 == $this->current_stop_time
1678
        ) {
1679
            if ($debug) {
1680
                error_log(
1681
                    'learnpathItem::get_total_time() - Current stop time was '
1682
                    .'greater than the current time or was empty',
1683
                    0
1684
                );
1685
            }
1686
            // If this case occurs, then we risk to write huge time data in db.
1687
            // In theory, stop time should be *always* updated here, but it
1688
            // might be used in some unknown goal.
1689
            $this->current_stop_time = time();
1690
        }
1691
1692
        $time = $this->current_stop_time - $this->current_start_time;
1693
1694
        if ($time < 0) {
1695
            if ($debug) {
1696
                error_log(
1697
                    'learnpathItem::get_total_time() - Time smaller than 0. Returning 0',
1698
                    0
1699
                );
1700
            }
1701
1702
            return 0;
1703
        } else {
1704
            $time = $this->fixAbusiveTime($time);
1705
            if ($debug) {
1706
                error_log(
1707
                    'Current start time = '.$this->current_start_time.', current stop time = '.
1708
                    $this->current_stop_time.' Returning '.$time."-----------\n"
1709
                );
1710
            }
1711
1712
            return $time;
1713
        }
1714
    }
1715
1716
    /**
1717
     * Sometimes time recorded for a learning path item is superior to the maximum allowed duration of the session.
1718
     * In this case, this session resets the time for that particular learning path item to 5 minutes
1719
     * (something more realistic, that is also used when leaving the portal without closing one's session).
1720
     *
1721
     * @param int $time
1722
     *
1723
     * @return int
1724
     */
1725
    public function fixAbusiveTime($time)
1726
    {
1727
        // Code based from Event::courseLogout
1728
        $sessionLifetime = api_get_configuration_value('session_lifetime');
1729
        // If session life time too big use 1 hour
1730
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
1731
            $sessionLifetime = 3600;
1732
        }
1733
1734
        if (!Tracking::minimumTimeAvailable(api_get_session_id(), api_get_course_int_id())) {
1735
            $fixedAddedMinute = 5 * 60; // Add only 5 minutes
1736
            if ($time > $sessionLifetime) {
1737
                error_log("fixAbusiveTime: Total time is too big: $time replaced with: $fixedAddedMinute");
1738
                error_log("item_id : ".$this->db_id." lp_item_view.iid: ".$this->db_item_view_id);
1739
                $time = $fixedAddedMinute;
1740
            }
1741
1742
            return $time;
1743
        } else {
1744
            // Calulate minimum and accumulated time
1745
            $user_id = api_get_user_id();
1746
            $myLP = learnpath::getLpFromSession(api_get_course_id(), $this->lp_id, $user_id);
1747
            $timeLp = $myLP->getAccumulateWorkTime();
1748
            $timeTotalCourse = $myLP->getAccumulateWorkTimeTotalCourse();
1749
            /*
1750
            $timeLp = $_SESSION['oLP']->getAccumulateWorkTime();
1751
            $timeTotalCourse = $_SESSION['oLP']->getAccumulateWorkTimeTotalCourse();
1752
            */
1753
            // Minimum connection percentage
1754
            $perc = 100;
1755
            // Time from the course
1756
            $tc = $timeTotalCourse;
1757
            /*if (!empty($sessionId) && $sessionId != 0) {
1758
                $sql = "SELECT hours, perc FROM plugin_licences_course_session WHERE session_id = $sessionId";
1759
                $res = Database::query($sql);
1760
                if (Database::num_rows($res) > 0) {
1761
                    $aux = Database::fetch_assoc($res);
1762
                    $perc = $aux['perc'];
1763
                    $tc = $aux['hours'] * 60;
1764
                }
1765
            }*/
1766
            // Percentage of the learning paths
1767
            $pl = 0;
1768
            if (!empty($timeTotalCourse)) {
1769
                $pl = $timeLp / $timeTotalCourse;
1770
            }
1771
1772
            // Minimum time for each learning path
1773
            $accumulateWorkTime = ($pl * $tc * $perc / 100);
1774
            $time_seg = intval($accumulateWorkTime * 60);
1775
1776
            if ($time_seg < $sessionLifetime) {
1777
                $sessionLifetime = $time_seg;
1778
            }
1779
1780
            if ($time > $sessionLifetime) {
1781
                $fixedAddedMinute = $time_seg + mt_rand(0, 300);
1782
                if (self::DEBUG > 2) {
1783
                    error_log("Total time is too big: $time replaced with: $fixedAddedMinute");
1784
                }
1785
                $time = $fixedAddedMinute;
1786
            }
1787
1788
            return $time;
1789
        }
1790
    }
1791
1792
    /**
1793
     * Gets the item type.
1794
     *
1795
     * @return string The item type (can be doc, dir, sco, asset)
1796
     */
1797
    public function get_type()
1798
    {
1799
        $res = 'asset';
1800
        if (!empty($this->type)) {
1801
            $res = $this->type;
1802
        }
1803
1804
        return $res;
1805
    }
1806
1807
    /**
1808
     * Gets the view count for this item.
1809
     *
1810
     * @return int Number of attempts or 0
1811
     */
1812
    public function get_view_count()
1813
    {
1814
        if (!empty($this->attempt_id)) {
1815
            return $this->attempt_id;
1816
        }
1817
1818
        return 0;
1819
    }
1820
1821
    /**
1822
     * Tells if an item is done ('completed','passed','succeeded') or not.
1823
     *
1824
     * @return bool True if the item is done ('completed','passed','succeeded'),
1825
     *              false otherwise
1826
     */
1827
    public function is_done()
1828
    {
1829
        $completedStatusList = [
1830
            'completed',
1831
            'passed',
1832
            'succeeded',
1833
            'failed',
1834
        ];
1835
1836
        if ($this->status_is($completedStatusList)) {
1837
1838
            return true;
1839
        }
1840
1841
        return false;
1842
    }
1843
1844
    /**
1845
     * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status).
1846
     *
1847
     * @return int -1 if retaking the sco another time for credit is not allowed,
1848
     *             0 if it is not allowed but the item has to be finished
1849
     *             1 if it is allowed. Defaults to 1
1850
     */
1851
    public function isRestartAllowed()
1852
    {
1853
        $restart = 1;
1854
        $mystatus = $this->get_status(true);
1855
        if ($this->get_prevent_reinit() > 0) {
1856
            // If prevent_reinit == 1 (or more)
1857
            // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise:
1858
            if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) {
1859
                $restart = -1;
1860
            } else { //status incompleted or not attempted
1861
                $restart = 0;
1862
            }
1863
        } else {
1864
            if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) {
1865
                $restart = -1;
1866
            }
1867
        }
1868
1869
        return $restart;
1870
    }
1871
1872
    /**
1873
     * Opens/launches the item. Initialises runtime values.
1874
     *
1875
     * @param bool $allow_new_attempt
1876
     *
1877
     * @return bool true on success, false on failure
1878
     */
1879
    public function open($allow_new_attempt = false)
1880
    {
1881
        if (0 == $this->prevent_reinit) {
1882
            $this->current_score = 0;
1883
            $this->current_start_time = time();
1884
            // In this case, as we are opening the item, what is important to us
1885
            // is the database status, in order to know if this item has already
1886
            // been used in the past (rather than just loaded and modified by
1887
            // some javascript but not written in the database).
1888
            // If the database status is different from 'not attempted', we can
1889
            // consider this item has already been used, and as such we can
1890
            // open a new attempt. Otherwise, we'll just reuse the current
1891
            // attempt, which is generally created the first time the item is
1892
            // loaded (for example as part of the table of contents).
1893
            $stat = $this->get_status(true);
1894
            if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) {
1895
                $this->attempt_id = $this->attempt_id + 1; // Open a new attempt.
1896
            }
1897
            $this->status = $this->possible_status[1];
1898
        } else {
1899
            /*if ($this->current_start_time == 0) {
1900
                // Small exception for start time, to avoid amazing values.
1901
                $this->current_start_time = time();
1902
            }*/
1903
            // If we don't init start time here, the time is sometimes calculated from the last start time.
1904
            $this->current_start_time = time();
1905
        }
1906
    }
1907
1908
    /**
1909
     * Outputs the item contents.
1910
     *
1911
     * @return string HTML file (displayable in an <iframe>) or empty string if no path defined
1912
     */
1913
    public function output()
1914
    {
1915
        if (!empty($this->path) and is_file($this->path)) {
1916
            $output = '';
1917
            $output .= file_get_contents($this->path);
1918
1919
            return $output;
1920
        }
1921
1922
        return '';
1923
    }
1924
1925
    /**
1926
     * Parses the prerequisites string with the AICC logic language.
1927
     *
1928
     * @param string $prereqs_string The prerequisites string as it figures in imsmanifest.xml
1929
     * @param array  $items          Array of items in the current learnpath object.
1930
     *                               Although we're in the learnpathItem object, it's necessary to have
1931
     *                               a list of all items to be able to check the current item's prerequisites
1932
     * @param array  $refs_list      list of references
1933
     *                               (the "ref" column in the lp_item table) that are strings used in the
1934
     *                               expression of prerequisites
1935
     * @param int    $user_id        The user ID. In some cases like Chamilo quizzes,
1936
     *                               it's necessary to have the user ID to query other tables (like the results of quizzes)
1937
     *
1938
     * @return bool True if the list of prerequisites given is entirely satisfied, false otherwise
1939
     */
1940
    public function parse_prereq($prereqs_string, $items, $refs_list, $user_id)
1941
    {
1942
        $debug = self::DEBUG;
1943
        if ($debug > 0) {
1944
            error_log(
1945
                'learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string,
1946
                0
1947
            );
1948
        }
1949
1950
        $course_id = api_get_course_int_id();
1951
        $sessionId = api_get_session_id();
1952
1953
        // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order.
1954
        $this->prereq_alert = '';
1955
1956
        // First parse all parenthesis by using a sequential loop
1957
        //  (looking for less-inclusives first).
1958
        if ('_true_' == $prereqs_string) {
1959
            return true;
1960
        }
1961
1962
        if ('_false_' == $prereqs_string) {
1963
            if (empty($this->prereq_alert)) {
1964
                $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
1965
            }
1966
1967
            return false;
1968
        }
1969
1970
        while (false !== strpos($prereqs_string, '(')) {
1971
            // Remove any () set and replace with its value.
1972
            $matches = [];
1973
            $res = preg_match_all(
1974
                '/(\(([^\(\)]*)\))/',
1975
                $prereqs_string,
1976
                $matches
1977
            );
1978
            if ($res) {
1979
                foreach ($matches[2] as $id => $match) {
1980
                    $str_res = $this->parse_prereq(
1981
                        $match,
1982
                        $items,
1983
                        $refs_list,
1984
                        $user_id
1985
                    );
1986
                    if ($str_res) {
1987
                        $prereqs_string = str_replace(
1988
                            $matches[1][$id],
1989
                            '_true_',
1990
                            $prereqs_string
1991
                        );
1992
                    } else {
1993
                        $prereqs_string = str_replace(
1994
                            $matches[1][$id],
1995
                            '_false_',
1996
                            $prereqs_string
1997
                        );
1998
                    }
1999
                }
2000
            }
2001
        }
2002
2003
        // Parenthesis removed, now look for ORs as it is the lesser-priority
2004
        //  binary operator (= always uses one text operand).
2005
        if (false === strpos($prereqs_string, '|')) {
2006
            if ($debug) {
2007
                error_log('New LP - Didnt find any OR, looking for AND', 0);
2008
            }
2009
            if (false !== strpos($prereqs_string, '&')) {
2010
                $list = explode('&', $prereqs_string);
2011
                if (count($list) > 1) {
2012
                    $andstatus = true;
2013
                    foreach ($list as $condition) {
2014
                        $andstatus = $andstatus && $this->parse_prereq(
2015
                            $condition,
2016
                            $items,
2017
                            $refs_list,
2018
                            $user_id
2019
                        );
2020
2021
                        if (!$andstatus) {
2022
                            if ($debug) {
2023
                                error_log(
2024
                                    'New LP - One condition in AND was false, short-circuit',
2025
                                    0
2026
                                );
2027
                            }
2028
                            break;
2029
                        }
2030
                    }
2031
2032
                    if (empty($this->prereq_alert) && !$andstatus) {
2033
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2034
                    }
2035
2036
                    return $andstatus;
2037
                } else {
2038
                    if (isset($items[$refs_list[$list[0]]])) {
2039
                        $status = $items[$refs_list[$list[0]]]->get_status(true);
2040
                        $returnstatus = ($status == $this->possible_status[2]) || ($status == $this->possible_status[3]);
2041
                        if (empty($this->prereq_alert) && !$returnstatus) {
2042
                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2043
                        }
2044
2045
                        return $returnstatus;
2046
                    }
2047
                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2048
2049
                    return false;
2050
                }
2051
            } else {
2052
                // No ORs found, now look for ANDs.
2053
                if ($debug) {
2054
                    error_log('New LP - Didnt find any AND, looking for =', 0);
2055
                }
2056
2057
                if (false !== strpos($prereqs_string, '=')) {
2058
                    if ($debug) {
2059
                        error_log('New LP - Found =, looking into it', 0);
2060
                    }
2061
                    // We assume '=' signs only appear when there's nothing else around.
2062
                    $params = explode('=', $prereqs_string);
2063
                    if (2 == count($params)) {
2064
                        // Right number of operands.
2065
                        if (isset($items[$refs_list[$params[0]]])) {
2066
                            $status = $items[$refs_list[$params[0]]]->get_status(true);
2067
                            $returnstatus = $status == $params[1];
2068
                            if (empty($this->prereq_alert) && !$returnstatus) {
2069
                                $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2070
                            }
2071
2072
                            return $returnstatus;
2073
                        }
2074
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2075
2076
                        return false;
2077
                    }
2078
                } else {
2079
                    // No ANDs found, look for <>
2080
                    if ($debug) {
2081
                        error_log(
2082
                            'New LP - Didnt find any =, looking for <>',
2083
                            0
2084
                        );
2085
                    }
2086
2087
                    if (false !== strpos($prereqs_string, '<>')) {
2088
                        if ($debug) {
2089
                            error_log('New LP - Found <>, looking into it', 0);
2090
                        }
2091
                        // We assume '<>' signs only appear when there's nothing else around.
2092
                        $params = explode('<>', $prereqs_string);
2093
                        if (2 == count($params)) {
2094
                            // Right number of operands.
2095
                            if (isset($items[$refs_list[$params[0]]])) {
2096
                                $status = $items[$refs_list[$params[0]]]->get_status(true);
2097
                                $returnstatus = $status != $params[1];
2098
                                if (empty($this->prereq_alert) && !$returnstatus) {
2099
                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2100
                                }
2101
2102
                                return $returnstatus;
2103
                            }
2104
                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2105
2106
                            return false;
2107
                        }
2108
                    } else {
2109
                        // No <> found, look for ~ (unary)
2110
                        if ($debug) {
2111
                            error_log(
2112
                                'New LP - Didnt find any =, looking for ~',
2113
                                0
2114
                            );
2115
                        }
2116
                        // Only remains: ~ and X*{}
2117
                        if (false !== strpos($prereqs_string, '~')) {
2118
                            // Found NOT.
2119
                            if ($debug) {
2120
                                error_log(
2121
                                    'New LP - Found ~, looking into it',
2122
                                    0
2123
                                );
2124
                            }
2125
                            $list = [];
2126
                            $myres = preg_match(
2127
                                '/~([^(\d+\*)\{]*)/',
2128
                                $prereqs_string,
2129
                                $list
2130
                            );
2131
                            if ($myres) {
2132
                                $returnstatus = !$this->parse_prereq(
2133
                                    $list[1],
2134
                                    $items,
2135
                                    $refs_list,
2136
                                    $user_id
2137
                                );
2138
                                if (empty($this->prereq_alert) && !$returnstatus) {
2139
                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2140
                                }
2141
2142
                                return $returnstatus;
2143
                            } else {
2144
                                // Strange...
2145
                                if ($debug) {
2146
                                    error_log(
2147
                                        'New LP - Found ~ but strange string: '.$prereqs_string,
2148
                                        0
2149
                                    );
2150
                                }
2151
                            }
2152
                        } else {
2153
                            // Finally, look for sets/groups
2154
                            if ($debug) {
2155
                                error_log(
2156
                                    'New LP - Didnt find any ~, looking for groups',
2157
                                    0
2158
                                );
2159
                            }
2160
                            // Only groups here.
2161
                            $groups = [];
2162
                            $groups_there = preg_match_all(
2163
                                '/((\d+\*)?\{([^\}]+)\}+)/',
2164
                                $prereqs_string,
2165
                                $groups
2166
                            );
2167
2168
                            if ($groups_there) {
2169
                                foreach ($groups[1] as $gr) {
2170
                                    // Only take the results that correspond to
2171
                                    //  the big brackets-enclosed condition.
2172
                                    if ($debug) {
2173
                                        error_log(
2174
                                            'New LP - Dealing with group '.$gr,
2175
                                            0
2176
                                        );
2177
                                    }
2178
                                    $multi = [];
2179
                                    $mycond = false;
2180
                                    if (preg_match(
2181
                                        '/(\d+)\*\{([^\}]+)\}/',
2182
                                        $gr,
2183
                                        $multi
2184
                                    )
2185
                                    ) {
2186
                                        if ($debug) {
2187
                                            error_log(
2188
                                                'New LP - Found multiplier '.$multi[0],
2189
                                                0
2190
                                            );
2191
                                        }
2192
                                        $count = $multi[1];
2193
                                        $list = explode(',', $multi[2]);
2194
                                        $mytrue = 0;
2195
                                        foreach ($list as $cond) {
2196
                                            if (isset($items[$refs_list[$cond]])) {
2197
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2198
                                                if ($status == $this->possible_status[2] ||
2199
                                                    $status == $this->possible_status[3]
2200
                                                ) {
2201
                                                    $mytrue++;
2202
                                                    if ($debug) {
2203
                                                        error_log(
2204
                                                            'New LP - Found true item, counting.. ('.($mytrue).')',
2205
                                                            0
2206
                                                        );
2207
                                                    }
2208
                                                }
2209
                                            } else {
2210
                                                if ($debug) {
2211
                                                    error_log(
2212
                                                        'New LP - item '.$cond.' does not exist in items list',
2213
                                                        0
2214
                                                    );
2215
                                                }
2216
                                            }
2217
                                        }
2218
                                        if ($mytrue >= $count) {
2219
                                            if ($debug) {
2220
                                                error_log(
2221
                                                    'New LP - Got enough true results, return true',
2222
                                                    0
2223
                                                );
2224
                                            }
2225
                                            $mycond = true;
2226
                                        } else {
2227
                                            if ($debug) {
2228
                                                error_log(
2229
                                                    'New LP - Not enough true results',
2230
                                                    0
2231
                                                );
2232
                                            }
2233
                                        }
2234
                                    } else {
2235
                                        if ($debug) {
2236
                                            error_log(
2237
                                                'New LP - No multiplier',
2238
                                                0
2239
                                            );
2240
                                        }
2241
                                        $list = explode(',', $gr);
2242
                                        $mycond = true;
2243
                                        foreach ($list as $cond) {
2244
                                            if (isset($items[$refs_list[$cond]])) {
2245
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2246
                                                if ($status == $this->possible_status[2] ||
2247
                                                    $status == $this->possible_status[3]
2248
                                                ) {
2249
                                                    $mycond = true;
2250
                                                    if ($debug) {
2251
                                                        error_log(
2252
                                                            'New LP - Found true item',
2253
                                                            0
2254
                                                        );
2255
                                                    }
2256
                                                } else {
2257
                                                    if ($debug) {
2258
                                                        error_log(
2259
                                                            'New LP - '.
2260
                                                            ' Found false item, the set is not true, return false',
2261
                                                            0
2262
                                                        );
2263
                                                    }
2264
                                                    $mycond = false;
2265
                                                    break;
2266
                                                }
2267
                                            } else {
2268
                                                if ($debug) {
2269
                                                    error_log(
2270
                                                        'New LP - item '.$cond.' does not exist in items list',
2271
                                                        0
2272
                                                    );
2273
                                                }
2274
                                                if ($debug) {
2275
                                                    error_log(
2276
                                                        'New LP - Found false item, the set is not true, return false',
2277
                                                        0
2278
                                                    );
2279
                                                }
2280
                                                $mycond = false;
2281
                                                break;
2282
                                            }
2283
                                        }
2284
                                    }
2285
                                    if (!$mycond && empty($this->prereq_alert)) {
2286
                                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2287
                                    }
2288
2289
                                    return $mycond;
2290
                                }
2291
                            } else {
2292
                                // Nothing found there either. Now return the
2293
                                // value of the corresponding resource completion status.
2294
                                if (isset($refs_list[$prereqs_string]) &&
2295
                                    isset($items[$refs_list[$prereqs_string]])
2296
                                ) {
2297
                                    /** @var learnpathItem $itemToCheck */
2298
                                    $itemToCheck = $items[$refs_list[$prereqs_string]];
2299
2300
                                    if ('quiz' === $itemToCheck->type) {
2301
                                        // 1. Checking the status in current items.
2302
                                        $status = $itemToCheck->get_status(true);
2303
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2304
2305
                                        if (!$returnstatus) {
2306
                                            $explanation = sprintf(
2307
                                                get_lang('Item %s blocks this step'),
2308
                                                $itemToCheck->get_title()
2309
                                            );
2310
                                            $this->prereq_alert = $explanation;
2311
                                        }
2312
2313
                                        // For one and first attempt.
2314
                                        if (1 == $this->prevent_reinit) {
2315
                                            // 2. If is completed we check the results in the DB of the quiz.
2316
                                            if ($returnstatus) {
2317
                                                $sql = 'SELECT score, max_score
2318
                                                        FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2319
                                                        WHERE
2320
                                                            exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2321
                                                            exe_user_id = '.$user_id.' AND
2322
                                                            orig_lp_id = '.$this->lp_id.' AND
2323
                                                            orig_lp_item_id = '.$prereqs_string.' AND
2324
                                                            status <> "incomplete" AND
2325
                                                            c_id = '.$course_id.'
2326
                                                        ORDER BY exe_date DESC
2327
                                                        LIMIT 0, 1';
2328
                                                $rs_quiz = Database::query($sql);
2329
                                                if ($quiz = Database::fetch_array($rs_quiz)) {
2330
                                                    /** @var learnpathItem $myItemToCheck */
2331
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2332
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2333
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2334
2335
                                                    if (isset($minScore) && isset($minScore)) {
2336
                                                        // Taking min/max prerequisites values see BT#5776
2337
                                                        if ($quiz['score'] >= $minScore &&
2338
                                                            $quiz['score'] <= $maxScore
2339
                                                        ) {
2340
                                                            $returnstatus = true;
2341
                                                        } else {
2342
                                                            $explanation = sprintf(
2343
                                                                get_lang('Your result at %s blocks this step'),
2344
                                                                $itemToCheck->get_title()
2345
                                                            );
2346
                                                            $this->prereq_alert = $explanation;
2347
                                                            $returnstatus = false;
2348
                                                        }
2349
                                                    } else {
2350
                                                        // Classic way
2351
                                                        if ($quiz['score'] >=
2352
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2353
                                                        ) {
2354
                                                            $returnstatus = true;
2355
                                                        } else {
2356
                                                            $explanation = sprintf(
2357
                                                                get_lang('Your result at %s blocks this step'),
2358
                                                                $itemToCheck->get_title()
2359
                                                            );
2360
                                                            $this->prereq_alert = $explanation;
2361
                                                            $returnstatus = false;
2362
                                                        }
2363
                                                    }
2364
                                                } else {
2365
                                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2366
                                                    $returnstatus = false;
2367
                                                }
2368
                                            }
2369
                                        } else {
2370
                                            // 3. For multiple attempts we check that there are minimum 1 item completed
2371
                                            // Checking in the database.
2372
                                            $sql = 'SELECT score, max_score
2373
                                                    FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2374
                                                    WHERE
2375
                                                        c_id = '.$course_id.' AND
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;
2380
2381
                                            $rs_quiz = Database::query($sql);
2382
                                            if (Database::num_rows($rs_quiz) > 0) {
2383
                                                while ($quiz = Database::fetch_array($rs_quiz)) {
2384
                                                    /** @var learnpathItem $myItemToCheck */
2385
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2386
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2387
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2388
2389
                                                    if (empty($minScore)) {
2390
                                                        // Try with mastery_score
2391
                                                        $masteryScoreAsMin = $myItemToCheck->get_mastery_score();
2392
                                                        if (!empty($masteryScoreAsMin)) {
2393
                                                            $minScore = $masteryScoreAsMin;
2394
                                                        }
2395
                                                    }
2396
2397
                                                    if (isset($minScore) && isset($minScore)) {
2398
                                                        // Taking min/max prerequisites values see BT#5776
2399
                                                        if ($quiz['score'] >= $minScore && $quiz['score'] <= $maxScore) {
2400
                                                            $returnstatus = true;
2401
                                                            break;
2402
                                                        } else {
2403
                                                            $explanation = sprintf(
2404
                                                                get_lang('Your result at %s blocks this step'),
2405
                                                                $itemToCheck->get_title()
2406
                                                            );
2407
                                                            $this->prereq_alert = $explanation;
2408
                                                            $returnstatus = false;
2409
                                                        }
2410
                                                    } else {
2411
                                                        if ($quiz['score'] >=
2412
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2413
                                                        ) {
2414
                                                            $returnstatus = true;
2415
                                                            break;
2416
                                                        } else {
2417
                                                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2418
                                                            $returnstatus = false;
2419
                                                        }
2420
                                                    }
2421
                                                }
2422
                                            } else {
2423
                                                $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2424
                                                $returnstatus = false;
2425
                                            }
2426
                                        }
2427
2428
                                        if (false === $returnstatus) {
2429
                                            // Check results from another sessions.
2430
                                            $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
2431
                                            if ($checkOtherSessions) {
2432
                                                $returnstatus = $this->getStatusFromOtherSessions(
2433
                                                    $user_id,
2434
                                                    $prereqs_string,
2435
                                                    $refs_list
2436
                                                );
2437
                                            }
2438
                                        }
2439
2440
                                        return $returnstatus;
2441
                                    } elseif ('student_publication' === $itemToCheck->type) {
2442
                                        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
2443
                                        $workId = $items[$refs_list[$prereqs_string]]->path;
2444
                                        $count = get_work_count_by_student($user_id, $workId);
2445
                                        if ($count >= 1) {
2446
                                            $returnstatus = true;
2447
                                        } else {
2448
                                            $returnstatus = false;
2449
                                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2450
                                        }
2451
2452
                                        return $returnstatus;
2453
                                    } else {
2454
                                        $status = $itemToCheck->get_status(false);
2455
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2456
2457
                                        // Check results from another sessions.
2458
                                        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
2459
                                        if ($checkOtherSessions && !$returnstatus) {
2460
                                            $returnstatus = $this->getStatusFromOtherSessions(
2461
                                                $user_id,
2462
                                                $prereqs_string,
2463
                                                $refs_list
2464
                                            );
2465
                                        }
2466
2467
                                        if (!$returnstatus) {
2468
                                            $explanation = sprintf(
2469
                                                get_lang('Item %s blocks this step'),
2470
                                                $itemToCheck->get_title()
2471
                                            );
2472
                                            $this->prereq_alert = $explanation;
2473
                                        }
2474
2475
                                        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2476
                                        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
2477
2478
                                        if ($returnstatus && 1 == $this->prevent_reinit) {
2479
                                            $sql = "SELECT iid FROM $lp_view
2480
                                                    WHERE
2481
                                                        c_id = $course_id AND
2482
                                                        user_id = $user_id  AND
2483
                                                        lp_id = $this->lp_id AND
2484
                                                        session_id = $sessionId
2485
                                                    LIMIT 0, 1";
2486
                                            $rs_lp = Database::query($sql);
2487
                                            if (Database::num_rows($rs_lp)) {
2488
                                                $lp_id = Database::fetch_row($rs_lp);
2489
                                                $my_lp_id = $lp_id[0];
2490
2491
                                                $sql = "SELECT status FROM $lp_item_view
2492
                                                        WHERE
2493
                                                            c_id = $course_id AND
2494
                                                            lp_view_id = $my_lp_id AND
2495
                                                            lp_item_id = $refs_list[$prereqs_string]
2496
                                                        LIMIT 0, 1";
2497
                                                $rs_lp = Database::query($sql);
2498
                                                $status_array = Database::fetch_row($rs_lp);
2499
                                                $status = $status_array[0];
2500
2501
                                                $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2502
                                                if (!$returnstatus && empty($this->prereq_alert)) {
2503
                                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2504
                                                }
2505
                                            }
2506
2507
                                            if ($checkOtherSessions && false === $returnstatus) {
2508
                                                $returnstatus = $returnstatus = $this->getStatusFromOtherSessions(
2509
                                                    $user_id,
2510
                                                    $prereqs_string,
2511
                                                    $refs_list
2512
                                                );
2513
                                            }
2514
                                        }
2515
2516
                                        return $returnstatus;
2517
                                    }
2518
                                }
2519
                            }
2520
                        }
2521
                    }
2522
                }
2523
            }
2524
        } else {
2525
            $list = explode("\|", $prereqs_string);
2526
            if (count($list) > 1) {
2527
                if (self::DEBUG > 1) {
2528
                    error_log('New LP - Found OR, looking into it', 0);
2529
                }
2530
                $orstatus = false;
2531
                foreach ($list as $condition) {
2532
                    if (self::DEBUG > 1) {
2533
                        error_log(
2534
                            'New LP - Found OR, adding it ('.$condition.')',
2535
                            0
2536
                        );
2537
                    }
2538
                    $orstatus = $orstatus || $this->parse_prereq(
2539
                        $condition,
2540
                        $items,
2541
                        $refs_list,
2542
                        $user_id
2543
                    );
2544
                    if ($orstatus) {
2545
                        // Shortcircuit OR.
2546
                        if (self::DEBUG > 1) {
2547
                            error_log(
2548
                                'New LP - One condition in OR was true, short-circuit',
2549
                                0
2550
                            );
2551
                        }
2552
                        break;
2553
                    }
2554
                }
2555
                if (!$orstatus && empty($this->prereq_alert)) {
2556
                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2557
                }
2558
2559
                return $orstatus;
2560
            } else {
2561
                if (self::DEBUG > 1) {
2562
                    error_log(
2563
                        'New LP - OR was found but only one elem present !?',
2564
                        0
2565
                    );
2566
                }
2567
                if (isset($items[$refs_list[$list[0]]])) {
2568
                    $status = $items[$refs_list[$list[0]]]->get_status(true);
2569
                    $returnstatus = 'completed' == $status || 'passed' == $status;
2570
                    if (!$returnstatus && empty($this->prereq_alert)) {
2571
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2572
                    }
2573
2574
                    return $returnstatus;
2575
                }
2576
            }
2577
        }
2578
        if (empty($this->prereq_alert)) {
2579
            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2580
        }
2581
2582
        if (self::DEBUG > 1) {
2583
            error_log(
2584
                'New LP - End of parse_prereq. Error code is now '.$this->prereq_alert,
2585
                0
2586
            );
2587
        }
2588
2589
        return false;
2590
    }
2591
2592
    /**
2593
     * Reinits all local values as the learnpath is restarted.
2594
     *
2595
     * @return bool True on success, false otherwise
2596
     */
2597
    public function restart()
2598
    {
2599
        if (self::DEBUG > 0) {
2600
            error_log('learnpathItem::restart()', 0);
2601
        }
2602
        $seriousGame = $this->get_seriousgame_mode();
2603
        //For serious game  : We reuse same attempt_id
2604
        if (1 == $seriousGame && 'sco' == $this->type) {
2605
            // If this is a sco, Chamilo can't update the time without an
2606
            //  explicit scorm call
2607
            $this->current_start_time = 0;
2608
            $this->current_stop_time = 0; //Those 0 value have this effect
2609
            $this->last_scorm_session_time = 0;
2610
            $this->save();
2611
2612
            return true;
2613
        }
2614
        $this->save();
2615
2616
        $allowed = $this->isRestartAllowed();
2617
        if (-1 === $allowed) {
2618
            // Nothing allowed, do nothing.
2619
        } elseif (1 === $allowed) {
2620
            // Restart as new attempt is allowed, record a new attempt.
2621
            $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id.
2622
            $this->current_score = 0;
2623
            $this->current_start_time = 0;
2624
            $this->current_stop_time = 0;
2625
            $this->current_data = '';
2626
            $this->status = $this->possible_status[0];
2627
            $this->interactions_count = 0;
2628
            $this->interactions = [];
2629
            $this->objectives_count = 0;
2630
            $this->objectives = [];
2631
            $this->lesson_location = '';
2632
            if (TOOL_QUIZ != $this->type) {
2633
                $this->write_to_db();
2634
            }
2635
        } else {
2636
            // Restart current element is allowed (because it's not finished yet),
2637
            // reinit current.
2638
            //$this->current_score = 0;
2639
            $this->current_start_time = 0;
2640
            $this->current_stop_time = 0;
2641
            $this->interactions_count = $this->get_interactions_count(true);
2642
        }
2643
2644
        return true;
2645
    }
2646
2647
    /**
2648
     * Saves data in the database.
2649
     *
2650
     * @param bool $from_outside     Save from URL params (1) or from object attributes (0)
2651
     * @param bool $prereqs_complete The results of a check on prerequisites for this item.
2652
     *                               True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au
2653
     *
2654
     * @return bool True on success, false on failure
2655
     */
2656
    public function save($from_outside = true, $prereqs_complete = false)
2657
    {
2658
        $debug = self::DEBUG;
2659
        if ($debug) {
2660
            error_log('learnpathItem::save()', 0);
2661
        }
2662
        // First check if parameters passed via GET can be saved here
2663
        // in case it's a SCORM, we should get:
2664
        if ('sco' == $this->type || 'au' == $this->type) {
2665
            $status = $this->get_status(true);
2666
            if (1 == $this->prevent_reinit &&
2667
                $status != $this->possible_status[0] && // not attempted
2668
                $status != $this->possible_status[1]    //incomplete
2669
            ) {
2670
                if ($debug) {
2671
                    error_log(
2672
                        'learnpathItem::save() - save reinit blocked by setting',
2673
                        0
2674
                    );
2675
                }
2676
                // Do nothing because the status has already been set. Don't allow it to change.
2677
                // TODO: Check there isn't a special circumstance where this should be saved.
2678
            } else {
2679
                if ($debug) {
2680
                    error_log(
2681
                        'learnpathItem::save() - SCORM save request received',
2682
                        0
2683
                    );
2684
                }
2685
2686
                // Get all new settings from the URL
2687
                if ($from_outside) {
2688
                    if ($debug) {
2689
                        error_log(
2690
                            'learnpathItem::save() - Getting item data from outside',
2691
                            0
2692
                        );
2693
                    }
2694
                    foreach ($_GET as $param => $value) {
2695
                        switch ($param) {
2696
                            case 'score':
2697
                                $this->set_score($value);
2698
                                if ($debug) {
2699
                                    error_log(
2700
                                        'learnpathItem::save() - setting score to '.$value,
2701
                                        0
2702
                                    );
2703
                                }
2704
                                break;
2705
                            case 'max':
2706
                                $this->set_max_score($value);
2707
                                if ($debug) {
2708
                                    error_log(
2709
                                        'learnpathItem::save() - setting view_max_score to '.$value,
2710
                                        0
2711
                                    );
2712
                                }
2713
                                break;
2714
                            case 'min':
2715
                                $this->min_score = $value;
2716
                                if ($debug) {
2717
                                    error_log(
2718
                                        'learnpathItem::save() - setting min_score to '.$value,
2719
                                        0
2720
                                    );
2721
                                }
2722
                                break;
2723
                            case 'lesson_status':
2724
                                if (!empty($value)) {
2725
                                    $this->set_status($value);
2726
                                    if ($debug) {
2727
                                        error_log(
2728
                                            'learnpathItem::save() - setting status to '.$value,
2729
                                            0
2730
                                        );
2731
                                    }
2732
                                }
2733
                                break;
2734
                            case 'time':
2735
                                $this->set_time($value);
2736
                                if ($debug) {
2737
                                    error_log(
2738
                                        'learnpathItem::save() - setting time to '.$value,
2739
                                        0
2740
                                    );
2741
                                }
2742
                                break;
2743
                            case 'suspend_data':
2744
                                $this->current_data = $value;
2745
                                if ($debug) {
2746
                                    error_log(
2747
                                        'learnpathItem::save() - setting suspend_data to '.$value,
2748
                                        0
2749
                                    );
2750
                                }
2751
                                break;
2752
                            case 'lesson_location':
2753
                                $this->set_lesson_location($value);
2754
                                if ($debug) {
2755
                                    error_log(
2756
                                        'learnpathItem::save() - setting lesson_location to '.$value,
2757
                                        0
2758
                                    );
2759
                                }
2760
                                break;
2761
                            case 'core_exit':
2762
                                $this->set_core_exit($value);
2763
                                if ($debug) {
2764
                                    error_log(
2765
                                        'learnpathItem::save() - setting core_exit to '.$value,
2766
                                        0
2767
                                    );
2768
                                }
2769
                                break;
2770
                            case 'interactions':
2771
                                break;
2772
                            case 'objectives':
2773
                                break;
2774
                            default:
2775
                                // Ignore.
2776
                                break;
2777
                        }
2778
                    }
2779
                } else {
2780
                    if ($debug) {
2781
                        error_log(
2782
                            'learnpathItem::save() - Using inside item status',
2783
                            0
2784
                        );
2785
                    }
2786
                    // Do nothing, just let the local attributes be used.
2787
                }
2788
            }
2789
        } else {
2790
            // If not SCO, such messages should not be expected.
2791
            $type = strtolower($this->type);
2792
            if ($debug) {
2793
                error_log("type: $type");
2794
            }
2795
            switch ($type) {
2796
                case 'asset':
2797
                    if ($prereqs_complete) {
2798
                        $this->set_status($this->possible_status[2]);
2799
                    }
2800
                    break;
2801
                case TOOL_HOTPOTATOES:
2802
                    break;
2803
                case TOOL_QUIZ:
2804
                    return false;
2805
                    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...
2806
                default:
2807
                    // For now, everything that is not sco and not asset is set to
2808
                    // completed when saved.
2809
                    if ($prereqs_complete) {
2810
                        $this->set_status($this->possible_status[2]);
2811
                    }
2812
                    break;
2813
            }
2814
        }
2815
2816
        if ($debug) {
2817
            error_log(
2818
                'New LP - End of learnpathItem::save() - Calling write_to_db()',
2819
                0
2820
            );
2821
        }
2822
2823
        return $this->write_to_db();
2824
    }
2825
2826
    /**
2827
     * Sets the number of attempt_id to a given value.
2828
     *
2829
     * @param int $num The given value to set attempt_id to
2830
     *
2831
     * @return bool TRUE on success, FALSE otherwise
2832
     */
2833
    public function set_attempt_id($num)
2834
    {
2835
        if ($num == strval(intval($num)) && $num >= 0) {
2836
            $this->attempt_id = $num;
2837
2838
            return true;
2839
        }
2840
2841
        return false;
2842
    }
2843
2844
    /**
2845
     * Sets the core_exit value to the one given.
2846
     *
2847
     * @return bool $value  True (always)
2848
     */
2849
    public function set_core_exit($value)
2850
    {
2851
        switch ($value) {
2852
            case '':
2853
                $this->core_exit = '';
2854
                break;
2855
            case 'suspend':
2856
                $this->core_exit = 'suspend';
2857
                break;
2858
            default:
2859
                $this->core_exit = 'none';
2860
                break;
2861
        }
2862
2863
        return true;
2864
    }
2865
2866
    /**
2867
     * Sets the item's description.
2868
     *
2869
     * @param string $string Description
2870
     */
2871
    public function set_description($string = '')
2872
    {
2873
        if (!empty($string)) {
2874
            $this->description = $string;
2875
        }
2876
    }
2877
2878
    /**
2879
     * Sets the lesson_location value.
2880
     *
2881
     * @param string $location lesson_location as provided by the SCO
2882
     *
2883
     * @return bool True on success, false otherwise
2884
     */
2885
    public function set_lesson_location($location)
2886
    {
2887
        if (isset($location)) {
2888
            $this->lesson_location = $location;
2889
2890
            return true;
2891
        }
2892
2893
        return false;
2894
    }
2895
2896
    /**
2897
     * Sets the item's depth level in the LP tree (0 is at root).
2898
     *
2899
     * @param int $int Level
2900
     */
2901
    public function set_level($int = 0)
2902
    {
2903
        $this->level = (int) $int;
2904
    }
2905
2906
    /**
2907
     * Sets the lp_view id this item view is registered to.
2908
     *
2909
     * @param int $lp_view_id lp_view DB ID
2910
     * @param int $course_id
2911
     *
2912
     * @return bool
2913
     *
2914
     * @todo //todo insert into lp_item_view if lp_view not exists
2915
     */
2916
    public function set_lp_view($lp_view_id, $course_id = null)
2917
    {
2918
        $lp_view_id = (int) $lp_view_id;
2919
        $course_id = (int) $course_id;
2920
2921
        if (empty($course_id)) {
2922
            $course_id = api_get_course_int_id();
2923
        }
2924
2925
        $lpItemId = $this->get_id();
2926
2927
        if (empty($lpItemId)) {
2928
            return false;
2929
        }
2930
2931
        if (empty($lp_view_id)) {
2932
            return false;
2933
        }
2934
2935
        if (self::DEBUG > 0) {
2936
            error_log('learnpathItem::set_lp_view('.$lp_view_id.')', 0);
2937
        }
2938
2939
        $this->view_id = $lp_view_id;
2940
2941
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2942
        // Get the lp_item_view with the highest view_count.
2943
        $sql = "SELECT * FROM $item_view_table
2944
                WHERE
2945
                    c_id = $course_id AND
2946
                    lp_item_id = $lpItemId AND
2947
                    lp_view_id = $lp_view_id
2948
                ORDER BY view_count DESC";
2949
2950
        if (self::DEBUG > 2) {
2951
            error_log(
2952
                'learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql,
2953
                0
2954
            );
2955
        }
2956
        $res = Database::query($sql);
2957
        if (Database::num_rows($res) > 0) {
2958
            $row = Database::fetch_array($res);
2959
            $this->db_item_view_id = $row['iid'];
2960
            $this->attempt_id = $row['view_count'];
2961
            $this->current_score = $row['score'];
2962
            $this->current_data = $row['suspend_data'];
2963
            $this->view_max_score = $row['max_score'];
2964
            $this->status = $row['status'];
2965
            $this->current_start_time = $row['start_time'];
2966
            $this->current_stop_time = $this->current_start_time + $row['total_time'];
2967
            $this->lesson_location = $row['lesson_location'];
2968
            $this->core_exit = $row['core_exit'];
2969
2970
            if (self::DEBUG > 2) {
2971
                error_log(
2972
                    'learnpathItem::set_lp_view() - Updated item object with database values',
2973
                    0
2974
                );
2975
            }
2976
2977
            // Now get the number of interactions for this little guy.
2978
            $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2979
            $sql = "SELECT * FROM $table
2980
                    WHERE
2981
                        c_id = $course_id AND
2982
                        lp_iv_id = '".$this->db_item_view_id."'";
2983
2984
            $res = Database::query($sql);
2985
            if (false !== $res) {
2986
                $this->interactions_count = Database::num_rows($res);
2987
            } else {
2988
                $this->interactions_count = 0;
2989
            }
2990
            // Now get the number of objectives for this little guy.
2991
            $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2992
            $sql = "SELECT * FROM $table
2993
                    WHERE
2994
                        c_id = $course_id AND
2995
                        lp_iv_id = '".$this->db_item_view_id."'";
2996
2997
            $this->objectives_count = 0;
2998
            $res = Database::query($sql);
2999
            if (false !== $res) {
3000
                $this->objectives_count = Database::num_rows($res);
3001
            }
3002
        }
3003
3004
        return true;
3005
    }
3006
3007
    /**
3008
     * Sets the path.
3009
     *
3010
     * @param string $string Path
3011
     */
3012
    public function set_path($string = '')
3013
    {
3014
        if (!empty($string)) {
3015
            $this->path = $string;
3016
        }
3017
    }
3018
3019
    /**
3020
     * Sets the prevent_reinit attribute.
3021
     * This is based on the LP value and is set at creation time for
3022
     * each learnpathItem. It is a (bad?) way of avoiding
3023
     * a reference to the LP when saving an item.
3024
     *
3025
     * @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...
3026
     * saving freshened values (new "not attempted" status etc)
3027
     */
3028
    public function set_prevent_reinit($prevent)
3029
    {
3030
        $this->prevent_reinit = 0;
3031
        if ($prevent) {
3032
            $this->prevent_reinit = 1;
3033
        }
3034
    }
3035
3036
    /**
3037
     * Sets the score value. If the mastery_score is set and the score reaches
3038
     * it, then set the status to 'passed'.
3039
     *
3040
     * @param float $score Score
3041
     *
3042
     * @return bool True on success, false otherwise
3043
     */
3044
    public function set_score($score)
3045
    {
3046
        $debug = self::DEBUG;
3047
        if ($debug > 0) {
3048
            error_log('learnpathItem::set_score('.$score.')', 0);
3049
        }
3050
        if (($this->max_score <= 0 || $score <= $this->max_score) && ($score >= $this->min_score)) {
3051
            $this->current_score = $score;
3052
            $masteryScore = $this->get_mastery_score();
3053
            $current_status = $this->get_status(false);
3054
3055
            // Fixes bug when SCORM doesn't send a mastery score even if they sent a score!
3056
            if (-1 == $masteryScore) {
3057
                $masteryScore = $this->max_score;
3058
            }
3059
3060
            if ($debug > 0) {
3061
                error_log('get_mastery_score: '.$masteryScore);
3062
                error_log('current_status: '.$current_status);
3063
                error_log('current score : '.$this->current_score);
3064
            }
3065
3066
            // If mastery_score is set AND the current score reaches the mastery
3067
            //  score AND the current status is different from 'completed', then
3068
            //  set it to 'passed'.
3069
            /*
3070
            if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) {
3071
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[3]);
3072
                $this->set_status($this->possible_status[3]); //passed
3073
            } elseif ($master != -1 && $this->current_score < $master) {
3074
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[4]);
3075
                $this->set_status($this->possible_status[4]); //failed
3076
            }*/
3077
            return true;
3078
        }
3079
3080
        return false;
3081
    }
3082
3083
    /**
3084
     * Sets the maximum score for this item.
3085
     *
3086
     * @param int $score Maximum score - must be a decimal or an empty string
3087
     *
3088
     * @return bool True on success, false on error
3089
     */
3090
    public function set_max_score($score)
3091
    {
3092
        if (is_int($score) || '' == $score) {
3093
            $this->view_max_score = $score;
3094
3095
            return true;
3096
        }
3097
3098
        return false;
3099
    }
3100
3101
    /**
3102
     * Sets the status for this item.
3103
     *
3104
     * @param string $status Status - must be one of the values defined in $this->possible_status
3105
     *                       (this affects the status setting)
3106
     *
3107
     * @return bool True on success, false on error
3108
     */
3109
    public function set_status($status)
3110
    {
3111
        if (self::DEBUG > 0) {
3112
            error_log('learnpathItem::set_status('.$status.')', 0);
3113
        }
3114
3115
        $found = false;
3116
        foreach ($this->possible_status as $possible) {
3117
            if (preg_match('/^'.$possible.'$/i', $status)) {
3118
                $found = true;
3119
            }
3120
        }
3121
3122
        if ($found) {
3123
            $this->status = $status;
3124
            if (self::DEBUG > 1) {
3125
                error_log(
3126
                    'learnpathItem::set_status() - '.
3127
                        'Updated object status of item '.$this->db_id.
3128
                        ' to '.$this->status,
3129
                    0
3130
                );
3131
            }
3132
3133
            return true;
3134
        }
3135
3136
        $this->status = $this->possible_status[0];
3137
3138
        return false;
3139
    }
3140
3141
    /**
3142
     * Set the (indexing) terms for this learnpath item.
3143
     *
3144
     * @param string $terms Terms, as a comma-split list
3145
     *
3146
     * @return bool Always return true
3147
     */
3148
    public function set_terms($terms)
3149
    {
3150
        global $charset;
3151
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
3152
        $a_terms = preg_split('/,/', $terms);
3153
        $i_terms = preg_split('/,/', $this->get_terms());
3154
        foreach ($i_terms as $term) {
3155
            if (!in_array($term, $a_terms)) {
3156
                array_push($a_terms, $term);
3157
            }
3158
        }
3159
        $new_terms = $a_terms;
3160
        $new_terms_string = implode(',', $new_terms);
3161
3162
        // TODO: Validate csv string.
3163
        $terms = Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset));
3164
        $sql = "UPDATE $lp_item
3165
                SET terms = '$terms'
3166
                WHERE iid=".$this->get_id();
3167
        Database::query($sql);
3168
        // Save it to search engine.
3169
        if ('true' == api_get_setting('search_enabled')) {
3170
            $di = new ChamiloIndexer();
3171
            $di->update_terms($this->get_search_did(), $new_terms, 'T');
3172
        }
3173
3174
        return true;
3175
    }
3176
3177
    /**
3178
     * Get the document ID from inside the text index database.
3179
     *
3180
     * @return int Search index database document ID
3181
     */
3182
    public function get_search_did()
3183
    {
3184
        return $this->search_did;
3185
    }
3186
3187
    /**
3188
     * Sets the item viewing time in a usable form, given that SCORM packages
3189
     * often give it as 00:00:00.0000.
3190
     *
3191
     * @param    string    Time as given by SCORM
3192
     * @param string $format
3193
     */
3194
    public function set_time($scorm_time, $format = 'scorm')
3195
    {
3196
        $debug = self::DEBUG;
3197
        if ($debug) {
3198
            error_log("learnpathItem::set_time($scorm_time, $format)");
3199
            error_log("this->type: ".$this->type);
3200
            error_log("this->current_start_time: ".$this->current_start_time);
3201
        }
3202
3203
        if ('0' == $scorm_time &&
3204
            'sco' != $this->type &&
3205
            0 != $this->current_start_time
3206
        ) {
3207
            $myTime = time() - $this->current_start_time;
3208
            if ($myTime > 0) {
3209
                $this->update_time($myTime);
3210
                if ($debug) {
3211
                    error_log('found asset - set time to '.$myTime);
3212
                }
3213
            } else {
3214
                if ($debug) {
3215
                    error_log('Time not set');
3216
                }
3217
            }
3218
        } else {
3219
            switch ($format) {
3220
                case 'scorm':
3221
                    $res = [];
3222
                    if (preg_match(
3223
                        '/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/',
3224
                        $scorm_time,
3225
                        $res
3226
                    )
3227
                    ) {
3228
                        $hour = $res[1];
3229
                        $min = $res[2];
3230
                        $sec = $res[3];
3231
                        // Getting total number of seconds spent.
3232
                        $totalSec = $hour * 3600 + $min * 60 + $sec;
3233
                        if ($debug) {
3234
                            error_log("totalSec : $totalSec");
3235
                            error_log("Now calling to scorm_update_time()");
3236
                        }
3237
                        $this->scorm_update_time($totalSec);
3238
                    }
3239
                    break;
3240
                case 'int':
3241
                    if ($debug) {
3242
                        error_log("scorm_time = $scorm_time");
3243
                        error_log("Now calling to scorm_update_time()");
3244
                    }
3245
                    $this->scorm_update_time($scorm_time);
3246
                    break;
3247
            }
3248
        }
3249
    }
3250
3251
    /**
3252
     * Sets the item's title.
3253
     *
3254
     * @param string $string Title
3255
     */
3256
    public function set_title($string = '')
3257
    {
3258
        if (!empty($string)) {
3259
            $this->title = $string;
3260
        }
3261
    }
3262
3263
    /**
3264
     * Sets the item's type.
3265
     *
3266
     * @param string $string Type
3267
     */
3268
    public function set_type($string = '')
3269
    {
3270
        if (!empty($string)) {
3271
            $this->type = $string;
3272
        }
3273
    }
3274
3275
    /**
3276
     * Checks if the current status is part of the list of status given.
3277
     *
3278
     * @param array $list An array of status to check for.
3279
     *                    If the current status is one of the strings, return true
3280
     *
3281
     * @return bool True if the status was one of the given strings,
3282
     *              false otherwise
3283
     */
3284
    public function status_is($list = [])
3285
    {
3286
        if (self::DEBUG > 1) {
3287
            error_log(
3288
                'learnpathItem::status_is('.print_r(
3289
                    $list,
3290
                    true
3291
                ).') on item '.$this->db_id,
3292
                0
3293
            );
3294
        }
3295
        $currentStatus = $this->get_status(true);
3296
        if (empty($currentStatus)) {
3297
            return false;
3298
        }
3299
        $found = false;
3300
        foreach ($list as $status) {
3301
            if (preg_match('/^'.$status.'$/i', $currentStatus)) {
3302
                if (self::DEBUG > 2) {
3303
                    error_log(
3304
                        'New LP - learnpathItem::status_is() - Found status '.
3305
                            $status.' corresponding to current status',
3306
                        0
3307
                    );
3308
                }
3309
                $found = true;
3310
3311
                return $found;
3312
            }
3313
        }
3314
        if (self::DEBUG > 2) {
3315
            error_log(
3316
                'New LP - learnpathItem::status_is() - Status '.
3317
                    $currentStatus.' did not match request',
3318
                0
3319
            );
3320
        }
3321
3322
        return $found;
3323
    }
3324
3325
    /**
3326
     * Updates the time info according to the given session_time.
3327
     *
3328
     * @param int $totalSec Time in seconds
3329
     */
3330
    public function update_time($totalSec = 0)
3331
    {
3332
        if (self::DEBUG > 0) {
3333
            error_log('learnpathItem::update_time('.$totalSec.')');
3334
        }
3335
        if ($totalSec >= 0) {
3336
            // Getting start time from finish time. The only problem in the calculation is it might be
3337
            // modified by the scripts processing time.
3338
            $now = time();
3339
            $start = $now - $totalSec;
3340
            $this->current_start_time = $start;
3341
            $this->current_stop_time = $now;
3342
        }
3343
    }
3344
3345
    /**
3346
     * Special scorm update time function. This function will update time
3347
     * directly into db for scorm objects.
3348
     *
3349
     * @param int $total_sec Total number of seconds
3350
     */
3351
    public function scorm_update_time($total_sec = 0)
3352
    {
3353
        $debug = self::DEBUG;
3354
        if ($debug) {
3355
            error_log('learnpathItem::scorm_update_time()');
3356
            error_log("total_sec: $total_sec");
3357
        }
3358
3359
        // Step 1 : get actual total time stored in db
3360
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3361
        $course_id = api_get_course_int_id();
3362
3363
        $sql = 'SELECT total_time, status
3364
                FROM '.$item_view_table.'
3365
                WHERE
3366
                    c_id = '.$course_id.' AND
3367
                    lp_item_id = "'.$this->db_id.'" AND
3368
                    lp_view_id = "'.$this->view_id.'" AND
3369
                    view_count = "'.$this->get_attempt_id().'"';
3370
        $result = Database::query($sql);
3371
        $row = Database::fetch_array($result);
3372
3373
        if (!isset($row['total_time'])) {
3374
            $total_time = 0;
3375
        } else {
3376
            $total_time = $row['total_time'];
3377
        }
3378
        if ($debug) {
3379
            error_log("Original total_time: $total_time");
3380
        }
3381
3382
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3383
        $lp_id = (int) $this->lp_id;
3384
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
3385
        $res = Database::query($sql);
3386
        $accumulateScormTime = 'false';
3387
        if (Database::num_rows($res) > 0) {
3388
            $row = Database::fetch_assoc($res);
3389
            $accumulateScormTime = $row['accumulate_scorm_time'];
3390
        }
3391
3392
        // Step 2.1 : if normal mode total_time = total_time + total_sec
3393
        if ('sco' == $this->type && 0 != $accumulateScormTime) {
3394
            if ($debug) {
3395
                error_log("accumulateScormTime is on. total_time modified: $total_time + $total_sec");
3396
            }
3397
            $total_time += $total_sec;
3398
        } else {
3399
            // Step 2.2 : if not cumulative mode total_time = total_time - last_update + total_sec
3400
            $total_sec = $this->fixAbusiveTime($total_sec);
3401
            if ($debug) {
3402
                error_log("after fix abusive: $total_sec");
3403
                error_log("total_time: $total_time");
3404
                error_log("this->last_scorm_session_time: ".$this->last_scorm_session_time);
3405
            }
3406
3407
            $total_time = $total_time - $this->last_scorm_session_time + $total_sec;
3408
            $this->last_scorm_session_time = $total_sec;
3409
3410
            if ($total_time < 0) {
3411
                $total_time = $total_sec;
3412
            }
3413
        }
3414
3415
        if ($debug) {
3416
            error_log("accumulate_scorm_time: $accumulateScormTime");
3417
            error_log("total_time modified: $total_time");
3418
        }
3419
3420
        // Step 3 update db only if status != completed, passed, browsed or seriousgamemode not activated
3421
        // @todo complete
3422
        $case_completed = [
3423
            'completed',
3424
            'passed',
3425
            'browsed',
3426
            'failed',
3427
        ];
3428
3429
        if (1 != $this->seriousgame_mode ||
3430
            !in_array($row['status'], $case_completed)
3431
        ) {
3432
            $sql = "UPDATE $item_view_table
3433
                      SET total_time = '$total_time'
3434
                    WHERE
3435
                        c_id = $course_id AND
3436
                        lp_item_id = {$this->db_id} AND
3437
                        lp_view_id = {$this->view_id} AND
3438
                        view_count = {$this->get_attempt_id()}";
3439
            if ($debug) {
3440
                error_log('-------------total_time updated ------------------------');
3441
                error_log($sql);
3442
                error_log('-------------------------------------');
3443
            }
3444
            Database::query($sql);
3445
        }
3446
    }
3447
3448
    /**
3449
     * Set the total_time to 0 into db.
3450
     */
3451
    public function scorm_init_time()
3452
    {
3453
        $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3454
        $course_id = api_get_course_int_id();
3455
        $sql = 'UPDATE '.$table.'
3456
                SET total_time = 0,
3457
                    start_time = '.time().'
3458
                WHERE
3459
                    c_id = '.$course_id.' AND
3460
                    lp_item_id = "'.$this->db_id.'" AND
3461
                    lp_view_id = "'.$this->view_id.'" AND
3462
                    view_count = "'.$this->attempt_id.'"';
3463
        Database::query($sql);
3464
    }
3465
3466
    /**
3467
     * Write objectives to DB. This method is separate from write_to_db() because otherwise
3468
     * objectives are lost as a side effect to AJAX and session concurrent access.
3469
     *
3470
     * @return bool True or false on error
3471
     */
3472
    public function write_objectives_to_db()
3473
    {
3474
        if (self::DEBUG > 0) {
3475
            error_log('learnpathItem::write_objectives_to_db()', 0);
3476
        }
3477
        if (api_is_invitee()) {
3478
            // If the user is an invitee, we don't write anything to DB
3479
            return true;
3480
        }
3481
        $course_id = api_get_course_int_id();
3482
        if (is_array($this->objectives) && count($this->objectives) > 0) {
3483
            // Save objectives.
3484
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3485
            $sql = "SELECT iid
3486
                    FROM $tbl
3487
                    WHERE
3488
                        c_id = $course_id AND
3489
                        lp_item_id = ".$this->db_id." AND
3490
                        lp_view_id = ".$this->view_id." AND
3491
                        view_count = ".$this->attempt_id;
3492
            $res = Database::query($sql);
3493
            if (Database::num_rows($res) > 0) {
3494
                $row = Database::fetch_array($res);
3495
                $lp_iv_id = $row[0];
3496
                if (self::DEBUG > 2) {
3497
                    error_log(
3498
                        'learnpathItem::write_to_db() - Got item_view_id '.
3499
                            $lp_iv_id.', now checking objectives ',
3500
                        0
3501
                    );
3502
                }
3503
                foreach ($this->objectives as $index => $objective) {
3504
                    $iva_table = Database::get_course_table(
3505
                        TABLE_LP_IV_OBJECTIVE
3506
                    );
3507
                    $iva_sql = "SELECT iid FROM $iva_table
3508
                                WHERE
3509
                                    c_id = $course_id AND
3510
                                    lp_iv_id = $lp_iv_id AND
3511
                                    objective_id = '".Database::escape_string($objective[0])."'";
3512
                    $iva_res = Database::query($iva_sql);
3513
                    // id(0), type(1), time(2), weighting(3),
3514
                    // correct_responses(4), student_response(5),
3515
                    // result(6), latency(7)
3516
                    if (Database::num_rows($iva_res) > 0) {
3517
                        // Update (or don't).
3518
                        $iva_row = Database::fetch_array($iva_res);
3519
                        $iva_id = $iva_row[0];
3520
                        $ivau_sql = "UPDATE $iva_table ".
3521
                            "SET objective_id = '".Database::escape_string($objective[0])."',".
3522
                            "status = '".Database::escape_string($objective[1])."',".
3523
                            "score_raw = '".Database::escape_string($objective[2])."',".
3524
                            "score_min = '".Database::escape_string($objective[4])."',".
3525
                            "score_max = '".Database::escape_string($objective[3])."' ".
3526
                            "WHERE c_id = $course_id AND iid = $iva_id";
3527
                        Database::query($ivau_sql);
3528
                    } else {
3529
                        // Insert new one.
3530
                        $params = [
3531
                            'c_id' => $course_id,
3532
                            'lp_iv_id' => $lp_iv_id,
3533
                            'order_id' => $index,
3534
                            'objective_id' => $objective[0],
3535
                            'status' => $objective[1],
3536
                            'score_raw' => $objective[2],
3537
                            'score_min' => $objective[4],
3538
                            'score_max' => $objective[3],
3539
                        ];
3540
3541
                        $insertId = Database::insert($iva_table, $params);
3542
                        if ($insertId) {
3543
                            $sql = "UPDATE $iva_table SET id = iid
3544
                                    WHERE iid = $insertId";
3545
                            Database::query($sql);
3546
                        }
3547
                    }
3548
                }
3549
            }
3550
        }
3551
    }
3552
3553
    /**
3554
     * Writes the current data to the database.
3555
     *
3556
     * @return bool Query result
3557
     */
3558
    public function write_to_db()
3559
    {
3560
        $debug = self::DEBUG;
3561
        if ($debug) {
3562
            error_log('learnpathItem::write_to_db()', 0);
3563
        }
3564
3565
        // Check the session visibility.
3566
        if (!api_is_allowed_to_session_edit()) {
3567
            if ($debug) {
3568
                error_log('return false api_is_allowed_to_session_edit');
3569
            }
3570
3571
            return false;
3572
        }
3573
        if (api_is_invitee()) {
3574
            // If the user is an invitee, we don't write anything to DB
3575
            return true;
3576
        }
3577
3578
        $course_id = api_get_course_int_id();
3579
        $mode = $this->get_lesson_mode();
3580
        $credit = $this->get_credit();
3581
        $total_time = ' ';
3582
        $my_status = ' ';
3583
3584
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3585
        $sql = 'SELECT status, total_time FROM '.$item_view_table.'
3586
                WHERE
3587
                    c_id = '.$course_id.' AND
3588
                    lp_item_id="'.$this->db_id.'" AND
3589
                    lp_view_id="'.$this->view_id.'" AND
3590
                    view_count="'.$this->get_attempt_id().'" ';
3591
        $rs_verified = Database::query($sql);
3592
        $row_verified = Database::fetch_array($rs_verified);
3593
3594
        $my_case_completed = [
3595
            'completed',
3596
            'passed',
3597
            'browsed',
3598
            'failed',
3599
        ];
3600
3601
        $oldTotalTime = $row_verified['total_time'];
3602
        $this->oldTotalTime = $oldTotalTime;
3603
3604
        $save = true;
3605
        if (isset($row_verified) && isset($row_verified['status'])) {
3606
            if (in_array($row_verified['status'], $my_case_completed)) {
3607
                $save = false;
3608
            }
3609
        }
3610
3611
        if (((false === $save && 'sco' == $this->type) ||
3612
           ('sco' == $this->type && ('no-credit' == $credit || 'review' == $mode || 'browse' == $mode))) &&
3613
           (1 != $this->seriousgame_mode && 'sco' == $this->type)
3614
        ) {
3615
            if ($debug) {
3616
                error_log(
3617
                    "This info shouldn't be saved as the credit or lesson mode info prevent it"
3618
                );
3619
                error_log(
3620
                    'learnpathItem::write_to_db() - credit('.$credit.') or'.
3621
                    ' lesson_mode('.$mode.') prevent recording!',
3622
                    0
3623
                );
3624
            }
3625
        } else {
3626
            // Check the row exists.
3627
            $inserted = false;
3628
            // This a special case for multiple attempts and Chamilo exercises.
3629
            if ('quiz' == $this->type &&
3630
                0 == $this->get_prevent_reinit() &&
3631
                'completed' == $this->get_status()
3632
            ) {
3633
                // We force the item to be restarted.
3634
                $this->restart();
3635
                $params = [
3636
                    "c_id" => $course_id,
3637
                    "total_time" => $this->get_total_time(),
3638
                    "start_time" => $this->current_start_time,
3639
                    "score" => $this->get_score(),
3640
                    "status" => $this->get_status(false),
3641
                    "max_score" => $this->get_max(),
3642
                    "lp_item_id" => $this->db_id,
3643
                    "lp_view_id" => $this->view_id,
3644
                    "view_count" => $this->get_attempt_id(),
3645
                    "suspend_data" => $this->current_data,
3646
                    //"max_time_allowed" => ,
3647
                    "lesson_location" => $this->lesson_location,
3648
                ];
3649
                if ($debug) {
3650
                    error_log(
3651
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3652
                        0
3653
                    );
3654
                }
3655
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3656
                if ($this->db_item_view_id) {
3657
                    $sql = "UPDATE $item_view_table SET id = iid
3658
                            WHERE iid = ".$this->db_item_view_id;
3659
                    Database::query($sql);
3660
                    $inserted = true;
3661
                }
3662
            }
3663
3664
            $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3665
            $sql = "SELECT * FROM $item_view_table
3666
                    WHERE
3667
                        c_id = $course_id AND
3668
                        lp_item_id = ".$this->db_id." AND
3669
                        lp_view_id = ".$this->view_id." AND
3670
                        view_count = ".$this->get_attempt_id();
3671
            if ($debug) {
3672
                error_log(
3673
                    'learnpathItem::write_to_db() - Querying item_view: '.$sql,
3674
                    0
3675
                );
3676
            }
3677
            $check_res = Database::query($sql);
3678
            // Depending on what we want (really), we'll update or insert a new row
3679
            // now save into DB.
3680
            if (!$inserted && Database::num_rows($check_res) < 1) {
3681
                $params = [
3682
                    "c_id" => $course_id,
3683
                    "total_time" => $this->get_total_time(),
3684
                    "start_time" => $this->current_start_time,
3685
                    "score" => $this->get_score(),
3686
                    "status" => $this->get_status(false),
3687
                    "max_score" => $this->get_max(),
3688
                    "lp_item_id" => $this->db_id,
3689
                    "lp_view_id" => $this->view_id,
3690
                    "view_count" => $this->get_attempt_id(),
3691
                    "suspend_data" => $this->current_data,
3692
                    //"max_time_allowed" => ,$this->get_max_time_allowed()
3693
                    "lesson_location" => $this->lesson_location,
3694
                ];
3695
3696
                if ($debug) {
3697
                    error_log(
3698
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3699
                        0
3700
                    );
3701
                }
3702
3703
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3704
                if ($this->db_item_view_id) {
3705
                    $sql = "UPDATE $item_view_table SET id = iid
3706
                            WHERE iid = ".$this->db_item_view_id;
3707
                    Database::query($sql);
3708
                }
3709
            } else {
3710
                if ('hotpotatoes' === $this->type) {
3711
                    $params = [
3712
                        'total_time' => $this->get_total_time(),
3713
                        'start_time' => $this->get_current_start_time(),
3714
                        'score' => $this->get_score(),
3715
                        'status' => $this->get_status(false),
3716
                        'max_score' => $this->get_max(),
3717
                        'suspend_data' => $this->current_data,
3718
                        'lesson_location' => $this->lesson_location,
3719
                    ];
3720
                    $where = [
3721
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' => [
3722
                            $course_id,
3723
                            $this->db_id,
3724
                            $this->view_id,
3725
                            $this->get_attempt_id(),
3726
                        ],
3727
                    ];
3728
                    Database::update($item_view_table, $params, $where);
3729
                } else {
3730
                    // For all other content types...
3731
                    if ('quiz' == $this->type) {
3732
                        if ($debug) {
3733
                            error_log("item is quiz:");
3734
                        }
3735
                        $my_status = ' ';
3736
                        $total_time = ' ';
3737
                        if (!empty($_REQUEST['exeId'])) {
3738
                            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3739
                            $exeId = (int) $_REQUEST['exeId'];
3740
                            $sql = "SELECT exe_duration
3741
                                    FROM $table
3742
                                    WHERE exe_id = $exeId";
3743
                            if ($debug) {
3744
                                error_log($sql);
3745
                            }
3746
                            $res = Database::query($sql);
3747
                            $exeRow = Database::fetch_array($res);
3748
                            $duration = $exeRow['exe_duration'];
3749
                            $total_time = " total_time = ".$duration.", ";
3750
                            if ($debug) {
3751
                                error_log("quiz: $total_time");
3752
                            }
3753
                        }
3754
                    } else {
3755
                        $my_type_lp = learnpath::get_type_static($this->lp_id);
3756
                        // This is a array containing values finished
3757
                        $case_completed = [
3758
                            'completed',
3759
                            'passed',
3760
                            'browsed',
3761
                            'failed',
3762
                        ];
3763
3764
                        // Is not multiple attempts
3765
                        if (1 == $this->seriousgame_mode && 'sco' == $this->type) {
3766
                            $total_time = " total_time = total_time +".$this->get_total_time().", ";
3767
                            $my_status = " status = '".$this->get_status(false)."' ,";
3768
                            if ($debug) {
3769
                                error_log("seriousgame_mode time changed: $total_time");
3770
                            }
3771
                        } elseif (1 == $this->get_prevent_reinit()) {
3772
                            // Process of status verified into data base.
3773
                            $sql = 'SELECT status FROM '.$item_view_table.'
3774
                                    WHERE
3775
                                        c_id = '.$course_id.' AND
3776
                                        lp_item_id="'.$this->db_id.'" AND
3777
                                        lp_view_id="'.$this->view_id.'" AND
3778
                                        view_count="'.$this->get_attempt_id().'"
3779
                                    ';
3780
                            $rs_verified = Database::query($sql);
3781
                            $row_verified = Database::fetch_array($rs_verified);
3782
3783
                            // Get type lp: 1=lp dokeos and  2=scorm.
3784
                            // If not is completed or passed or browsed and learning path is scorm.
3785
                            if (!in_array($this->get_status(false), $case_completed) &&
3786
                                2 == $my_type_lp
3787
                            ) {
3788
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3789
                                $my_status = " status = '".$this->get_status(false)."' ,";
3790
                                if ($debug) {
3791
                                    error_log("get_prevent_reinit = 1 time changed: $total_time");
3792
                                }
3793
                            } else {
3794
                                // Verified into database.
3795
                                if (!in_array($row_verified['status'], $case_completed) &&
3796
                                    2 == $my_type_lp
3797
                                ) {
3798
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3799
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3800
                                    if ($debug) {
3801
                                        error_log("total_time time changed case 1: $total_time");
3802
                                    }
3803
                                } elseif (in_array($row_verified['status'], $case_completed) &&
3804
                                    2 == $my_type_lp && 'sco' != $this->type
3805
                                ) {
3806
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3807
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3808
                                    if ($debug) {
3809
                                        error_log("total_time time changed case 2: $total_time");
3810
                                    }
3811
                                } else {
3812
                                    if ((3 == $my_type_lp && 'au' == $this->type) ||
3813
                                        (1 == $my_type_lp && 'dir' != $this->type)) {
3814
                                        // Is AICC or Chamilo LP
3815
                                        $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3816
                                        $my_status = " status = '".$this->get_status(false)."' ,";
3817
                                        if ($debug) {
3818
                                            error_log("total_time time changed case 3: $total_time");
3819
                                        }
3820
                                    }
3821
                                }
3822
                            }
3823
                        } else {
3824
                            // Multiple attempts are allowed.
3825
                            if (in_array($this->get_status(false), $case_completed) && 2 == $my_type_lp) {
3826
                                // Reset zero new attempt ?
3827
                                $my_status = " status = '".$this->get_status(false)."' ,";
3828
                                if ($debug) {
3829
                                    error_log("total_time time changed Multiple attempt case 1: $total_time");
3830
                                }
3831
                            } elseif (!in_array($this->get_status(false), $case_completed) && 2 == $my_type_lp) {
3832
                                $total_time = " total_time = ".$this->get_total_time().", ";
3833
                                $my_status = " status = '".$this->get_status(false)."' ,";
3834
                                if ($debug) {
3835
                                    error_log("total_time time changed Multiple attempt case 2: $total_time");
3836
                                }
3837
                            } else {
3838
                                // It is chamilo LP.
3839
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3840
                                $my_status = " status = '".$this->get_status(false)."' ,";
3841
                                if ($debug) {
3842
                                    error_log("total_time time changed Multiple attempt case 3: $total_time");
3843
                                }
3844
                            }
3845
3846
                            // This code line fixes the problem of wrong status.
3847
                            if (2 == $my_type_lp) {
3848
                                // Verify current status in multiples attempts.
3849
                                $sql = 'SELECT status FROM '.$item_view_table.'
3850
                                        WHERE
3851
                                            c_id = '.$course_id.' AND
3852
                                            lp_item_id="'.$this->db_id.'" AND
3853
                                            lp_view_id="'.$this->view_id.'" AND
3854
                                            view_count="'.$this->get_attempt_id().'" ';
3855
                                $rs_status = Database::query($sql);
3856
                                $current_status = Database::result(
3857
                                    $rs_status,
3858
                                    0,
3859
                                    'status'
3860
                                );
3861
                                if (in_array($current_status, $case_completed)) {
3862
                                    $my_status = '';
3863
                                    $total_time = '';
3864
                                } else {
3865
                                    $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3866
                                }
3867
3868
                                if ($debug) {
3869
                                    error_log("total_time time my_type_lp: $total_time");
3870
                                }
3871
                            }
3872
                        }
3873
                    }
3874
3875
                    if ('sco' == $this->type) {
3876
                        //IF scorm scorm_update_time has already updated total_time in db
3877
                        //" . //start_time = ".$this->get_current_start_time().", " . //scorm_init_time does it
3878
                        ////" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3879
                        $sql = "UPDATE $item_view_table SET
3880
                                    score = ".$this->get_score().",
3881
                                    $my_status
3882
                                    max_score = '".$this->get_max()."',
3883
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3884
                                    lesson_location = '".$this->lesson_location."'
3885
                                WHERE
3886
                                    c_id = $course_id AND
3887
                                    lp_item_id = ".$this->db_id." AND
3888
                                    lp_view_id = ".$this->view_id."  AND
3889
                                    view_count = ".$this->get_attempt_id();
3890
                    } else {
3891
                        //" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3892
                        $sql = "UPDATE $item_view_table SET
3893
                                    $total_time
3894
                                    start_time = ".$this->get_current_start_time().",
3895
                                    score = ".$this->get_score().",
3896
                                    $my_status
3897
                                    max_score = '".$this->get_max()."',
3898
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3899
                                    lesson_location = '".$this->lesson_location."'
3900
                                WHERE
3901
                                    c_id = $course_id AND
3902
                                    lp_item_id = ".$this->db_id." AND
3903
                                    lp_view_id = ".$this->view_id." AND
3904
                                    view_count = ".$this->get_attempt_id();
3905
                    }
3906
                    $this->current_start_time = time();
3907
                }
3908
                if ($debug) {
3909
                    error_log('-------------------------------------------');
3910
                    error_log('learnpathItem::write_to_db() - Updating item_view:');
3911
                    error_log($sql);
3912
                    error_log('-------------------------------------------');
3913
                }
3914
                Database::query($sql);
3915
            }
3916
3917
            if (is_array($this->interactions) &&
3918
                count($this->interactions) > 0
3919
            ) {
3920
                // Save interactions.
3921
                $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3922
                $sql = "SELECT iid FROM $tbl
3923
                        WHERE
3924
                            c_id = $course_id AND
3925
                            lp_item_id = ".$this->db_id." AND
3926
                            lp_view_id = ".$this->view_id." AND
3927
                            view_count = ".$this->get_attempt_id();
3928
                $res = Database::query($sql);
3929
                if (Database::num_rows($res) > 0) {
3930
                    $row = Database::fetch_array($res);
3931
                    $lp_iv_id = $row[0];
3932
                    if ($debug) {
3933
                        error_log(
3934
                            'learnpathItem::write_to_db() - Got item_view_id '.
3935
                            $lp_iv_id.', now checking interactions ',
3936
                            0
3937
                        );
3938
                    }
3939
                    foreach ($this->interactions as $index => $interaction) {
3940
                        $correct_resp = '';
3941
                        if (is_array($interaction[4]) && !empty($interaction[4][0])) {
3942
                            foreach ($interaction[4] as $resp) {
3943
                                $correct_resp .= $resp.',';
3944
                            }
3945
                            $correct_resp = substr(
3946
                                $correct_resp,
3947
                                0,
3948
                                strlen($correct_resp) - 1
3949
                            );
3950
                        }
3951
                        $iva_table = Database::get_course_table(
3952
                            TABLE_LP_IV_INTERACTION
3953
                        );
3954
3955
                        //also check for the interaction ID as it must be unique for this SCO view
3956
                        $iva_sql = "SELECT iid FROM $iva_table
3957
                                    WHERE
3958
                                        c_id = $course_id AND
3959
                                        lp_iv_id = $lp_iv_id AND
3960
                                        (
3961
                                            order_id = $index OR
3962
                                            interaction_id = '".Database::escape_string($interaction[0])."'
3963
                                        )
3964
                                    ";
3965
                        $iva_res = Database::query($iva_sql);
3966
3967
                        $interaction[0] = isset($interaction[0]) ? $interaction[0] : '';
3968
                        $interaction[1] = isset($interaction[1]) ? $interaction[1] : '';
3969
                        $interaction[2] = isset($interaction[2]) ? $interaction[2] : '';
3970
                        $interaction[3] = isset($interaction[3]) ? $interaction[3] : '';
3971
                        $interaction[4] = isset($interaction[4]) ? $interaction[4] : '';
3972
                        $interaction[5] = isset($interaction[5]) ? $interaction[5] : '';
3973
                        $interaction[6] = isset($interaction[6]) ? $interaction[6] : '';
3974
                        $interaction[7] = isset($interaction[7]) ? $interaction[7] : '';
3975
3976
                        // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
3977
                        if (Database::num_rows($iva_res) > 0) {
3978
                            // Update (or don't).
3979
                            $iva_row = Database::fetch_array($iva_res);
3980
                            $iva_id = $iva_row[0];
3981
                            // Insert new one.
3982
                            $params = [
3983
                                'interaction_id' => $interaction[0],
3984
                                'interaction_type' => $interaction[1],
3985
                                'weighting' => $interaction[3],
3986
                                'completion_time' => $interaction[2],
3987
                                'correct_responses' => $correct_resp,
3988
                                'student_response' => $interaction[5],
3989
                                'result' => $interaction[6],
3990
                                'latency' => $interaction[7],
3991
                            ];
3992
                            Database::update(
3993
                                $iva_table,
3994
                                $params,
3995
                                [
3996
                                    'c_id = ? AND iid = ?' => [
3997
                                        $course_id,
3998
                                        $iva_id,
3999
                                    ],
4000
                                ]
4001
                            );
4002
                        } else {
4003
                            // Insert new one.
4004
                            $params = [
4005
                                'c_id' => $course_id,
4006
                                'order_id' => $index,
4007
                                'lp_iv_id' => $lp_iv_id,
4008
                                'interaction_id' => $interaction[0],
4009
                                'interaction_type' => $interaction[1],
4010
                                'weighting' => $interaction[3],
4011
                                'completion_time' => $interaction[2],
4012
                                'correct_responses' => $correct_resp,
4013
                                'student_response' => $interaction[5],
4014
                                'result' => $interaction[6],
4015
                                'latency' => $interaction[7],
4016
                            ];
4017
4018
                            $insertId = Database::insert($iva_table, $params);
4019
                            if ($insertId) {
4020
                                $sql = "UPDATE $iva_table SET id = iid
4021
                                        WHERE iid = $insertId";
4022
                                Database::query($sql);
4023
                            }
4024
                        }
4025
                    }
4026
                }
4027
            }
4028
        }
4029
4030
        if ($debug) {
4031
            error_log('End of learnpathItem::write_to_db()', 0);
4032
        }
4033
4034
        return true;
4035
    }
4036
4037
    /**
4038
     * Adds an audio file attached to the current item (store on disk and in db).
4039
     *
4040
     * @return bool|string|null
4041
     */
4042
    public function add_audio()
4043
    {
4044
        $course_info = api_get_course_info();
4045
        $filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/';
4046
4047
        if (!is_dir($filepath.'audio')) {
4048
            mkdir(
4049
                $filepath.'audio',
4050
                api_get_permissions_for_new_directories()
4051
            );
4052
            DocumentManager::addDocument(
4053
                $course_info,
4054
                '/audio',
4055
                'folder',
4056
                0,
4057
                'audio'
4058
            );
4059
        }
4060
4061
        $key = 'file';
4062
        if (!isset($_FILES[$key]['name']) || !isset($_FILES[$key]['tmp_name'])) {
4063
            return false;
4064
        }
4065
        $result = DocumentManager::upload_document(
4066
            $_FILES,
4067
            '/audio',
4068
            null,
4069
            null,
4070
            0,
4071
            'rename',
4072
            false,
4073
            false
4074
        );
4075
        $file_path = null;
4076
4077
        if ($result) {
4078
            $file_path = basename($result['path']);
4079
4080
            // Store the mp3 file in the lp_item table.
4081
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4082
            $sql = "UPDATE $tbl_lp_item SET
4083
                        audio = '".Database::escape_string($file_path)."'
4084
                    WHERE iid = ".intval($this->db_id);
4085
            Database::query($sql);
4086
        }
4087
4088
        return $file_path;
4089
    }
4090
4091
    /**
4092
     * Adds an audio file to the current item, using a file already in documents.
4093
     *
4094
     * @param int $doc_id
4095
     *
4096
     * @return string
4097
     */
4098
    public function add_audio_from_documents($doc_id)
4099
    {
4100
        $course_info = api_get_course_info();
4101
        $document_data = DocumentManager::get_document_data_by_id(
4102
            $doc_id,
4103
            $course_info['code']
4104
        );
4105
4106
        $file_path = '';
4107
        if (!empty($document_data)) {
4108
            $file_path = basename($document_data['path']);
4109
            // Store the mp3 file in the lp_item table.
4110
            $table = Database::get_course_table(TABLE_LP_ITEM);
4111
            $sql = "UPDATE $table SET
4112
                        audio = '".Database::escape_string($file_path)."'
4113
                    WHERE iid = ".intval($this->db_id);
4114
            Database::query($sql);
4115
        }
4116
4117
        return $file_path;
4118
    }
4119
4120
    /**
4121
     * Removes the relation between the current item and an audio file. The file
4122
     * is only removed from the lp_item table, but remains in the document table
4123
     * and directory.
4124
     *
4125
     * @return bool
4126
     */
4127
    public function remove_audio()
4128
    {
4129
        if (empty($this->db_id)) {
4130
            return false;
4131
        }
4132
        $table = Database::get_course_table(TABLE_LP_ITEM);
4133
        $sql = "UPDATE $table SET
4134
                audio = ''
4135
                WHERE iid IN (".$this->db_id.")";
4136
        Database::query($sql);
4137
    }
4138
4139
    /**
4140
     * Transform the SCORM status to a string that can be translated by Chamilo
4141
     * in different user languages.
4142
     *
4143
     * @param $status
4144
     * @param bool   $decorate
4145
     * @param string $type     classic|simple
4146
     *
4147
     * @return array|string
4148
     */
4149
    public static function humanize_status($status, $decorate = true, $type = 'classic')
4150
    {
4151
        $statusList = [
4152
            'completed' => 'ScormCompstatus',
4153
            'incomplete' => 'ScormIncomplete',
4154
            'failed' => 'ScormFailed',
4155
            'passed' => 'ScormPassed',
4156
            'browsed' => 'ScormBrowsed',
4157
            'not attempted' => 'ScormNotAttempted',
4158
        ];
4159
4160
        $myLessonStatus = get_lang($statusList[$status]);
4161
4162
        switch ($status) {
4163
            case 'completed':
4164
            case 'browsed':
4165
                $classStatus = 'info';
4166
                break;
4167
            case 'incomplete':
4168
                $classStatus = 'warning';
4169
                break;
4170
            case 'passed':
4171
                $classStatus = 'success';
4172
                break;
4173
            case 'failed':
4174
                $classStatus = 'important';
4175
                break;
4176
            default:
4177
                $classStatus = 'default';
4178
                break;
4179
        }
4180
4181
        if ('simple' == $type) {
4182
            if (in_array($status, ['failed', 'passed', 'browsed'])) {
4183
                $myLessonStatus = get_lang('Incomplete');
4184
4185
                $classStatus = 'warning';
4186
            }
4187
        }
4188
4189
        if ($decorate) {
4190
            return Display::label($myLessonStatus, $classStatus);
4191
        } else {
4192
            return $myLessonStatus;
4193
        }
4194
    }
4195
4196
    /**
4197
     * @return float
4198
     */
4199
    public function getPrerequisiteMaxScore()
4200
    {
4201
        return $this->prerequisiteMaxScore;
4202
    }
4203
4204
    /**
4205
     * @param float $prerequisiteMaxScore
4206
     */
4207
    public function setPrerequisiteMaxScore($prerequisiteMaxScore)
4208
    {
4209
        $this->prerequisiteMaxScore = $prerequisiteMaxScore;
4210
    }
4211
4212
    /**
4213
     * @return float
4214
     */
4215
    public function getPrerequisiteMinScore()
4216
    {
4217
        return $this->prerequisiteMinScore;
4218
    }
4219
4220
    /**
4221
     * @param float $prerequisiteMinScore
4222
     */
4223
    public function setPrerequisiteMinScore($prerequisiteMinScore)
4224
    {
4225
        $this->prerequisiteMinScore = $prerequisiteMinScore;
4226
    }
4227
4228
    /**
4229
     * Check if this LP item has a created thread in the basis course from the forum of its LP.
4230
     *
4231
     * @param int $lpCourseId The course ID
4232
     *
4233
     * @return bool
4234
     */
4235
    public function lpItemHasThread($lpCourseId)
4236
    {
4237
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4238
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4239
4240
        $fakeFrom = "
4241
            $forumThreadTable ft
4242
            INNER JOIN $itemProperty ip
4243
            ON (ft.thread_id = ip.ref AND ft.c_id = ip.c_id)
4244
        ";
4245
4246
        $resultData = Database::select(
4247
            'COUNT(ft.iid) AS qty',
4248
            $fakeFrom,
4249
            [
4250
                'where' => [
4251
                    'ip.visibility != ? AND ' => 2,
4252
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4253
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4254
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4255
                        intval($this->db_id),
4256
                        "{$this->title} - {$this->db_id}",
4257
                        intval($this->db_id),
4258
                    ],
4259
                ],
4260
            ],
4261
            'first'
4262
        );
4263
4264
        if ($resultData['qty'] > 0) {
4265
            return true;
4266
        }
4267
4268
        return false;
4269
    }
4270
4271
    /**
4272
     * Get the forum thread info.
4273
     *
4274
     * @param int $courseId  The course ID from the learning path
4275
     * @param int $sessionId Optional. The session ID from the learning path
4276
     *
4277
     * @return \Chamilo\CourseBundle\Entity\CForumThread
4278
     */
4279
    public function getForumThread($courseId, $sessionId = 0)
4280
    {
4281
        $repo = Container::getForumThreadRepository();
4282
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity($sessionId));
4283
        $qb->andWhere('resource.threadTitle = :title')->setParameter('title', "{$this->title} - {$this->db_id}");
4284
4285
        return $qb->getQuery()->getFirstResult();
4286
4287
        $lpSessionId = (int) $lpSessionId;
0 ignored issues
show
Unused Code introduced by
$lpSessionId = (int)$lpSessionId is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
4288
        $lpCourseId = (int) $lpCourseId;
4289
4290
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4291
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4292
4293
        $fakeFrom = "$forumThreadTable ft INNER JOIN $itemProperty ip ";
4294
4295
        if (0 == $lpSessionId) {
4296
            $fakeFrom .= "
4297
                ON (
4298
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND (
4299
                        ft.session_id = ip.session_id OR ip.session_id IS NULL
4300
                    )
4301
                )
4302
            ";
4303
        } else {
4304
            $fakeFrom .= "
4305
                ON (
4306
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND ft.session_id = ip.session_id
4307
                )
4308
            ";
4309
        }
4310
4311
        $resultData = Database::select(
4312
            'ft.*',
4313
            $fakeFrom,
4314
            [
4315
                'where' => [
4316
                    'ip.visibility != ? AND ' => 2,
4317
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4318
                    'ft.session_id = ? AND ' => $lpSessionId,
4319
                    'ft.c_id = ? AND ' => $lpCourseId,
4320
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4321
                        intval($this->db_id),
4322
                        "{$this->title} - {$this->db_id}",
4323
                        intval($this->db_id),
4324
                    ],
4325
                ],
4326
            ],
4327
            'first'
4328
        );
4329
4330
        if (empty($resultData)) {
4331
            return false;
4332
        }
4333
4334
        return $resultData;
4335
    }
4336
4337
    /**
4338
     * Create a forum thread for this learning path item.
4339
     *
4340
     * @param int $currentForumId The forum ID to add the new thread
4341
     *
4342
     * @return int The forum thread if was created. Otherwise return false
4343
     */
4344
    public function createForumThread($currentForumId)
4345
    {
4346
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
4347
4348
        $currentForumId = (int) $currentForumId;
4349
4350
        $em = Database::getManager();
4351
        $threadRepo = $em->getRepository('ChamiloCourseBundle:CForumThread');
4352
        $forumThread = $threadRepo->findOneBy([
4353
            'threadTitle' => "{$this->title} - {$this->db_id}",
4354
            'forumId' => $currentForumId,
4355
        ]);
4356
4357
        if (!$forumThread) {
4358
            $repo = Container::getForumRepository();
4359
            $forumInfo = $repo->find($currentForumId);
4360
            store_thread(
4361
                $forumInfo,
4362
                [
4363
                    'forum_id' => $currentForumId,
4364
                    'thread_id' => 0,
4365
                    'gradebook' => 0,
4366
                    'post_title' => "{$this->name} - {$this->db_id}",
4367
                    'post_text' => $this->description,
4368
                    'category_id' => 1,
4369
                    'numeric_calification' => 0,
4370
                    'calification_notebook_title' => 0,
4371
                    'weight_calification' => 0.00,
4372
                    'thread_peer_qualify' => 0,
4373
                    'lp_item_id' => $this->db_id,
4374
                ],
4375
                [],
4376
                false
4377
            );
4378
4379
            return;
4380
        }
4381
4382
        $forumThread->setLpItemId($this->db_id);
4383
4384
        $em->persist($forumThread);
4385
        $em->flush();
4386
    }
4387
4388
    /**
4389
     * Allow dissociate a forum to this LP item.
4390
     *
4391
     * @param int $threadIid The thread id
4392
     *
4393
     * @return bool
4394
     */
4395
    public function dissociateForumThread($threadIid)
4396
    {
4397
        $threadIid = (int) $threadIid;
4398
        $em = Database::getManager();
4399
4400
        $forumThread = $em->find('ChamiloCourseBundle:CForumThread', $threadIid);
4401
4402
        if (!$forumThread) {
4403
            return false;
4404
        }
4405
4406
        $forumThread->setThreadTitle("{$this->get_title()} - {$this->db_id}");
4407
        $forumThread->setLpItemId(0);
4408
4409
        $em->persist($forumThread);
4410
        $em->flush();
4411
4412
        return true;
4413
    }
4414
4415
    /**
4416
     * @return int
4417
     */
4418
    public function getLastScormSessionTime()
4419
    {
4420
        return $this->last_scorm_session_time;
4421
    }
4422
4423
    /**
4424
     * @return int
4425
     */
4426
    public function getIid()
4427
    {
4428
        return $this->iId;
4429
    }
4430
4431
    /**
4432
     * @param int    $user_id
4433
     * @param string $prereqs_string
4434
     * @param array  $refs_list
4435
     *
4436
     * @return bool
4437
     */
4438
    public function getStatusFromOtherSessions($user_id, $prereqs_string, $refs_list)
4439
    {
4440
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4441
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
4442
        $course_id = api_get_course_int_id();
4443
        $user_id = (int) $user_id;
4444
4445
        // Check results from another sessions:
4446
        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
4447
        if ($checkOtherSessions) {
4448
            // Check items
4449
            $sql = "SELECT iid FROM $lp_view
4450
                    WHERE
4451
                        c_id = $course_id AND
4452
                        user_id = $user_id  AND
4453
                        lp_id = $this->lp_id AND
4454
                        session_id <> 0
4455
                    ";
4456
            $result = Database::query($sql);
4457
            $resultFromOtherSessions = false;
4458
            while ($row = Database::fetch_array($result)) {
4459
                $lpIid = $row['iid'];
4460
                $sql = "SELECT status FROM $lp_item_view
4461
                        WHERE
4462
                            c_id = $course_id AND
4463
                            lp_view_id = $lpIid AND
4464
                            lp_item_id = $refs_list[$prereqs_string]
4465
                        LIMIT 1";
4466
                $resultRow = Database::query($sql);
4467
                if (Database::num_rows($resultRow)) {
4468
                    $statusResult = Database::fetch_array($resultRow);
4469
                    $status = $statusResult['status'];
4470
                    $checked = $status == $this->possible_status[2] || $status == $this->possible_status[3];
4471
                    if ($checked) {
4472
                        $resultFromOtherSessions = true;
4473
                        break;
4474
                    }
4475
                }
4476
            }
4477
4478
            return $resultFromOtherSessions;
4479
        }
4480
    }
4481
}
4482