Test Failed
Push — master ( 482637...7bef58 )
by Julito
33:32
created

learnpathItem::get_max_time_allowed()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
nop 0
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * Class learnpathItem
6
 * lp_item defines items belonging to a learnpath. Each item has a name,
7
 * a score, a use time and additional information that enables tracking a user's
8
 * progress in a learning path.
9
 *
10
 * @package chamilo.learnpath
11
 *
12
 * @author  Yannick Warnier <[email protected]>
13
 */
14
class learnpathItem
15
{
16
    const DEBUG = 0; // Logging parameter.
17
    public $attempt_id; // Also called "objectives" SCORM-wise.
18
    public $audio; // The path to an audio file (stored in document/audio/).
19
    public $children = []; // Contains the ids of children items.
20
    public $condition; // If this item has a special condition embedded.
21
    public $current_score;
22
    public $current_start_time;
23
    public $current_stop_time;
24
    public $current_data = '';
25
    public $db_id;
26
    public $db_item_view_id = '';
27
    public $description = '';
28
    public $file;
29
    /**
30
     * At the moment, interactions are just an array of arrays with a structure
31
     * of 8 text fields: id(0), type(1), time(2), weighting(3),
32
     * correct_responses(4), student_response(5), result(6), latency(7).
33
     */
34
    public $interactions = [];
35
    public $interactions_count = 0;
36
    public $objectives = [];
37
    public $objectives_count = 0;
38
    public $launch_data = '';
39
    public $lesson_location = '';
40
    public $level = 0;
41
    public $core_exit = '';
42
    //var $location; // Only set this for SCORM?
43
    public $lp_id;
44
    public $max_score;
45
    public $mastery_score;
46
    public $min_score;
47
    public $max_time_allowed = '';
48
    public $name;
49
    public $next;
50
    public $parent;
51
    public $path; // In some cases the exo_id = exercise_id in courseDb exercices table.
52
    public $possible_status = [
53
        'not attempted',
54
        'incomplete',
55
        'completed',
56
        'passed',
57
        'failed',
58
        'browsed',
59
    ];
60
    public $prereq_string = '';
61
    public $prereq_alert = '';
62
    public $prereqs = [];
63
    public $previous;
64
    public $prevent_reinit = 1; // 0 =  multiple attempts   1 = one attempt
65
    public $seriousgame_mode;
66
    public $ref;
67
    public $save_on_close = true;
68
    public $search_did = null;
69
    public $status;
70
    public $title;
71
    /**
72
     * Type attribute can contain one of
73
     * link|student_publication|dir|quiz|document|forum|thread.
74
     */
75
    public $type;
76
    public $view_id;
77
    public $oldTotalTime;
78
    //var used if absolute session time mode is used
79
    private $last_scorm_session_time = 0;
80
    private $prerequisiteMaxScore;
81
    private $prerequisiteMinScore;
82
83
    /**
84
     * Prepares the learning path item for later launch.
85
     * Don't forget to use set_lp_view() if applicable after creating the item.
86
     * Setting an lp_view will finalise the item_view data collection.
87
     *
88
     * @param int        $id           Learning path item ID
89
     * @param int        $user_id      User ID
90
     * @param int        $course_id    Course int id
91
     * @param null|array $item_content An array with the contents of the item
92
     */
93
    public function __construct(
94
        $id,
95
        $user_id = 0,
96
        $course_id = 0,
97
        $item_content = null
98
    ) {
99
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
100
101
        // Get items table.
102
        if (!isset($user_id)) {
103
            $user_id = api_get_user_id();
104
        }
105
        if (self::DEBUG > 0) {
106
            error_log(
107
                "learnpathItem constructor: id: $id user_id: ".
108
                "$user_id course_id: $course_id item_content: ".print_r($item_content, 1)
109
            );
110
        }
111
        $id = intval($id);
112
        if (empty($item_content)) {
113
            if (empty($course_id)) {
114
                $course_id = api_get_course_int_id();
115
            } else {
116
                $course_id = intval($course_id);
117
            }
118
            $sql = "SELECT * FROM $items_table
119
                    WHERE iid = $id";
120
            $res = Database::query($sql);
121
            if (Database::num_rows($res) < 1) {
122
                $this->error = 'Could not find given learnpath item in learnpath_item table';
0 ignored issues
show
Bug Best Practice introduced by
The property error does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
123
            }
124
            $row = Database::fetch_array($res);
125
        } else {
126
            $row = $item_content;
127
        }
128
129
        $this->lp_id = $row['lp_id'];
130
        $this->max_score = $row['max_score'];
131
        $this->min_score = $row['min_score'];
132
        $this->name = $row['title'];
133
        $this->type = $row['item_type'];
134
        $this->ref = $row['ref'];
135
        $this->title = $row['title'];
136
        $this->description = $row['description'];
137
        $this->path = $row['path'];
138
        $this->mastery_score = $row['mastery_score'];
139
        $this->parent = $row['parent_item_id'];
140
        $this->next = $row['next_item_id'];
141
        $this->previous = $row['previous_item_id'];
142
        $this->display_order = $row['display_order'];
0 ignored issues
show
Bug Best Practice introduced by
The property display_order does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
143
        $this->prereq_string = $row['prerequisite'];
144
        $this->max_time_allowed = $row['max_time_allowed'];
145
        $this->setPrerequisiteMaxScore($row['prerequisite_max_score']);
146
        $this->setPrerequisiteMinScore($row['prerequisite_min_score']);
147
        $this->oldTotalTime = 0;
148
149
        if (isset($row['launch_data'])) {
150
            $this->launch_data = $row['launch_data'];
151
        }
152
        $this->save_on_close = true;
153
        $this->db_id = $id;
154
155
        // Load children list
156
        if (!empty($this->lp_id)) {
157
            $sql = "SELECT iid FROM $items_table
158
                    WHERE
159
                        c_id = $course_id AND
160
                        lp_id = ".$this->lp_id." AND
161
                        parent_item_id = $id";
162
            $res = Database::query($sql);
163
            if (Database::num_rows($res) > 0) {
164
                while ($row = Database::fetch_assoc($res)) {
165
                    $this->children[] = $row['iid'];
166
                }
167
            }
168
169
            // Get search_did.
170
            if (api_get_setting('search_enabled') == 'true') {
171
                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
172
                $sql = 'SELECT *
173
                        FROM %s
174
                        WHERE 
175
                            course_code=\'%s\' AND 
176
                            tool_id=\'%s\' AND 
177
                            ref_id_high_level=%s AND 
178
                            ref_id_second_level=%d
179
                        LIMIT 1';
180
                // TODO: Verify if it's possible to assume the actual course instead
181
                // of getting it from db.
182
                $sql = sprintf(
183
                    $sql,
184
                    $tbl_se_ref,
185
                    api_get_course_id(),
186
                    TOOL_LEARNPATH,
187
                    $this->lp_id,
188
                    $id
189
                );
190
                $res = Database::query($sql);
191
                if (Database::num_rows($res) > 0) {
192
                    $se_ref = Database::fetch_array($res);
193
                    $this->search_did = (int) $se_ref['search_did'];
194
                }
195
            }
196
        }
197
        $this->seriousgame_mode = 0;
198
        $this->audio = $row['audio'];
199
        if (self::DEBUG > 0) {
200
            error_log(
201
                'New LP - End of learnpathItem constructor for item '.$id,
202
                0
203
            );
204
        }
205
    }
206
207
    /**
208
     * Adds a child to the current item.
209
     *
210
     * @param int $item The child item ID
211
     */
212
    public function add_child($item)
213
    {
214
        if (self::DEBUG > 0) {
215
            error_log('learnpathItem::add_child()', 0);
216
        }
217
        if (!empty($item)) {
218
            // Do not check in DB as we expect the call to come from the
219
            // learnpath class which should be aware of any fake.
220
            $this->children[] = $item;
221
        }
222
    }
223
224
    /**
225
     * Adds an interaction to the current item.
226
     *
227
     * @param int   $index  Index (order ID) of the interaction inside this item
228
     * @param array $params Array of parameters:
229
     *                      id(0), type(1), time(2), weighting(3), correct_responses(4),
230
     *                      student_response(5), result(6), latency(7)
231
     */
232
    public function add_interaction($index, $params)
233
    {
234
        $this->interactions[$index] = $params;
235
        // Take the current maximum index to generate the interactions_count.
236
        if (($index + 1) > $this->interactions_count) {
237
            $this->interactions_count = $index + 1;
238
        }
239
    }
240
241
    /**
242
     * Adds an objective to the current item.
243
     *
244
     * @param    array    Array of parameters:
245
     * id(0), status(1), score_raw(2), score_max(3), score_min(4)
246
     */
247
    public function add_objective($index, $params)
248
    {
249
        if (empty($params[0])) {
250
            return null;
251
        }
252
        $this->objectives[$index] = $params;
253
        // Take the current maximum index to generate the objectives_count.
254
        if ((count($this->objectives) + 1) > $this->objectives_count) {
255
            $this->objectives_count = (count($this->objectives) + 1);
256
        }
257
    }
258
259
    /**
260
     * Closes/stops the item viewing. Finalises runtime values.
261
     * If required, save to DB.
262
     *
263
     * @return bool True on success, false otherwise
264
     */
265
    public function close()
266
    {
267
        if (self::DEBUG > 0) {
268
            error_log('learnpathItem::close()', 0);
269
        }
270
        $this->current_stop_time = time();
271
        $type = $this->get_type();
272
        if ($type != 'sco') {
273
            if ($type == TOOL_QUIZ || $type == TOOL_HOTPOTATOES) {
274
                $this->get_status(
275
                    true,
276
                    true
277
                ); // Update status (second option forces the update).
278
            } else {
279
                $this->status = $this->possible_status[2];
280
            }
281
        }
282
        if ($this->save_on_close) {
283
            $this->save();
284
        }
285
286
        return true;
287
    }
288
289
    /**
290
     * Deletes all traces of this item in the database.
291
     *
292
     * @return bool true. Doesn't check for errors yet.
293
     */
294
    public function delete()
295
    {
296
        if (self::DEBUG > 0) {
297
            error_log('learnpath_item::delete() for item '.$this->db_id, 0);
298
        }
299
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
300
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
301
        $course_id = api_get_course_int_id();
302
303
        $sql = "DELETE FROM $lp_item_view
304
                WHERE c_id = $course_id AND lp_item_id = ".$this->db_id;
305
        if (self::DEBUG > 0) {
306
            error_log('Deleting from lp_item_view: '.$sql, 0);
307
        }
308
        Database::query($sql);
309
310
        $sql = "SELECT * FROM $lp_item
311
                WHERE iid = ".$this->db_id;
312
        $res_sel = Database::query($sql);
313
        if (Database::num_rows($res_sel) < 1) {
314
            return false;
315
        }
316
317
        $sql = "DELETE FROM $lp_item
318
                WHERE iid = ".$this->db_id;
319
        Database::query($sql);
320
        if (self::DEBUG > 0) {
321
            error_log('Deleting from lp_item: '.$sql);
322
        }
323
324
        if (api_get_setting('search_enabled') == 'true') {
325
            if (!is_null($this->search_did)) {
326
                $di = new ChamiloIndexer();
327
                $di->remove_document($this->search_did);
328
            }
329
        }
330
331
        return true;
332
    }
333
334
    /**
335
     * Drops a child from the children array.
336
     *
337
     * @param string $item index of child item to drop
338
     */
339
    public function drop_child($item)
340
    {
341
        if (self::DEBUG > 0) {
342
            error_log('learnpathItem::drop_child()', 0);
343
        }
344
        if (!empty($item)) {
345
            foreach ($this->children as $index => $child) {
346
                if ($child == $item) {
347
                    $this->children[$index] = null;
348
                }
349
            }
350
        }
351
    }
352
353
    /**
354
     * Gets the current attempt_id for this user on this item.
355
     *
356
     * @return int attempt_id for this item view by this user or 1 if none defined
357
     */
358
    public function get_attempt_id()
359
    {
360
        if (self::DEBUG > 0) {
361
            error_log(
362
                'learnpathItem::get_attempt_id() on item '.$this->db_id,
363
                0
364
            );
365
        }
366
        $res = 1;
367
        if (!empty($this->attempt_id)) {
368
            $res = intval($this->attempt_id);
369
        }
370
        if (self::DEBUG > 0) {
371
            error_log(
372
                'New LP - End of learnpathItem::get_attempt_id() on item '.
373
                $this->db_id.' - Returning '.$res,
374
                0
375
            );
376
        }
377
378
        return $res;
379
    }
380
381
    /**
382
     * Gets a list of the item's children.
383
     *
384
     * @return array Array of children items IDs
385
     */
386
    public function get_children()
387
    {
388
        if (self::DEBUG > 0) {
389
            error_log('learnpathItem::get_children()', 0);
390
        }
391
        $list = [];
392
        foreach ($this->children as $child) {
393
            if (!empty($child)) {
394
                $list[] = $child;
395
            }
396
        }
397
398
        return $list;
399
    }
400
401
    /**
402
     * Gets the core_exit value from the database.
403
     */
404
    public function get_core_exit()
405
    {
406
        return $this->core_exit;
407
    }
408
409
    /**
410
     * Gets the credit information (rather scorm-stuff) based on current status
411
     * and reinit autorization. Credit tells the sco(content) if Chamilo will
412
     * record the data it is sent (credit) or not (no-credit).
413
     *
414
     * @return string 'credit' or 'no-credit'. Defaults to 'credit'
415
     *                Because if we don't know enough about this item, it's probably because
416
     *                it was never used before.
417
     */
418
    public function get_credit()
419
    {
420
        if (self::DEBUG > 1) {
421
            error_log('learnpathItem::get_credit()', 0);
422
        }
423
        $credit = 'credit';
424
        // Now check the value of prevent_reinit (if it's 0, return credit as
425
        // the default was).
426
        // If prevent_reinit == 1 (or more).
427
        if ($this->get_prevent_reinit() != 0) {
428
            // If status is not attempted or incomplete, credit anyway.
429
            // Otherwise:
430
            // Check the status in the database rather than in the object, as
431
            // checking in the object would always return "no-credit" when we
432
            // want to set it to completed.
433
            $status = $this->get_status(true);
434
            if (self::DEBUG > 2) {
435
                error_log(
436
                    'learnpathItem::get_credit() - get_prevent_reinit!=0 and '.
437
                    'status is '.$status,
438
                    0
439
                );
440
            }
441
            //0=not attempted - 1 = incomplete
442
            if ($status != $this->possible_status[0] &&
443
                $status != $this->possible_status[1]
444
            ) {
445
                $credit = 'no-credit';
446
            }
447
        }
448
        if (self::DEBUG > 1) {
449
            error_log("learnpathItem::get_credit() returns: $credit");
450
        }
451
452
        return $credit;
453
    }
454
455
    /**
456
     * Gets the current start time property.
457
     *
458
     * @return int Current start time, or current time if none
459
     */
460
    public function get_current_start_time()
461
    {
462
        if (self::DEBUG > 0) {
463
            error_log('learnpathItem::get_current_start_time()', 0);
464
        }
465
        if (empty($this->current_start_time)) {
466
            return time();
467
        } else {
468
            return $this->current_start_time;
469
        }
470
    }
471
472
    /**
473
     * Gets the item's description.
474
     *
475
     * @return string Description
476
     */
477
    public function get_description()
478
    {
479
        if (self::DEBUG > 0) {
480
            error_log('learnpathItem::get_description()', 0);
481
        }
482
        if (empty($this->description)) {
483
            return '';
484
        }
485
486
        return $this->description;
487
    }
488
489
    /**
490
     * Gets the file path from the course's root directory, no matter what
491
     * tool it is from.
492
     *
493
     * @param string $path_to_scorm_dir
494
     *
495
     * @return string The file path, or an empty string if there is no file
496
     *                attached, or '-1' if the file must be replaced by an error page
497
     */
498
    public function get_file_path($path_to_scorm_dir = '')
499
    {
500
        $course_id = api_get_course_int_id();
501
        if (self::DEBUG > 0) {
502
            error_log('learnpathItem::get_file_path()', 0);
503
        }
504
        $path = $this->get_path();
505
        $type = $this->get_type();
506
        if (empty($path)) {
507
            if ($type == 'dir') {
508
                return '';
509
            } else {
510
                return '-1';
511
            }
512
        } elseif ($path == strval(intval($path))) {
513
            // The path is numeric, so it is a reference to a Chamilo object.
514
            switch ($type) {
515
                case 'dir':
516
                    return '';
517
                case TOOL_DOCUMENT:
518
                    $table_doc = Database::get_course_table(TABLE_DOCUMENT);
519
                    $sql = 'SELECT path
520
                            FROM '.$table_doc.'
521
                            WHERE
522
                                c_id = '.$course_id.' AND
523
                                iid = '.$path;
524
                    $res = Database::query($sql);
525
                    $row = Database::fetch_array($res);
526
                    $real_path = 'document'.$row['path'];
527
528
                    return $real_path;
529
                case TOOL_STUDENTPUBLICATION:
530
                case TOOL_QUIZ:
531
                case TOOL_FORUM:
532
                case TOOL_THREAD:
533
                case TOOL_LINK:
534
                default:
535
                    return '-1';
536
            }
537
        } else {
538
            if (!empty($path_to_scorm_dir)) {
539
                $path = $path_to_scorm_dir.$path;
540
            }
541
542
            return $path;
543
        }
544
    }
545
546
    /**
547
     * Gets the DB ID.
548
     *
549
     * @return int Database ID for the current item
550
     */
551
    public function get_id()
552
    {
553
        if (self::DEBUG > 1) {
554
            error_log('learnpathItem::get_id()', 0);
555
        }
556
        if (!empty($this->db_id)) {
557
            return $this->db_id;
558
        }
559
        // TODO: Check this return value is valid for children classes (SCORM?).
560
        return 0;
561
    }
562
563
    /**
564
     * Loads the interactions into the item object, from the database.
565
     * If object interactions exist, they will be overwritten by this function,
566
     * using the database elements only.
567
     */
568
    public function load_interactions()
569
    {
570
        $this->interactions = [];
571
        $course_id = api_get_course_int_id();
572
        $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
573
        $sql = "SELECT id FROM $tbl
574
                WHERE
575
                    c_id = $course_id AND
576
                    lp_item_id = ".$this->db_id." AND
577
                    lp_view_id = ".$this->view_id." AND
578
                    view_count = ".$this->attempt_id;
579
        $res = Database::query($sql);
580
        if (Database::num_rows($res) > 0) {
581
            $row = Database::fetch_array($res);
582
            $lp_iv_id = $row[0];
583
            $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
584
            $sql = "SELECT * FROM $iva_table
585
                    WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id ";
586
            $res_sql = Database::query($sql);
587
            while ($row = Database::fetch_array($res_sql)) {
588
                $this->interactions[$row['interaction_id']] = [
589
                    $row['interaction_id'],
590
                    $row['interaction_type'],
591
                    $row['weighting'],
592
                    $row['completion_time'],
593
                    $row['correct_responses'],
594
                    $row['student_responses'],
595
                    $row['result'],
596
                    $row['latency'],
597
                ];
598
            }
599
        }
600
    }
601
602
    /**
603
     * Gets the current count of interactions recorded in the database.
604
     *
605
     * @param bool $checkdb Whether to count from database or not (defaults to no)
606
     *
607
     * @return int The current number of interactions recorder
608
     */
609
    public function get_interactions_count($checkdb = false)
610
    {
611
        if (self::DEBUG > 1) {
612
            error_log('learnpathItem::get_interactions_count()', 0);
613
        }
614
        $return = 0;
615
        $course_id = api_get_course_int_id();
616
617
        if ($checkdb) {
618
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
619
            $sql = "SELECT iid FROM $tbl
620
                    WHERE
621
                        c_id = $course_id AND
622
                        lp_item_id = ".$this->db_id." AND
623
                        lp_view_id = ".$this->view_id." AND
624
                        view_count = ".$this->get_attempt_id();
625
            $res = Database::query($sql);
626
            if (Database::num_rows($res) > 0) {
627
                $row = Database::fetch_array($res);
628
                $lp_iv_id = $row[0];
629
                $iva_table = Database::get_course_table(
630
                    TABLE_LP_IV_INTERACTION
631
                );
632
                $sql = "SELECT count(id) as mycount
633
                        FROM $iva_table
634
                        WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id ";
635
                $res_sql = Database::query($sql);
636
                if (Database::num_rows($res_sql) > 0) {
637
                    $row = Database::fetch_array($res_sql);
638
                    $return = $row['mycount'];
639
                }
640
            }
641
        } else {
642
            if (!empty($this->interactions_count)) {
643
                $return = $this->interactions_count;
644
            }
645
        }
646
647
        return $return;
648
    }
649
650
    /**
651
     * Gets the JavaScript array content to fill the interactions array.
652
     *
653
     * @param bool $checkdb Whether to check directly into the database (default no)
654
     *
655
     * @return string An empty string if no interaction, a JS array definition otherwise
656
     */
657
    public function get_interactions_js_array($checkdb = false)
658
    {
659
        $return = '';
660
        if ($checkdb) {
661
            $this->load_interactions(true);
662
        }
663
        foreach ($this->interactions as $id => $in) {
664
            $return .= "[
665
                '$id',
666
                '".$in[1]."',
667
                '".$in[2]."',
668
                '".$in[3]."',
669
                '".$in[4]."',
670
                '".$in[5]."',
671
                '".$in[6]."',
672
                '".$in[7]."'],";
673
        }
674
        if (!empty($return)) {
675
            $return = substr($return, 0, -1);
676
        }
677
678
        return $return;
679
    }
