Completed
Push — master ( 7d9ab3...0334ca )
by Julito
08:48
created

learnpathItem::__construct()   B

Complexity

Conditions 9
Paths 42

Size

Total Lines 89
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 62
c 0
b 0
f 0
nc 42
nop 4
dl 0
loc 89
rs 7.2735

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

    return false;
}

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

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