Completed
Push — master ( 27e209...a08afa )
by Julito
186:04 queued 150:53
created

learnpathItem::set_terms()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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

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

2670
        $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...
2671
        //For serious game  : We reuse same attempt_id
2672
        if ($seriousGame == 1 && $this->type == 'sco') {
2673
            // If this is a sco, Chamilo can't update the time without an
2674
            //  explicit scorm call
2675
            $this->current_start_time = 0;
2676
            $this->current_stop_time = 0; //Those 0 value have this effect
2677
            $this->last_scorm_session_time = 0;
2678
            $this->save();
2679
            return true;
2680
        }
2681
        $this->save();
2682
2683
        $allowed = $this->isRestartAllowed();
2684
        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...
2685
            // Nothing allowed, do nothing.
2686
        } elseif ($allowed === 1) {
2687
            // Restart as new attempt is allowed, record a new attempt.
2688
            $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id.
2689
            $this->current_score = 0;
2690
            $this->current_start_time = 0;
2691
            $this->current_stop_time = 0;
2692
            $this->current_data = '';
2693
            $this->status = $this->possible_status[0];
2694
            $this->interactions_count = 0;
2695
            $this->interactions = [];
2696
            $this->objectives_count = 0;
2697
            $this->objectives = [];
2698
            $this->lesson_location = '';
2699
            if ($this->type != TOOL_QUIZ) {
2700
                $this->write_to_db();
2701
            }
2702
        } else {
2703
            // Restart current element is allowed (because it's not finished yet),
2704
            // reinit current.
2705
            //$this->current_score = 0;
2706
            $this->current_start_time = 0;
2707
            $this->current_stop_time = 0;
2708
            //$this->current_data = '';scorm
2709
            //$this->status = $this->possible_status[0];
2710
            $this->interactions_count = $this->get_interactions_count(true);
2711
        }
2712
        return true;
2713
    }
2714
2715
    /**
2716
     * Saves data in the database
2717
     * @param boolean $from_outside Save from URL params (1) or from object attributes (0)
2718
     * @param boolean $prereqs_complete The results of a check on prerequisites for this item.
2719
     * True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au
2720
     * @return    boolean    True on success, false on failure
2721
     */
2722
    public function save($from_outside = true, $prereqs_complete = false)
2723
    {
2724
        if (self::DEBUG > 0) {
2725
            error_log('learnpathItem::save()', 0);
2726
        }
2727
        // First check if parameters passed via GET can be saved here
2728
        // in case it's a SCORM, we should get:
2729
        if ($this->type == 'sco' || $this->type == 'au') {
2730
            $status = $this->get_status(true);
2731
            if ($this->prevent_reinit == 1 &&
2732
                $status != $this->possible_status[0] && // not attempted
2733
                $status != $this->possible_status[1]    //incomplete
2734
            ) {
2735
                if (self::DEBUG > 1) {
2736
                    error_log(
2737
                        'learnpathItem::save() - save reinit blocked by setting',
2738
                        0
2739
                    );
2740
                }
2741
                // Do nothing because the status has already been set. Don't allow it to change.
2742
                // TODO: Check there isn't a special circumstance where this should be saved.
2743
            } else {
2744
                if (self::DEBUG > 1) {
2745
                    error_log(
2746
                        'learnpathItem::save() - SCORM save request received',
2747
                        0
2748
                    );
2749
                }
2750
2751
                // Get all new settings from the URL
2752
                if ($from_outside) {
2753
                    if (self::DEBUG > 1) {
2754
                        error_log(
2755
                            'learnpathItem::save() - Getting item data from outside',
2756
                            0
2757
                        );
2758
                    }
2759
                    foreach ($_GET as $param => $value) {
2760
                        switch ($param) {
2761
                            case 'score':
2762
                                $this->set_score($value);
2763
                                if (self::DEBUG > 2) {
2764
                                    error_log(
2765
                                        'learnpathItem::save() - setting score to '.$value,
2766
                                        0
2767
                                    );
2768
                                }
2769
                                break;
2770
                            case 'max':
2771
                                $this->set_max_score($value);
2772
                                if (self::DEBUG > 2) {
2773
                                    error_log(
2774
                                        'learnpathItem::save() - setting view_max_score to '.$value,
2775
                                        0
2776
                                    );
2777
                                }
2778
                                break;
2779
                            case 'min':
2780
                                $this->min_score = $value;
2781
                                if (self::DEBUG > 2) {
2782
                                    error_log(
2783
                                        'learnpathItem::save() - setting min_score to '.$value,
2784
                                        0
2785
                                    );
2786
                                }
2787
                                break;
2788
                            case 'lesson_status':
2789
                                if (!empty($value)) {
2790
                                    $this->set_status($value);
2791
                                    if (self::DEBUG > 2) {
2792
                                        error_log(
2793
                                            'learnpathItem::save() - setting status to '.$value,
2794
                                            0
2795
                                        );
2796
                                    }
2797
                                }
2798
                                break;
2799
                            case 'time':
2800
                                $this->set_time($value);
2801
                                if (self::DEBUG > 2) {
2802
                                    error_log(
2803
                                        'learnpathItem::save() - setting time to '.$value,
2804
                                        0
2805
                                    );
2806
                                }
2807
                                break;
2808
                            case 'suspend_data':
2809
                                $this->current_data = $value;
2810
                                if (self::DEBUG > 2) {
2811
                                    error_log(
2812
                                        'learnpathItem::save() - setting suspend_data to '.$value,
2813
                                        0
2814
                                    );
2815
                                }
2816
                                break;
2817
                            case 'lesson_location':
2818
                                $this->set_lesson_location($value);
2819
                                if (self::DEBUG > 2) {
2820
                                    error_log(
2821
                                        'learnpathItem::save() - setting lesson_location to '.$value,
2822
                                        0
2823
                                    );
2824
                                }
2825
                                break;
2826
                            case 'core_exit':
2827
                                $this->set_core_exit($value);
2828
                                if (self::DEBUG > 2) {
2829
                                    error_log(
2830
                                        'learnpathItem::save() - setting core_exit to '.$value,
2831
                                        0
2832
                                    );
2833
                                }
2834
                                break;
2835
                            case 'interactions':
2836
                                //$interactions = unserialize($value);
2837
                                //foreach($interactions as $interaction){
2838
                                //	;
2839
                                //}
2840
                                break;
2841
                            case 'objectives':
2842
                                break;
2843
                            //case 'maxtimeallowed':
2844
                            //$this->set_max_time_allowed($value);
2845
                            //break;
2846
                            /*
2847
                            case 'objectives._count':
2848
                                $this->attempt_id = $value;
2849
                                break;
2850
                            */
2851
                            default:
2852
                                // Ignore.
2853
                                break;
2854
                        }
2855
                    }
2856
                } else {
2857
                    if (self::DEBUG > 1) {
2858
                        error_log(
2859
                            'learnpathItem::save() - Using inside item status',
2860
                            0
2861
                        );
2862
                    }
2863
                    // Do nothing, just let the local attributes be used.
2864
                }
2865
            }
2866
        } else {
2867
            // If not SCO, such messages should not be expected.
2868
            $type = strtolower($this->type);
2869
            switch ($type) {
2870
                case 'asset':
2871
                    if ($prereqs_complete) {
2872
                        $this->set_status($this->possible_status[2]);
2873
                    }
2874
                    break;
2875
                case TOOL_HOTPOTATOES:
2876
                    break;
2877
                case TOOL_QUIZ:
2878
                    return false;
2879
                    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...
2880
                default:
2881
                    // For now, everything that is not sco and not asset is set to
2882
                    // completed when saved.
2883
                    if ($prereqs_complete) {
2884
                        $this->set_status($this->possible_status[2]);
2885
                    }
2886
                    break;
2887
            }
2888
        }
2889
2890
        if (self::DEBUG > 1) {
2891
            error_log(
2892
                'New LP - End of learnpathItem::save() - Calling write_to_db()',
2893
                0
2894
            );
2895
        }
2896
2897
        return $this->write_to_db();
2898
    }
2899
2900
    /**
2901
     * Sets the number of attempt_id to a given value
2902
     * @param    integer  $num  The given value to set attempt_id to
2903
     * @return    boolean    TRUE on success, FALSE otherwise
2904
     */