680
681
    /**
682
     * Gets the current count of objectives recorded in the database.
683
     *
684
     * @return int The current number of objectives recorder
685
     */
686
    public function get_objectives_count()
687
    {
688
        if (self::DEBUG > 1) {
689
            error_log('learnpathItem::get_objectives_count()', 0);
690
        }
691
        $res = 0;
692
        if (!empty($this->objectives_count)) {
693
            $res = $this->objectives_count;
694
        }
695
696
        return $res;
697
    }
698
699
    /**
700
     * Gets the launch_data field found in imsmanifests (this is SCORM- or
701
     * AICC-related, really).
702
     *
703
     * @return string Launch data as found in imsmanifest and stored in
704
     *                Chamilo (read only). Defaults to ''.
705
     */
706
    public function get_launch_data()
707
    {
708
        if (self::DEBUG > 0) {
709
            error_log('learnpathItem::get_launch_data()', 0);
710
        }
711
        if (!empty($this->launch_data)) {
712
            return str_replace(
713
                ["\r", "\n", "'"],
714
                ['\r', '\n', "\\'"],
715
                $this->launch_data
716
            );
717
        }
718
719
        return '';
720
    }
721
722
    /**
723
     * Gets the lesson location.
724
     *
725
     * @return string lesson location as recorded by the SCORM and AICC
726
     *                elements. Defaults to ''
727
     */
728
    public function get_lesson_location()
729
    {
730
        if (self::DEBUG > 0) {
731
            error_log('learnpathItem::get_lesson_location()', 0);
732
        }
733
        if (!empty($this->lesson_location)) {
734
            return str_replace(
735
                ["\r", "\n", "'"],
736
                ['\r', '\n', "\\'"],
737
                $this->lesson_location
738
            );
739
        } else {
740
            return '';
741
        }
742
    }
743
744
    /**
745
     * Gets the lesson_mode (scorm feature, but might be used by aicc as well
746
     * as chamilo paths).
747
     *
748
     * The "browse" mode is not supported yet (because there is no such way of
749
     * seeing a sco in Chamilo)
750
     *
751
     * @return string 'browse','normal' or 'review'. Defaults to 'normal'
752
     */
753
    public function get_lesson_mode()
754
    {
755
        $mode = 'normal';
756
        if ($this->get_prevent_reinit() != 0) {
757
            // If prevent_reinit == 0
758
            $my_status = $this->get_status();
759
            if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) {
760
                $mode = 'review';
761
            }
762
        }
763
764
        return $mode;
765
    }
766
767
    /**
768
     * Gets the depth level.
769
     *
770
     * @return int Level. Defaults to 0
771
     */
772
    public function get_level()
773
    {
774
        if (self::DEBUG > 0) {
775
            error_log('learnpathItem::get_level()', 0);
776
        }
777
        if (empty($this->level)) {
778
            return 0;
779
        }
780
781
        return $this->level;
782
    }
783
784
    /**
785
     * Gets the mastery score.
786
     */
787
    public function get_mastery_score()
788
    {
789
        if (self::DEBUG > 0) {
790
            error_log('learnpathItem::get_mastery_score()', 0);
791
        }
792
        if (isset($this->mastery_score)) {
793
            return $this->mastery_score;
794
        } else {
795
            return -1;
796
        }
797
    }
798
799
    /**
800
     * Gets the maximum (score).
801
     *
802
     * @return int Maximum score. Defaults to 100 if nothing else is defined
803
     */
804
    public function get_max()
805
    {
806
        if (self::DEBUG > 0) {
807
            error_log('learnpathItem::get_max()', 0);
808
        }
809
        if ($this->type == 'sco') {
810
            if (isset($this->view_max_score) &&
811
                !empty($this->view_max_score) &&
812
                $this->view_max_score > 0
813
            ) {
814
                return $this->view_max_score;
815
            } elseif (isset($this->view_max_score) &&
816
                $this->view_max_score === ''
817
            ) {
818
                return $this->view_max_score;
819
            } else {
820
                if (!empty($this->max_score)) {
821
                    return $this->max_score;
822
                } else {
823
                    return 100;
824
                }
825
            }
826
        } else {
827
            if (!empty($this->max_score)) {
828
                return $this->max_score;
829
            } else {
830
                return 100;
831
            }
832
        }
833
    }
834
835
    /**
836
     * Gets the maximum time allowed for this user in this attempt on this item.
837
     *
838
     * @return string Time string in SCORM format
839
     *                (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS)
840
     */
841
    public function get_max_time_allowed()
842
    {
843
        if (self::DEBUG > 0) {
844
            error_log('learnpathItem::get_max_time_allowed()', 0);
845
        }
846
        if (!empty($this->max_time_allowed)) {
847
            return $this->max_time_allowed;
848
        } else {
849
            return '';
850
        }
851
    }
852
853
    /**
854
     * Gets the minimum (score).
855
     *
856
     * @return int Minimum score. Defaults to 0
857
     */
858
    public function get_min()
859
    {
860
        if (self::DEBUG > 0) {
861
            error_log('learnpathItem::get_min()', 0);
862
        }
863
        if (!empty($this->min_score)) {
864
            return $this->min_score;
865
        } else {
866
            return 0;
867
        }
868
    }
869
870
    /**
871
     * Gets the parent ID.
872
     *
873
     * @return int Parent ID. Defaults to null
874
     */
875
    public function get_parent()
876
    {
877
        if (self::DEBUG > 0) {
878
            error_log('learnpathItem::get_parent()', 0);
879
        }
880
        if (!empty($this->parent)) {
881
            return $this->parent;
882
        }
883
        // TODO: Check this return value is valid for children classes (SCORM?).
884
        return null;
885
    }
886
887
    /**
888
     * Gets the path attribute.
889
     *
890
     * @return string Path. Defaults to ''
891
     */
892
    public function get_path()
893
    {
894
        if (self::DEBUG > 0) {
895
            error_log('learnpathItem::get_path()', 0);
896
        }
897
        if (empty($this->path)) {
898
            return '';
899
        }
900
901
        return $this->path;
902
    }
903
904
    /**
905
     * Gets the prerequisites string.
906
     *
907
     * @return string empty string or prerequisites string if defined
908
     */
909
    public function get_prereq_string()
910
    {
911
        if (self::DEBUG > 0) {
912
            error_log('learnpathItem::get_prereq_string()', 0);
913
        }
914
        if (!empty($this->prereq_string)) {
915
            return $this->prereq_string;
916
        } else {
917
            return '';
918
        }
919
    }
920
921
    /**
922
     * Gets the prevent_reinit attribute value (and sets it if not set already).
923
     *
924
     * @return int 1 or 0 (defaults to 1)
925
     */
926
    public function get_prevent_reinit()
927
    {
928
        if (self::DEBUG > 2) {
929
            error_log('learnpathItem::get_prevent_reinit()', 0);
930
        }
931
        if (!isset($this->prevent_reinit)) {
932
            if (!empty($this->lp_id)) {
933
                $table = Database::get_course_table(TABLE_LP_MAIN);
934
                $sql = "SELECT prevent_reinit
935
                    FROM $table
936
                    WHERE iid = ".$this->lp_id;
937
                $res = Database::query($sql);
938
                if (Database::num_rows($res) < 1) {
939
                    $this->error = "Could not find parent learnpath in lp table";
0 ignored issues
show
Bug Best Practice introduced by
The property error does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
940
                    if (self::DEBUG > 2) {
941
                        error_log(
942
                            'New LP - End of learnpathItem::get_prevent_reinit() - Returning false',
943
                            0
944
                        );
945
                    }
946
947
                    return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
948
                } else {
949
                    $row = Database::fetch_array($res);
950
                    $this->prevent_reinit = $row['prevent_reinit'];
951
                }
952
            } else {
953
                // Prevent reinit is always 1 by default - see learnpath.class.php
954
                $this->prevent_reinit = 1;
955
            }
956
        }
957
        if (self::DEBUG > 2) {
958
            error_log(
959
                'New LP - End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit,
960
                0
961
            );
962
        }
963
964
        return $this->prevent_reinit;
965
    }
966
967
    /**
968
     * Returns 1 if seriousgame_mode is activated, 0 otherwise.
969
     *
970
     * @return int (0 or 1)
971
     *
972
     * @deprecated seriousgame_mode seems not to be used
973
     *
974
     * @author ndiechburg <[email protected]>
975
     */
976
    public function get_seriousgame_mode()
977
    {
978
        if (self::DEBUG > 2) {
979
            error_log('learnpathItem::get_seriousgame_mode()', 0);
980
        }
981
        if (!isset($this->seriousgame_mode)) {
982
            if (!empty($this->lp_id)) {
983
                $table = Database::get_course_table(TABLE_LP_MAIN);
984
                $sql = "SELECT seriousgame_mode
985
                        FROM $table
986
                        WHERE iid = ".$this->lp_id;
987
                $res = Database::query($sql);
988
                if (Database::num_rows($res) < 1) {
989
                    $this->error = "Could not find parent learnpath in learnpath table";
0 ignored issues
show
Bug Best Practice introduced by
The property error does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
990
                    if (self::DEBUG > 2) {
991
                        error_log(
992
                            'New LP - End of learnpathItem::get_seriousgame_mode() - Returning false',
993
                            0
994
                        );
995
                    }
996
997
                    return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
998
                } else {
999
                    $row = Database::fetch_array($res);
1000
                    $this->seriousgame_mode = isset($row['seriousgame_mode']) ? $row['seriousgame_mode'] : 0;
1001
                }
1002
            } else {
1003
                $this->seriousgame_mode = 0; //SeriousGame mode is always off by default
1004
            }
1005
        }
1006
        if (self::DEBUG > 2) {
1007
            error_log(
1008
                'New LP - End of learnpathItem::get_seriousgame_mode() - Returned '.$this->seriousgame_mode,
1009
                0
1010
            );
1011
        }
1012
1013
        return $this->seriousgame_mode;
1014
    }
1015
1016
    /**
1017
     * Gets the item's reference column.
1018
     *
1019
     * @return string The item's reference field (generally used for SCORM identifiers)
1020
     */
1021
    public function get_ref()
1022
    {
1023
        return $this->ref;
1024
    }
1025
1026
    /**
1027
     * Gets the list of included resources as a list of absolute or relative
1028
     * paths of resources included in the current item. This allows for a
1029
     * better SCORM export. The list will generally include pictures, flash
1030
     * objects, java applets, or any other stuff included in the source of the
1031
     * current item. The current item is expected to be an HTML file. If it
1032
     * is not, then the function will return and empty list.
1033
     *
1034
     * @param string $type        (one of the Chamilo tools) - optional (otherwise takes the current item's type)
1035
     * @param string $abs_path    absolute file path - optional (otherwise takes the current item's path)
1036
     * @param int    $recursivity level of recursivity we're in
1037
     *
1038
     * @return array List of file paths.
1039
     *               An additional field containing 'local' or 'remote' helps determine if
1040
     *               the file should be copied into the zip or just linked
1041
     */
