Passed
Push — 1.11.x ( 88d8b8...039984 )
by Julito
13:18
created

learnpathItem::fixAudio()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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