2905
    public function set_attempt_id($num)
2906
    {
2907
        if (self::DEBUG > 0) {
2908
            error_log('learnpathItem::set_attempt_id()', 0);
2909
        }
2910
        if ($num == strval(intval($num)) && $num >= 0) {
2911
            $this->attempt_id = $num;
2912
            return true;
2913
        }
2914
        return false;
2915
    }
2916
2917
    /**
2918
     * Sets the core_exit value to the one given
2919
     * @return  bool  $value  True (always)
2920
     */
2921
    public function set_core_exit($value)
2922
    {
2923
        switch ($value) {
2924
            case '':
2925
                $this->core_exit = '';
2926
                break;
2927
            case 'suspend':
2928
                $this->core_exit = 'suspend';
2929
                break;
2930
            default:
2931
                $this->core_exit = 'none';
2932
                break;
2933
        }
2934
        return true;
2935
    }
2936
2937
    /**
2938
     * Sets the item's description
2939
     * @param    string  $string  Description
2940
     *
2941
     * @return  void
2942
     */
2943
    public function set_description($string = '')
2944
    {
2945
        if (self::DEBUG > 0) {
2946
            error_log('learnpathItem::set_description()', 0);
2947
        }
2948
        if (!empty($string)) {
2949
            $this->description = $string;
2950
        }
2951
    }
2952
2953
    /**
2954
     * Sets the lesson_location value
2955
     * @param    string  $location  lesson_location as provided by the SCO
2956
     * @return    boolean    True on success, false otherwise
2957
     */
2958
    public function set_lesson_location($location)
2959
    {
2960
        if (self::DEBUG > 0) {
2961
            error_log('learnpathItem::set_lesson_location()', 0);
2962
        }
2963
        if (isset($location)) {
2964
            $this->lesson_location = $location;
2965
            return true;
2966
        }
2967
        return false;
2968
    }
2969
2970
    /**
2971
     * Sets the item's depth level in the LP tree (0 is at root)
2972
     * @param    integer $int   Level
2973
     * @return  void
2974
     */
2975
    public function set_level($int = 0)
2976
    {
2977
        if (self::DEBUG > 0) {
2978
            error_log('learnpathItem::set_level('.$int.')', 0);
2979
        }
2980
        if (!empty($int) && $int == strval(intval($int))) {
2981
            $this->level = $int;
2982
        }
2983
    }
2984
2985
    /**
2986
     * Sets the lp_view id this item view is registered to
2987
     * @param int $lp_view_id   lp_view DB ID
2988
     * @param int $course_id
2989
     * @return  bool
2990
     * @todo //todo insert into lp_item_view if lp_view not exists
2991
     */
2992
    public function set_lp_view($lp_view_id, $course_id = null)
2993
    {
2994
        $lp_view_id = intval($lp_view_id);
2995
2996
        if (empty($course_id)) {
2997
            $course_id = api_get_course_int_id();
2998
        } else {
2999
            $course_id = intval($course_id);
3000
        }
3001
3002
        $lpItemId = $this->get_id();
3003
3004
        if (empty($lpItemId)) {
3005
            if (self::DEBUG > 0) {
3006
                error_log(
3007
                    'learnpathItem::set_lp_view('.$lp_view_id.') $lpItemId is empty',
3008
                    0
3009
                );
3010
            }
3011
3012
            return false;
3013
        }
3014
3015
        if (empty($lp_view_id)) {
3016
            if (self::DEBUG > 0) {
3017
                error_log(
3018
                    'learnpathItem::set_lp_view('.$lp_view_id.') $lp_view_id is empty',
3019
                    0
3020
                );
3021
            }
3022
3023
            return false;
3024
        }
3025
3026
        if (self::DEBUG > 0) {
3027
            error_log('learnpathItem::set_lp_view('.$lp_view_id.')', 0);
3028
        }
3029
3030
        $this->view_id = $lp_view_id;
3031
3032
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3033
        // Get the lp_item_view with the highest view_count.
3034
        $sql = "SELECT * FROM $item_view_table
3035
                WHERE
3036
                    c_id = $course_id AND
3037
                    lp_item_id = ".$lpItemId." AND
3038
                    lp_view_id = " . $lp_view_id."
3039
                ORDER BY view_count DESC";
3040
3041
        if (self::DEBUG > 2) {
3042
            error_log(
3043
                'learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql,
3044
                0
3045
            );
3046
        }
3047
        $res = Database::query($sql);
3048
        if (Database::num_rows($res) > 0) {
3049
            $row = Database::fetch_array($res);
3050
            $this->db_item_view_id = $row['iid'];
3051
            $this->attempt_id = $row['view_count'];
3052
            $this->current_score = $row['score'];
3053
            $this->current_data = $row['suspend_data'];
3054
            $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...
3055
            $this->status = $row['status'];
3056
            $this->current_start_time = $row['start_time'];
3057
            $this->current_stop_time = $this->current_start_time + $row['total_time'];
3058
            $this->lesson_location = $row['lesson_location'];
3059
            $this->core_exit = $row['core_exit'];
3060
3061
            if (self::DEBUG > 2) {
3062
                error_log(
3063
                    'learnpathItem::set_lp_view() - Updated item object with database values',
3064
                    0
3065
                );
3066
            }
3067
3068
            // Now get the number of interactions for this little guy.
3069
            $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3070
            $sql = "SELECT * FROM $table
3071
                    WHERE
3072
                        c_id = $course_id AND
3073
                        lp_iv_id = '".$this->db_item_view_id."'";
3074
3075
            $res = Database::query($sql);
3076
            if ($res !== false) {
3077
                $this->interactions_count = Database::num_rows($res);
3078
            } else {
3079
                $this->interactions_count = 0;
3080
            }
3081
            // Now get the number of objectives for this little guy.
3082
            $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3083
            $sql = "SELECT * FROM $table
3084
                    WHERE
3085
                        c_id = $course_id AND
3086
                        lp_iv_id = '".$this->db_item_view_id."'";
3087
3088
            $res = Database::query($sql);
3089
            if ($res !== false) {
3090
                $this->objectives_count = Database::num_rows($res);
3091
            } else {
3092
                $this->objectives_count = 0;
3093
            }
3094
        }
3095
3096
        // End
3097
        if (self::DEBUG > 2) {
3098
            error_log('New LP - End of learnpathItem::set_lp_view()', 0);
3099
        }
3100
3101
        return true;
3102
    }
3103
3104
    /**
3105
     * Sets the path
3106
     * @param   string $string Path
3107
     * @return  void
3108
     */
3109
    public function set_path($string = '')
3110
    {
3111
        if (self::DEBUG > 0) {
3112
            error_log('learnpathItem::set_path()', 0);
3113
        }
3114
        if (!empty($string)) {
3115
            $this->path = $string;
3116
        }
3117
    }
3118
3119
    /**
3120
     * Sets the prevent_reinit attribute.
3121
     * This is based on the LP value and is set at creation time for
3122
     * each learnpathItem. It is a (bad?) way of avoiding
3123
     * a reference to the LP when saving an item.
3124
     * @param int 1 for "prevent", 0 for "don't prevent"
3125
     * saving freshened values (new "not attempted" status etc)
3126
     */
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...
3127
    public function set_prevent_reinit($prevent)
3128
    {
3129
        if (self::DEBUG > 0) {
3130
            error_log('learnpathItem::set_prevent_reinit()', 0);
3131
        }
3132
        if ($prevent) {
3133
            $this->prevent_reinit = 1;
3134
        } else {
3135
            $this->prevent_reinit = 0;
3136
        }
3137
    }
3138
3139
    /**
3140
     * Sets the score value. If the mastery_score is set and the score reaches
3141
     * it, then set the status to 'passed'.
3142
     * @param   float    $score   Score
3143
     * @return    boolean   True on success, false otherwise
3144
     */
3145
    public function set_score($score)