1042
    public function get_resources_from_source(
1043
        $type = null,
1044
        $abs_path = null,
1045
        $recursivity = 1
1046
    ) {
1047
        $max = 5;
1048
        if ($recursivity > $max) {
1049
            return [];
1050
        }
1051
        if (!isset($type)) {
1052
            $type = $this->get_type();
1053
        }
1054
        if (!isset($abs_path)) {
1055
            $path = $this->get_file_path();
1056
            $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path;
1057
        }
1058
1059
        $files_list = [];
1060
        $type = $this->get_type();
1061
1062
        switch ($type) {
1063
            case TOOL_DOCUMENT:
1064
            case TOOL_QUIZ:
1065
            case 'sco':
1066
                // Get the document and, if HTML, open it.
1067
                if (!is_file($abs_path)) {
1068
                    // The file could not be found.
1069
                    return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1070
                }
1071
1072
                // for now, read the whole file in one go (that's gonna be
1073
                // a problem when the file is too big).
1074
                $info = pathinfo($abs_path);
1075
                $ext = $info['extension'];
1076
1077
                switch (strtolower($ext)) {
1078
                    case 'html':
1079
                    case 'htm':
1080
                    case 'shtml':
1081
                    case 'css':
1082
                        $wanted_attributes = [
1083
                            'src',
1084
                            'url',
1085
                            '@import',
1086
                            'href',
1087
                            'value',
1088
                        ];
1089
                        // Parse it for included resources.
1090
                        $file_content = file_get_contents($abs_path);
1091
                        // Get an array of attributes from the HTML source.
1092
                        $attributes = DocumentManager::parse_HTML_attributes(
1093
                            $file_content,
1094
                            $wanted_attributes
1095
                        );
1096
1097
                        // Look at 'src' attributes in this file
1098
                        foreach ($wanted_attributes as $attr) {
1099
                            if (isset($attributes[$attr])) {
1100
                                // Find which kind of path these are (local or remote).
1101
                                $sources = $attributes[$attr];
1102
1103
                                foreach ($sources as $source) {
1104
                                    // Skip what is obviously not a resource.
1105
                                    if (strpos($source, "+this.")) {
1106
                                        continue;
1107
                                    } // javascript code - will still work unaltered.
1108
                                    if (strpos($source, '.') === false) {
1109
                                        continue;
1110
                                    } // No dot, should not be an external file anyway.
1111
                                    if (strpos($source, 'mailto:')) {
1112
                                        continue;
1113
                                    } // mailto link.
1114
                                    if (strpos($source, ';') &&
1115
                                        !strpos($source, '&amp;')
1116
                                    ) {
1117
                                        continue;
1118
                                    } // Avoid code - that should help.
1119
1120
                                    if ($attr == 'value') {
1121
                                        if (strpos($source, 'mp3file')) {
1122
                                            $files_list[] = [
1123
                                                substr(
1124
                                                    $source,
1125
                                                    0,
1126
                                                    strpos(
1127
                                                        $source,
1128
                                                        '.swf'
1129
                                                    ) + 4
1130
                                                ),
1131
                                                'local',
1132
                                                'abs',
1133
                                            ];
1134
                                            $mp3file = substr(
1135
                                                $source,
1136
                                                strpos(
1137
                                                    $source,
1138
                                                    'mp3file='
1139
                                                ) + 8
1140
                                            );
1141
                                            if (substr($mp3file, 0, 1) == '/') {
1142
                                                $files_list[] = [
1143
                                                    $mp3file,
1144
                                                    'local',
1145
                                                    'abs',
1146
                                                ];
1147
                                            } else {
1148
                                                $files_list[] = [
1149
                                                    $mp3file,
1150
                                                    'local',
1151
                                                    'rel',
1152
                                                ];
1153
                                            }
1154
                                        } elseif (strpos($source, 'flv=') === 0) {
1155
                                            $source = substr($source, 4);
1156
                                            if (strpos($source, '&') > 0) {
1157
                                                $source = substr(
1158
                                                    $source,
1159
                                                    0,
1160
                                                    strpos($source, '&')
1161
                                                );
1162
                                            }
1163
                                            if (strpos($source, '://') > 0) {
1164
                                                if (strpos($source, api_get_path(WEB_PATH)) !== false) {
1165
                                                    // We found the current portal url.
1166
                                                    $files_list[] = [
1167
                                                        $source,
1168
                                                        'local',
1169
                                                        'url',
1170
                                                    ];
1171
                                                } else {
1172
                                                    // We didn't find any trace of current portal.
1173
                                                    $files_list[] = [
1174
                                                        $source,
1175
                                                        'remote',
1176
                                                        'url',
1177
                                                    ];
1178
                                                }
1179
                                            } else {
1180
                                                $files_list[] = [
1181
                                                    $source,
1182
                                                    'local',
1183
                                                    'abs',
1184
                                                ];
1185
                                            }
1186
                                            continue; // Skipping anything else to avoid two entries
1187
                                            //(while the others can have sub-files in their url, flv's can't).
1188
                                        }
1189
                                    }
1190
1191
                                    if (strpos($source, '://') > 0) {
1192
                                        // Cut at '?' in a URL with params.
1193
                                        if (strpos($source, '?') > 0) {
1194
                                            $second_part = substr(
1195
                                                $source,
1196
                                                strpos($source, '?')
1197
                                            );
1198
                                            if (strpos($second_part, '://') > 0) {
1199
                                                // If the second part of the url contains a url too,
1200
                                                // treat the second one before cutting.
1201
                                                $pos1 = strpos(
1202
                                                    $second_part,
1203
                                                    '='
1204
                                                );
1205
                                                $pos2 = strpos(
1206
                                                    $second_part,
1207
                                                    '&'
1208
                                                );
1209
                                                $second_part = substr(
1210
                                                    $second_part,
1211
                                                    $pos1 + 1,
1212
                                                    $pos2 - ($pos1 + 1)
1213
                                                );
1214
                                                if (strpos($second_part, api_get_path(WEB_PATH)) !== false) {
1215
                                                    // We found the current portal url.
1216
                                                    $files_list[] = [
1217
                                                        $second_part,
1218
                                                        'local',
1219
                                                        'url',
1220
                                                    ];
1221
                                                    $in_files_list[] = self::get_resources_from_source(
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_resources_from_source() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1221
                                                    /** @scrutinizer ignore-call */ 
1222
                                                    $in_files_list[] = self::get_resources_from_source(
Loading history...
1222
                                                        TOOL_DOCUMENT,
1223
                                                        $second_part,
1224
                                                        $recursivity + 1
1225
                                                    );
1226
                                                    if (count($in_files_list) > 0) {
1227
                                                        $files_list = array_merge(
1228
                                                            $files_list,
1229
                                                            $in_files_list
1230
                                                        );
1231
                                                    }
1232
                                                } else {
1233
                                                    // We didn't find any trace of current portal.
1234
                                                    $files_list[] = [
1235
                                                        $second_part,
1236
                                                        'remote',
1237
                                                        'url',
1238
                                                    ];
1239
                                                }
1240
                                            } elseif (strpos($second_part, '=') > 0) {
1241
                                                if (substr($second_part, 0, 1) === '/') {
1242
                                                    // Link starts with a /,
1243
                                                    // making it absolute (relative to DocumentRoot).
1244
                                                    $files_list[] = [
1245
                                                        $second_part,
1246
                                                        'local',
1247
                                                        'abs',
1248
                                                    ];
1249
                                                    $in_files_list[] = self::get_resources_from_source(
1250
                                                        TOOL_DOCUMENT,
1251
                                                        $second_part,
1252
                                                        $recursivity + 1
1253
                                                    );
1254
                                                    if (count($in_files_list) > 0) {
1255
                                                        $files_list = array_merge(
1256
                                                            $files_list,
1257
                                                            $in_files_list
1258
                                                        );
1259
                                                    }
1260
                                                } elseif (strstr($second_part, '..') === 0) {
1261
                                                    // Link is relative but going back in the hierarchy.
1262
                                                    $files_list[] = [
1263
                                                        $second_part,
1264
                                                        'local',
1265
                                                        'rel',
1266
                                                    ];
1267
                                                    $dir = dirname(
1268
                                                        $abs_path
1269
                                                    );
1270
                                                    $new_abs_path = realpath(
1271
                                                        $dir.'/'.$second_part
1272
                                                    );
1273
                                                    $in_files_list[] = self::get_resources_from_source(
1274
                                                        TOOL_DOCUMENT,
1275
                                                        $new_abs_path,
1276
                                                        $recursivity + 1
1277
                                                    );
1278
                                                    if (count($in_files_list) > 0) {
1279
                                                        $files_list = array_merge(
1280
                                                            $files_list,
1281
                                                            $in_files_list
1282
                                                        );
1283
                                                    }
1284
                                                } else {
1285
                                                    // No starting '/', making it relative to current document's path.
1286
                                                    if (substr($second_part, 0, 2) == './') {
1287
                                                        $second_part = substr(
1288
                                                            $second_part,
1289
                                                            2
1290
                                                        );
1291
                                                    }
1292
                                                    $files_list[] = [
1293
                                                        $second_part,
1294
                                                        'local',
1295
                                                        'rel',
1296
                                                    ];
1297
                                                    $dir = dirname(
1298
                                                        $abs_path
1299
                                                    );
1300
                                                    $new_abs_path = realpath(
1301
                                                        $dir.'/'.$second_part
1302
                                                    );
1303
                                                    $in_files_list[] = self::get_resources_from_source(
1304
                                                        TOOL_DOCUMENT,
1305
                                                        $new_abs_path,
1306
                                                        $recursivity + 1
1307
                                                    );
1308
                                                    if (count($in_files_list) > 0) {
1309
                                                        $files_list = array_merge(
1310
                                                            $files_list,
1311
                                                            $in_files_list
1312
                                                        );
1313
                                                    }
1314
                                                }
1315
                                            }
1316
                                            // Leave that second part behind now.
1317
                                            $source = substr(
1318
                                                $source,
1319
                                                0,
1320
                                                strpos($source, '?')
1321
                                            );
1322
                                            if (strpos($source, '://') > 0) {
1323
                                                if (strpos($source, api_get_path(WEB_PATH)) !== false) {
1324
                                                    // We found the current portal url.
1325
                                                    $files_list[] = [
1326
                                                        $source,
1327
                                                        'local',
1328
                                                        'url',
1329
                                                    ];
1330
                                                    $in_files_list[] = self::get_resources_from_source(
1331
                                                        TOOL_DOCUMENT,
1332
                                                        $source,
1333
                                                        $recursivity + 1
1334
                                                    );
1335
                                                    if (count($in_files_list) > 0) {
1336
                                                        $files_list = array_merge(
1337
                                                            $files_list,
1338
                                                            $in_files_list
1339
                                                        );
1340
                                                    }
1341
                                                } else {
1342
                                                    // We didn't find any trace of current portal.
1343
                                                    $files_list[] = [
1344
                                                        $source,
1345
                                                        'remote',
1346
                                                        'url',
1347
                                                    ];
1348
                                                }
1349
                                            } else {
1350
                                                // No protocol found, make link local.
1351
                                                if (substr($source, 0, 1) === '/') {
1352
                                                    // Link starts with a /, making it absolute (relative to DocumentRoot).
1353
                                                    $files_list[] = [
1354
                                                        $source,
1355
                                                        'local',
1356
                                                        'abs',
1357
                                                    ];
1358
                                                    $in_files_list[] = self::get_resources_from_source(
1359
                                                        TOOL_DOCUMENT,
1360
                                                        $source,
1361
                                                        $recursivity + 1
1362
                                                    );
1363
                                                    if (count($in_files_list) > 0) {
1364
                                                        $files_list = array_merge(
1365
                                                            $files_list,
1366
                                                            $in_files_list
1367
                                                        );
1368
                                                    }
1369
                                                } elseif (strstr($source, '..') === 0) {
1370
                                                    // Link is relative but going back in the hierarchy.
1371
                                                    $files_list[] = [
1372
                                                        $source,
1373
                                                        'local',
1374
                                                        'rel',
1375
                                                    ];
1376
                                                    $dir = dirname(
1377
                                                        $abs_path
1378
                                                    );
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 (substr($source, 0, 2) == './') {
1396
                                                        $source = substr(
1397
                                                            $source,
1398
                                                            2
1399
                                                        );
1400
                                                    }
1401
                                                    $files_list[] = [
1402
                                                        $source,
1403
                                                        'local',
1404
                                                        'rel',
1405
                                                    ];
1406
                                                    $dir = dirname(
1407
                                                        $abs_path
1408
                                                    );
1409
                                                    $new_abs_path = realpath(
1410
                                                        $dir.'/'.$source
1411
                                                    );
1412
                                                    $in_files_list[] = self::get_resources_from_source(
1413
                                                        TOOL_DOCUMENT,
1414
                                                        $new_abs_path,
1415
                                                        $recursivity + 1
1416
                                                    );
1417
                                                    if (count($in_files_list) > 0) {
1418
                                                        $files_list = array_merge(
1419
                                                            $files_list,
1420
                                                            $in_files_list
1421
                                                        );
1422
                                                    }
1423
                                                }
1424
                                            }
1425
                                        }
1426
1427
                                        // Found some protocol there.
1428
                                        if (strpos($source, api_get_path(WEB_PATH)) !== false) {
1429
                                            // We found the current portal url.
1430
                                            $files_list[] = [
1431
                                                $source,
1432
                                                'local',
1433
                                                'url',
1434
                                            ];
1435
                                            $in_files_list[] = self::get_resources_from_source(
1436
                                                TOOL_DOCUMENT,
1437
                                                $source,
1438
                                                $recursivity + 1
1439
                                            );
1440
                                            if (count($in_files_list) > 0) {
1441
                                                $files_list = array_merge(
1442
                                                    $files_list,
1443
                                                    $in_files_list
1444
                                                );
1445
                                            }
1446
                                        } else {
1447
                                            // We didn't find any trace of current portal.
1448
                                            $files_list[] = [
1449
                                                $source,
1450
                                                'remote',
1451
                                                'url',
1452
                                            ];
1453
                                        }
1454
                                    } else {
1455
                                        // No protocol found, make link local.
1456
                                        if (substr($source, 0, 1) === '/') {
1457
                                            // Link starts with a /, making it absolute (relative to DocumentRoot).
1458
                                            $files_list[] = [
1459
                                                $source,
1460
                                                'local',
1461
                                                'abs',
1462
                                            ];
1463
                                            $in_files_list[] = self::get_resources_from_source(
1464
                                                TOOL_DOCUMENT,
1465
                                                $source,
1466
                                                $recursivity + 1
1467
                                            );
1468
                                            if (count($in_files_list) > 0) {
1469
                                                $files_list = array_merge(
1470
                                                    $files_list,
1471
                                                    $in_files_list
1472
                                                );
1473
                                            }
1474
                                        } elseif (strstr($source, '..') === 0) {
1475
                                            // Link is relative but going back in the hierarchy.
1476
                                            $files_list[] = [
1477
                                                $source,
1478
                                                'local',
1479
                                                'rel',
1480
                                            ];
1481
                                            $dir = dirname($abs_path);
1482
                                            $new_abs_path = realpath(
1483
                                                $dir.'/'.$source
1484
                                            );
1485
                                            $in_files_list[] = self::get_resources_from_source(
1486
                                                TOOL_DOCUMENT,
1487
                                                $new_abs_path,
1488
                                                $recursivity + 1
1489
                                            );
1490
                                            if (count($in_files_list) > 0) {
1491
                                                $files_list = array_merge(
1492
                                                    $files_list,
1493
                                                    $in_files_list
1494
                                                );
1495
                                            }
1496
                                        } else {
1497
                                            // No starting '/', making it relative to current document's path.
1498
                                            if (strpos($source, 'width=') ||
1499
                                                strpos($source, 'autostart=')
1500
                                            ) {
1501
                                                continue;
1502
                                            }
1503
1504
                                            if (substr($source, 0, 2) == './') {
1505
                                                $source = substr(
1506
                                                    $source,
1507
                                                    2
1508
                                                );
1509
                                            }
1510
                                            $files_list[] = [
1511
                                                $source,
1512
                                                'local',
1513
                                                'rel',
1514
                                            ];
1515
                                            $dir = dirname($abs_path);
1516
                                            $new_abs_path = realpath(
1517
                                                $dir.'/'.$source
1518
                                            );
1519
                                            $in_files_list[] = self::get_resources_from_source(
1520
                                                TOOL_DOCUMENT,
1521
                                                $new_abs_path,
1522
                                                $recursivity + 1
1523
                                            );
1524
                                            if (count($in_files_list) > 0) {
1525
                                                $files_list = array_merge(
1526
                                                    $files_list,
1527
                                                    $in_files_list
1528
                                                );
1529
                                            }
1530
                                        }
1531
                                    }
1532
                                }
1533
                            }
1534
                        }
1535
                        break;
1536
                    default:
1537
                        break;
1538
                }
1539
1540
                break;
1541
            default: // Ignore.
1542
                break;
1543
        }
1544
1545
        $checked_files_list = [];
1546
        $checked_array_list = [];
1547
        foreach ($files_list as $idx => $file) {
1548
            if (!empty($file[0])) {
1549
                if (!in_array($file[0], $checked_files_list)) {
1550
                    $checked_files_list[] = $files_list[$idx][0];
1551
                    $checked_array_list[] = $files_list[$idx];
1552
                }
1553
            }
1554
        }
1555
1556
        return $checked_array_list;
1557
    }
1558
1559
    /**
1560
     * Gets the score.
1561
     *
1562
     * @return float The current score or 0 if no score set yet
1563
     */
1564
    public function get_score()
1565
    {
1566
        if (self::DEBUG > 0) {
1567
            error_log('learnpathItem::get_score()', 0);
1568
        }
1569
        $res = 0;
1570
        if (!empty($this->current_score)) {
1571
            $res = $this->current_score;
1572
        }
1573
        if (self::DEBUG > 1) {
1574
            error_log(
1575
                'New LP - Out of learnpathItem::get_score() - returning '.$res,
1576
                0
1577
            );
1578
        }
1579
1580
        return $res;
1581
    }
1582
1583
    /**
1584
     * Gets the item status.
1585
     *
1586
     * @param bool $check_db     Do or don't check into the database for the
1587
     *                           latest value. Optional. Default is true
1588
     * @param bool $update_local Do or don't update the local attribute
1589
     *                           value with what's been found in DB
1590
     *
1591
     * @return string Current status or 'Not attempted' if no status set yet
1592
     */
1593
    public function get_status($check_db = true, $update_local = false)
1594
    {
1595
        $course_id = api_get_course_int_id();
1596
        $debug = self::DEBUG;
1597
        if ($debug > 0) {
1598
            error_log('learnpathItem::get_status() on item '.$this->db_id, 0);
1599
        }
1600
        if ($check_db) {
1601
            if ($debug > 2) {
1602
                error_log('learnpathItem::get_status(): checking db', 0);
1603
            }
1604
            if (!empty($this->db_item_view_id) && !empty($course_id)) {
1605
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1606
                $sql = "SELECT status FROM $table
1607
                        WHERE
1608
                            c_id = $course_id AND
1609
                            iid = '".$this->db_item_view_id."' AND
1610
                            view_count = '".$this->get_attempt_id()."'";
1611
1612
                if ($debug > 2) {
1613
                    error_log(
1614
                        'learnpathItem::get_status() - Checking DB: '.$sql,
1615
                        0
1616
                    );
1617
                }
1618
1619
                $res = Database::query($sql);
1620
                if (Database::num_rows($res) == 1) {
1621
                    $row = Database::fetch_array($res);
1622
                    if ($update_local) {
1623
                        if ($debug > 2) {
1624
                            error_log(
1625
                                'learnpathItem::set_status() :'.$row['status'],
1626
                                0
1627
                            );
1628
                        }
1629
                        $this->set_status($row['status']);
1630
                    }
1631
                    if ($debug > 2) {
1632
                        error_log(
1633
                            'learnpathItem::get_status() - Returning db value '.$row['status'],
1634
                            0
1635
                        );
1636
                    }
1637
1638
                    return $row['status'];
1639
                }
1640
            }
1641
        } else {
1642
            if ($debug > 2) {
1643
                error_log(
1644
                    'learnpathItem::get_status() - in get_status: using attrib',
1645
                    0
1646
                );
1647
            }
1648
            if (!empty($this->status)) {
1649
                if ($debug > 2) {
1650
                    error_log(
1651
                        'learnpathItem::get_status() - Returning attrib: '.$this->status,
1652
                        0
1653
                    );
1654
                }
1655
1656
                return $this->status;
1657
            }
1658
        }
1659
1660
        if ($debug > 2) {
1661
            error_log(
1662
                'learnpathItem::get_status() - Returning default '.$this->possible_status[0],
1663
                0
1664
            );
1665
        }
1666
1667
        return $this->possible_status[0];
1668
    }
1669
1670
    /**
1671
     * Gets the suspend data.
1672
     */
1673
    public function get_suspend_data()
1674
    {
1675
        if (self::DEBUG > 0) {
1676
            error_log('learnpathItem::get_suspend_data()', 0);
1677
        }
1678
        // TODO: Improve cleaning of breaklines ... it works but is it really
1679
        // a beautiful way to do it ?
1680
        if (!empty($this->current_data)) {
1681
            return str_replace(
1682
                ["\r", "\n", "'"],
1683
                ['\r', '\n', "\\'"],
1684
                $this->current_data
1685
            );
1686
        } else {
1687
            return '';
1688
        }
1689
    }
1690
1691
    /**
1692
     * @param string $origin
1693
     * @param string $time
1694
     *
1695
     * @return string
1696
     */
1697
    public static function getScormTimeFromParameter(
1698
        $origin = 'php',
1699
        $time = null
1700
    ) {
1701
        $h = get_lang('h');
1702
        if (!isset($time)) {
1703
            if ($origin == 'js') {
1704
                return '00 : 00: 00';
1705
            } else {
1706
                return '00 '.$h.' 00 \' 00"';
1707
            }
1708
        } else {
1709
            return api_format_time($time, $origin);
1710
        }
1711
    }
1712
1713
    /**
1714
     * Gets the total time spent on this item view so far.
1715
     *
1716
     * @param string   $origin     Origin of the request. If coming from PHP,
1717
     *                             send formatted as xxhxx'xx", otherwise use scorm format 00:00:00
1718
     * @param int|null $given_time Given time is a default time to return formatted
1719
     * @param bool     $query_db   Whether to get the value from db or from memory
1720
     *
1721
     * @return string A string with the time in SCORM format
1722
     */
1723
    public function get_scorm_time(
1724
        $origin = 'php',
1725
        $given_time = null,
1726
        $query_db = false
1727
    ) {
1728
        $time = null;
1729
        $course_id = api_get_course_int_id();
1730
        if (!isset($given_time)) {
1731
            if (self::DEBUG > 2) {
1732
                error_log(
1733
                    'learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time,
1734
                    0
1735
                );
1736
            }
1737
            if ($query_db === true) {
1738
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1739
                $sql = "SELECT start_time, total_time
1740
                        FROM $table
1741
                        WHERE
1742
                            c_id = $course_id AND
1743
                            iid = '".$this->db_item_view_id."' AND
1744
                            view_count = '".$this->get_attempt_id()."'";
1745
                $res = Database::query($sql);
1746
                $row = Database::fetch_array($res);
1747
                $start = $row['start_time'];
1748
                $stop = $start + $row['total_time'];
1749
            } else {
1750
                $start = $this->current_start_time;
1751
                $stop = $this->current_stop_time;
1752
            }
1753
            if (!empty($start)) {
1754
                if (!empty($stop)) {
1755
                    $time = $stop - $start;
1756
                } else {
1757
                    $time = time() - $start;
1758
                }
1759
            }
1760
        } else {
1761
            $time = $given_time;
1762
        }
1763
        if (self::DEBUG > 2) {
1764
            error_log(
1765
                'learnpathItem::get_scorm_time(): intermediate = '.$time,
1766
                0
1767
            );
1768
        }
1769
        $time = api_format_time($time, $origin);
1770
1771
        return $time;
1772
    }
1773
1774
    /**
1775
     * Get the extra terms (tags) that identify this item.
1776
     *
1777
     * @return mixed
1778
     */
1779
    public function get_terms()
1780
    {
1781
        $table = Database::get_course_table(TABLE_LP_ITEM);
1782
        $sql = "SELECT * FROM $table
1783
                WHERE iid = ".intval($this->db_id);
1784
        $res = Database::query($sql);
1785
        $row = Database::fetch_array($res);
1786
1787
        return $row['terms'];
1788
    }
1789
1790
    /**
1791
     * Returns the item's title.
1792
     *
1793
     * @return string Title
1794
     */
1795
    public function get_title()
1796
    {
1797
        if (self::DEBUG > 0) {
1798
            error_log('learnpathItem::get_title()', 0);
1799
        }
1800
        if (empty($this->title)) {
1801
            return '';
1802
        }
1803
1804
        return $this->title;
1805
    }
1806
1807
    /**
1808
     * Returns the total time used to see that item.
1809
     *
1810
     * @return int Total time
1811
     */
1812
    public function get_total_time()
1813
    {
1814
        $debug = self::DEBUG;
1815
        if ($debug) {
1816
            error_log(
1817
                'learnpathItem::get_total_time() for item '.$this->db_id.
1818
                ' - Initially, current_start_time = '.$this->current_start_time.
1819
                ' and current_stop_time = '.$this->current_stop_time,
1820
                0
1821
            );
1822
        }
1823
        if ($this->current_start_time == 0) {
1824
            // Shouldn't be necessary thanks to the open() method.
1825
            if ($debug) {
1826
                error_log(
1827
                    'learnpathItem::get_total_time() - Current start time was empty',
1828
                    0
1829
                );
1830
            }
1831
            $this->current_start_time = time();
1832
        }
1833
1834
        if (time() < $this->current_stop_time ||
1835
            $this->current_stop_time == 0
1836
        ) {
1837
            if ($debug) {
1838
                error_log(
1839
                    'learnpathItem::get_total_time() - Current stop time was '
1840
                    .'greater than the current time or was empty',
1841
                    0
1842
                );
1843
            }
1844
            // If this case occurs, then we risk to write huge time data in db.
1845
            // In theory, stop time should be *always* updated here, but it
1846
            // might be used in some unknown goal.
1847
            $this->current_stop_time = time();
1848
        }
1849
1850
        $time = $this->current_stop_time - $this->current_start_time;
1851
1852
        if ($time < 0) {
1853
            if ($debug) {
1854
                error_log(
1855
                    'learnpathItem::get_total_time() - Time smaller than 0. Returning 0',
1856
                    0
1857
                );
1858
            }
1859
1860
            return 0;
1861
        } else {
1862
            $time = $this->fixAbusiveTime($time);
1863
            if ($debug) {
1864
                error_log(
1865
                    'Current start time = '.$this->current_start_time.', current stop time = '.
1866
                    $this->current_stop_time.' Returning '.$time."-----------\n"
1867
                );
1868
            }
1869
1870
            return $time;
1871
        }
1872
    }
1873
1874
    /**
1875
     * Sometimes time recorded for a learning path item is superior to the maximum allowed duration of the session.
1876
     * In this case, this session resets the time for that particular learning path item to 5 minutes
1877
     * (something more realistic, that is also used when leaving the portal without closing one's session).
1878
     *
1879
     * @param int $time
1880
     *
1881
     * @return int
1882
     */
1883
    public function fixAbusiveTime($time)
1884
    {
1885
        // Code based from Event::courseLogout
1886
        $sessionLifetime = api_get_configuration_value('session_lifetime');
1887
        // If session life time too big use 1 hour
1888
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
1889
            $sessionLifetime = 3600;
1890
        }
1891
1892
        $fixedAddedMinute = 5 * 60; // Add only 5 minutes
1893
        if ($time > $sessionLifetime) {
1894
            error_log("fixAbusiveTime: Total time is too big: $time replaced with: $fixedAddedMinute");
1895
            error_log("item_id : ".$this->db_id." lp_item_view.iid: ".$this->db_item_view_id);
1896
            $time = $fixedAddedMinute;
1897
        }
1898
1899
        return $time;
1900
    }
