Passed
Push — master ( 447a4c...2f567c )
by Julito
09:40
created

learnpathItem::status_is()   B

Complexity

Conditions 7
Paths 14

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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