3146
    {
3147
        $debug = self::DEBUG;
3148
        if ($debug > 0) {
3149
            error_log('learnpathItem::set_score('.$score.')', 0);
3150
        }
3151
        if (($this->max_score <= 0 || $score <= $this->max_score) && ($score >= $this->min_score)) {
3152
            $this->current_score = $score;
3153
            $masteryScore = $this->get_mastery_score();
3154
            $current_status = $this->get_status(false);
3155
3156
            // Fixes bug when SCORM doesn't send a mastery score even if they sent a score!
3157
            if ($masteryScore == -1) {
3158
                $masteryScore = $this->max_score;
3159
            }
3160
3161
            if ($debug > 0) {
3162
                error_log('get_mastery_score: '.$masteryScore);
3163
                error_log('current_status: '.$current_status);
3164
                error_log('current score : '.$this->current_score);
3165
            }
3166
3167
            // If mastery_score is set AND the current score reaches the mastery
3168
            //  score AND the current status is different from 'completed', then
3169
            //  set it to 'passed'.
3170
            /*
3171
            if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) {
3172
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[3]);
3173
                $this->set_status($this->possible_status[3]); //passed
3174
            } elseif ($master != -1 && $this->current_score < $master) {
3175
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[4]);
3176
                $this->set_status($this->possible_status[4]); //failed
3177
            }*/
3178
            return true;
3179
        }
3180
        return false;
3181
    }
3182
3183
    /**
3184
     * Sets the maximum score for this item
3185
     * @param  int $score Maximum score - must be a decimal or an empty string
3186
     *
3187
     * @return boolean    True on success, false on error
3188
     */
3189
    public function set_max_score($score)
3190
    {
3191
        if (self::DEBUG > 0) {
3192
            error_log('learnpathItem::set_max_score('.$score.')', 0);
3193
        }
3194
        if (is_int($score) || $score == '') {
3195
            $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...
3196
            if (self::DEBUG > 1) {
3197
                error_log(
3198
                    'learnpathItem::set_max_score() - '.
3199
                    'Updated object score of item '.$this->db_id.
3200
                    ' to '.$this->view_max_score,
3201
                    0
3202
                );
3203
            }
3204
            return true;
3205
        }
3206
        return false;
3207
    }
3208
3209
    /**
3210
     * Sets the status for this item
3211
     * @param    string $status Status - must be one of the values defined in $this->possible_status
3212
     * (this affects the status setting)
3213
     * @return    boolean    True on success, false on error
3214
     */
3215
    public function set_status($status)
3216
    {
3217
        if (self::DEBUG > 0) {
3218
            error_log('learnpathItem::set_status('.$status.')', 0);
3219
        }
3220
3221
        $found = false;
3222
        foreach ($this->possible_status as $possible) {
3223
            if (preg_match('/^'.$possible.'$/i', $status)) {
3224
                $found = true;
3225
            }
3226
        }
3227
3228
        if ($found) {
3229
            $this->status = $status;
3230
            if (self::DEBUG > 1) {
3231
                error_log(
3232
                    'learnpathItem::set_status() - '.
3233
                        'Updated object status of item '.$this->db_id.
3234
                        ' to '.$this->status,
3235
                    0
3236
                );
3237
            }
3238
            return true;
3239
        }
3240
3241
        $this->status = $this->possible_status[0];
3242
3243
        return false;
3244
    }
3245
3246
    /**
3247
     * Set the (indexing) terms for this learnpath item
3248
     * @param   string  $terms Terms, as a comma-split list
3249
     * @return  boolean Always return true
3250
     */
3251
    public function set_terms($terms)
3252
    {
3253
        global $charset;
3254
        $course_id = api_get_course_int_id();
3255
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
3256
        $a_terms = preg_split('/,/', $terms);
3257
        $i_terms = preg_split('/,/', $this->get_terms());
3258
        foreach ($i_terms as $term) {
3259
            if (!in_array($term, $a_terms)) {
0 ignored issues
show
Bug introduced by
It seems like $a_terms can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3259
            if (!in_array($term, /** @scrutinizer ignore-type */ $a_terms)) {
Loading history...
3260
                array_push($a_terms, $term);
0 ignored issues
show
Bug introduced by
It seems like $a_terms can also be of type false; however, parameter $array of array_push() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3260
                array_push(/** @scrutinizer ignore-type */ $a_terms, $term);
Loading history...
3261
            }
3262
        }
3263
        $new_terms = $a_terms;
3264
        $new_terms_string = implode(',', $new_terms);
0 ignored issues
show
Bug introduced by
It seems like $new_terms can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3264
        $new_terms_string = implode(',', /** @scrutinizer ignore-type */ $new_terms);
Loading history...
3265
3266
        // TODO: Validate csv string.
3267
        $terms = Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset));
3268
        $sql = "UPDATE $lp_item
3269
                SET terms = '$terms'
3270
                WHERE iid=".$this->get_id();
3271
        Database::query($sql);
3272
        // Save it to search engine.
3273
        if (api_get_setting('search_enabled') == 'true') {
3274
            $di = new ChamiloIndexer();
3275
            $di->update_terms($this->get_search_did(), $new_terms, 'T');
0 ignored issues
show
Bug introduced by
It seems like $new_terms can also be of type false; however, parameter $terms of XapianIndexer::update_terms() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3275
            $di->update_terms($this->get_search_did(), /** @scrutinizer ignore-type */ $new_terms, 'T');
Loading history...
3276
        }
3277
        return true;
3278
    }
3279
3280
    /**
3281
     * Get the document ID from inside the text index database
3282
     * @return  int     Search index database document ID
3283
     */
3284
    public function get_search_did()
3285
    {
3286
        return $this->search_did;
3287
    }
3288
3289
    /**
3290
     * Sets the item viewing time in a usable form, given that SCORM packages
3291
     * often give it as 00:00:00.0000
3292
     * @param    string    Time as given by SCORM
3293
     */
3294
    public function set_time($scorm_time, $format = 'scorm')
3295
    {
3296
        $debug = self::DEBUG;
3297
        if ($debug > 0) {
3298
            error_log('learnpathItem::set_time('.$scorm_time.')', 0);
3299
        }
3300
3301
        if ($scorm_time == '0' &&
3302
            $this->type != 'sco' &&
3303
            $this->current_start_time != 0
3304
        ) {
3305
            $my_time = time() - $this->current_start_time;
3306
            if ($my_time > 0) {
3307
                $this->update_time($my_time);
3308
                if ($debug > 0) {
3309
                    error_log(
3310
                        'learnpathItem::set_time('.$scorm_time.') - '.
3311
                            'found asset - set time to '.$my_time,
3312
                        0
3313
                    );
3314
                }
3315
            }
3316
        } else {
3317
            switch ($format) {
3318
                case 'scorm':
3319
                    $res = [];
3320
                    if (preg_match(
3321
                        '/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/',
3322
                        $scorm_time,
3323
                        $res
3324
                    )
3325
                    ) {
3326
                        $hour = $res[1];
3327
                        $min = $res[2];
3328
                        $sec = $res[3];
3329
                        // Getting total number of seconds spent.
3330
                        $total_sec = $hour * 3600 + $min * 60 + $sec;
3331
                        if ($debug > 0) {
3332
                            error_log("total_sec : $total_sec");
3333
                        }
3334
                        $this->scorm_update_time($total_sec);
3335
                    }
3336
                    break;
3337
                case 'int':
3338
                    $this->scorm_update_time($scorm_time);
3339
                    break;
3340
            }
3341
        }
3342
    }
3343
3344
    /**
3345
     * Sets the item's title
3346
     * @param    string  $string  Title
3347
     */
3348
    public function set_title($string = '')
3349
    {
3350
        if (self::DEBUG > 0) {
3351
            error_log('learnpathItem::set_title()', 0);
3352
        }
3353
        if (!empty($string)) {
3354
            $this->title = $string;
3355
        }
3356
    }
3357
3358
    /**
3359
     * Sets the item's type
3360
     * @param    string  $string  Type
3361
     * @return  void
3362
     */
3363
    public function set_type($string = '')
3364
    {
3365
        if (self::DEBUG > 0) {
3366
            error_log('learnpathItem::set_type()', 0);
3367
        }
3368
        if (!empty($string)) {
3369
            $this->type = $string;
3370
        }
3371
    }