1901
1902
    /**
1903
     * Gets the item type.
1904
     *
1905
     * @return string The item type (can be doc, dir, sco, asset)
1906
     */
1907
    public function get_type()
1908
    {
1909
        $res = 'asset';
1910
        if (!empty($this->type)) {
1911
            $res = $this->type;
1912
        }
1913
        if (self::DEBUG > 2) {
1914
            error_log(
1915
                'learnpathItem::get_type() - Returning '.$res.' for item '.$this->db_id,
1916
                0
1917
            );
1918
        }
1919
1920
        return $res;
1921
    }
1922
1923
    /**
1924
     * Gets the view count for this item.
1925
     *
1926
     * @return int Number of attempts or 0
1927
     */
1928
    public function get_view_count()
1929
    {
1930
        if (self::DEBUG > 0) {
1931
            error_log('learnpathItem::get_view_count()', 0);
1932
        }
1933
        if (!empty($this->attempt_id)) {
1934
            return $this->attempt_id;
1935
        } else {
1936
            return 0;
1937
        }
1938
    }
1939
1940
    /**
1941
     * Tells if an item is done ('completed','passed','succeeded') or not.
1942
     *
1943
     * @return bool True if the item is done ('completed','passed','succeeded'),
1944
     *              false otherwise
1945
     */
1946
    public function is_done()
1947
    {
1948
        $completedStatusList = [
1949
            'completed',
1950
            'passed',
1951
            'succeeded',
1952
            'failed',
1953
        ];
1954
1955
        if ($this->status_is($completedStatusList)) {
1956
            if (self::DEBUG > 2) {
1957
                error_log(
1958
                    'learnpath::is_done() - Item '.$this->get_id(
1959
                    ).' is complete',
1960
                    0
1961
                );
1962
            }
1963
1964
            return true;
1965
        } else {
1966
            if (self::DEBUG > 2) {
1967
                error_log(
1968
                    'learnpath::is_done() - Item '.$this->get_id(
1969
                    ).' is not complete',
1970
                    0
1971
                );
1972
            }
1973
1974
            return false;
1975
        }
1976
    }
1977
1978
    /**
1979
     * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status).
1980
     *
1981
     * @return int -1 if retaking the sco another time for credit is not allowed,
1982
     *             0 if it is not allowed but the item has to be finished
1983
     *             1 if it is allowed. Defaults to 1
1984
     */
1985
    public function isRestartAllowed()
1986
    {
1987
        if (self::DEBUG > 2) {
1988
            error_log('learnpathItem::isRestartAllowed()', 0);
1989
        }
1990
        $restart = 1;
1991
        $mystatus = $this->get_status(true);
1992
        if ($this->get_prevent_reinit() > 0
1993
        ) { // If prevent_reinit == 1 (or more)
1994
            // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise:
1995
            if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) {
1996
                $restart = -1;
1997
            } else { //status incompleted or not attempted
1998
                $restart = 0;
1999
            }
2000
        } else {
2001
            if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) {
2002
                $restart = -1;
2003
            }
2004
        }
2005
        if (self::DEBUG > 2) {
2006
            error_log(
2007
                'New LP - End of learnpathItem::isRestartAllowed() - Returning '.$restart,
2008
                0
2009
            );
2010
        }
2011
2012
        return $restart;
2013
    }
2014
2015
    /**
2016
     * Opens/launches the item. Initialises runtime values.
2017
     *
2018
     * @param bool $allow_new_attempt
2019
     *
2020
     * @return bool true on success, false on failure
2021
     */
2022
    public function open($allow_new_attempt = false)
2023
    {
2024
        if (self::DEBUG > 0) {
2025
            error_log('learnpathItem::open()', 0);
2026
        }
2027
        if ($this->prevent_reinit == 0) {
2028
            $this->current_score = 0;
2029
            $this->current_start_time = time();
2030
            // In this case, as we are opening the item, what is important to us
2031
            // is the database status, in order to know if this item has already
2032
            // been used in the past (rather than just loaded and modified by
2033
            // some javascript but not written in the database).
2034
            // If the database status is different from 'not attempted', we can
2035
            // consider this item has already been used, and as such we can
2036
            // open a new attempt. Otherwise, we'll just reuse the current
2037
            // attempt, which is generally created the first time the item is
2038
            // loaded (for example as part of the table of contents).
2039
            $stat = $this->get_status(true);
2040
            if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) {
2041
                $this->attempt_id = $this->attempt_id + 1; // Open a new attempt.
2042
            }
2043
            $this->status = $this->possible_status[1];
2044
        } else {
2045
            /*if ($this->current_start_time == 0) {
2046
                // Small exception for start time, to avoid amazing values.
2047
                $this->current_start_time = time();
2048
            }*/
2049
            // If we don't init start time here, the time is sometimes calculated from the last start time.
2050
            $this->current_start_time = time();
2051
        }
2052
    }
2053
2054
    /**
2055
     * Outputs the item contents.
2056
     *
2057
     * @return string HTML file (displayable in an <iframe>) or empty string if no path defined
2058
     */
2059
    public function output()
2060
    {
2061
        if (self::DEBUG > 0) {
2062
            error_log('learnpathItem::output()', 0);
2063
        }
2064
        if (!empty($this->path) and is_file($this->path)) {
2065
            $output = '';
2066
            $output .= file_get_contents($this->path);
2067
2068
            return $output;
2069
        }
2070
2071
        return '';
2072
    }
2073
2074
    /**
2075
     * Parses the prerequisites string with the AICC logic language.
2076
     *
2077
     * @param string $prereqs_string The prerequisites string as it figures in imsmanifest.xml
2078
     * @param array  $items          Array of items in the current learnpath object.
2079
     *                               Although we're in the learnpathItem object, it's necessary to have
2080
     *                               a list of all items to be able to check the current item's prerequisites
2081
     * @param array  $refs_list      list of references
2082
     *                               (the "ref" column in the lp_item table) that are strings used in the
2083
     *                               expression of prerequisites
2084
     * @param int    $user_id        The user ID. In some cases like Chamilo quizzes,
2085
     *                               it's necessary to have the user ID to query other tables (like the results of quizzes)
2086
     *
2087
     * @return bool True if the list of prerequisites given is entirely satisfied, false otherwise
2088
     */
2089
    public function parse_prereq($prereqs_string, $items, $refs_list, $user_id)
