Passed
Push — 1.11.x ( 08587c...c85e98 )
by Yannick
10:54
created

learnpathItem::save()   F

Complexity

Conditions 40
Paths 84

Size

Total Lines 162
Code Lines 100

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
c 1
b 0
f 0
dl 0
loc 162
rs 3.3333
cc 40
nc 84
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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