3372
3373
    /**
3374
     * Checks if the current status is part of the list of status given
3375
     * @param  array  $list  An array of status to check for.
3376
     * If the current status is one of the strings, return true
3377
     *
3378
     * @return boolean True if the status was one of the given strings,
3379
     * false otherwise
3380
     */
3381
    public function status_is($list = [])
3382
    {
3383
        if (self::DEBUG > 1) {
3384
            error_log(
3385
                'learnpathItem::status_is('.print_r(
3386
                    $list,
3387
                    true
3388
                ).') on item '.$this->db_id,
3389
                0
3390
            );
3391
        }
3392
        $currentStatus = $this->get_status(true);
3393
        if (empty($currentStatus)) {
3394
            return false;
3395
        }
3396
        $found = false;
3397
        foreach ($list as $status) {
3398
            if (preg_match('/^'.$status.'$/i', $currentStatus)) {
3399
                if (self::DEBUG > 2) {
3400
                    error_log(
3401
                        'New LP - learnpathItem::status_is() - Found status '.
3402
                            $status.' corresponding to current status',
3403
                        0
3404
                    );
3405
                }
3406
                $found = true;
3407
3408
                return $found;
3409
            }
3410
        }
3411
        if (self::DEBUG > 2) {
3412
            error_log(
3413
                'New LP - learnpathItem::status_is() - Status '.
3414
                    $currentStatus.' did not match request',
3415
                0
3416
            );
3417
        }
3418
3419
        return $found;
3420
    }
3421
3422
    /**
3423
     * Updates the time info according to the given session_time
3424
     * @param    integer  $total_sec  Time in seconds
3425
     * @return  void
3426
     * TODO: Make this method better by allowing better/multiple time slices.
3427
     */
3428
    public function update_time($total_sec = 0)
3429
    {
3430
        if (self::DEBUG > 0) {
3431
            error_log('learnpathItem::update_time('.$total_sec.')', 0);
3432
        }
3433
        if ($total_sec >= 0) {
3434
            // Getting start time from finish time. The only problem in the calculation is it might be
3435
            // modified by the scripts processing time.
3436
            $now = time();
3437
            $start = $now - $total_sec;
3438
            $this->current_start_time = $start;
3439
            $this->current_stop_time = $now;
3440
            /*if (empty($this->current_start_time)) {
3441
                $this->current_start_time = $start;
3442
                $this->current_stop_time  = $now;
3443
            } else {
3444
                //if ($this->current_stop_time != $this->current_start_time) {
3445
                    // If the stop time has already been set before to something else
3446
                    // than the start time, add the given time to what's already been
3447
                    // recorder.
3448
                    // This is the SCORM way of doing things, because the time comes from
3449
                    // core.session_time, not core.total_time
3450
                    // UPDATE: adding time to previous time is only done on SCORM's finish()
3451
                    // call, not normally, so for now ignore this section.
3452
                    //$this->current_stop_time = $this->current_stop_time + $stop;
3453
                    //error_log('New LP - Adding '.$stop.' seconds - now '.$this->current_stop_time, 0);
3454
                //} else {
3455
                    // If no previous stop time set, use the one just calculated now from
3456
                    // start time.
3457
                    //$this->current_start_time = $start;
3458
                    //$this->current_stop_time  = $now;
3459
                    //error_log('New LP - Setting '.$stop.' seconds - now '.$this->current_stop_time, 0);
3460
                //}
3461
            }*/
3462
        }
3463
    }
3464
3465
    /**
3466
     * Special scorm update time function. This function will update time
3467
     * directly into db for scorm objects
3468
     * @param   int $total_sec  Total number of seconds
3469
     **/
3470
    public function scorm_update_time($total_sec = 0)
3471
    {
3472
        $debug = self::DEBUG;
3473
        if ($debug > 0) {
3474
            error_log('learnpathItem::scorm_update_time()');
3475
            error_log("total_sec: $total_sec");
3476
        }
3477
3478
        // Step 1 : get actual total time stored in db
3479
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3480
        $course_id = api_get_course_int_id();
3481
3482
        $sql = 'SELECT total_time, status 
3483
                FROM '.$item_view_table.'
3484
                WHERE 
3485
                    c_id = '.$course_id.' AND 
3486
                    lp_item_id = "'.$this->db_id.'" AND 
3487
                    lp_view_id = "'.$this->view_id.'" AND 
3488
                    view_count = "'.$this->get_attempt_id().'"';
3489
        $result = Database::query($sql);
3490
        $row = Database::fetch_array($result);
3491
3492
        if (!isset($row['total_time'])) {
3493
            $total_time = 0;
3494
        } else {
3495
            $total_time = $row['total_time'];
3496
        }
3497
        if ($debug > 0) {
3498
            error_log("Original total_time: $total_time");
3499
        }
3500
3501
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3502
        $lp_id = intval($this->lp_id);
3503
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
3504
        $res = Database::query($sql);
3505
        $accumulateScormTime = 'false';
3506
        if (Database::num_rows($res) > 0) {
3507
            $row = Database::fetch_assoc($res);
3508
            $accumulateScormTime = $row['accumulate_scorm_time'];
3509
        }
3510
3511
        // Step 2.1 : if normal mode total_time = total_time + total_sec
3512
        if ($accumulateScormTime != 0) {
3513
            $total_time += $total_sec;
3514
        } else {
3515
            // Step 2.2 : if not cumulative mode total_time = total_time - last_update + total_sec
3516
            $total_sec = $this->fixAbusiveTime($total_sec);
3517
3518
            if ($debug > 0) {
3519
                error_log("after fix abusive: $total_sec");
3520
                error_log("total_time: $total_time");
3521
                error_log("this->last_scorm_session_time: ".$this->last_scorm_session_time);
3522
            }
3523
3524
            $total_time = $total_time - $this->last_scorm_session_time + $total_sec;
3525
            $this->last_scorm_session_time = $total_sec;
3526
3527
            if ($total_time < 0) {
3528
                $total_time = $total_sec;
3529
            }
3530
        }
3531
3532
        if ($debug > 0) {
3533
            error_log("accumulate_scorm_time: $accumulateScormTime");
3534
            error_log("total_time modified: $total_time");
3535
        }
3536
3537
        // Step 3 update db only if status != completed, passed, browsed or seriousgamemode not activated
3538
        // @todo complete
3539
        $case_completed = [
3540
            'completed',
3541
            'passed',
3542
            'browsed',
3543
            'failed'
3544
        ];
3545
3546
        if ($this->seriousgame_mode != 1 ||
3547
            !in_array($row['status'], $case_completed)
3548
        ) {
3549
            $sql = "UPDATE $item_view_table
3550
                      SET total_time = '$total_time'
3551
                    WHERE 
3552
                        c_id = $course_id AND 
3553
                        lp_item_id = {$this->db_id} AND 
3554
                        lp_view_id = {$this->view_id} AND 
3555
                        view_count = {$this->get_attempt_id()}";
3556
            if ($debug > 0) {
3557
                error_log($sql);
3558
            }
3559
            Database::query($sql);
3560
        }
3561
    }
3562
3563
    /**
3564
     * Set the total_time to 0 into db
3565
     **/
3566
    public function scorm_init_time()
3567
    {
3568
        $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3569
        $course_id = api_get_course_int_id();
3570
        $sql = 'UPDATE '.$table.'
3571
                SET total_time = 0, 
3572
                    start_time = '.time().'
3573
                WHERE 
3574
                    c_id = '.$course_id.' AND 
3575
                    lp_item_id = "'.$this->db_id.'" AND 
3576
                    lp_view_id = "'.$this->view_id.'" AND 
3577
                    view_count = "'.$this->attempt_id.'"';
3578
        Database::query($sql);
3579
    }
3580
3581
    /**
3582
     * Write objectives to DB. This method is separate from write_to_db() because otherwise
3583
     * objectives are lost as a side effect to AJAX and session concurrent access
3584
     * @return boolean True or false on error
3585
     */
3586
    public function write_objectives_to_db()