2090
    {
2091
        if (self::DEBUG > 0) {
2092
            error_log(
2093
                'learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string,
2094
                0
2095
            );
2096
        }
2097
2098
        $course_id = api_get_course_int_id();
2099
        $sessionId = api_get_session_id();
2100
2101
        // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order.
2102
        $this->prereq_alert = '';
2103
        // First parse all parenthesis by using a sequential loop
2104
        //  (looking for less-inclusives first).
2105
        if ($prereqs_string == '_true_') {
2106
            return true;
2107
        }
2108
2109
        if ($prereqs_string == '_false_') {
2110
            if (empty($this->prereq_alert)) {
2111
                $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2112
            }
2113
2114
            return false;
2115
        }
2116
        while (strpos($prereqs_string, '(') !== false) {
2117
            // Remove any () set and replace with its value.
2118
            $matches = [];
2119
            $res = preg_match_all(
2120
                '/(\(([^\(\)]*)\))/',
2121
                $prereqs_string,
2122
                $matches
2123
            );
2124
            if ($res) {
2125
                foreach ($matches[2] as $id => $match) {
2126
                    $str_res = $this->parse_prereq(
2127
                        $match,
2128
                        $items,
2129
                        $refs_list,
2130
                        $user_id
2131
                    );
2132
                    if ($str_res) {
2133
                        $prereqs_string = str_replace(
2134
                            $matches[1][$id],
2135
                            '_true_',
2136
                            $prereqs_string
2137
                        );
2138
                    } else {
2139
                        $prereqs_string = str_replace(
2140
                            $matches[1][$id],
2141
                            '_false_',
2142
                            $prereqs_string
2143
                        );
2144
                    }
2145
                }
2146
            }
2147
        }
2148
2149
        // Parenthesis removed, now look for ORs as it is the lesser-priority
2150
        //  binary operator (= always uses one text operand).
2151
        if (strpos($prereqs_string, '|') === false) {
2152
            if (self::DEBUG > 1) {
2153
                error_log('New LP - Didnt find any OR, looking for AND', 0);
2154
            }
2155
            if (strpos($prereqs_string, '&') !== false) {
2156
                $list = explode('&', $prereqs_string);
2157
                if (count($list) > 1) {
2158
                    $andstatus = true;
2159
                    foreach ($list as $condition) {
2160
                        $andstatus = $andstatus && $this->parse_prereq(
2161
                            $condition,
2162
                            $items,
2163
                            $refs_list,
2164
                            $user_id
2165
                        );
2166
2167
                        if (!$andstatus) {
2168
                            if (self::DEBUG > 1) {
2169
                                error_log(
2170
                                    'New LP - One condition in AND was false, short-circuit',
2171
                                    0
2172
                                );
2173
                            }
2174
                            break;
2175
                        }
2176
                    }
2177
                    if (empty($this->prereq_alert) && !$andstatus) {
2178
                        $this->prereq_alert = get_lang(
2179
                            'LearnpathPrereqNotCompleted'
2180
                        );
2181
                    }
2182
2183
                    return $andstatus;
2184
                } else {
2185
                    if (isset($items[$refs_list[$list[0]]])) {
2186
                        $status = $items[$refs_list[$list[0]]]->get_status(true);
2187
                        $returnstatus = ($status == $this->possible_status[2]) || ($status == $this->possible_status[3]);
2188
                        if (empty($this->prereq_alert) && !$returnstatus) {
2189
                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2190
                        }
2191
2192
                        return $returnstatus;
2193
                    }
2194
                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2195
2196
                    return false;
2197
                }
2198
            } else {
2199
                // No ORs found, now look for ANDs.
2200
2201
                if (self::DEBUG > 1) {
2202
                    error_log('New LP - Didnt find any AND, looking for =', 0);
2203
                }
2204
2205
                if (strpos($prereqs_string, '=') !== false) {
2206
                    if (self::DEBUG > 1) {
2207
                        error_log('New LP - Found =, looking into it', 0);
2208
                    }
2209
                    // We assume '=' signs only appear when there's nothing else around.
2210
                    $params = explode('=', $prereqs_string);
2211
                    if (count($params) == 2) {
2212
                        // Right number of operands.
2213
                        if (isset($items[$refs_list[$params[0]]])) {
2214
                            $status = $items[$refs_list[$params[0]]]->get_status(true);
2215
                            $returnstatus = $status == $params[1];
2216
                            if (empty($this->prereq_alert) && !$returnstatus) {
2217
                                $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2218
                            }
2219
2220
                            return $returnstatus;
2221
                        }
2222
                        $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2223
2224
                        return false;
2225
                    }
2226
                } else {
2227
                    // No ANDs found, look for <>
2228
                    if (self::DEBUG > 1) {
2229
                        error_log(
2230
                            'New LP - Didnt find any =, looking for <>',
2231
                            0
2232
                        );
2233
                    }
2234
2235
                    if (strpos($prereqs_string, '<>') !== false) {
2236
                        if (self::DEBUG > 1) {
2237
                            error_log('New LP - Found <>, looking into it', 0);
2238
                        }
2239
                        // We assume '<>' signs only appear when there's nothing else around.
2240
                        $params = explode('<>', $prereqs_string);
2241
                        if (count($params) == 2) {
2242
                            // Right number of operands.
2243
                            if (isset($items[$refs_list[$params[0]]])) {
2244
                                $status = $items[$refs_list[$params[0]]]->get_status(true);
2245
                                $returnstatus = $status != $params[1];
2246
                                if (empty($this->prereq_alert) && !$returnstatus) {
2247
                                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2248
                                }
2249
2250
                                return $returnstatus;
2251
                            }
2252
                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2253
2254
                            return false;
2255
                        }
2256
                    } else {
2257
                        // No <> found, look for ~ (unary)
2258
                        if (self::DEBUG > 1) {
2259
                            error_log(
2260
                                'New LP - Didnt find any =, looking for ~',
2261
                                0
2262
                            );
2263
                        }
2264
                        // Only remains: ~ and X*{}
2265
                        if (strpos($prereqs_string, '~') !== false) {
2266
                            // Found NOT.
2267
                            if (self::DEBUG > 1) {
2268
                                error_log(
2269
                                    'New LP - Found ~, looking into it',
2270
                                    0
2271
                                );
2272
                            }
2273
                            $list = [];
2274
                            $myres = preg_match(
2275
                                '/~([^(\d+\*)\{]*)/',
2276
                                $prereqs_string,
2277
                                $list
2278
                            );
2279
                            if ($myres) {
2280
                                $returnstatus = !$this->parse_prereq(
2281
                                    $list[1],
2282
                                    $items,
2283
                                    $refs_list,
2284
                                    $user_id
2285
                                );
2286
                                if (empty($this->prereq_alert) && !$returnstatus) {
2287
                                    $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2288
                                }
2289
2290
                                return $returnstatus;
2291
                            } else {
2292
                                // Strange...
2293
                                if (self::DEBUG > 1) {
2294
                                    error_log(
2295
                                        'New LP - Found ~ but strange string: '.$prereqs_string,
2296
                                        0
2297
                                    );
2298
                                }
2299
                            }
2300
                        } else {
2301
                            // Finally, look for sets/groups
2302
                            if (self::DEBUG > 1) {
2303
                                error_log(
2304
                                    'New LP - Didnt find any ~, looking for groups',
2305
                                    0
2306
                                );
2307
                            }
2308
                            // Only groups here.
2309
                            $groups = [];
2310
                            $groups_there = preg_match_all(
2311
                                '/((\d+\*)?\{([^\}]+)\}+)/',
2312
                                $prereqs_string,
2313
                                $groups
2314
                            );
2315
2316
                            if ($groups_there) {
2317
                                foreach ($groups[1] as $gr) {
2318
                                    // Only take the results that correspond to
2319
                                    //  the big brackets-enclosed condition.
2320
                                    if (self::DEBUG > 1) {
2321
                                        error_log(
2322
                                            'New LP - Dealing with group '.$gr,
2323
                                            0
2324
                                        );
2325
                                    }
2326
                                    $multi = [];
2327
                                    $mycond = false;
2328
                                    if (preg_match(
2329
                                        '/(\d+)\*\{([^\}]+)\}/',
2330
                                        $gr,
2331
                                        $multi
2332
                                    )
2333
                                    ) {
2334
                                        if (self::DEBUG > 1) {
2335
                                            error_log(
2336
                                                'New LP - Found multiplier '.$multi[0],
2337
                                                0
2338
                                            );
2339
                                        }
2340
                                        $count = $multi[1];
2341
                                        $list = explode(',', $multi[2]);
2342
                                        $mytrue = 0;
2343
                                        foreach ($list as $cond) {
2344
                                            if (isset($items[$refs_list[$cond]])) {
2345
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2346
                                                if ($status == $this->possible_status[2] ||
2347
                                                    $status == $this->possible_status[3]
2348
                                                ) {
2349
                                                    $mytrue++;
2350
                                                    if (self::DEBUG > 1) {
2351
                                                        error_log(
2352
                                                            'New LP - Found true item, counting.. ('.($mytrue).')',
2353
                                                            0
2354
                                                        );
2355
                                                    }
2356
                                                }
2357
                                            } else {
2358
                                                if (self::DEBUG > 1) {
2359
                                                    error_log(
2360
                                                        'New LP - item '.$cond.' does not exist in items list',
2361
                                                        0
2362
                                                    );
2363
                                                }
2364
                                            }
2365
                                        }
2366
                                        if ($mytrue >= $count) {
2367
                                            if (self::DEBUG > 1) {
2368
                                                error_log(
2369
                                                    'New LP - Got enough true results, return true',
2370
                                                    0
2371
                                                );
2372
                                            }
2373
                                            $mycond = true;
2374
                                        } else {
2375
                                            if (self::DEBUG > 1) {
2376
                                                error_log(
2377
                                                    'New LP - Not enough true results',
2378
                                                    0
2379
                                                );
2380
                                            }
2381
                                        }
2382
                                    } else {
2383
                                        if (self::DEBUG > 1) {
2384
                                            error_log(
2385
                                                'New LP - No multiplier',
2386
                                                0
2387
                                            );
2388
                                        }
2389
                                        $list = explode(',', $gr);
2390
                                        $mycond = true;
2391
                                        foreach ($list as $cond) {
2392
                                            if (isset($items[$refs_list[$cond]])) {
2393
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2394
                                                if ($status == $this->possible_status[2] ||
2395
                                                    $status == $this->possible_status[3]
2396
                                                ) {
2397
                                                    $mycond = true;
2398
                                                    if (self::DEBUG > 1) {
2399
                                                        error_log(
2400
                                                            'New LP - Found true item',
2401
                                                            0
2402
                                                        );
2403
                                                    }
2404
                                                } else {
2405
                                                    if (self::DEBUG > 1) {
2406
                                                        error_log(
2407
                                                            'New LP - '.
2408
                                                            ' Found false item, the set is not true, return false',
2409
                                                            0
2410
                                                        );
2411
                                                    }
2412
                                                    $mycond = false;
2413
                                                    break;
2414
                                                }
2415
                                            } else {
2416
                                                if (self::DEBUG > 1) {
2417
                                                    error_log(
2418
                                                        'New LP - item '.$cond.' does not exist in items list',
2419
                                                        0
2420
                                                    );
2421
                                                }
2422
                                                if (self::DEBUG > 1) {
2423
                                                    error_log(
2424
                                                        'New LP - Found false item, the set is not true, return false',
2425
                                                        0
2426
                                                    );
2427
                                                }
2428
                                                $mycond = false;
2429
                                                break;
2430
                                            }
2431
                                        }
2432
                                    }
2433
                                    if (!$mycond && empty($this->prereq_alert)) {
2434
                                        $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2435
                                    }
2436
2437
                                    return $mycond;
2438
                                }
2439
                            } else {
2440
                                // Nothing found there either. Now return the
2441
                                //  value of the corresponding resource completion status.
2442
                                if (self::DEBUG > 1) {
2443
                                    error_log(
2444
                                        'New LP - Didnt find any group, returning value for '.$prereqs_string,
2445
                                        0
2446
                                    );
2447
                                }
2448
2449
                                if (isset($refs_list[$prereqs_string]) &&
2450
                                    isset($items[$refs_list[$prereqs_string]])
2451
                                ) {
2452
                                    if ($items[$refs_list[$prereqs_string]]->type == 'quiz') {
2453
                                        // 1. Checking the status in current items.
2454
                                        $status = $items[$refs_list[$prereqs_string]]->get_status(true);
2455
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2456
2457
                                        if (!$returnstatus) {
2458
                                            if (self::DEBUG > 1) {
2459
                                                error_log(
2460
                                                    'New LP - Prerequisite '.$prereqs_string.' not complete',
2461
                                                    0
2462
                                                );
2463
                                            }
2464
                                        } else {
2465
                                            if (self::DEBUG > 1) {
2466
                                                error_log(
2467
                                                    'New LP - Prerequisite '.$prereqs_string.' complete',
2468
                                                    0
2469
                                                );
2470
                                            }
2471
                                        }
2472
2473
                                        // For one and first attempt.
2474
                                        if ($this->prevent_reinit == 1) {
2475
                                            // 2. If is completed we check the results in the DB of the quiz.
2476
                                            if ($returnstatus) {
2477
                                                //AND origin_lp_item_id = '.$user_id.'
2478
                                                $sql = 'SELECT exe_result, exe_weighting
2479
                                                        FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2480
                                                        WHERE
2481
                                                            exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2482
                                                            exe_user_id = '.$user_id.' AND
2483
                                                            orig_lp_id = '.$this->lp_id.' AND
2484
                                                            orig_lp_item_id = '.$prereqs_string.' AND
2485
                                                            status <> "incomplete"
2486
                                                        ORDER BY exe_date DESC
2487
                                                        LIMIT 0, 1';
2488
                                                $rs_quiz = Database::query($sql);
2489
                                                if ($quiz = Database::fetch_array($rs_quiz)) {
2490
                                                    $minScore = $items[$refs_list[$this->get_id()]]->getPrerequisiteMinScore();
2491
                                                    $maxScore = $items[$refs_list[$this->get_id()]]->getPrerequisiteMaxScore();
2492
2493
                                                    if (isset($minScore) && isset($minScore)) {
2494
                                                        // Taking min/max prerequisites values see BT#5776
2495
                                                        if ($quiz['exe_result'] >= $minScore &&
2496
                                                            $quiz['exe_result'] <= $maxScore
2497
                                                        ) {
2498
                                                            $returnstatus = true;
2499
                                                        } else {
2500
                                                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2501
                                                            $returnstatus = false;
2502
                                                        }
2503
                                                    } else {
2504
                                                        // Classic way
2505
                                                        if ($quiz['exe_result'] >=
2506
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2507
                                                        ) {
2508
                                                            $returnstatus = true;
2509
                                                        } else {
2510
                                                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2511
                                                            $returnstatus = false;
2512
                                                        }
2513
                                                    }
2514
                                                } else {
2515
                                                    $this->prereq_alert = get_lang(
2516
                                                        'LearnpathPrereqNotCompleted'
2517
                                                    );
2518
                                                    $returnstatus = false;
2519
                                                }
2520
                                            }
2521
                                        } else {
2522
                                            // 3. for multiple attempts we check that there are minimum 1 item completed.
2523
                                            // Checking in the database.
2524
                                            $sql = 'SELECT exe_result, exe_weighting
2525
                                                    FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2526
                                                    WHERE
2527
                                                        exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2528
                                                        exe_user_id = '.$user_id.' AND
2529
                                                        orig_lp_id = '.$this->lp_id.' AND
2530
                                                        orig_lp_item_id = '.$prereqs_string.' ';
2531
2532
                                            $rs_quiz = Database::query($sql);
2533
                                            if (Database::num_rows($rs_quiz) > 0) {
2534
                                                while ($quiz = Database::fetch_array($rs_quiz)) {
2535
                                                    $minScore = $items[$refs_list[$this->get_id()]]->getPrerequisiteMinScore();
2536
                                                    $maxScore = $items[$refs_list[$this->get_id()]]->getPrerequisiteMaxScore();
2537
2538
                                                    if (isset($minScore) && isset($minScore)) {
2539
                                                        // Taking min/max prerequisites values see BT#5776
2540
                                                        if ($quiz['exe_result'] >= $minScore && $quiz['exe_result'] <= $maxScore) {
2541
                                                            $returnstatus = true;
2542
                                                            break;
2543
                                                        } else {
2544
                                                            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2545
                                                            $returnstatus = false;
2546
                                                        }
2547
                                                    } else {
2548
                                                        if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) {
2549
                                                            $returnstatus = true;
2550
                                                            break;
2551
                                                        } else {
2552
                                                            $this->prereq_alert = get_lang(
2553
                                                                'LearnpathPrereqNotCompleted'
2554
                                                            );
2555
                                                            $returnstatus = false;
2556
                                                        }
2557
                                                    }
2558
                                                }
2559
                                            } else {
2560
                                                $this->prereq_alert = get_lang(
2561
                                                    'LearnpathPrereqNotCompleted'
2562
                                                );
2563
                                                $returnstatus = false;
2564
                                            }
2565
                                        }
2566
2567
                                        return $returnstatus;
2568
                                    } else {
2569
                                        $status = $items[$refs_list[$prereqs_string]]->get_status(false);
2570
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2571
                                        if (!$returnstatus) {
2572
                                            if (self::DEBUG > 1) {
2573
                                                error_log(
2574
                                                    'New LP - Prerequisite '.$prereqs_string.' not complete',
2575
                                                    0
2576
                                                );
2577
                                            }
2578
                                        } else {
2579
                                            if (self::DEBUG > 1) {
2580
                                                error_log(
2581
                                                    'New LP - Prerequisite '.$prereqs_string.' complete',
2582
                                                    0
2583
                                                );
2584
                                            }
2585
                                        }
2586
2587
                                        if ($returnstatus && $this->prevent_reinit == 1) {
2588
                                            // I would prefer check in the database.
2589
                                            $lp_item_view = Database::get_course_table(
2590
                                                TABLE_LP_ITEM_VIEW
2591
                                            );
2592
                                            $lp_view = Database::get_course_table(
2593
                                                TABLE_LP_VIEW
2594
                                            );
2595
2596
                                            $sql = 'SELECT iid FROM '.$lp_view.'
2597
                                                    WHERE
2598
                                                        c_id = '.$course_id.' AND
2599
                                                        user_id = '.$user_id.'  AND
2600
                                                        lp_id = '.$this->lp_id.' AND
2601
                                                        session_id = '.$sessionId.'
2602
                                                    LIMIT 0, 1';
2603
                                            $rs_lp = Database::query($sql);
2604
                                            $lp_id = Database::fetch_row(
2605
                                                $rs_lp
2606
                                            );
2607
                                            $my_lp_id = $lp_id[0];
2608
2609
                                            $sql = 'SELECT status FROM '.$lp_item_view.'
2610
                                                   WHERE
2611
                                                        c_id = '.$course_id.' AND
2612
                                                        lp_view_id = '.$my_lp_id.' AND
2613
                                                        lp_item_id = '.$refs_list[$prereqs_string].'
2614
                                                    LIMIT 0, 1';
2615
                                            $rs_lp = Database::query($sql);
2616
                                            $status_array = Database::fetch_row($rs_lp);
2617
                                            $status = $status_array[0];
2618
2619
                                            $returnstatus = ($status == $this->possible_status[2]) || ($status == $this->possible_status[3]);
2620
                                            if (!$returnstatus && empty($this->prereq_alert)) {
2621
                                                $this->prereq_alert = get_lang(
2622
                                                    'LearnpathPrereqNotCompleted'
2623
                                                );
2624
                                            }
2625
                                            if (!$returnstatus) {
2626
                                                if (self::DEBUG > 1) {
2627
                                                    error_log(
2628
                                                        'New LP - Prerequisite '.$prereqs_string.' not complete',
2629
                                                        0
2630
                                                    );
2631
                                                }
2632
                                            } else {
2633
                                                if (self::DEBUG > 1) {
2634
                                                    error_log(
2635
                                                        'New LP - Prerequisite '.$prereqs_string.' complete',
2636
                                                        0
2637
                                                    );
2638
                                                }
2639
                                            }
2640
                                        }
2641
2642
                                        return $returnstatus;
2643
                                    }
2644
                                } else {
2645
                                    if (self::DEBUG > 1) {
2646
                                        error_log(
2647
                                            'New LP - Could not find '.$prereqs_string.' in '.print_r(
2648
                                                $refs_list,
2649
                                                true
2650
                                            ),
2651
                                            0
2652
                                        );
2653
                                    }
2654
                                }
2655
                            }
2656
                        }
2657
                    }
2658
                }
2659
            }
2660
        } else {
2661
            $list = explode("\|", $prereqs_string);
2662
            if (count($list) > 1) {
2663
                if (self::DEBUG > 1) {
2664
                    error_log('New LP - Found OR, looking into it', 0);
2665
                }
2666
                $orstatus = false;
2667
                foreach ($list as $condition) {
2668
                    if (self::DEBUG > 1) {
2669
                        error_log(
2670
                            'New LP - Found OR, adding it ('.$condition.')',
2671
                            0
2672
                        );
2673
                    }
2674
                    $orstatus = $orstatus || $this->parse_prereq(
2675
                        $condition,
2676
                        $items,
2677
                        $refs_list,
2678
                        $user_id
2679
                    );
2680
                    if ($orstatus) {
2681
                        // Shortcircuit OR.
2682
                        if (self::DEBUG > 1) {
2683
                            error_log(
2684
                                'New LP - One condition in OR was true, short-circuit',
2685
                                0
2686
                            );
2687
                        }
2688
                        break;
2689
                    }
2690
                }
2691
                if (!$orstatus && empty($this->prereq_alert)) {
2692
                    $this->prereq_alert = get_lang(
2693
                        'LearnpathPrereqNotCompleted'
2694
                    );
2695
                }
2696
2697
                return $orstatus;
2698
            } else {
2699
                if (self::DEBUG > 1) {
2700
                    error_log(
2701
                        'New LP - OR was found but only one elem present !?',
2702
                        0
2703
                    );
2704
                }
2705
                if (isset($items[$refs_list[$list[0]]])) {
2706
                    $status = $items[$refs_list[$list[0]]]->get_status(true);
2707
                    $returnstatus = $status == 'completed' || $status == 'passed';
2708
                    if (!$returnstatus && empty($this->prereq_alert)) {
2709
                        $this->prereq_alert = get_lang(
2710
                            'LearnpathPrereqNotCompleted'
2711
                        );
2712
                    }
2713
2714
                    return $returnstatus;
2715
                }
2716
            }
2717
        }
2718
2719
        if (empty($this->prereq_alert)) {
2720
            $this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
2721
        }
2722
2723
        if (self::DEBUG > 1) {
2724
            error_log(
2725
                'New LP - End of parse_prereq. Error code is now '.$this->prereq_alert,
2726
                0
2727
            );
2728
        }
2729
2730
        return false;
2731
    }
2732
2733
    /**
2734
     * Reinits all local values as the learnpath is restarted.
2735
     *
2736
     * @return bool True on success, false otherwise
2737
     */
2738
    public function restart()
2739
    {
2740
        if (self::DEBUG > 0) {
2741
            error_log('learnpathItem::restart()', 0);
2742
        }
2743
        $seriousGame = $this->get_seriousgame_mode();
0 ignored issues
show
Deprecated Code introduced by
The function learnpathItem::get_seriousgame_mode() has been deprecated: seriousgame_mode seems not to be used ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2743
        $seriousGame = /** @scrutinizer ignore-deprecated */ $this->get_seriousgame_mode();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2744
        //For serious game  : We reuse same attempt_id
2745
        if ($seriousGame == 1 && $this->type == 'sco') {
2746
            // If this is a sco, Chamilo can't update the time without an
2747
            //  explicit scorm call
2748
            $this->current_start_time = 0;
2749
            $this->current_stop_time = 0; //Those 0 value have this effect
2750
            $this->last_scorm_session_time = 0;
2751
            $this->save();
2752
2753
            return true;
2754
        }
2755
        $this->save();
2756
2757
        $allowed = $this->isRestartAllowed();
2758
        if ($allowed === -1) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2759
            // Nothing allowed, do nothing.
2760
        } elseif ($allowed === 1) {
2761
            // Restart as new attempt is allowed, record a new attempt.
2762
            $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id.
2763
            $this->current_score = 0;
2764
            $this->current_start_time = 0;
2765
            $this->current_stop_time = 0;
2766
            $this->current_data = '';
2767
            $this->status = $this->possible_status[0];
2768
            $this->interactions_count = 0;
2769
            $this->interactions = [];
2770
            $this->objectives_count = 0;
2771
            $this->objectives = [];
2772
            $this->lesson_location = '';
2773
            if ($this->type != TOOL_QUIZ) {
2774
                $this->write_to_db();
2775
            }
2776
        } else {
2777
            // Restart current element is allowed (because it's not finished yet),
2778
            // reinit current.
2779
            //$this->current_score = 0;
2780
            $this->current_start_time = 0;
2781
            $this->current_stop_time = 0;
2782
            $this->interactions_count = $this->get_interactions_count(true);
2783
        }
2784
2785
        return true;
2786
    }
2787
2788
    /**
2789
     * Saves data in the database.
2790
     *
2791
     * @param bool $from_outside     Save from URL params (1) or from object attributes (0)
2792
     * @param bool $prereqs_complete The results of a check on prerequisites for this item.
2793
     *                               True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au
2794
     *
2795
     * @return bool True on success, false on failure
2796
     */
2797
    public function save($from_outside = true, $prereqs_complete = false)
2798
    {
2799
        $debug = self::DEBUG;
2800
        if ($debug) {
2801
            error_log('learnpathItem::save()', 0);
2802
        }
2803
        // First check if parameters passed via GET can be saved here
2804
        // in case it's a SCORM, we should get:
2805
        if ($this->type == 'sco' || $this->type == 'au') {
2806
            $status = $this->get_status(true);
2807
            if ($this->prevent_reinit == 1 &&
2808
                $status != $this->possible_status[0] && // not attempted
2809
                $status != $this->possible_status[1]    //incomplete
2810
            ) {
2811
                if ($debug) {
2812
                    error_log(
2813
                        'learnpathItem::save() - save reinit blocked by setting',
2814
                        0
2815
                    );
2816
                }
2817
                // Do nothing because the status has already been set. Don't allow it to change.
2818
                // TODO: Check there isn't a special circumstance where this should be saved.
2819
            } else {
2820
                if ($debug) {
2821
                    error_log(
2822
                        'learnpathItem::save() - SCORM save request received',
2823
                        0
2824
                    );
2825
                }
2826
2827
                // Get all new settings from the URL
2828
                if ($from_outside) {
2829
                    if ($debug) {
2830
                        error_log(
2831
                            'learnpathItem::save() - Getting item data from outside',
2832
                            0
2833
                        );
2834
                    }
2835
                    foreach ($_GET as $param => $value) {
2836
                        switch ($param) {
2837
                            case 'score':
2838
                                $this->set_score($value);
2839
                                if ($debug) {
2840
                                    error_log(
2841
                                        'learnpathItem::save() - setting score to '.$value,
2842
                                        0
2843
                                    );
2844
                                }
2845
                                break;
2846
                            case 'max':
2847
                                $this->set_max_score($value);
2848
                                if ($debug) {
2849
                                    error_log(
2850
                                        'learnpathItem::save() - setting view_max_score to '.$value,
2851
                                        0
2852
                                    );
2853
                                }
2854
                                break;
2855
                            case 'min':
2856
                                $this->min_score = $value;
2857
                                if ($debug) {
2858
                                    error_log(
2859
                                        'learnpathItem::save() - setting min_score to '.$value,
2860
                                        0
2861
                                    );
2862
                                }
2863
                                break;
2864
                            case 'lesson_status':
2865
                                if (!empty($value)) {
2866
                                    $this->set_status($value);
2867
                                    if ($debug) {
2868
                                        error_log(
2869
                                            'learnpathItem::save() - setting status to '.$value,
2870
                                            0
2871
                                        );
2872
                                    }
2873
                                }
2874
                                break;
2875
                            case 'time':
2876
                                $this->set_time($value);
2877
                                if ($debug) {
2878
                                    error_log(
2879
                                        'learnpathItem::save() - setting time to '.$value,
2880
                                        0
2881
                                    );
2882
                                }
2883
                                break;
2884
                            case 'suspend_data':
2885
                                $this->current_data = $value;
2886
                                if ($debug) {
2887
                                    error_log(
2888
                                        'learnpathItem::save() - setting suspend_data to '.$value,
2889
                                        0
2890
                                    );
2891
                                }
2892
                                break;
2893
                            case 'lesson_location':
2894
                                $this->set_lesson_location($value);
2895
                                if ($debug) {
2896
                                    error_log(
2897
                                        'learnpathItem::save() - setting lesson_location to '.$value,
2898
                                        0
2899
                                    );
2900
                                }
2901
                                break;
2902
                            case 'core_exit':
2903
                                $this->set_core_exit($value);
2904
                                if ($debug) {
2905
                                    error_log(
2906
                                        'learnpathItem::save() - setting core_exit to '.$value,
2907
                                        0
2908
                                    );
2909
                                }
2910
                                break;
2911
                            case 'interactions':
2912
                                break;
2913
                            case 'objectives':
2914
                                break;
2915
                            default:
2916
                                // Ignore.
2917
                                break;
2918
                        }
2919
                    }
2920
                } else {
2921
                    if ($debug) {
2922
                        error_log(
2923
                            'learnpathItem::save() - Using inside item status',
2924
                            0
2925
                        );
2926
                    }
2927
                    // Do nothing, just let the local attributes be used.
2928
                }
2929
            }
2930
        } else {
2931
            // If not SCO, such messages should not be expected.
2932
            $type = strtolower($this->type);
2933
            if ($debug) {
2934
                error_log("type: $type");
2935
            }
2936
            switch ($type) {
2937
                case 'asset':
2938
                    if ($prereqs_complete) {
2939
                        $this->set_status($this->possible_status[2]);
2940
                    }
2941
                    break;
2942
                case TOOL_HOTPOTATOES:
2943
                    break;
2944
                case TOOL_QUIZ:
2945
                    return false;
2946
                    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...
2947
                default:
2948
                    // For now, everything that is not sco and not asset is set to
2949
                    // completed when saved.
2950
                    if ($prereqs_complete) {
2951
                        $this->set_status($this->possible_status[2]);
2952
                    }
2953
                    break;
2954
            }
2955
        }
2956
2957
        if ($debug) {
2958
            error_log(
2959
                'New LP - End of learnpathItem::save() - Calling write_to_db()',
2960
                0
2961
            );
2962
        }
2963
2964
        return $this->write_to_db();
2965
    }
