Passed
Push — master ( e80e7a...4357b6 )
by Julito
11:11 queued 02:04
created

learnpathItem::status_is()   B

Complexity

Conditions 7
Paths 14

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

    return false;
}

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

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