3587
    {
3588
        if (self::DEBUG > 0) {
3589
            error_log('learnpathItem::write_objectives_to_db()', 0);
3590
        }
3591
        $course_id = api_get_course_int_id();
3592
        if (is_array($this->objectives) && count($this->objectives) > 0) {
3593
            // Save objectives.
3594
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3595
            $sql = "SELECT iid
3596
                    FROM $tbl
3597
                    WHERE
3598
                        c_id = $course_id AND
3599
                        lp_item_id = ".$this->db_id." AND
3600
                        lp_view_id = " . $this->view_id." AND
3601
                        view_count = " . $this->attempt_id;
3602
            $res = Database::query($sql);
3603
            if (Database::num_rows($res) > 0) {
3604
                $row = Database::fetch_array($res);
3605
                $lp_iv_id = $row[0];
3606
                if (self::DEBUG > 2) {
3607
                    error_log(
3608
                        'learnpathItem::write_to_db() - Got item_view_id '.
3609
                            $lp_iv_id.', now checking objectives ',
3610
                        0
3611
                    );
3612
                }
3613
                foreach ($this->objectives as $index => $objective) {
3614
                    $iva_table = Database::get_course_table(
3615
                        TABLE_LP_IV_OBJECTIVE
3616
                    );
3617
                    $iva_sql = "SELECT iid FROM $iva_table
3618
                                WHERE
3619
                                    c_id = $course_id AND
3620
                                    lp_iv_id = $lp_iv_id AND
3621
                                    objective_id = '".Database::escape_string($objective[0])."'";
3622
                    $iva_res = Database::query($iva_sql);
3623
                    // id(0), type(1), time(2), weighting(3),
3624
                    // correct_responses(4), student_response(5),
3625
                    // result(6), latency(7)
3626
                    if (Database::num_rows($iva_res) > 0) {
3627
                        // Update (or don't).
3628
                        $iva_row = Database::fetch_array($iva_res);
3629
                        $iva_id = $iva_row[0];
3630
                        $ivau_sql = "UPDATE $iva_table ".
3631
                            "SET objective_id = '".Database::escape_string($objective[0])."',".
3632
                            "status = '".Database::escape_string($objective[1])."',".
3633
                            "score_raw = '".Database::escape_string($objective[2])."',".
3634
                            "score_min = '".Database::escape_string($objective[4])."',".
3635
                            "score_max = '".Database::escape_string($objective[3])."' ".
3636
                            "WHERE c_id = $course_id AND iid = $iva_id";
3637
                        Database::query($ivau_sql);
3638
                    } else {
3639
                        // Insert new one.
3640
                        $params = [
3641
                            'c_id' => $course_id,
3642
                            'lp_iv_id' => $lp_iv_id,
3643
                            'order_id' => $index,
3644
                            'objective_id' => $objective[0],
3645
                            'status' => $objective[1],
3646
                            'score_raw' => $objective[2],
3647
                            'score_min' => $objective[4],
3648
                            'score_max' => $objective[3]
3649
                        ];
3650
3651
                        $insertId = Database::insert($iva_table, $params);
3652
                        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...
3653
                            $sql = "UPDATE $iva_table SET id = iid 
3654
                                    WHERE iid = $insertId";
3655
                            Database::query($sql);
3656
                        }
3657
                    }
3658
                }
3659
            }
3660
        }
3661
    }
3662
3663
    /**
3664
     * Writes the current data to the database
3665
     * @return    boolean    Query result
3666
     */
3667
    public function write_to_db()