2966
2967
    /**
2968
     * Sets the number of attempt_id to a given value.
2969
     *
2970
     * @param int $num The given value to set attempt_id to
2971
     *
2972
     * @return bool TRUE on success, FALSE otherwise
2973
     */
2974
    public function set_attempt_id($num)
2975
    {
2976
        if (self::DEBUG > 0) {
2977
            error_log('learnpathItem::set_attempt_id()', 0);
2978
        }
2979
        if ($num == strval(intval($num)) && $num >= 0) {
2980
            $this->attempt_id = $num;
2981
2982
            return true;
2983
        }
2984
2985
        return false;
2986
    }
2987
2988
    /**
2989
     * Sets the core_exit value to the one given.
2990
     *
2991
     * @return bool $value  True (always)
2992
     */
2993
    public function set_core_exit($value)
2994
    {
2995
        switch ($value) {
2996
            case '':
2997
                $this->core_exit = '';
2998
                break;
2999
            case 'suspend':
3000
                $this->core_exit = 'suspend';
3001
                break;
3002
            default:
3003
                $this->core_exit = 'none';
3004
                break;
3005
        }
3006
3007
        return true;
3008
    }
3009
3010
    /**
3011
     * Sets the item's description.
3012
     *
3013
     * @param string $string Description
3014
     */
3015
    public function set_description($string = '')
3016
    {
3017
        if (self::DEBUG > 0) {
3018
            error_log('learnpathItem::set_description()', 0);
3019
        }
3020
        if (!empty($string)) {
3021
            $this->description = $string;
3022
        }
3023
    }
3024
3025
    /**
3026
     * Sets the lesson_location value.
3027
     *
3028
     * @param string $location lesson_location as provided by the SCO
3029
     *
3030
     * @return bool True on success, false otherwise
3031
     */
3032
    public function set_lesson_location($location)
3033
    {
3034
        if (self::DEBUG > 0) {
3035
            error_log('learnpathItem::set_lesson_location()', 0);
3036
        }
3037
        if (isset($location)) {
3038
            $this->lesson_location = $location;
3039
3040
            return true;
3041
        }
3042
3043
        return false;
3044
    }
3045
3046
    /**
3047
     * Sets the item's depth level in the LP tree (0 is at root).
3048
     *
3049
     * @param int $int Level
3050
     */
3051
    public function set_level($int = 0)
3052
    {
3053
        if (self::DEBUG > 0) {
3054
            error_log('learnpathItem::set_level('.$int.')', 0);
3055
        }
3056
        if (!empty($int) && $int == strval(intval($int))) {
3057
            $this->level = $int;
3058
        }
3059
    }
3060
3061
    /**
3062
     * Sets the lp_view id this item view is registered to.
3063
     *
3064
     * @param int $lp_view_id lp_view DB ID
3065
     * @param int $course_id
3066
     *
3067
     * @return bool
3068
     *
3069
     * @todo //todo insert into lp_item_view if lp_view not exists
3070
     */
3071
    public function set_lp_view($lp_view_id, $course_id = null)
3072
    {
3073
        $lp_view_id = intval($lp_view_id);
3074
3075
        if (empty($course_id)) {
3076
            $course_id = api_get_course_int_id();
3077
        } else {
3078
            $course_id = intval($course_id);
3079
        }
3080
3081
        $lpItemId = $this->get_id();
3082
3083
        if (empty($lpItemId)) {
3084
            if (self::DEBUG > 0) {
3085
                error_log(
3086
                    'learnpathItem::set_lp_view('.$lp_view_id.') $lpItemId is empty',
3087
                    0
3088
                );
3089
            }
3090
3091
            return false;
3092
        }
3093
3094
        if (empty($lp_view_id)) {
3095
            if (self::DEBUG > 0) {
3096
                error_log(
3097
                    'learnpathItem::set_lp_view('.$lp_view_id.') $lp_view_id is empty',
3098
                    0
3099
                );
3100
            }
3101
3102
            return false;
3103
        }
3104
3105
        if (self::DEBUG > 0) {
3106
            error_log('learnpathItem::set_lp_view('.$lp_view_id.')', 0);
3107
        }
3108
3109
        $this->view_id = $lp_view_id;
3110
3111
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3112
        // Get the lp_item_view with the highest view_count.
3113
        $sql = "SELECT * FROM $item_view_table
3114
                WHERE
3115
                    c_id = $course_id AND
3116
                    lp_item_id = ".$lpItemId." AND
3117
                    lp_view_id = ".$lp_view_id."
3118
                ORDER BY view_count DESC";
3119
3120
        if (self::DEBUG > 2) {
3121
            error_log(
3122
                'learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql,
3123
                0
3124
            );
3125
        }
3126
        $res = Database::query($sql);
3127
        if (Database::num_rows($res) > 0) {
3128
            $row = Database::fetch_array($res);
3129
            $this->db_item_view_id = $row['iid'];
3130
            $this->attempt_id = $row['view_count'];
3131
            $this->current_score = $row['score'];
3132
            $this->current_data = $row['suspend_data'];
3133
            $this->view_max_score = $row['max_score'];
0 ignored issues
show
Bug Best Practice introduced by
The property view_max_score does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
3134
            $this->status = $row['status'];
3135
            $this->current_start_time = $row['start_time'];
3136
            $this->current_stop_time = $this->current_start_time + $row['total_time'];
3137
            $this->lesson_location = $row['lesson_location'];
3138
            $this->core_exit = $row['core_exit'];
3139
3140
            if (self::DEBUG > 2) {
3141
                error_log(
3142
                    'learnpathItem::set_lp_view() - Updated item object with database values',
3143
                    0
3144
                );
3145
            }
3146
3147
            // Now get the number of interactions for this little guy.
3148
            $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3149
            $sql = "SELECT * FROM $table
3150
                    WHERE
3151
                        c_id = $course_id AND
3152
                        lp_iv_id = '".$this->db_item_view_id."'";
3153
3154
            $res = Database::query($sql);
3155
            if ($res !== false) {
3156
                $this->interactions_count = Database::num_rows($res);
3157
            } else {
3158
                $this->interactions_count = 0;
3159
            }
3160
            // Now get the number of objectives for this little guy.
3161
            $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3162
            $sql = "SELECT * FROM $table
3163
                    WHERE
3164
                        c_id = $course_id AND
3165
                        lp_iv_id = '".$this->db_item_view_id."'";
3166
3167
            $res = Database::query($sql);
3168
            if ($res !== false) {
3169
                $this->objectives_count = Database::num_rows($res);
3170
            } else {
3171
                $this->objectives_count = 0;
3172
            }
3173
        }
3174
3175
        // End
3176
        if (self::DEBUG > 2) {
3177
            error_log('New LP - End of learnpathItem::set_lp_view()', 0);
3178
        }
3179
3180
        return true;
3181
    }
3182
3183
    /**
3184
     * Sets the path.
3185
     *
3186
     * @param string $string Path
3187
     */
3188
    public function set_path($string = '')
3189
    {
3190
        if (self::DEBUG > 0) {
3191
            error_log('learnpathItem::set_path()', 0);
3192
        }
3193
        if (!empty($string)) {
3194
            $this->path = $string;
3195
        }
3196
    }
3197
3198
    /**
3199
     * Sets the prevent_reinit attribute.
3200
     * This is based on the LP value and is set at creation time for
3201
     * each learnpathItem. It is a (bad?) way of avoiding
3202
     * a reference to the LP when saving an item.
3203
     *
3204
     * @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...
3205
     * saving freshened values (new "not attempted" status etc)
3206
     */
3207
    public function set_prevent_reinit($prevent)
3208
    {
3209
        if (self::DEBUG > 0) {
3210
            error_log('learnpathItem::set_prevent_reinit()', 0);
3211
        }
3212
        if ($prevent) {
3213
            $this->prevent_reinit = 1;
3214
        } else {
3215
            $this->prevent_reinit = 0;
3216
        }
3217
    }
3218
3219
    /**
3220
     * Sets the score value. If the mastery_score is set and the score reaches
3221
     * it, then set the status to 'passed'.
3222
     *
3223
     * @param float $score Score
3224
     *
3225
     * @return bool True on success, false otherwise
3226
     */
3227
    public function set_score($score)
3228
    {
3229
        $debug = self::DEBUG;
3230
        if ($debug > 0) {
3231
            error_log('learnpathItem::set_score('.$score.')', 0);
3232
        }
3233
        if (($this->max_score <= 0 || $score <= $this->max_score) && ($score >= $this->min_score)) {
3234
            $this->current_score = $score;
3235
            $masteryScore = $this->get_mastery_score();
3236
            $current_status = $this->get_status(false);
3237
3238
            // Fixes bug when SCORM doesn't send a mastery score even if they sent a score!
3239
            if ($masteryScore == -1) {
3240
                $masteryScore = $this->max_score;
3241
            }
3242
3243
            if ($debug > 0) {
3244
                error_log('get_mastery_score: '.$masteryScore);
3245
                error_log('current_status: '.$current_status);
3246
                error_log('current score : '.$this->current_score);
3247
            }
3248
3249
            // If mastery_score is set AND the current score reaches the mastery
3250
            //  score AND the current status is different from 'completed', then
3251
            //  set it to 'passed'.
3252
            /*
3253
            if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) {
3254
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[3]);
3255
                $this->set_status($this->possible_status[3]); //passed
3256
            } elseif ($master != -1 && $this->current_score < $master) {
3257
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[4]);
3258
                $this->set_status($this->possible_status[4]); //failed
3259
            }*/
3260
            return true;
3261
        }
3262
3263
        return false;
3264
    }
3265
3266
    /**
3267
     * Sets the maximum score for this item.
3268
     *
3269
     * @param int $score Maximum score - must be a decimal or an empty string
3270
     *
3271
     * @return bool True on success, false on error
3272
     */
3273
    public function set_max_score($score)
3274
    {
3275
        if (self::DEBUG > 0) {
3276
            error_log('learnpathItem::set_max_score('.$score.')', 0);
3277
        }
3278
        if (is_int($score) || $score == '') {
3279
            $this->view_max_score = $score;
0 ignored issues
show
Bug Best Practice introduced by
The property view_max_score does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
3280
            if (self::DEBUG > 1) {
3281
                error_log(
3282
                    'learnpathItem::set_max_score() - '.
3283
                    'Updated object score of item '.$this->db_id.
3284
                    ' to '.$this->view_max_score,
3285
                    0
3286
                );
3287
            }
3288
3289
            return true;
3290
        }
3291
3292
        return false;
3293
    }
3294
3295
    /**
3296
     * Sets the status for this item.
3297
     *
3298
     * @param string $status Status - must be one of the values defined in $this->possible_status
3299
     *                       (this affects the status setting)
3300
     *
3301
     * @return bool True on success, false on error
3302
     */
3303
    public function set_status($status)
3304
    {
3305
        if (self::DEBUG > 0) {
3306
            error_log('learnpathItem::set_status('.$status.')', 0);
3307
        }
3308
3309
        $found = false;
3310
        foreach ($this->possible_status as $possible) {
3311
            if (preg_match('/^'.$possible.'$/i', $status)) {
3312
                $found = true;
3313
            }
3314
        }
3315
3316
        if ($found) {
3317
            $this->status = $status;
3318
            if (self::DEBUG > 1) {
3319
                error_log(
3320
                    'learnpathItem::set_status() - '.
3321
                        'Updated object status of item '.$this->db_id.
3322
                        ' to '.$this->status,
3323
                    0
3324
                );
3325
            }
3326
3327
            return true;
3328
        }
3329
3330
        $this->status = $this->possible_status[0];
3331
3332
        return false;
3333
    }
3334
3335
    /**
3336
     * Set the (indexing) terms for this learnpath item.
3337
     *
3338
     * @param string $terms Terms, as a comma-split list
3339
     *
3340
     * @return bool Always return true
3341
     */
3342
    public function set_terms($terms)
3343
    {
3344
        global $charset;
3345
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
3346
        $a_terms = preg_split('/,/', $terms);
3347
        $i_terms = preg_split('/,/', $this->get_terms());
3348
        foreach ($i_terms as $term) {
3349
            if (!in_array($term, $a_terms)) {
3350
                array_push($a_terms, $term);
3351
            }
3352
        }
3353
        $new_terms = $a_terms;
3354
        $new_terms_string = implode(',', $new_terms);
3355
3356
        // TODO: Validate csv string.
3357
        $terms = Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset));
3358
        $sql = "UPDATE $lp_item
3359
                SET terms = '$terms'
3360
                WHERE iid=".$this->get_id();
3361
        Database::query($sql);
3362
        // Save it to search engine.
3363
        if (api_get_setting('search_enabled') == 'true') {
3364
            $di = new ChamiloIndexer();
3365
            $di->update_terms($this->get_search_did(), $new_terms, 'T');
3366
        }
3367
3368
        return true;
3369
    }
3370
3371
    /**
3372
     * Get the document ID from inside the text index database.
3373
     *
3374
     * @return int Search index database document ID
3375
     */
3376
    public function get_search_did()
3377
    {
3378
        return $this->search_did;
3379
    }
3380
3381
    /**
3382
     * Sets the item viewing time in a usable form, given that SCORM packages
3383
     * often give it as 00:00:00.0000.
3384
     *
3385
     * @param    string    Time as given by SCORM
3386
     * @param string $format
3387
     */
3388
    public function set_time($scorm_time, $format = 'scorm')
3389
    {
3390
        $debug = self::DEBUG;
3391
        if ($debug) {
3392
            error_log("learnpathItem::set_time($scorm_time, $format)");
3393
            error_log("this->type: ".$this->type);
3394
            error_log("this->current_start_time: ".$this->current_start_time);
3395
        }
3396
3397
        if ($scorm_time == '0' &&
3398
            $this->type != 'sco' &&
3399
            $this->current_start_time != 0
3400
        ) {
3401
            $myTime = time() - $this->current_start_time;
3402
            if ($myTime > 0) {
3403
                $this->update_time($myTime);
3404
                if ($debug) {
3405
                    error_log('found asset - set time to '.$myTime);
3406
                }
3407
            }
3408
        } else {
3409
            switch ($format) {
3410
                case 'scorm':
3411
                    $res = [];
3412
                    if (preg_match(
3413
                        '/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/',
3414
                        $scorm_time,
3415
                        $res
3416
                    )
3417
                    ) {
3418
                        $hour = $res[1];
3419
                        $min = $res[2];
3420
                        $sec = $res[3];
3421
                        // Getting total number of seconds spent.
3422
                        $totalSec = $hour * 3600 + $min * 60 + $sec;
3423
                        if ($debug) {
3424
                            error_log("totalSec : $totalSec");
3425
                        }
3426
                        $this->scorm_update_time($totalSec);
3427
                    }
3428
                    break;
3429
                case 'int':
3430
                    $this->scorm_update_time($scorm_time);
3431
                    break;
3432
            }
3433
        }
3434
    }
3435
3436
    /**
3437
     * Sets the item's title.
3438
     *
3439
     * @param string $string Title
3440
     */
3441
    public function set_title($string = '')
3442
    {
3443
        if (self::DEBUG > 0) {
3444
            error_log('learnpathItem::set_title()', 0);
3445
        }
3446
        if (!empty($string)) {
3447
            $this->title = $string;
3448
        }
3449
    }
3450
3451
    /**
3452
     * Sets the item's type.
3453
     *
3454
     * @param string $string Type
3455
     */
3456
    public function set_type($string = '')
3457
    {
3458
        if (self::DEBUG > 0) {
3459
            error_log('learnpathItem::set_type()', 0);
3460
        }
3461
        if (!empty($string)) {
3462
            $this->type = $string;
3463
        }
3464
    }
3465
3466
    /**
3467
     * Checks if the current status is part of the list of status given.
3468
     *
3469
     * @param array $list An array of status to check for.
3470
     *                    If the current status is one of the strings, return true
3471
     *
3472
     * @return bool True if the status was one of the given strings,
3473
     *              false otherwise
3474
     */
3475
    public function status_is($list = [])
3476
    {
3477
        if (self::DEBUG > 1) {
3478
            error_log(
3479
                'learnpathItem::status_is('.print_r(
3480
                    $list,
3481
                    true
3482
                ).') on item '.$this->db_id,
3483
                0
3484
            );
3485
        }
3486
        $currentStatus = $this->get_status(true);
3487
        if (empty($currentStatus)) {
3488
            return false;
3489
        }
3490
        $found = false;
3491
        foreach ($list as $status) {
3492
            if (preg_match('/^'.$status.'$/i', $currentStatus)) {
3493
                if (self::DEBUG > 2) {
3494
                    error_log(
3495
                        'New LP - learnpathItem::status_is() - Found status '.
3496
                            $status.' corresponding to current status',
3497
                        0
3498
                    );
3499
                }
3500
                $found = true;
3501
3502
                return $found;
3503
            }
3504
        }
3505
        if (self::DEBUG > 2) {
3506
            error_log(
3507
                'New LP - learnpathItem::status_is() - Status '.
3508
                    $currentStatus.' did not match request',
3509
                0
3510
            );
3511
        }
3512
3513
        return $found;
3514
    }
3515
3516
    /**
3517
     * Updates the time info according to the given session_time.
3518
     *
3519
     * @param int $totalSec Time in seconds
3520
     */
3521
    public function update_time($totalSec = 0)
3522
    {
3523
        if (self::DEBUG > 0) {
3524
            error_log('learnpathItem::update_time('.$totalSec.')');
3525
        }
3526
        if ($totalSec >= 0) {
3527
            // Getting start time from finish time. The only problem in the calculation is it might be
3528
            // modified by the scripts processing time.
3529
            $now = time();
3530
            $start = $now - $totalSec;
3531
            $this->current_start_time = $start;
3532
            $this->current_stop_time = $now;
3533
        }
3534
    }
3535
3536
    /**
3537
     * Special scorm update time function. This function will update time
3538
     * directly into db for scorm objects.
3539
     *
3540
     * @param int $total_sec Total number of seconds
3541
     */
3542
    public function scorm_update_time($total_sec = 0)
3543
    {
3544
        $debug = self::DEBUG;
3545
        if ($debug) {
3546
            error_log('learnpathItem::scorm_update_time()');
3547
            error_log("total_sec: $total_sec");
3548
        }
3549
3550
        // Step 1 : get actual total time stored in db
3551
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3552
        $course_id = api_get_course_int_id();
3553
3554
        $sql = 'SELECT total_time, status 
3555
                FROM '.$item_view_table.'
3556
                WHERE 
3557
                    c_id = '.$course_id.' AND 
3558
                    lp_item_id = "'.$this->db_id.'" AND 
3559
                    lp_view_id = "'.$this->view_id.'" AND 
3560
                    view_count = "'.$this->get_attempt_id().'"';
3561
        $result = Database::query($sql);
3562
        $row = Database::fetch_array($result);
3563
3564
        if (!isset($row['total_time'])) {
3565
            $total_time = 0;
3566
        } else {
3567
            $total_time = $row['total_time'];
3568
        }
3569
        if ($debug) {
3570
            error_log("Original total_time: $total_time");
3571
        }
3572
3573
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3574
        $lp_id = intval($this->lp_id);
3575
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
3576
        $res = Database::query($sql);
3577
        $accumulateScormTime = 'false';
3578
        if (Database::num_rows($res) > 0) {
3579
            $row = Database::fetch_assoc($res);
3580
            $accumulateScormTime = $row['accumulate_scorm_time'];
3581
        }
3582
3583
        // Step 2.1 : if normal mode total_time = total_time + total_sec
3584
        if ($this->type == 'sco' && $accumulateScormTime != 0) {
3585
            if ($debug) {
3586
                error_log("accumulateScormTime is on. total_time modified: $total_time + $total_sec");
3587
            }
3588
            $total_time += $total_sec;
3589
        } else {
3590
            // Step 2.2 : if not cumulative mode total_time = total_time - last_update + total_sec
3591
            $total_sec = $this->fixAbusiveTime($total_sec);
3592
            if ($debug) {
3593
                error_log("after fix abusive: $total_sec");
3594
                error_log("total_time: $total_time");
3595
                error_log("this->last_scorm_session_time: ".$this->last_scorm_session_time);
3596
            }
3597
3598
            $total_time = $total_time - $this->last_scorm_session_time + $total_sec;
3599
            $this->last_scorm_session_time = $total_sec;
3600
3601
            if ($total_time < 0) {
3602
                $total_time = $total_sec;
3603
            }
3604
        }
3605
3606
        if ($debug) {
3607
            error_log("accumulate_scorm_time: $accumulateScormTime");
3608
            error_log("total_time modified: $total_time");
3609
        }
3610
3611
        // Step 3 update db only if status != completed, passed, browsed or seriousgamemode not activated
3612
        // @todo complete
3613
        $case_completed = [
3614
            'completed',
3615
            'passed',
3616
            'browsed',
3617
            'failed',
3618
        ];
3619
3620
        if ($this->seriousgame_mode != 1 ||
3621
            !in_array($row['status'], $case_completed)
3622
        ) {
3623
            $sql = "UPDATE $item_view_table
3624
                      SET total_time = '$total_time'
3625
                    WHERE 
3626
                        c_id = $course_id AND 
3627
                        lp_item_id = {$this->db_id} AND 
3628
                        lp_view_id = {$this->view_id} AND 
3629
                        view_count = {$this->get_attempt_id()}";
3630
            if ($debug) {
3631
                error_log('-------------total_time updated ------------------------');
3632
                error_log($sql);
3633
                error_log('-------------------------------------');
3634
            }
3635
            Database::query($sql);
3636
        }
3637
    }
3638
3639
    /**
3640
     * Set the total_time to 0 into db.
3641
     */
3642
    public function scorm_init_time()
3643
    {
3644
        $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3645
        $course_id = api_get_course_int_id();
3646
        $sql = 'UPDATE '.$table.'
3647
                SET total_time = 0, 
3648
                    start_time = '.time().'
3649
                WHERE 
3650
                    c_id = '.$course_id.' AND 
3651
                    lp_item_id = "'.$this->db_id.'" AND 
3652
                    lp_view_id = "'.$this->view_id.'" AND 
3653
                    view_count = "'.$this->attempt_id.'"';
3654
        Database::query($sql);
3655
    }
3656
3657
    /**
3658
     * Write objectives to DB. This method is separate from write_to_db() because otherwise
3659
     * objectives are lost as a side effect to AJAX and session concurrent access.
3660
     *
3661
     * @return bool True or false on error
3662
     */
3663
    public function write_objectives_to_db()
3664
    {
3665
        if (self::DEBUG > 0) {
3666
            error_log('learnpathItem::write_objectives_to_db()', 0);
3667
        }
3668
        $course_id = api_get_course_int_id();
3669
        if (is_array($this->objectives) && count($this->objectives) > 0) {
3670
            // Save objectives.
3671
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3672
            $sql = "SELECT iid
3673
                    FROM $tbl
3674
                    WHERE
3675
                        c_id = $course_id AND
3676
                        lp_item_id = ".$this->db_id." AND
3677
                        lp_view_id = ".$this->view_id." AND
3678
                        view_count = ".$this->attempt_id;
3679
            $res = Database::query($sql);
3680
            if (Database::num_rows($res) > 0) {
3681
                $row = Database::fetch_array($res);
3682
                $lp_iv_id = $row[0];
3683
                if (self::DEBUG > 2) {
3684
                    error_log(
3685
                        'learnpathItem::write_to_db() - Got item_view_id '.
3686
                            $lp_iv_id.', now checking objectives ',
3687
                        0
3688
                    );
3689
                }
3690
                foreach ($this->objectives as $index => $objective) {
3691
                    $iva_table = Database::get_course_table(
3692
                        TABLE_LP_IV_OBJECTIVE
3693
                    );
3694
                    $iva_sql = "SELECT iid FROM $iva_table
3695
                                WHERE
3696
                                    c_id = $course_id AND
3697
                                    lp_iv_id = $lp_iv_id AND
3698
                                    objective_id = '".Database::escape_string($objective[0])."'";
3699
                    $iva_res = Database::query($iva_sql);
3700
                    // id(0), type(1), time(2), weighting(3),
3701
                    // correct_responses(4), student_response(5),
3702
                    // result(6), latency(7)
3703
                    if (Database::num_rows($iva_res) > 0) {
3704
                        // Update (or don't).
3705
                        $iva_row = Database::fetch_array($iva_res);
3706
                        $iva_id = $iva_row[0];
3707
                        $ivau_sql = "UPDATE $iva_table ".
3708
                            "SET objective_id = '".Database::escape_string($objective[0])."',".
3709
                            "status = '".Database::escape_string($objective[1])."',".
3710
                            "score_raw = '".Database::escape_string($objective[2])."',".
3711
                            "score_min = '".Database::escape_string($objective[4])."',".
3712
                            "score_max = '".Database::escape_string($objective[3])."' ".
3713
                            "WHERE c_id = $course_id AND iid = $iva_id";
3714
                        Database::query($ivau_sql);
3715
                    } else {
3716
                        // Insert new one.
3717
                        $params = [
3718
                            'c_id' => $course_id,
3719
                            'lp_iv_id' => $lp_iv_id,
3720
                            'order_id' => $index,
3721
                            'objective_id' => $objective[0],
3722
                            'status' => $objective[1],
3723
                            'score_raw' => $objective[2],
3724
                            'score_min' => $objective[4],
3725
                            'score_max' => $objective[3],
3726
                        ];
3727
3728
                        $insertId = Database::insert($iva_table, $params);
3729
                        if ($insertId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertId of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3730
                            $sql = "UPDATE $iva_table SET id = iid 
3731
                                    WHERE iid = $insertId";
3732
                            Database::query($sql);
3733
                        }
3734
                    }
3735
                }
3736
            }
3737
        }
3738
    }
3739
3740
    /**
3741
     * Writes the current data to the database.
3742
     *
3743
     * @return bool Query result
3744
     */
3745
    public function write_to_db()
3746
    {
3747
        $debug = self::DEBUG;
3748
        if ($debug) {
3749
            error_log('learnpathItem::write_to_db()', 0);
3750
        }
3751
3752
        // Check the session visibility.
3753
        if (!api_is_allowed_to_session_edit()) {
3754
            if ($debug) {
3755
                error_log('return false api_is_allowed_to_session_edit');
3756
            }
3757
3758
            return false;
3759
        }
3760
3761
        $course_id = api_get_course_int_id();
3762
        $mode = $this->get_lesson_mode();
3763
        $credit = $this->get_credit();
3764
        $total_time = ' ';
3765
        $my_status = ' ';
3766
3767
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3768
        $sql = 'SELECT status, total_time FROM '.$item_view_table.'
3769
                WHERE
3770
                    c_id = '.$course_id.' AND
3771
                    lp_item_id="'.$this->db_id.'" AND
3772
                    lp_view_id="'.$this->view_id.'" AND
3773
                    view_count="'.$this->get_attempt_id().'" ';
3774
        $rs_verified = Database::query($sql);
3775
        $row_verified = Database::fetch_array($rs_verified);
3776
3777
        $my_case_completed = [
3778
            'completed',
3779
            'passed',
3780
            'browsed',
3781
            'failed',
3782
        ];
3783
3784
        $oldTotalTime = $row_verified['total_time'];
3785
        $this->oldTotalTime = $oldTotalTime;
3786
3787
        $save = true;
3788
        if (isset($row_verified) && isset($row_verified['status'])) {
3789
            if (in_array($row_verified['status'], $my_case_completed)) {
3790
                $save = false;
3791
            }
3792
        }
3793
3794
        if ((($save === false && $this->type == 'sco') ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: {currentAssign}, Probably Intended Meaning: {alternativeAssign}
Loading history...
3795
           ($this->type == 'sco' && ($credit == 'no-credit' || $mode == 'review' || $mode == 'browse'))) &&
3796
           ($this->seriousgame_mode != 1 && $this->type == 'sco')
3797
        ) {
3798
            if ($debug) {
3799
                error_log(
3800
                    "This info shouldn't be saved as the credit or lesson mode info prevent it"
3801
                );
3802
                error_log(
3803
                    'learnpathItem::write_to_db() - credit('.$credit.') or'.
3804
                    ' lesson_mode('.$mode.') prevent recording!',
3805
                    0
3806
                );
3807
            }
3808
        } else {
3809
            // Check the row exists.
3810
            $inserted = false;
3811
            // This a special case for multiple attempts and Chamilo exercises.
3812
            if ($this->type == 'quiz' &&
3813
                $this->get_prevent_reinit() == 0 &&
3814
                $this->get_status() == 'completed'
3815
            ) {
3816
                // We force the item to be restarted.
3817
                $this->restart();
3818
                $params = [
3819
                    "c_id" => $course_id,
3820
                    "total_time" => $this->get_total_time(),
3821
                    "start_time" => $this->current_start_time,
3822
                    "score" => $this->get_score(),
3823
                    "status" => $this->get_status(false),
3824
                    "max_score" => $this->get_max(),
3825
                    "lp_item_id" => $this->db_id,
3826
                    "lp_view_id" => $this->view_id,
3827
                    "view_count" => $this->get_attempt_id(),
3828
                    "suspend_data" => $this->current_data,
3829
                    //"max_time_allowed" => ,
3830
                    "lesson_location" => $this->lesson_location,
3831
                ];
3832
                if ($debug) {
3833
                    error_log(
3834
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3835
                        0
3836
                    );
3837
                }
3838
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3839
                if ($this->db_item_view_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->db_item_view_id of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3840
                    $sql = "UPDATE $item_view_table SET id = iid
3841
                            WHERE iid = ".$this->db_item_view_id;
3842
                    Database::query($sql);
3843
                    $inserted = true;
3844
                }
3845
            }
3846
3847
            $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3848
            $sql = "SELECT * FROM $item_view_table
3849
                    WHERE
3850
                        c_id = $course_id AND
3851
                        lp_item_id = ".$this->db_id." AND
3852
                        lp_view_id = ".$this->view_id." AND
3853
                        view_count = ".intval($this->get_attempt_id());
3854
            if ($debug) {
3855
                error_log(
3856
                    'learnpathItem::write_to_db() - Querying item_view: '.$sql,
3857
                    0
3858
                );
3859
            }
3860
            $check_res = Database::query($sql);
3861
            // Depending on what we want (really), we'll update or insert a new row
3862
            // now save into DB.
3863
            if (!$inserted && Database::num_rows($check_res) < 1) {
3864
                $params = [
3865
                    "c_id" => $course_id,
3866
                    "total_time" => $this->get_total_time(),
3867
                    "start_time" => $this->current_start_time,
3868
                    "score" => $this->get_score(),
3869
                    "status" => $this->get_status(false),
3870
                    "max_score" => $this->get_max(),
3871
                    "lp_item_id" => $this->db_id,
3872
                    "lp_view_id" => $this->view_id,
3873
                    "view_count" => $this->get_attempt_id(),
3874
                    "suspend_data" => $this->current_data,
3875
                    //"max_time_allowed" => ,$this->get_max_time_allowed()
3876
                    "lesson_location" => $this->lesson_location,
3877
                ];
3878
3879
                if ($debug) {
3880
                    error_log(
3881
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3882
                        0
3883
                    );
3884
                }
3885
3886
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3887
                if ($this->db_item_view_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->db_item_view_id of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3888
                    $sql = "UPDATE $item_view_table SET id = iid
3889
                            WHERE iid = ".$this->db_item_view_id;
3890
                    Database::query($sql);
3891
                }
3892
            } else {
3893
                if ($this->type == 'hotpotatoes') {
3894
                    $params = [
3895
                        'total_time' => $this->get_total_time(),
3896
                        'start_time' => $this->get_current_start_time(),
3897
                        'score' => $this->get_score(),
3898
                        'status' => $this->get_status(false),
3899
                        'max_score' => $this->get_max(),
3900
                        'suspend_data' => $this->current_data,
3901
                        'lesson_location' => $this->lesson_location,
3902
                    ];
3903
                    $where = [
3904
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' => [
3905
                            $course_id,
3906
                            $this->db_id,
3907
                            $this->view_id,
3908
                            $this->get_attempt_id(),
3909
                        ],
3910
                    ];
3911
                    Database::update($item_view_table, $params, $where);
3912
                } else {
3913
                    // For all other content types...
3914
                    if ($this->type == 'quiz') {
3915
                        if ($debug) {
3916
                            error_log("item is quiz:");
3917
                        }
3918
                        $my_status = ' ';
3919
                        $total_time = ' ';
3920
                        if (!empty($_REQUEST['exeId'])) {
3921
                            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3922
                            $exeId = (int) $_REQUEST['exeId'];
3923
                            $sql = "SELECT exe_duration
3924
                                    FROM $table
3925
                                    WHERE exe_id = $exeId";
3926
                            if ($debug) {
3927
                                error_log($sql);
3928
                            }
3929
                            $res = Database::query($sql);
3930
                            $exeRow = Database::fetch_array($res);
3931
                            $duration = $exeRow['exe_duration'];
3932
                            $total_time = " total_time = ".$duration.", ";
3933
                            if ($debug) {
3934
                                error_log("quiz: $total_time");
3935
                            }
3936
                        }
3937
                    } else {
3938
                        $my_type_lp = learnpath::get_type_static($this->lp_id);
3939
                        // This is a array containing values finished
3940
                        $case_completed = [
3941
                            'completed',
3942
                            'passed',
3943
                            'browsed',
3944
                            'failed',
3945
                        ];
3946
3947
                        // Is not multiple attempts
3948
                        if ($this->seriousgame_mode == 1 && $this->type == 'sco') {
3949
                            $total_time = " total_time = total_time +".$this->get_total_time().", ";
3950
                            $my_status = " status = '".$this->get_status(false)."' ,";
3951
                            if ($debug) {
3952
                                error_log("seriousgame_mode time changed: $total_time");
3953
                            }
3954
                        } elseif ($this->get_prevent_reinit() == 1) {
3955
                            // Process of status verified into data base.
3956
                            $sql = 'SELECT status FROM '.$item_view_table.'
3957
                                    WHERE
3958
                                        c_id = '.$course_id.' AND
3959
                                        lp_item_id="'.$this->db_id.'" AND
3960
                                        lp_view_id="'.$this->view_id.'" AND
3961
                                        view_count="'.$this->get_attempt_id().'"
3962
                                    ';
3963
                            $rs_verified = Database::query($sql);
3964
                            $row_verified = Database::fetch_array($rs_verified);
3965
3966
                            // Get type lp: 1=lp dokeos and  2=scorm.
3967
                            // If not is completed or passed or browsed and learning path is scorm.
3968
                            if (!in_array($this->get_status(false), $case_completed) &&
3969
                                $my_type_lp == 2
3970
                            ) {
3971
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3972
                                $my_status = " status = '".$this->get_status(false)."' ,";
3973
                                if ($debug) {
3974
                                    error_log("get_prevent_reinit = 1 time changed: $total_time");
3975
                                }
3976
                            } else {
3977
                                // Verified into database.
3978
                                if (!in_array($row_verified['status'], $case_completed) &&
3979
                                    $my_type_lp == 2
3980
                                ) {
3981
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3982
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3983
                                    if ($debug) {
3984
                                        error_log("total_time time changed case 1: $total_time");
3985
                                    }
3986
                                } elseif (in_array($row_verified['status'], $case_completed) &&
3987
                                    $my_type_lp == 2 && $this->type != 'sco'
3988
                                ) {
3989
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3990
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3991
                                    if ($debug) {
3992
                                        error_log("total_time time changed case 2: $total_time");
3993
                                    }
3994
                                } else {
3995
                                    if (($my_type_lp == 3 && $this->type == 'au') ||
3996
                                        ($my_type_lp == 1 && $this->type != 'dir')) {
3997
                                        // Is AICC or Chamilo LP
3998
                                        $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3999
                                        $my_status = " status = '".$this->get_status(false)."' ,";
4000
                                        if ($debug) {
4001
                                            error_log("total_time time changed case 3: $total_time");
4002
                                        }
4003
                                    }
4004
                                }
4005
                            }
4006
                        } else {
4007
                            // Multiple attempts are allowed.
4008
                            if (in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) {
4009
                                // Reset zero new attempt ?
4010
                                $my_status = " status = '".$this->get_status(false)."' ,";
4011
                                if ($debug) {
4012
                                    error_log("total_time time changed Multiple attempt case 1: $total_time");
4013
                                }
4014
                            } elseif (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) {
4015
                                $total_time = " total_time = ".$this->get_total_time().", ";
4016
                                $my_status = " status = '".$this->get_status(false)."' ,";
4017
                                if ($debug) {
4018
                                    error_log("total_time time changed Multiple attempt case 2: $total_time");
4019
                                }
4020
                            } else {
4021
                                // It is chamilo LP.
4022
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
4023
                                $my_status = " status = '".$this->get_status(false)."' ,";
4024
                                if ($debug) {
4025
                                    error_log("total_time time changed Multiple attempt case 3: $total_time");
4026
                                }
4027
                            }
4028
4029
                            // This code line fixes the problem of wrong status.
4030
                            if ($my_type_lp == 2) {
4031
                                // Verify current status in multiples attempts.
4032
                                $sql = 'SELECT status FROM '.$item_view_table.'
4033
                                        WHERE
4034
                                            c_id = '.$course_id.' AND
4035
                                            lp_item_id="'.$this->db_id.'" AND
4036
                                            lp_view_id="'.$this->view_id.'" AND
4037
                                            view_count="'.$this->get_attempt_id().'" ';
4038
                                $rs_status = Database::query($sql);
4039
                                $current_status = Database::result(
4040
                                    $rs_status,
4041
                                    0,
4042
                                    'status'
4043
                                );
4044
                                if (in_array($current_status, $case_completed)) {
4045
                                    $my_status = '';
4046
                                    $total_time = '';
4047
                                } else {
4048
                                    $total_time = " total_time = total_time + ".$this->get_total_time().", ";
4049
                                }
4050
4051
                                if ($debug) {
4052
                                    error_log("total_time time my_type_lp: $total_time");
4053
                                }
4054
                            }
4055
                        }
4056
                    }
4057
4058
                    if ($this->type == 'sco') {
4059
                        //IF scorm scorm_update_time has already updated total_time in db
4060
                        //" . //start_time = ".$this->get_current_start_time().", " . //scorm_init_time does it
4061
                        ////" max_time_allowed = '".$this->get_max_time_allowed()."'," .
4062
                        $sql = "UPDATE $item_view_table SET
4063
                                    score = ".$this->get_score().",
4064
                                    $my_status
4065
                                    max_score = '".$this->get_max()."',
4066
                                    suspend_data = '".Database::escape_string($this->current_data)."',
4067
                                    lesson_location = '".$this->lesson_location."'
4068
                                WHERE
4069
                                    c_id = $course_id AND
4070
                                    lp_item_id = ".$this->db_id." AND
4071
                                    lp_view_id = ".$this->view_id."  AND
4072
                                    view_count = ".$this->get_attempt_id();
4073
                    } else {
4074
                        //" max_time_allowed = '".$this->get_max_time_allowed()."'," .
4075
                        $sql = "UPDATE $item_view_table SET
4076
                                    $total_time
4077
                                    start_time = ".$this->get_current_start_time().",
4078
                                    score = ".$this->get_score().",
4079
                                    $my_status
4080
                                    max_score = '".$this->get_max()."',
4081
                                    suspend_data = '".Database::escape_string($this->current_data)."',
4082
                                    lesson_location = '".$this->lesson_location."'
4083
                                WHERE
4084
                                    c_id = $course_id AND
4085
                                    lp_item_id = ".$this->db_id." AND
4086
                                    lp_view_id = ".$this->view_id." AND
4087
                                    view_count = ".$this->get_attempt_id();
4088
                    }
4089
                    $this->current_start_time = time();
4090
                }
4091
                if ($debug) {
4092
                    error_log('-------------------------------------------');
4093
                    error_log('learnpathItem::write_to_db() - Updating item_view:');
4094
                    error_log($sql);
4095
                    error_log('-------------------------------------------');
4096
                }
4097
                Database::query($sql);
4098
            }
4099
4100
            if (is_array($this->interactions) &&
4101
                count($this->interactions) > 0
4102
            ) {
4103
                // Save interactions.
4104
                $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4105
                $sql = "SELECT iid FROM $tbl
4106
                        WHERE
4107
                            c_id = $course_id AND
4108
                            lp_item_id = ".$this->db_id." AND
4109
                            lp_view_id = ".$this->view_id." AND
4110
                            view_count = ".$this->get_attempt_id();
4111
                $res = Database::query($sql);
4112
                if (Database::num_rows($res) > 0) {
4113
                    $row = Database::fetch_array($res);
4114
                    $lp_iv_id = $row[0];
4115
                    if ($debug) {
4116
                        error_log(
4117
                            'learnpathItem::write_to_db() - Got item_view_id '.
4118
                            $lp_iv_id.', now checking interactions ',
4119
                            0
4120
                        );
4121
                    }
4122
                    foreach ($this->interactions as $index => $interaction) {
4123
                        $correct_resp = '';
4124
                        if (is_array($interaction[4]) && !empty($interaction[4][0])) {
4125
                            foreach ($interaction[4] as $resp) {
4126
                                $correct_resp .= $resp.',';
4127
                            }
4128
                            $correct_resp = substr(
4129
                                $correct_resp,
4130
                                0,
4131
                                strlen($correct_resp) - 1
4132
                            );
4133
                        }
4134
                        $iva_table = Database::get_course_table(
4135
                            TABLE_LP_IV_INTERACTION
4136
                        );
4137
4138
                        //also check for the interaction ID as it must be unique for this SCO view
4139
                        $iva_sql = "SELECT iid FROM $iva_table
4140
                                    WHERE
4141
                                        c_id = $course_id AND
4142
                                        lp_iv_id = $lp_iv_id AND
4143
                                        (
4144
                                            order_id = $index OR
4145
                                            interaction_id = '".Database::escape_string($interaction[0])."'
4146
                                        )
4147
                                    ";
4148
                        $iva_res = Database::query($iva_sql);
4149
4150
                        $interaction[0] = isset($interaction[0]) ? $interaction[0] : '';
4151
                        $interaction[1] = isset($interaction[1]) ? $interaction[1] : '';
4152
                        $interaction[2] = isset($interaction[2]) ? $interaction[2] : '';
4153
                        $interaction[3] = isset($interaction[3]) ? $interaction[3] : '';
4154
                        $interaction[4] = isset($interaction[4]) ? $interaction[4] : '';
4155
                        $interaction[5] = isset($interaction[5]) ? $interaction[5] : '';
4156
                        $interaction[6] = isset($interaction[6]) ? $interaction[6] : '';
4157
                        $interaction[7] = isset($interaction[7]) ? $interaction[7] : '';
4158
4159
                        // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
4160
                        if (Database::num_rows($iva_res) > 0) {
4161
                            // Update (or don't).
4162
                            $iva_row = Database::fetch_array($iva_res);
4163
                            $iva_id = $iva_row[0];
4164
                            // Insert new one.
4165
                            $params = [
4166
                                'interaction_id' => $interaction[0],
4167
                                'interaction_type' => $interaction[1],
4168
                                'weighting' => $interaction[3],
4169
                                'completion_time' => $interaction[2],
4170
                                'correct_responses' => $correct_resp,
4171
                                'student_response' => $interaction[5],
4172
                                'result' => $interaction[6],
4173
                                'latency' => $interaction[7],
4174
                            ];
4175
                            Database::update(
4176
                                $iva_table,
4177
                                $params,
4178
                                [
4179
                                    'c_id = ? AND iid = ?' => [
4180
                                        $course_id,
4181
                                        $iva_id,
4182
                                    ],
4183
                                ]
4184
                            );
4185
                        } else {
4186
                            // Insert new one.
4187
                            $params = [
4188
                                'c_id' => $course_id,
4189
                                'order_id' => $index,
4190
                                'lp_iv_id' => $lp_iv_id,
4191
                                'interaction_id' => $interaction[0],
4192
                                'interaction_type' => $interaction[1],
4193
                                'weighting' => $interaction[3],
4194
                                'completion_time' => $interaction[2],
4195
                                'correct_responses' => $correct_resp,
4196
                                'student_response' => $interaction[5],
4197
                                'result' => $interaction[6],
4198
                                'latency' => $interaction[7],
4199
                            ];
4200
4201
                            $insertId = Database::insert($iva_table, $params);
4202
                            if ($insertId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertId of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
4203
                                $sql = "UPDATE $iva_table SET id = iid
4204
                                        WHERE iid = $insertId";
4205
                                Database::query($sql);
4206
                            }
4207
                        }
4208
                    }
4209
                }
4210
            }
4211
        }