3668
    {
3669
        if (self::DEBUG > 0) {
3670
            error_log('learnpathItem::write_to_db()', 0);
3671
        }
3672
3673
        // Check the session visibility.
3674
        if (!api_is_allowed_to_session_edit()) {
3675
            if (self::DEBUG > 0) {
3676
                error_log('return false api_is_allowed_to_session_edit');
3677
            }
3678
3679
            return false;
3680
        }
3681
3682
        $course_id = api_get_course_int_id();
3683
        $mode = $this->get_lesson_mode();
3684
        $credit = $this->get_credit();
3685
        $total_time = ' ';
3686
        $my_status = ' ';
3687
3688
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3689
        $sql = 'SELECT status, total_time FROM '.$item_view_table.'
3690
                WHERE
3691
                    c_id = '.$course_id.' AND
3692
                    lp_item_id="'.$this->db_id.'" AND
3693
                    lp_view_id="'.$this->view_id.'" AND
3694
                    view_count="'.$this->get_attempt_id().'" ';
3695
        $rs_verified = Database::query($sql);
3696
        $row_verified = Database::fetch_array($rs_verified);
3697
3698
        $my_case_completed = [
3699
            'completed',
3700
            'passed',
3701
            'browsed',
3702
            'failed'
3703
        ];
3704
3705
        $oldTotalTime = $row_verified['total_time'];
3706
        $this->oldTotalTime = $oldTotalTime;
3707
3708
        $save = true;
3709
        if (isset($row_verified) && isset($row_verified['status'])) {
3710
            if (in_array($row_verified['status'], $my_case_completed)) {
3711
                $save = false;
3712
            }
3713
        }
3714
3715
        if ((($save === false && $this->type == 'sco') ||
3716
           ($this->type == 'sco' && ($credit == 'no-credit' || $mode == 'review' || $mode == 'browse'))) &&
3717
           ($this->seriousgame_mode != 1 && $this->type == 'sco')
3718
        ) {
3719
            if (self::DEBUG > 1) {
3720
                error_log(
3721
                    "This info shouldn't be saved as the credit or lesson mode info prevent it"
3722
                );
3723
                error_log(
3724
                    'learnpathItem::write_to_db() - credit('.$credit.') or'.
3725
                        ' lesson_mode('.$mode.') prevent recording!',
3726
                    0
3727
                );
3728
            }
3729
        } else {
3730
            // Check the row exists.
3731
            $inserted = false;
3732
            // This a special case for multiple attempts and Chamilo exercises.
3733
            if ($this->type == 'quiz' &&
3734
                $this->get_prevent_reinit() == 0 &&
3735
                $this->get_status() == 'completed'
3736
            ) {
3737
                // We force the item to be restarted.
3738
                $this->restart();
3739
                $params = [
3740
                    "c_id" => $course_id,
3741
                    "total_time" => $this->get_total_time(),
3742
                    "start_time" => $this->current_start_time,
3743
                    "score" => $this->get_score(),
3744
                    "status" => $this->get_status(false),
3745
                    "max_score" => $this->get_max(),
3746
                    "lp_item_id" => $this->db_id,
3747
                    "lp_view_id" => $this->view_id,
3748
                    "view_count" => $this->get_attempt_id(),
3749
                    "suspend_data" => $this->current_data,
3750
                    //"max_time_allowed" => ,
3751
                    "lesson_location" => $this->lesson_location
3752
                ];
3753
                if (self::DEBUG > 2) {
3754
                    error_log(
3755
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3756
                        0
3757
                    );
3758
                }
3759
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3760
                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...
3761
                    $sql = "UPDATE $item_view_table SET id = iid
3762
                            WHERE iid = ".$this->db_item_view_id;
3763
                    Database::query($sql);
3764
                    $inserted = true;
3765
                }
3766
            }
3767
3768
            $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3769
            $sql = "SELECT * FROM $item_view_table
3770
                    WHERE
3771
                        c_id = $course_id AND
3772
                        lp_item_id = ".$this->db_id." AND
3773
                        lp_view_id = ".$this->view_id." AND
3774
                        view_count = ".intval($this->get_attempt_id());
3775
            if (self::DEBUG > 2) {
3776
                error_log(
3777
                    'learnpathItem::write_to_db() - Querying item_view: '.$sql,
3778
                    0
3779
                );
3780
            }
3781
            $check_res = Database::query($sql);
3782
            // Depending on what we want (really), we'll update or insert a new row
3783
            // now save into DB.
3784
            if (!$inserted && Database::num_rows($check_res) < 1) {
3785
                $params = [
3786
                    "c_id" => $course_id,
3787
                    "total_time" => $this->get_total_time(),
3788
                    "start_time" => $this->current_start_time,
3789
                    "score" => $this->get_score(),
3790
                    "status" => $this->get_status(false),
3791
                    "max_score" => $this->get_max(),
3792
                    "lp_item_id" => $this->db_id,
3793
                    "lp_view_id" => $this->view_id,
3794
                    "view_count" => $this->get_attempt_id(),
3795
                    "suspend_data" => $this->current_data,
3796
                    //"max_time_allowed" => ,$this->get_max_time_allowed()
3797
                    "lesson_location" => $this->lesson_location
3798
                ];
3799
3800
                if (self::DEBUG > 2) {
3801
                    error_log(
3802
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3803
                        0
3804
                    );
3805
                }
3806
3807
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3808
                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...
3809
                    $sql = "UPDATE $item_view_table SET id = iid
3810
                            WHERE iid = ".$this->db_item_view_id;
3811
                    Database::query($sql);
3812
                }
3813
            } else {
3814
                if ($this->type == 'hotpotatoes') {
3815
                    $params = [
3816
                        'total_time' => $this->get_total_time(),
3817
                        'start_time' => $this->get_current_start_time(),
3818
                        'score' =>  $this->get_score(),
3819
                        'status' => $this->get_status(false),
3820
                        'max_score' => $this->get_max(),
3821
                        'suspend_data' => $this->current_data,
3822
                        'lesson_location' => $this->lesson_location
3823
                    ];
3824
                    $where = [
3825
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' =>
3826
                        [
3827
                            $course_id,
3828
                            $this->db_id,
3829
                            $this->view_id,
3830
                            $this->get_attempt_id()
3831
                        ]
3832
                    ];
3833
                    Database::update($item_view_table, $params, $where);
3834
                } else {
3835
                    // For all other content types...
3836
                    if ($this->type == 'quiz') {
3837
                        $my_status = ' ';
3838
                        $total_time = ' ';
3839
                        if (!empty($_REQUEST['exeId'])) {
3840
                            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3841
3842
                            $safe_exe_id = intval($_REQUEST['exeId']);
3843
                            $sql = "SELECT start_date, exe_date
3844
                                    FROM $table
3845
                                    WHERE exe_id = $safe_exe_id";
3846
                            $res = Database::query($sql);
3847
                            $row_dates = Database::fetch_array($res);
3848
3849
                            $time_start_date = convert_sql_date(
3850
                                $row_dates['start_date']
3851
                            );
3852
                            $time_exe_date = convert_sql_date(
3853
                                $row_dates['exe_date']
3854
                            );
3855
                            $mytime = ((int) $time_exe_date - (int) $time_start_date);
3856
                            $mytime = $this->fixAbusiveTime($mytime);
3857
                            $total_time = " total_time = ".$mytime.", ";
3858
                        }
3859
                    } else {
3860
                        $my_type_lp = learnpath::get_type_static($this->lp_id);
3861
                        // This is a array containing values finished
3862
                        $case_completed = [
3863
                            'completed',
3864
                            'passed',
3865
                            'browsed',
3866
                            'failed'
3867
                        ];
3868
3869
                        // Is not multiple attempts
3870
                        if ($this->seriousgame_mode == 1 && $this->type == 'sco') {
3871
                            $total_time = " total_time = total_time +".$this->get_total_time().", ";
3872
                            $my_status = " status = '".$this->get_status(false)."' ,";
3873
                        } elseif ($this->get_prevent_reinit() == 1) {
3874
                            // Process of status verified into data base.
3875
                            $sql = 'SELECT status FROM '.$item_view_table.'
3876
                                    WHERE
3877
                                        c_id = '.$course_id.' AND
3878
                                        lp_item_id="'.$this->db_id.'" AND
3879
                                        lp_view_id="'.$this->view_id.'" AND
3880
                                        view_count="'.$this->get_attempt_id().'"
3881
                                    ';
3882
                            $rs_verified = Database::query($sql);
3883
                            $row_verified = Database::fetch_array($rs_verified);
3884
3885
                            // Get type lp: 1=lp dokeos and  2=scorm.
3886
                            // If not is completed or passed or browsed and learning path is scorm.
3887
                            if (!in_array($this->get_status(false), $case_completed) &&
3888
                                $my_type_lp == 2
3889
                            ) {
3890
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3891
                                $my_status = " status = '".$this->get_status(false)."' ,";
3892
                            } else {
3893
                                // Verified into database.
3894
                                if (!in_array($row_verified['status'], $case_completed) &&
3895
                                    $my_type_lp == 2
3896
                                ) {
3897
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3898
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3899
                                } elseif (in_array($row_verified['status'], $case_completed) &&
3900
                                    $my_type_lp == 2 && $this->type != 'sco'
3901
                                ) {
3902
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3903
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3904
                                } else {
3905
                                    if (($my_type_lp == 3 && $this->type == 'au') ||
3906
                                        ($my_type_lp == 1 && $this->type != 'dir')) {
3907
                                        // Is AICC or Chamilo LP
3908
                                        $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3909
                                        $my_status = " status = '".$this->get_status(false)."' ,";
3910
                                    }
3911
                                }
3912
                            }
3913
                        } else {
3914
                            // Multiple attempts are allowed.
3915
                            if (in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) {
3916
                                // Reset zero new attempt ?
3917
                                $my_status = " status = '".$this->get_status(false)."' ,";
3918
                            } elseif (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) {
3919
                                $total_time = " total_time = ".$this->get_total_time().", ";
3920
                                $my_status = " status = '".$this->get_status(false)."' ,";
3921
                            } else {
3922
                                // It is chamilo LP.
3923
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3924
                                $my_status = " status = '".$this->get_status(false)."' ,";
3925
                            }
3926
3927
                            // This code line fixes the problem of wrong status.
3928
                            if ($my_type_lp == 2) {
3929
                                // Verify current status in multiples attempts.
3930
                                $sql = 'SELECT status FROM '.$item_view_table.'
3931
                                        WHERE
3932
                                            c_id = '.$course_id.' AND
3933
                                            lp_item_id="'.$this->db_id.'" AND
3934
                                            lp_view_id="'.$this->view_id.'" AND
3935
                                            view_count="'.$this->get_attempt_id().'" ';
3936
                                $rs_status = Database::query($sql);
3937
                                $current_status = Database::result(
3938
                                    $rs_status,
3939
                                    0,
3940
                                    'status'
3941
                                );
3942
                                if (in_array($current_status, $case_completed)) {
3943
                                    $my_status = '';
3944
                                    $total_time = '';
3945
                                } else {
3946
                                    $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3947
                                }
3948
                            }
3949
                        }
3950
                    }
3951
3952
                    if ($this->type == 'sco') {
3953
                        //IF scorm scorm_update_time has already updated total_time in db
3954
                        //" . //start_time = ".$this->get_current_start_time().", " . //scorm_init_time does it
3955
                        ////" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3956
                        $sql = "UPDATE $item_view_table SET
3957
                                    score = ".$this->get_score().",
3958
                                    $my_status
3959
                                    max_score = '".$this->get_max()."',
3960
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3961
                                    lesson_location = '".$this->lesson_location."'
3962
                                WHERE
3963
                                    c_id = $course_id AND
3964
                                    lp_item_id = ".$this->db_id." AND
3965
                                    lp_view_id = ".$this->view_id."  AND
3966
                                    view_count = ".$this->get_attempt_id();
3967
                    } else {
3968
                        //" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3969
                        $sql = "UPDATE $item_view_table SET
3970
                                    $total_time
3971
                                    start_time = ".$this->get_current_start_time().",
3972
                                    score = ".$this->get_score().",
3973
                                    $my_status
3974
                                    max_score = '".$this->get_max()."',
3975
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3976
                                    lesson_location = '".$this->lesson_location."'
3977
                                WHERE
3978
                                    c_id = $course_id AND
3979
                                    lp_item_id = ".$this->db_id." AND
3980
                                    lp_view_id = ".$this->view_id." AND
3981
                                    view_count = ".$this->get_attempt_id();
3982
                    }
3983
                    $this->current_start_time = time();
3984
                }
3985
                if (self::DEBUG > 2) {
3986
                    error_log(
3987
                        'learnpathItem::write_to_db() - Updating item_view: '.$sql,
3988
                        0
3989
                    );
3990
                }
3991
                Database::query($sql);
3992
            }