4212
4213
        if ($debug) {
4214
            error_log('End of learnpathItem::write_to_db()', 0);
4215
        }
4216
4217
        return true;
4218
    }
4219
4220
    /**
4221
     * Adds an audio file attached to the current item (store on disk and in db).
4222
     *
4223
     * @return bool|null|string
4224
     */
4225
    public function add_audio()
4226
    {
4227
        $course_info = api_get_course_info();
4228
        $filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/';
4229
4230
        if (!is_dir($filepath.'audio')) {
4231
            mkdir(
4232
                $filepath.'audio',
4233
                api_get_permissions_for_new_directories()
4234
            );
4235
            $audio_id = add_document(
4236
                $course_info,
4237
                '/audio',
4238
                'folder',
4239
                0,
4240
                'audio'
4241
            );
4242
            api_item_property_update(
4243
                $course_info,
4244
                TOOL_DOCUMENT,
4245
                $audio_id,
4246
                'FolderCreated',
4247
                api_get_user_id(),
4248
                null,
4249
                null,
4250
                null,
4251
                null,
4252
                api_get_session_id()
4253
            );
4254
            api_item_property_update(
4255
                $course_info,
4256
                TOOL_DOCUMENT,
4257
                $audio_id,
4258
                'invisible',
4259
                api_get_user_id(),
4260
                null,
4261
                null,
4262
                null,
4263
                null,
4264
                api_get_session_id()
4265
            );
4266
        }
4267
4268
        $key = 'file';
4269
        if (!isset($_FILES[$key]['name']) || !isset($_FILES[$key]['tmp_name'])) {
4270
            return false;
4271
        }
4272
        $result = DocumentManager::upload_document(
4273
            $_FILES,
4274
            '/audio',
4275
            null,
4276
            null,
4277
            0,
4278
            'rename',
4279
            false,
4280
            false
4281
        );
4282
        $file_path = null;
4283
4284
        if ($result) {
4285
            $file_path = basename($result['path']);
4286
4287
            // Store the mp3 file in the lp_item table.
4288
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4289
            $sql = "UPDATE $tbl_lp_item SET
4290
                        audio = '".Database::escape_string($file_path)."'
4291
                    WHERE iid = ".intval($this->db_id);
4292
            Database::query($sql);
4293
        }
4294
4295
        return $file_path;
4296
    }
4297
4298
    /**
4299
     * Adds an audio file to the current item, using a file already in documents.
4300
     *
4301
     * @param int $doc_id
4302
     *
4303
     * @return string
4304
     */
4305
    public function add_audio_from_documents($doc_id)
4306
    {
4307
        $course_info = api_get_course_info();
4308
        $document_data = DocumentManager::get_document_data_by_id(
4309
            $doc_id,
4310
            $course_info['code']
4311
        );
4312
4313
        $file_path = '';
4314
        if (!empty($document_data)) {
4315
            $file_path = basename($document_data['path']);
4316
            // Store the mp3 file in the lp_item table.
4317
            $table = Database::get_course_table(TABLE_LP_ITEM);
4318
            $sql = "UPDATE $table SET
4319
                        audio = '".Database::escape_string($file_path)."'
4320
                    WHERE iid = ".intval($this->db_id);
4321
            Database::query($sql);
4322
        }
4323
4324
        return $file_path;
4325
    }
4326
4327
    /**
4328
     * Removes the relation between the current item and an audio file. The file
4329
     * is only removed from the lp_item table, but remains in the document table
4330
     * and directory.
4331
     *
4332
     * @return bool
4333
     */
4334
    public function remove_audio()
4335
    {
4336
        if (empty($this->db_id)) {
4337
            return false;
4338
        }
4339
        $table = Database::get_course_table(TABLE_LP_ITEM);
4340
        $sql = "UPDATE $table SET
4341
                audio = ''
4342
                WHERE iid IN (".$this->db_id.")";
4343
        Database::query($sql);
4344
    }
4345
4346
    /**
4347
     * Transform the SCORM status to a string that can be translated by Chamilo
4348
     * in different user languages.
4349
     *
4350
     * @param $status
4351
     * @param bool   $decorate
4352
     * @param string $type     classic|simple
4353
     *
4354
     * @return array|string
4355
     */
4356
    public static function humanize_status($status, $decorate = true, $type = 'classic')
4357
    {
4358
        $statusList = [
4359
            'completed' => 'ScormCompstatus',
4360
            'incomplete' => 'ScormIncomplete',
4361
            'failed' => 'ScormFailed',
4362
            'passed' => 'ScormPassed',
4363
            'browsed' => 'ScormBrowsed',
4364
            'not attempted' => 'ScormNotAttempted',
4365
        ];
4366
4367
        $myLessonStatus = get_lang($statusList[$status]);
4368
4369
        switch ($status) {
4370
            case 'completed':
4371
            case 'browsed':
4372
                $classStatus = 'info';
4373
                break;
4374
            case 'incomplete':
4375
                $classStatus = 'warning';
4376
                break;
4377
            case 'passed':
4378
                $classStatus = 'success';
4379
                break;
4380
            case 'failed':
4381
                $classStatus = 'important';
4382
                break;
4383
            default:
4384
                $classStatus = 'default';
4385
                break;
4386
        }
4387
4388
        if ($type == 'simple') {
4389
            if (in_array($status, ['failed', 'passed', 'browsed'])) {
4390
                $myLessonStatus = get_lang('ScormIncomplete');
4391
4392
                $classStatus = 'warning';
4393
            }
4394
        }
4395
4396
        if ($decorate) {
4397
            return Display::label($myLessonStatus, $classStatus);
4398
        } else {
4399
            return $myLessonStatus;
4400
        }
4401
    }
4402
4403
    /**
4404
     * @return float
4405
     */
4406
    public function getPrerequisiteMaxScore()
4407
    {
4408
        return $this->prerequisiteMaxScore;
4409
    }
4410
4411
    /**
4412
     * @param float $prerequisiteMaxScore
4413
     */
4414
    public function setPrerequisiteMaxScore($prerequisiteMaxScore)
4415
    {
4416
        $this->prerequisiteMaxScore = $prerequisiteMaxScore;
4417
    }
4418
4419
    /**
4420
     * @return float
4421
     */
4422
    public function getPrerequisiteMinScore()
4423
    {
4424
        return $this->prerequisiteMinScore;
4425
    }
4426
4427
    /**
4428
     * @param float $prerequisiteMinScore
4429
     */
4430
    public function setPrerequisiteMinScore($prerequisiteMinScore)
4431
    {
4432
        $this->prerequisiteMinScore = $prerequisiteMinScore;
4433
    }
4434
4435
    /**
4436
     * Check if this LP item has a created thread in the basis course from the forum of its LP.
4437
     *
4438
     * @param int $lpCourseId The course ID
4439
     *
4440
     * @return bool
4441
     */
4442
    public function lpItemHasThread($lpCourseId)
4443
    {
4444
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4445
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4446
4447
        $fakeFrom = "
4448
            $forumThreadTable ft
4449
            INNER JOIN $itemProperty ip
4450
            ON (ft.thread_id = ip.ref AND ft.c_id = ip.c_id)
4451
        ";
4452
4453
        $resultData = Database::select(
4454
            'COUNT(ft.iid) AS qty',
4455
            $fakeFrom,
4456
            [
4457
                'where' => [
4458
                    'ip.visibility != ? AND ' => 2,
4459
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4460
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4461
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4462
                        intval($this->db_id),
4463
                        "{$this->title} - {$this->db_id}",
4464
                        intval($this->db_id),
4465
                    ],
4466
                ],
4467
            ],
4468
            'first'
4469
        );
4470
4471
        if ($resultData['qty'] > 0) {
4472
            return true;
4473
        }
4474
4475
        return false;
4476
    }
4477
4478
    /**
4479
     * Get the forum thread info.
4480
     *
4481
     * @param int $lpCourseId  The course ID from the learning path
4482
     * @param int $lpSessionId Optional. The session ID from the learning path
4483
     *
4484
     * @return bool
4485
     */
4486
    public function getForumThread($lpCourseId, $lpSessionId = 0)
4487
    {
4488
        $lpSessionId = intval($lpSessionId);
4489
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4490
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4491
4492
        $fakeFrom = "$forumThreadTable ft
4493
            INNER JOIN $itemProperty ip ";
4494
4495
        if ($lpSessionId == 0) {
4496
            $fakeFrom .= "
4497
                ON (
4498
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND (
4499
                        ft.session_id = ip.session_id OR ip.session_id IS NULL
4500
                    )
4501
                )
4502
            ";
4503
        } else {
4504
            $fakeFrom .= "
4505
                ON (
4506
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND ft.session_id = ip.session_id
4507
                )
4508
            ";
4509
        }
4510
4511
        $resultData = Database::select(
4512
            'ft.*',
4513
            $fakeFrom,
4514
            [
4515
                'where' => [
4516
                    'ip.visibility != ? AND ' => 2,
4517
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4518
                    'ft.session_id = ? AND ' => $lpSessionId,
4519
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4520
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4521
                        intval($this->db_id),
4522
                        "{$this->title} - {$this->db_id}",
4523
                        intval($this->db_id),
4524
                    ],
4525
                ],
4526
            ],
4527
            'first'
4528
        );
4529
4530
        if (empty($resultData)) {
4531
            return false;
4532
        }
4533
4534
        return $resultData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $resultData returns the type array which is incompatible with the documented return type boolean.
Loading history...
4535
    }
4536
4537
    /**
4538
     * Create a forum thread for this learning path item.
4539
     *
4540
     * @param int $currentForumId The forum ID to add the new thread
4541
     *
4542
     * @return int The forum thread if was created. Otherwise return false
4543
     */
4544
    public function createForumThread($currentForumId)
4545
    {
4546
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
4547
4548
        $em = Database::getManager();
4549
        $threadRepo = $em->getRepository('ChamiloCourseBundle:CForumThread');
4550
        $forumThread = $threadRepo->findOneBy([
4551
            'threadTitle' => "{$this->title} - {$this->db_id}",
4552
            'forumId' => intval($currentForumId),
4553
        ]);
4554
4555
        if (!$forumThread) {
4556
            $forumInfo = get_forum_information($currentForumId);
0 ignored issues
show
Deprecated Code introduced by
The function get_forum_information() has been deprecated: this functionality is now moved to get_forums($forum_id) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4556
            $forumInfo = /** @scrutinizer ignore-deprecated */ get_forum_information($currentForumId);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4557
4558
            store_thread(
4559
                $forumInfo,
4560
                [
4561
                    'forum_id' => intval($currentForumId),
4562
                    'thread_id' => 0,
4563
                    'gradebook' => 0,
4564
                    'post_title' => "{$this->name} - {$this->db_id}",
4565
                    'post_text' => $this->description,
4566
                    'category_id' => 1,
4567
                    'numeric_calification' => 0,
4568
                    'calification_notebook_title' => 0,
4569
                    'weight_calification' => 0.00,
4570
                    'thread_peer_qualify' => 0,
4571
                    'lp_item_id' => $this->db_id,
4572
                ],
4573
                [],
4574
                false
4575
            );
4576
4577
            return;
4578
        }
4579
4580
        $forumThread->setLpItemId($this->db_id);
4581
4582
        $em->persist($forumThread);
4583
        $em->flush();
4584
    }
4585
4586
    /**
4587
     * Allow dissociate a forum to this LP item.
4588
     *
4589
     * @param int $threadIid The thread id
4590
     *
4591
     * @return bool
4592
     */
4593
    public function dissociateForumThread($threadIid)
4594
    {
4595
        $threadIid = (int) $threadIid;
4596
        $em = Database::getManager();
4597
4598
        $forumThread = $em->find('ChamiloCourseBundle:CForumThread', $threadIid);
4599
4600
        if (!$forumThread) {
4601
            return false;
4602
        }
4603
4604
        $forumThread->setThreadTitle("{$this->get_title()} - {$this->db_id}");
4605
        $forumThread->setLpItemId(0);
4606
4607
        $em->persist($forumThread);
4608
        $em->flush();
4609
4610
        return true;
4611
    }
4612
4613
    /**
4614
     * @return int
4615
     */
4616
    public function getLastScormSessionTime()
4617
    {
4618
        return $this->last_scorm_session_time;
4619
    }
4620
}
4621