3993
3994
            if (is_array($this->interactions) &&
3995
                count($this->interactions) > 0
3996
            ) {
3997
                // Save interactions.
3998
                $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3999
                $sql = "SELECT iid FROM $tbl
4000
                        WHERE
4001
                            c_id = $course_id AND
4002
                            lp_item_id = ".$this->db_id." AND
4003
                            lp_view_id = ".$this->view_id." AND
4004
                            view_count = ".$this->get_attempt_id();
4005
                $res = Database::query($sql);
4006
                if (Database::num_rows($res) > 0) {
4007
                    $row = Database::fetch_array($res);
4008
                    $lp_iv_id = $row[0];
4009
                    if (self::DEBUG > 2) {
4010
                        error_log(
4011
                            'learnpathItem::write_to_db() - Got item_view_id '.
4012
                                $lp_iv_id.', now checking interactions ',
4013
                            0
4014
                        );
4015
                    }
4016
                    foreach ($this->interactions as $index => $interaction) {
4017
                        $correct_resp = '';
4018
                        if (is_array($interaction[4]) && !empty($interaction[4][0])) {
4019
                            foreach ($interaction[4] as $resp) {
4020
                                $correct_resp .= $resp.',';
4021
                            }
4022
                            $correct_resp = substr(
4023
                                $correct_resp,
4024
                                0,
4025
                                strlen($correct_resp) - 1
4026
                            );
4027
                        }
4028
                        $iva_table = Database::get_course_table(
4029
                            TABLE_LP_IV_INTERACTION
4030
                        );
4031
4032
                        //also check for the interaction ID as it must be unique for this SCO view
4033
                        $iva_sql = "SELECT iid FROM $iva_table
4034
                                    WHERE
4035
                                        c_id = $course_id AND
4036
                                        lp_iv_id = $lp_iv_id AND
4037
                                        (
4038
                                            order_id = $index OR
4039
                                            interaction_id = '".Database::escape_string($interaction[0])."'
4040
                                        )
4041
                                    ";
4042
                        $iva_res = Database::query($iva_sql);
4043
4044
                        $interaction[0] = isset($interaction[0]) ? $interaction[0] : '';
4045
                        $interaction[1] = isset($interaction[1]) ? $interaction[1] : '';
4046
                        $interaction[2] = isset($interaction[2]) ? $interaction[2] : '';
4047
                        $interaction[3] = isset($interaction[3]) ? $interaction[3] : '';
4048
                        $interaction[4] = isset($interaction[4]) ? $interaction[4] : '';
4049
                        $interaction[5] = isset($interaction[5]) ? $interaction[5] : '';
4050
                        $interaction[6] = isset($interaction[6]) ? $interaction[6] : '';
4051
                        $interaction[7] = isset($interaction[7]) ? $interaction[7] : '';
4052
4053
                        // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
4054
                        if (Database::num_rows($iva_res) > 0) {
4055
                            // Update (or don't).
4056
                            $iva_row = Database::fetch_array($iva_res);
4057
                            $iva_id = $iva_row[0];
4058
                            // Insert new one.
4059
                            $params = [
4060
                                'interaction_id' => $interaction[0],
4061
                                'interaction_type' => $interaction[1],
4062
                                'weighting' => $interaction[3],
4063
                                'completion_time' => $interaction[2],
4064
                                'correct_responses' => $correct_resp,
4065
                                'student_response' => $interaction[5],
4066
                                'result' => $interaction[6],
4067
                                'latency' => $interaction[7]
4068
                            ];
4069
                            Database::update(
4070
                                $iva_table,
4071
                                $params,
4072
                                [
4073
                                    'c_id = ? AND iid = ?' => [
4074
                                        $course_id,
4075
                                        $iva_id
4076
                                    ]
4077
                                ]
4078
                            );
4079
                        } else {
4080
                            // Insert new one.
4081
                            $params = [
4082
                                'c_id' => $course_id,
4083
                                'order_id' => $index,
4084
                                'lp_iv_id' => $lp_iv_id,
4085
                                'interaction_id' => $interaction[0],
4086
                                'interaction_type' => $interaction[1],
4087
                                'weighting' => $interaction[3],
4088
                                'completion_time' => $interaction[2],
4089
                                'correct_responses' => $correct_resp,
4090
                                'student_response' => $interaction[5],
4091
                                'result' => $interaction[6],
4092
                                'latency' => $interaction[7]
4093
                            ];
4094
4095
                            $insertId = Database::insert($iva_table, $params);
4096
                            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...
4097
                                $sql = "UPDATE $iva_table SET id = iid
4098
                                        WHERE iid = $insertId";
4099
                                Database::query($sql);
4100
                            }
4101
                        }
4102
                    }
4103
                }
4104
            }
4105
        }
4106
4107
        if (self::DEBUG > 2) {
4108
            error_log('End of learnpathItem::write_to_db()', 0);
4109
        }
4110
4111
        return true;
4112
    }
4113
4114
    /**
4115
     * Adds an audio file attached to the current item (store on disk and in db)
4116
     * @return bool|null|string
4117
     */
4118
    public function add_audio()
4119
    {
4120
        $course_info = api_get_course_info();
4121
        $filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/';
4122
4123
        if (!is_dir($filepath.'audio')) {
4124
            mkdir(
4125
                $filepath.'audio',
4126
                api_get_permissions_for_new_directories()
4127
            );
4128
            $audio_id = add_document(
4129
                $course_info,
4130
                '/audio',
4131
                'folder',
4132
                0,
4133
                'audio'
4134
            );
4135
            api_item_property_update(
4136
                $course_info,
4137
                TOOL_DOCUMENT,
4138
                $audio_id,
4139
                'FolderCreated',
4140
                api_get_user_id(),
4141
                null,
4142
                null,
4143
                null,
4144
                null,
4145
                api_get_session_id()
4146
            );
4147
            api_item_property_update(
4148
                $course_info,
4149
                TOOL_DOCUMENT,
4150
                $audio_id,
4151
                'invisible',
4152
                api_get_user_id(),
4153
                null,
4154
                null,
4155
                null,
4156
                null,
4157
                api_get_session_id()
4158
            );
4159
        }
4160
4161
        $key = 'file';
4162
        if (!isset($_FILES[$key]['name']) || !isset($_FILES[$key]['tmp_name'])) {
4163
            return false;
4164
        }
4165
        $result = DocumentManager::upload_document(
4166
            $_FILES,
4167
            '/audio',
4168
            null,
4169
            null,
4170
            0,
4171
            'rename',
4172
            false,
4173
            false
4174
        );
4175
        $file_path = null;
4176
4177
        if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4178
            $file_path = basename($result['path']);
4179
4180
            // Store the mp3 file in the lp_item table.
4181
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4182
            $sql = "UPDATE $tbl_lp_item SET
4183
                        audio = '".Database::escape_string($file_path)."'
4184
                    WHERE iid = ".intval($this->db_id);
4185
            Database::query($sql);
4186
        }
4187
4188
        return $file_path;
4189
    }
4190
4191
    /**
4192
     * Adds an audio file to the current item, using a file already in documents
4193
     * @param int $doc_id
4194
     * @return string
4195
     */
4196
    public function add_audio_from_documents($doc_id)
4197
    {
4198
        $course_info = api_get_course_info();
4199
        $document_data = DocumentManager::get_document_data_by_id(
4200
            $doc_id,
4201
            $course_info['code']
4202
        );
4203
4204
        if (!empty($document_data)) {
4205
            $file_path = basename($document_data['path']);
4206
            // Store the mp3 file in the lp_item table.
4207
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4208
            $sql = "UPDATE $tbl_lp_item SET
4209
                        audio = '".Database::escape_string($file_path)."'
4210
                    WHERE iid = ".intval($this->db_id);
4211
            Database::query($sql);
4212
        }
4213
        return $file_path;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $file_path does not seem to be defined for all execution paths leading up to this point.
Loading history...
4214
    }
4215
4216
    /**
4217
     * Removes the relation between the current item and an audio file. The file
4218
     * is only removed from the lp_item table, but remains in the document table
4219
     * and directory
4220
     * @return bool
4221
     */
4222
    public function remove_audio()
4223
    {
4224
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4225
        $course_id = api_get_course_int_id();
4226
        if (empty($this->db_id)) {
4227
            return false;
4228
        }
4229
        $sql = "UPDATE $tbl_lp_item SET
4230
                audio = ''
4231
                WHERE iid IN (".$this->db_id.")";
4232
        Database::query($sql);
4233
    }
4234
4235
    /**
4236
     * Transform the SCORM status to a string that can be translated by Chamilo
4237
     * in different user languages
4238
     * @param $status
4239
     * @param bool $decorate
4240
     * @param string $type classic|simple
4241
     * @return array|string
4242
     */
4243
    public static function humanize_status($status, $decorate = true, $type = 'classic')
4244
    {
4245
        $statusList = [
4246
            'completed' => 'ScormCompstatus',
4247
            'incomplete' => 'ScormIncomplete',
4248
            'failed' => 'ScormFailed',
4249
            'passed' => 'ScormPassed',
4250
            'browsed' => 'ScormBrowsed',
4251
            'not attempted' => 'ScormNotAttempted'
4252
        ];
4253
4254
        $myLessonStatus = get_lang($statusList[$status]);
4255
4256
        switch ($status) {
4257
            case 'completed':
4258
            case 'browsed':
4259
                $classStatus = 'info';
4260
                break;
4261
            case 'incomplete':
4262
                $classStatus = 'warning';
4263
                break;
4264
            case 'passed':
4265
                $classStatus = 'success';
4266
                break;
4267
            case 'failed':
4268
                $classStatus = 'important';
4269
                break;
4270
            default:
4271
                $classStatus = 'default';
4272
                break;
4273
        }
4274
4275
        if ($type == 'simple') {
4276
            if (in_array($status, ['failed', 'passed', 'browsed'])) {
4277
                $myLessonStatus = get_lang('ScormIncomplete');
4278
                ;
4279
                $classStatus = 'warning';
4280
            }
4281
        }
4282
4283
        if ($decorate) {
4284
            return Display::label($myLessonStatus, $classStatus);
4285
        } else {
4286
            return $myLessonStatus;
4287
        }
4288
    }
4289
4290
    /**
4291
     * @return float
4292
     */
4293
    public function getPrerequisiteMaxScore()
4294
    {
4295
        return $this->prerequisiteMaxScore;
4296
    }
4297
4298
    /**
4299
     * @param float $prerequisiteMaxScore
4300
     */
4301
    public function setPrerequisiteMaxScore($prerequisiteMaxScore)
4302
    {
4303
        $this->prerequisiteMaxScore = $prerequisiteMaxScore;
4304
    }
4305
4306
    /**
4307
     * @return float
4308
     */
4309
    public function getPrerequisiteMinScore()
4310
    {
4311
        return $this->prerequisiteMinScore;
4312
    }
4313
4314
    /**
4315
     * @param float $prerequisiteMinScore
4316
     */
4317
    public function setPrerequisiteMinScore($prerequisiteMinScore)
4318
    {
4319
        $this->prerequisiteMinScore = $prerequisiteMinScore;
4320
    }
4321
4322
    /**
4323
     * Check if this LP item has a created thread in the basis course from the forum of its LP
4324
     * @param int $lpCourseId The course ID
4325
     * @return boolean
4326
     */
4327
    public function lpItemHasThread($lpCourseId)
4328
    {
4329
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4330
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4331
4332
        $fakeFrom = "
4333
            $forumThreadTable ft
4334
            INNER JOIN $itemProperty ip
4335
            ON (ft.thread_id = ip.ref AND ft.c_id = ip.c_id)
4336
        ";
4337
4338
        $resultData = Database::select(
4339
            'COUNT(ft.iid) AS qty',
4340
            $fakeFrom,
4341
            [
4342
                'where' => [
4343
                    'ip.visibility != ? AND ' => 2,
4344
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4345
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4346
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4347
                        intval($this->db_id),
4348
                        "{$this->title} - {$this->db_id}",
4349
                        intval($this->db_id)
4350
                    ]
4351
                ]
4352
            ],
4353
            'first'
4354
        );
4355
4356
        if ($resultData['qty'] > 0) {
4357
            return true;
4358
        }
4359
4360
        return false;
4361
    }
4362
4363
    /**
4364
     * Get the forum thread info
4365
     * @param int $lpCourseId The course ID from the learning path
4366
     * @param int $lpSessionId Optional. The session ID from the learning path
4367
     * @return boolean
4368
     */
4369
    public function getForumThread($lpCourseId, $lpSessionId = 0)
4370
    {
4371
        $lpSessionId = intval($lpSessionId);
4372
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4373
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4374
4375
        $fakeFrom = "$forumThreadTable ft
4376
            INNER JOIN $itemProperty ip ";
4377
4378
        if ($lpSessionId == 0) {
4379
            $fakeFrom .= "
4380
                ON (
4381
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND (
4382
                        ft.session_id = ip.session_id OR ip.session_id IS NULL
4383
                    )
4384
                )
4385
            ";
4386
        } else {
4387
            $fakeFrom .= "
4388
                ON (
4389
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND ft.session_id = ip.session_id
4390
                )
4391
            ";
4392
        }
4393
4394
        $resultData = Database::select(
4395
            'ft.*',
4396
            $fakeFrom,
4397
            [
4398
                'where' => [
4399
                    'ip.visibility != ? AND ' => 2,
4400
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4401
                    'ft.session_id = ? AND ' => $lpSessionId,
4402
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4403
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4404
                        intval($this->db_id),
4405
                        "{$this->title} - {$this->db_id}",
4406
                        intval($this->db_id)
4407
                    ]
4408
                ]
4409
            ],
4410
            'first'
4411
        );
4412
4413
        if (empty($resultData)) {
4414
            return false;
4415
        }
4416
4417
        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...
4418
    }
4419
4420
    /**
4421
     * Create a forum thread for this learning path item
4422
     * @param int $currentForumId The forum ID to add the new thread
4423
     * @return int The forum thread if was created. Otherwise return false
4424
     */
4425
    public function createForumThread($currentForumId)
4426
    {
4427
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
4428
4429
        $em = Database::getManager();
4430
        $threadRepo = $em->getRepository('ChamiloCourseBundle:CForumThread');
4431
        $forumThread = $threadRepo->findOneBy([
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $forumThread is correct as $threadRepo->findOneBy(a...tval($currentForumId))) targeting Doctrine\ORM\EntityRepository::findOneBy() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
4432
            'threadTitle' => "{$this->title} - {$this->db_id}",
4433
            'forumId' => intval($currentForumId)
4434
        ]);
4435
4436
        if (!$forumThread) {
4437
            $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

4437
            $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...
4438
4439
            store_thread(
4440
                $forumInfo,
4441
                [
4442
                    'forum_id' => intval($currentForumId),
4443
                    'thread_id' => 0,
4444
                    'gradebook' => 0,
4445
                    'post_title' => "{$this->name} - {$this->db_id}",
4446
                    'post_text' => $this->description,
4447
                    'category_id' => 1,
4448
                    'numeric_calification' => 0,
4449
                    'calification_notebook_title' => 0,
4450
                    'weight_calification' => 0.00,
4451
                    'thread_peer_qualify' => 0,
4452
                    'lp_item_id' => $this->db_id
4453
                ],
4454
                [],
4455
                false
4456
            );
4457
4458
            return;
4459
        }
4460
4461
        $forumThread->setLpItemId($this->db_id);
4462
4463
        $em->persist($forumThread);
4464
        $em->flush();
4465
    }
4466
4467
    /**
4468
     * Allow dissociate a forum to this LP item
4469
     * @param int $threadIid The thread id
4470
     * @return boolean
4471
     */
4472
    public function dissociateForumThread($threadIid)
4473
    {
4474
        $threadIid = intval($threadIid);
4475
        $em = Database::getManager();
4476
4477
        $forumThread = $em->find('ChamiloCourseBundle:CForumThread', $threadIid);
4478
4479
        if (!$forumThread) {
4480
            return false;
4481
        }
4482
4483
        $forumThread->setThreadTitle(
4484
            "{$this->get_title()} - {$this->db_id}"
4485
        );
4486
        $forumThread->setLpItemId(0);
4487
4488
        $em->persist($forumThread);
4489
        $em->flush();
4490
4491
        return true;
4492
    }
4493
}
4494